Decompiled source of ImmersiveFirstPerson v1.2.1

BepInEx/plugins/ImmersiveFirstPerson/ImmersiveFirstPerson.dll

Decompiled 20 minutes ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("ImmersiveBuildCamera")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ImmersiveBuildCamera")]
[assembly: AssemblyTitle("ImmersiveBuildCamera")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 ImmersiveFirstPerson
{
	internal static class BodyVisibilityController
	{
		private static readonly Dictionary<Renderer, bool> OriginalRendererStates = new Dictionary<Renderer, bool>();

		private static readonly List<Renderer?> DeadRenderers = new List<Renderer>();

		private static Player? _cachedPlayer;

		private static bool _captured;

		internal static void Update(Player player)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			if (!Plugin.EnableMod.Value || !FirstPersonState.Active || !Plugin.ForceBodyVisible.Value)
			{
				Reset();
				return;
			}
			if ((Object)(object)_cachedPlayer != (Object)null && (Object)(object)_cachedPlayer != (Object)(object)player)
			{
				Reset();
			}
			_cachedPlayer = player;
			if (!_captured)
			{
				CaptureVisibleBodyRenderers(player);
			}
			ForceCapturedBodyRenderersVisible();
		}

		internal static void Reset()
		{
			OriginalRendererStates.Clear();
			DeadRenderers.Clear();
			_cachedPlayer = null;
			_captured = false;
		}

		private static void CaptureVisibleBodyRenderers(Player player)
		{
			OriginalRendererStates.Clear();
			Renderer[] componentsInChildren = ((Component)player).GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && val.enabled && !LooksLikeHeadRenderer(val))
				{
					OriginalRendererStates[val] = true;
				}
			}
			_captured = true;
			Plugin.DebugLog($"Captured {OriginalRendererStates.Count} visible body renderers for first-person body restore.");
		}

		private static void ForceCapturedBodyRenderersVisible()
		{
			RemoveDestroyedRenderers();
			foreach (Renderer key in OriginalRendererStates.Keys)
			{
				if (!((Object)(object)key == (Object)null) && !key.enabled)
				{
					key.enabled = true;
				}
			}
		}

		private static bool LooksLikeHeadRenderer(Renderer renderer)
		{
			string text = (((Object)(object)((Component)renderer).transform != (Object)null) ? RendererScanner.GetPath(((Component)renderer).transform) : string.Empty);
			string value = (((Object)renderer).name + " " + text).ToLowerInvariant();
			if (!Contains(value, "head") && !Contains(value, "hair") && !Contains(value, "face") && !Contains(value, "jaw") && !Contains(value, "eye") && !Contains(value, "helmet"))
			{
				return Contains(value, "helm");
			}
			return true;
		}

		private static bool Contains(string value, string keyword)
		{
			return value.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0;
		}

		private static void RemoveDestroyedRenderers()
		{
			DeadRenderers.Clear();
			foreach (Renderer key in OriginalRendererStates.Keys)
			{
				if ((Object)(object)key == (Object)null)
				{
					DeadRenderers.Add(key);
				}
			}
			foreach (Renderer deadRenderer in DeadRenderers)
			{
				if (deadRenderer != null)
				{
					OriginalRendererStates.Remove(deadRenderer);
				}
			}
			DeadRenderers.Clear();
		}
	}
	[HarmonyPatch(typeof(GameCamera))]
	[HarmonyPatch("UpdateCamera")]
	internal static class GameCameraUpdatePatch
	{
		private static void Postfix(GameCamera __instance)
		{
			FirstPersonCamera.Update(__instance);
		}
	}
	internal static class FirstPersonCamera
	{
		private static readonly FieldInfo? CameraField = AccessTools.Field(typeof(GameCamera), "m_camera");

		private static readonly MethodInfo? IsCrouchingMethod = AccessTools.Method(typeof(Player), "IsCrouching", (Type[])null, (Type[])null) ?? AccessTools.Method(typeof(Character), "IsCrouching", (Type[])null, (Type[])null);

		private static readonly MethodInfo? InSneakMethod = AccessTools.Method(typeof(Player), "InSneak", (Type[])null, (Type[])null) ?? AccessTools.Method(typeof(Character), "InSneak", (Type[])null, (Type[])null);

		private static readonly FieldInfo? CrouchField = AccessTools.Field(typeof(Player), "m_crouchToggled") ?? AccessTools.Field(typeof(Player), "m_crouch") ?? AccessTools.Field(typeof(Character), "m_crouch");

		private static Camera? _lastCamera;

		private static Player? _cachedAnchorPlayer;

		private static Transform? _cachedHeadAnchor;

		private static float _originalFov;

		private static float _originalNearClip;

		private static bool _savedOriginals;

		private static Vector3 _smoothPosition;

		private static bool _hasSmoothPosition;

		internal static void Update(GameCamera gameCamera)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)gameCamera == (Object)null)
			{
				return;
			}
			Camera val = GetCamera(gameCamera) ?? Camera.main;
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			_lastCamera = val;
			SaveOriginalCameraValues(val);
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || !FirstPersonState.ShouldApplyCamera(localPlayer))
			{
				RestoreCamera(val);
				ResetSmoothing();
				RestoreLocalVisibilityForSuppressedCamera();
				return;
			}
			Quaternion rotation = ((Component)gameCamera).transform.rotation;
			if (Plugin.LockBodyToCamera.Value)
			{
				LockBodyYawToCamera(localPlayer, rotation);
			}
			ApplyFirstPersonCamera(gameCamera, val, localPlayer, rotation);
			LocalPlayerVisibilityOverride.ForceVisible(localPlayer);
			BodyVisibilityController.Update(localPlayer);
			HeadVisibilityController.Update(localPlayer);
		}

		internal static void RestoreLastCamera()
		{
			if ((Object)(object)_lastCamera != (Object)null)
			{
				RestoreCamera(_lastCamera);
			}
			ResetSmoothing();
			ResetAnchorCache();
		}

		private static Camera? GetCamera(GameCamera gameCamera)
		{
			if (CameraField == null)
			{
				return null;
			}
			object? value = CameraField.GetValue(gameCamera);
			return (Camera?)((value is Camera) ? value : null);
		}

		private static void SaveOriginalCameraValues(Camera camera)
		{
			if (!_savedOriginals)
			{
				_originalFov = camera.fieldOfView;
				_originalNearClip = camera.nearClipPlane;
				_savedOriginals = true;
			}
		}

		private static void RestoreLocalVisibilityForSuppressedCamera()
		{
			if (FirstPersonState.Active)
			{
				BodyVisibilityController.Reset();
				HeadVisibilityController.ForceVisible();
			}
		}

		private static void LockBodyYawToCamera(Player player, Quaternion cameraRotation)
		{
			//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_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = cameraRotation * Vector3.forward;
			val.y = 0f;
			if (!(((Vector3)(ref val)).sqrMagnitude <= 0.0001f))
			{
				Quaternion val2 = Quaternion.LookRotation(((Vector3)(ref val)).normalized, Vector3.up);
				float num = Mathf.Max(0f, Plugin.BodyRotationFollowSpeed.Value);
				((Component)player).transform.rotation = ((num <= 0f) ? val2 : Quaternion.Slerp(((Component)player).transform.rotation, val2, 1f - Mathf.Exp((0f - num) * Time.unscaledDeltaTime)));
			}
		}

		private static void ApplyFirstPersonCamera(GameCamera gameCamera, Camera camera, Player player, Quaternion vanillaCameraRotation)
		{
			//IL_002c: 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_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: 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_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: 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_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: 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_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			Transform cameraAnchor = GetCameraAnchor(player);
			bool num = (Object)(object)cameraAnchor != (Object)null && Plugin.UseHeadTrackedAnchor.Value && (Object)(object)cameraAnchor != (Object)(object)((Character)player).m_eye;
			Vector3 position = cameraAnchor.position;
			position += Vector3.up * Plugin.CameraVerticalOffset.Value;
			Vector3 val = vanillaCameraRotation * Vector3.forward;
			val.y = 0f;
			if (((Vector3)(ref val)).sqrMagnitude > 0.0001f)
			{
				position += ((Vector3)(ref val)).normalized * Plugin.CameraForwardOffset.Value;
			}
			float num2 = Mathf.Clamp01(Vector3.Dot(vanillaCameraRotation * Vector3.forward, Vector3.down));
			if (num2 > 0f)
			{
				if (((Vector3)(ref val)).sqrMagnitude > 0.0001f)
				{
					position += ((Vector3)(ref val)).normalized * Plugin.DownLookExtraForwardOffset.Value * num2;
				}
				position += Vector3.up * Plugin.DownLookExtraVerticalOffset.Value * num2;
			}
			if (!num && IsCrouchingOrSneaking(player))
			{
				position += Vector3.up * Plugin.CrouchVerticalOffset.Value;
			}
			ApplyTransform(gameCamera, camera, position, vanillaCameraRotation);
			if (Plugin.UseCustomFov.Value)
			{
				camera.fieldOfView = Mathf.Clamp(Plugin.Fov.Value, 40f, 120f);
			}
			camera.nearClipPlane = Mathf.Clamp(Plugin.NearClip.Value, 0.005f, 0.5f);
		}

		private static Transform GetCameraAnchor(Player player)
		{
			if (!Plugin.UseHeadTrackedAnchor.Value)
			{
				if (!((Object)(object)((Character)player).m_eye != (Object)null))
				{
					return ((Component)player).transform;
				}
				return ((Character)player).m_eye;
			}
			if ((Object)(object)_cachedAnchorPlayer == (Object)(object)player && (Object)(object)_cachedHeadAnchor != (Object)null)
			{
				return _cachedHeadAnchor;
			}
			_cachedAnchorPlayer = player;
			_cachedHeadAnchor = FindBestHeadAnchor(player);
			if ((Object)(object)_cachedHeadAnchor != (Object)null)
			{
				Plugin.DebugLog("Using head-tracked camera anchor: " + RendererScanner.GetPath(_cachedHeadAnchor));
				return _cachedHeadAnchor;
			}
			if (!((Object)(object)((Character)player).m_eye != (Object)null))
			{
				return ((Component)player).transform;
			}
			return ((Character)player).m_eye;
		}

		private static Transform? FindBestHeadAnchor(Player player)
		{
			Transform[] componentsInChildren = ((Component)player).GetComponentsInChildren<Transform>(true);
			Transform result = null;
			int num = int.MinValue;
			Transform[] array = componentsInChildren;
			foreach (Transform val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					int num2 = ScoreHeadAnchor(val);
					if (num2 > num)
					{
						num = num2;
						result = val;
					}
				}
			}
			if (num <= 0)
			{
				return null;
			}
			return result;
		}

		private static int ScoreHeadAnchor(Transform transform)
		{
			string text = ((Object)transform).name.ToLowerInvariant();
			string text2 = RendererScanner.GetPath(transform).ToLowerInvariant();
			string text3 = text + " " + text2;
			if (!text3.Contains("head"))
			{
				return int.MinValue;
			}
			if (text3.Contains("helmet") || text3.Contains("helm") || text3.Contains("hair") || text3.Contains("beard"))
			{
				return int.MinValue;
			}
			int num = 10;
			if (text == "head")
			{
				num += 100;
			}
			if (text.Contains("head"))
			{
				num += 40;
			}
			if (text2.Contains("neck"))
			{
				num += 10;
			}
			if ((Object)(object)((Component)transform).GetComponent<Renderer>() != (Object)null)
			{
				num -= 50;
			}
			return num;
		}

		private static bool IsCrouchingOrSneaking(Player player)
		{
			if (TryInvokeBool(player, IsCrouchingMethod, out var value) && value)
			{
				return true;
			}
			if (TryInvokeBool(player, InSneakMethod, out var value2) && value2)
			{
				return true;
			}
			if (CrouchField != null)
			{
				object value3 = CrouchField.GetValue(player);
				if (value3 is bool)
				{
					return (bool)value3;
				}
			}
			return false;
		}

		private static bool TryInvokeBool(Player player, MethodInfo? method, out bool value)
		{
			value = false;
			if (method == null)
			{
				return false;
			}
			if (!(method.Invoke(player, null) is bool flag))
			{
				return false;
			}
			value = flag;
			return true;
		}

		private static void ApplyTransform(GameCamera gameCamera, Camera camera, Vector3 desiredPosition, Quaternion desiredRotation)
		{
			//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_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: 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_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = desiredPosition;
			if (Plugin.SmoothCamera.Value)
			{
				if (!_hasSmoothPosition)
				{
					_smoothPosition = ((Component)gameCamera).transform.position;
					_hasSmoothPosition = true;
				}
				float num = Mathf.Max(0f, Plugin.CameraSmoothing.Value);
				float num2 = ((num <= 0f) ? 1f : (1f - Mathf.Exp((0f - num) * Time.unscaledDeltaTime)));
				_smoothPosition = Vector3.Lerp(_smoothPosition, desiredPosition, num2);
				position = _smoothPosition;
			}
			else
			{
				ResetSmoothing();
			}
			((Component)gameCamera).transform.position = position;
			((Component)gameCamera).transform.rotation = desiredRotation;
			if ((Object)(object)((Component)camera).transform != (Object)(object)((Component)gameCamera).transform)
			{
				((Component)camera).transform.position = position;
				((Component)camera).transform.rotation = desiredRotation;
			}
		}

		private static void RestoreCamera(Camera camera)
		{
			if (_savedOriginals)
			{
				camera.fieldOfView = _originalFov;
				camera.nearClipPlane = _originalNearClip;
			}
		}

		private static void ResetSmoothing()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			_hasSmoothPosition = false;
			_smoothPosition = Vector3.zero;
		}

		private static void ResetAnchorCache()
		{
			_cachedAnchorPlayer = null;
			_cachedHeadAnchor = null;
		}
	}
	internal static class FirstPersonState
	{
		private static Player? _cachedPlayer;

		private static bool _defaultApplied;

		internal static bool Active { get; private set; }

		internal static void Update(Player player)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			if ((Object)(object)_cachedPlayer != (Object)null && (Object)(object)_cachedPlayer != (Object)(object)player)
			{
				ForceInactive();
				_defaultApplied = false;
			}
			_cachedPlayer = player;
			if (!Plugin.EnableMod.Value)
			{
				SetActive(active: false);
				return;
			}
			if (!IsSafePlayerState(player))
			{
				SetActive(active: false);
				return;
			}
			if (!_defaultApplied)
			{
				_defaultApplied = true;
				if (Plugin.DefaultToFirstPerson.Value)
				{
					SetActive(active: true);
				}
			}
			if (CanReadToggleInput() && Input.GetKeyDown(Plugin.ToggleFirstPersonKey.Value))
			{
				SetActive(!Active);
			}
			if (Active && Plugin.LogRendererNames.Value)
			{
				RendererScanner.LogRenderersOnce(player);
			}
		}

		internal static bool ShouldApplyCamera(Player player)
		{
			if (Plugin.EnableMod.Value && Active && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && IsSafePlayerState(player))
			{
				return !IsBlockingCameraUiVisible();
			}
			return false;
		}

		internal static void ForceInactive()
		{
			SetActive(active: false);
			_cachedPlayer = null;
			_defaultApplied = false;
		}

		private static void SetActive(bool active)
		{
			if (Active != active)
			{
				Active = active;
				if (!active)
				{
					HeadVisibilityController.ForceVisible();
					FirstPersonCamera.RestoreLastCamera();
					RendererScanner.ResetLogState();
				}
				Plugin.Log.LogInfo((object)(active ? "Immersive first person active." : "Immersive first person inactive."));
			}
		}

		private static bool CanReadToggleInput()
		{
			return !IsBlockingCameraUiVisible();
		}

		private static bool IsBlockingCameraUiVisible()
		{
			if (InventoryGui.IsVisible())
			{
				return true;
			}
			if (Menu.IsVisible())
			{
				return true;
			}
			if (Minimap.IsOpen())
			{
				return true;
			}
			return false;
		}

		private static bool IsSafePlayerState(Player player)
		{
			if (((Character)player).IsDead())
			{
				return false;
			}
			if (((Character)player).IsAttached())
			{
				return false;
			}
			return true;
		}
	}
	internal static class HeadVisibilityController
	{
		private static readonly Dictionary<Renderer, bool> OriginalRendererStates = new Dictionary<Renderer, bool>();

		private static readonly Dictionary<Transform, Vector3> OriginalBoneScales = new Dictionary<Transform, Vector3>();

		private static readonly List<Renderer?> RenderersToRemove = new List<Renderer>();

		private static readonly List<Transform?> BonesToRemove = new List<Transform>();

		private static Player? _cachedPlayer;

		private static bool _active;

		private static float _nextRefreshTime;

		private static readonly string[] HeadKeywords = new string[1] { "head" };

		private static readonly string[] HairKeywords = new string[1] { "hair" };

		private static readonly string[] FaceKeywords = new string[3] { "face", "jaw", "eye" };

		private static readonly string[] HelmetKeywords = new string[2] { "helmet", "helm" };

		private static readonly string[] ShoulderKeywords = new string[1] { "shoulder" };

		private static readonly string[] BackItemKeywords = new string[3] { "back", "cape", "cloak" };

		internal static void Update(Player player)
		{
			if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer))
			{
				bool shouldHide = Plugin.EnableMod.Value && FirstPersonState.Active && HasAnyVisibilityRuleEnabled();
				Apply(player, shouldHide);
			}
		}

		internal static void ForceVisible()
		{
			RestoreRendererStates();
			RestoreBoneScales();
			ResetCache();
		}

		private static void Apply(Player player, bool shouldHide)
		{
			if ((Object)(object)_cachedPlayer != (Object)null && (Object)(object)_cachedPlayer != (Object)(object)player)
			{
				ForceVisible();
			}
			_cachedPlayer = player;
			if (!shouldHide)
			{
				ForceVisible();
				return;
			}
			if (!_active)
			{
				OriginalRendererStates.Clear();
				OriginalBoneScales.Clear();
				_active = true;
				_nextRefreshTime = 0f;
			}
			ApplyBoneMode(player);
			if (Time.unscaledTime >= _nextRefreshTime)
			{
				RefreshRendererCache(player);
				_nextRefreshTime = Time.unscaledTime + 0.5f;
			}
			HideCachedRenderers();
		}

		private static bool HasAnyVisibilityRuleEnabled()
		{
			if (!Plugin.HideHead.Value && !Plugin.HideHair.Value && !Plugin.HideFace.Value && !Plugin.HideHelmet.Value && !Plugin.HideShoulderPads.Value)
			{
				return Plugin.HideBackItems.Value;
			}
			return true;
		}

		private static void ApplyBoneMode(Player player)
		{
			if (Plugin.HeadHideModeConfig.Value == HeadHideModeOption.BoneShrink && Plugin.HideHead.Value)
			{
				ShrinkHeadBones(player);
			}
			else
			{
				RestoreBoneScales();
			}
		}

		private static void RefreshRendererCache(Player player)
		{
			RemoveDestroyedRenderers();
			HashSet<Renderer> hashSet = new HashSet<Renderer>();
			Renderer[] componentsInChildren = ((Component)player).GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && ShouldHideRenderer(val))
				{
					hashSet.Add(val);
				}
			}
			RenderersToRemove.Clear();
			foreach (KeyValuePair<Renderer, bool> originalRendererState in OriginalRendererStates)
			{
				Renderer key = originalRendererState.Key;
				if ((Object)(object)key == (Object)null || !hashSet.Contains(key))
				{
					RenderersToRemove.Add(key);
				}
			}
			foreach (Renderer item in RenderersToRemove)
			{
				if (item != null)
				{
					RestoreRenderer(item);
					OriginalRendererStates.Remove(item);
				}
			}
			RenderersToRemove.Clear();
			foreach (Renderer item2 in hashSet)
			{
				if (!OriginalRendererStates.ContainsKey(item2))
				{
					OriginalRendererStates.Add(item2, item2.enabled);
				}
			}
		}

		private static bool ShouldHideRenderer(Renderer renderer)
		{
			string value = BuildRendererDescriptor(renderer);
			if (Plugin.HeadHideModeConfig.Value == HeadHideModeOption.RendererDisable && Plugin.HideHead.Value && ContainsAny(value, HeadKeywords))
			{
				return true;
			}
			if (Plugin.HideHair.Value && ContainsAny(value, HairKeywords))
			{
				return true;
			}
			if (Plugin.HideFace.Value && ContainsAny(value, FaceKeywords))
			{
				return true;
			}
			if (Plugin.HideHelmet.Value && ContainsAny(value, HelmetKeywords))
			{
				return true;
			}
			if (Plugin.HideShoulderPads.Value && ContainsAny(value, ShoulderKeywords))
			{
				return true;
			}
			if (Plugin.HideBackItems.Value && ContainsAny(value, BackItemKeywords))
			{
				return true;
			}
			return false;
		}

		private static string BuildRendererDescriptor(Renderer renderer)
		{
			string text = (((Object)(object)((Component)renderer).transform != (Object)null) ? RendererScanner.GetPath(((Component)renderer).transform) : string.Empty);
			return (((Object)renderer).name + " " + text).ToLowerInvariant();
		}

		private static bool ContainsAny(string value, string[] keywords)
		{
			foreach (string value2 in keywords)
			{
				if (value.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}

		private static void HideCachedRenderers()
		{
			foreach (Renderer key in OriginalRendererStates.Keys)
			{
				if (!((Object)(object)key == (Object)null) && key.enabled)
				{
					key.enabled = false;
				}
			}
		}

		private static void ShrinkHeadBones(Player player)
		{
			//IL_0055: 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)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			Transform[] componentsInChildren = ((Component)player).GetComponentsInChildren<Transform>(true);
			foreach (Transform val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null) && IsHeadBone(val))
				{
					if (!OriginalBoneScales.ContainsKey(val))
					{
						OriginalBoneScales.Add(val, val.localScale);
						Plugin.DebugLog("Shrinking head bone: " + RendererScanner.GetPath(val));
					}
					val.localScale = Vector3.one * 0.001f;
				}
			}
			RemoveDestroyedBones();
		}

		private static bool IsHeadBone(Transform transform)
		{
			return ((Object)transform).name.IndexOf("head", StringComparison.OrdinalIgnoreCase) >= 0;
		}

		private static void RestoreRendererStates()
		{
			if (OriginalRendererStates.Count == 0)
			{
				return;
			}
			foreach (KeyValuePair<Renderer, bool> originalRendererState in OriginalRendererStates)
			{
				Renderer key = originalRendererState.Key;
				if (!((Object)(object)key == (Object)null))
				{
					key.enabled = originalRendererState.Value;
				}
			}
			OriginalRendererStates.Clear();
			RenderersToRemove.Clear();
			_nextRefreshTime = 0f;
		}

		private static void RestoreRenderer(Renderer renderer)
		{
			if (!((Object)(object)renderer == (Object)null) && OriginalRendererStates.TryGetValue(renderer, out var value))
			{
				renderer.enabled = value;
			}
		}

		private static void RestoreBoneScales()
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (OriginalBoneScales.Count == 0)
			{
				return;
			}
			foreach (KeyValuePair<Transform, Vector3> originalBoneScale in OriginalBoneScales)
			{
				Transform key = originalBoneScale.Key;
				if (!((Object)(object)key == (Object)null))
				{
					key.localScale = originalBoneScale.Value;
				}
			}
			OriginalBoneScales.Clear();
			BonesToRemove.Clear();
		}

		private static void RemoveDestroyedRenderers()
		{
			RenderersToRemove.Clear();
			foreach (Renderer key in OriginalRendererStates.Keys)
			{
				if ((Object)(object)key == (Object)null)
				{
					RenderersToRemove.Add(key);
				}
			}
			foreach (Renderer item in RenderersToRemove)
			{
				if (item != null)
				{
					OriginalRendererStates.Remove(item);
				}
			}
			RenderersToRemove.Clear();
		}

		private static void RemoveDestroyedBones()
		{
			BonesToRemove.Clear();
			foreach (Transform key in OriginalBoneScales.Keys)
			{
				if ((Object)(object)key == (Object)null)
				{
					BonesToRemove.Add(key);
				}
			}
			foreach (Transform item in BonesToRemove)
			{
				if (item != null)
				{
					OriginalBoneScales.Remove(item);
				}
			}
			BonesToRemove.Clear();
		}

		private static void ResetCache()
		{
			_cachedPlayer = null;
			_active = false;
			_nextRefreshTime = 0f;
			OriginalRendererStates.Clear();
			OriginalBoneScales.Clear();
			RenderersToRemove.Clear();
			BonesToRemove.Clear();
		}
	}
	internal static class LocalPlayerVisibilityOverride
	{
		private static readonly MethodInfo? PlayerSetVisibleMethod = AccessTools.Method(typeof(Player), "SetVisible", (Type[])null, (Type[])null) ?? AccessTools.Method(typeof(Character), "SetVisible", (Type[])null, (Type[])null);

		private static readonly FieldInfo? VisEquipmentField = AccessTools.Field(typeof(Humanoid), "m_visEquipment") ?? AccessTools.Field(typeof(Character), "m_visEquipment");

		private static Type? _cachedVisEquipmentType;

		private static MethodInfo? _cachedVisEquipmentSetVisibleMethod;

		internal static void ForceVisible(Player player)
		{
			if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && Plugin.EnableMod.Value && FirstPersonState.Active && Plugin.ForceBodyVisible.Value)
			{
				TrySetPlayerVisible(player);
				TrySetVisEquipmentVisible(player);
			}
		}

		private static void TrySetPlayerVisible(Player player)
		{
			if (PlayerSetVisibleMethod == null)
			{
				return;
			}
			try
			{
				PlayerSetVisibleMethod.Invoke(player, new object[1] { true });
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("SetVisible override failed: " + ex.GetType().Name);
			}
		}

		private static void TrySetVisEquipmentVisible(Player player)
		{
			if (VisEquipmentField == null)
			{
				return;
			}
			object value = VisEquipmentField.GetValue(player);
			if (value == null)
			{
				return;
			}
			Type type = value.GetType();
			if (_cachedVisEquipmentType != type)
			{
				_cachedVisEquipmentType = type;
				_cachedVisEquipmentSetVisibleMethod = AccessTools.Method(type, "SetVisible", (Type[])null, (Type[])null);
			}
			if (_cachedVisEquipmentSetVisibleMethod == null)
			{
				return;
			}
			try
			{
				_cachedVisEquipmentSetVisibleMethod.Invoke(value, new object[1] { true });
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("VisEquipment visibility override failed: " + ex.GetType().Name);
			}
		}
	}
	[HarmonyPatch(typeof(Player))]
	[HarmonyPatch("Update")]
	internal static class PlayerUpdatePatch
	{
		private static void Postfix(Player __instance)
		{
			FirstPersonState.Update(__instance);
		}
	}
	internal enum HeadHideModeOption
	{
		RendererDisable,
		BoneShrink
	}
	[BepInPlugin("com.geronimo.valheim.immersivefirstperson", "Immersive First Person", "1.0.0")]
	[BepInProcess("valheim.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.geronimo.valheim.immersivefirstperson";

		public const string PluginName = "Immersive First Person";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> EnableMod;

		internal static ConfigEntry<KeyCode> ToggleFirstPersonKey;

		internal static ConfigEntry<bool> DefaultToFirstPerson;

		internal static ConfigEntry<bool> UseHeadTrackedAnchor;

		internal static ConfigEntry<float> CameraVerticalOffset;

		internal static ConfigEntry<float> CameraForwardOffset;

		internal static ConfigEntry<float> DownLookExtraForwardOffset;

		internal static ConfigEntry<float> DownLookExtraVerticalOffset;

		internal static ConfigEntry<float> CrouchVerticalOffset;

		internal static ConfigEntry<float> NearClip;

		internal static ConfigEntry<bool> UseCustomFov;

		internal static ConfigEntry<float> Fov;

		internal static ConfigEntry<bool> SmoothCamera;

		internal static ConfigEntry<float> CameraSmoothing;

		internal static ConfigEntry<bool> LockBodyToCamera;

		internal static ConfigEntry<float> BodyRotationFollowSpeed;

		internal static ConfigEntry<bool> HideHead;

		internal static ConfigEntry<bool> HideHair;

		internal static ConfigEntry<bool> HideFace;

		internal static ConfigEntry<bool> HideHelmet;

		internal static ConfigEntry<bool> HideShoulderPads;

		internal static ConfigEntry<bool> HideBackItems;

		internal static ConfigEntry<bool> ForceBodyVisible;

		internal static ConfigEntry<HeadHideModeOption> HeadHideModeConfig;

		internal static ConfigEntry<bool> EnableDebugLogs;

		internal static ConfigEntry<bool> LogRendererNames;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_0379: Unknown result type (might be due to invalid IL or missing references)
			//IL_0383: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			EnableMod = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableMod", true, "Enable Immersive First Person.");
			ToggleFirstPersonKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Input", "ToggleFirstPersonKey", (KeyCode)287, "Press this key to toggle first-person mode.");
			DefaultToFirstPerson = ((BaseUnityPlugin)this).Config.Bind<bool>("Input", "DefaultToFirstPerson", false, "Start in first-person mode when the local player is ready.");
			UseHeadTrackedAnchor = ((BaseUnityPlugin)this).Config.Bind<bool>("Camera", "UseHeadTrackedAnchor", true, "Anchor the camera to the animated head bone when found. Falls back to the player eye transform.");
			CameraVerticalOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Camera", "CameraVerticalOffset", 0.04f, "Vertical offset from the selected camera anchor.");
			CameraForwardOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Camera", "CameraForwardOffset", 0.16f, "Forward offset from the selected camera anchor to keep the torso out of the view.");
			DownLookExtraForwardOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Camera", "DownLookExtraForwardOffset", 0.16f, "Extra forward offset applied gradually when looking down.");
			DownLookExtraVerticalOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Camera", "DownLookExtraVerticalOffset", 0.06f, "Extra upward offset applied gradually when looking down.");
			CrouchVerticalOffset = ((BaseUnityPlugin)this).Config.Bind<float>("Camera", "CrouchVerticalOffset", -0.45f, "Additional vertical camera offset while crouching or sneaking when head tracking is unavailable.");
			NearClip = ((BaseUnityPlugin)this).Config.Bind<float>("Camera", "NearClip", 0.02f, "Near clipping plane while first-person mode is active.");
			UseCustomFov = ((BaseUnityPlugin)this).Config.Bind<bool>("Camera", "UseCustomFov", true, "Use the configured first-person FOV.");
			Fov = ((BaseUnityPlugin)this).Config.Bind<float>("Camera", "FOV", 75f, "Field of view while first-person mode is active.");
			SmoothCamera = ((BaseUnityPlugin)this).Config.Bind<bool>("Camera", "SmoothCamera", false, "Optional extra smoothing for camera position. Disabled by default so vanilla mouse behavior is preserved.");
			CameraSmoothing = ((BaseUnityPlugin)this).Config.Bind<float>("Camera", "CameraSmoothing", 18f, "How quickly the camera moves toward the first-person target if SmoothCamera is enabled.");
			LockBodyToCamera = ((BaseUnityPlugin)this).Config.Bind<bool>("Camera", "LockBodyToCamera", true, "Rotate the local player body yaw to match vanilla camera yaw while first-person mode is active.");
			BodyRotationFollowSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Camera", "BodyRotationFollowSpeed", 0f, "How quickly the body rotates to the camera yaw. Set to 0 for instant body lock.");
			HideHead = ((BaseUnityPlugin)this).Config.Bind<bool>("Visibility", "HideHead", false, "Hide the local player's head while first-person mode is active. Disabled by default to preserve the normal character shadow.");
			HideHair = ((BaseUnityPlugin)this).Config.Bind<bool>("Visibility", "HideHair", false, "Hide the local player's hair while first-person mode is active. Disabled by default to preserve the normal character shadow.");
			HideFace = ((BaseUnityPlugin)this).Config.Bind<bool>("Visibility", "HideFace", false, "Hide face-related local player renderers while first-person mode is active. Disabled by default to preserve the normal character shadow.");
			HideHelmet = ((BaseUnityPlugin)this).Config.Bind<bool>("Visibility", "HideHelmet", false, "Hide the local player's helmet while first-person mode is active. Disabled by default to preserve the normal character shadow.");
			HideShoulderPads = ((BaseUnityPlugin)this).Config.Bind<bool>("Visibility", "HideShoulderPads", false, "Hide shoulder-related renderers if armor clips into the camera.");
			HideBackItems = ((BaseUnityPlugin)this).Config.Bind<bool>("Visibility", "HideBackItems", false, "Hide back, cape, and cloak renderers if they clip into the camera.");
			ForceBodyVisible = ((BaseUnityPlugin)this).Config.Bind<bool>("Visibility", "ForceBodyVisible", true, "Force the local player and non-head renderers visible while first-person mode is active.");
			HeadHideModeConfig = ((BaseUnityPlugin)this).Config.Bind<HeadHideModeOption>("Visibility", "HeadHideMode", HeadHideModeOption.BoneShrink, "Optional fallback for head clipping. RendererDisable hides matched renderers. BoneShrink scales matched head bones down.");
			EnableDebugLogs = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "EnableDebugLogs", false, "Enable extra logs for camera, state, visibility, and cleanup behavior.");
			LogRendererNames = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogRendererNames", false, "Log local player renderer names, paths, materials, and enabled states once when first-person mode activates.");
			_harmony = new Harmony("com.geronimo.valheim.immersivefirstperson");
			_harmony.PatchAll();
			Log.LogInfo((object)"Immersive First Person 1.0.0 loaded.");
		}

		internal static void DebugLog(string message)
		{
			ConfigEntry<bool> enableDebugLogs = EnableDebugLogs;
			if (enableDebugLogs != null && enableDebugLogs.Value)
			{
				Log.LogInfo((object)("[Debug] " + message));
			}
		}

		private void OnDestroy()
		{
			FirstPersonState.ForceInactive();
			BodyVisibilityController.Reset();
			HeadVisibilityController.ForceVisible();
			FirstPersonCamera.RestoreLastCamera();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class RendererScanner
	{
		private static Player? _loggedPlayer;

		private static bool _logged;

		internal static void LogRenderersOnce(Player player)
		{
			if (!Plugin.EnableDebugLogs.Value || (_logged && (Object)(object)_loggedPlayer == (Object)(object)player))
			{
				return;
			}
			_logged = true;
			_loggedPlayer = player;
			Renderer[] componentsInChildren = ((Component)player).GetComponentsInChildren<Renderer>(true);
			Plugin.Log.LogInfo((object)$"[RendererScanner] Found {componentsInChildren.Length} local player renderers.");
			Renderer[] array = componentsInChildren;
			foreach (Renderer val in array)
			{
				if (!((Object)(object)val == (Object)null))
				{
					string text = (((Object)(object)((Component)val).transform != (Object)null) ? GetPath(((Component)val).transform) : "<no transform>");
					Plugin.Log.LogInfo((object)$"[RendererScanner] name='{((Object)val).name}' path='{text}' type='{((object)val).GetType().Name}' enabled={val.enabled}");
				}
			}
		}

		internal static void ResetLogState()
		{
			_loggedPlayer = null;
			_logged = false;
		}

		internal static string GetPath(Transform transform)
		{
			if ((Object)(object)transform == (Object)null)
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(((Object)transform).name);
			Transform parent = transform.parent;
			while ((Object)(object)parent != (Object)null)
			{
				stringBuilder.Insert(0, ((Object)parent).name + "/");
				parent = parent.parent;
			}
			return stringBuilder.ToString();
		}
	}
}