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 OriginalRendererStates = new Dictionary(); private static readonly List DeadRenderers = new List(); 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(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(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() != (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 OriginalRendererStates = new Dictionary(); private static readonly Dictionary OriginalBoneScales = new Dictionary(); private static readonly List RenderersToRemove = new List(); private static readonly List BonesToRemove = new List(); 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 hashSet = new HashSet(); Renderer[] componentsInChildren = ((Component)player).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && ShouldHideRenderer(val)) { hashSet.Add(val); } } RenderersToRemove.Clear(); foreach (KeyValuePair 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(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 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 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 EnableMod; internal static ConfigEntry ToggleFirstPersonKey; internal static ConfigEntry DefaultToFirstPerson; internal static ConfigEntry UseHeadTrackedAnchor; internal static ConfigEntry CameraVerticalOffset; internal static ConfigEntry CameraForwardOffset; internal static ConfigEntry DownLookExtraForwardOffset; internal static ConfigEntry DownLookExtraVerticalOffset; internal static ConfigEntry CrouchVerticalOffset; internal static ConfigEntry NearClip; internal static ConfigEntry UseCustomFov; internal static ConfigEntry Fov; internal static ConfigEntry SmoothCamera; internal static ConfigEntry CameraSmoothing; internal static ConfigEntry LockBodyToCamera; internal static ConfigEntry BodyRotationFollowSpeed; internal static ConfigEntry HideHead; internal static ConfigEntry HideHair; internal static ConfigEntry HideFace; internal static ConfigEntry HideHelmet; internal static ConfigEntry HideShoulderPads; internal static ConfigEntry HideBackItems; internal static ConfigEntry ForceBodyVisible; internal static ConfigEntry HeadHideModeConfig; internal static ConfigEntry EnableDebugLogs; internal static ConfigEntry 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("General", "EnableMod", true, "Enable Immersive First Person."); ToggleFirstPersonKey = ((BaseUnityPlugin)this).Config.Bind("Input", "ToggleFirstPersonKey", (KeyCode)287, "Press this key to toggle first-person mode."); DefaultToFirstPerson = ((BaseUnityPlugin)this).Config.Bind("Input", "DefaultToFirstPerson", false, "Start in first-person mode when the local player is ready."); UseHeadTrackedAnchor = ((BaseUnityPlugin)this).Config.Bind("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("Camera", "CameraVerticalOffset", 0.04f, "Vertical offset from the selected camera anchor."); CameraForwardOffset = ((BaseUnityPlugin)this).Config.Bind("Camera", "CameraForwardOffset", 0.16f, "Forward offset from the selected camera anchor to keep the torso out of the view."); DownLookExtraForwardOffset = ((BaseUnityPlugin)this).Config.Bind("Camera", "DownLookExtraForwardOffset", 0.16f, "Extra forward offset applied gradually when looking down."); DownLookExtraVerticalOffset = ((BaseUnityPlugin)this).Config.Bind("Camera", "DownLookExtraVerticalOffset", 0.06f, "Extra upward offset applied gradually when looking down."); CrouchVerticalOffset = ((BaseUnityPlugin)this).Config.Bind("Camera", "CrouchVerticalOffset", -0.45f, "Additional vertical camera offset while crouching or sneaking when head tracking is unavailable."); NearClip = ((BaseUnityPlugin)this).Config.Bind("Camera", "NearClip", 0.02f, "Near clipping plane while first-person mode is active."); UseCustomFov = ((BaseUnityPlugin)this).Config.Bind("Camera", "UseCustomFov", true, "Use the configured first-person FOV."); Fov = ((BaseUnityPlugin)this).Config.Bind("Camera", "FOV", 75f, "Field of view while first-person mode is active."); SmoothCamera = ((BaseUnityPlugin)this).Config.Bind("Camera", "SmoothCamera", false, "Optional extra smoothing for camera position. Disabled by default so vanilla mouse behavior is preserved."); CameraSmoothing = ((BaseUnityPlugin)this).Config.Bind("Camera", "CameraSmoothing", 18f, "How quickly the camera moves toward the first-person target if SmoothCamera is enabled."); LockBodyToCamera = ((BaseUnityPlugin)this).Config.Bind("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("Camera", "BodyRotationFollowSpeed", 0f, "How quickly the body rotates to the camera yaw. Set to 0 for instant body lock."); HideHead = ((BaseUnityPlugin)this).Config.Bind("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("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("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("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("Visibility", "HideShoulderPads", false, "Hide shoulder-related renderers if armor clips into the camera."); HideBackItems = ((BaseUnityPlugin)this).Config.Bind("Visibility", "HideBackItems", false, "Hide back, cape, and cloak renderers if they clip into the camera."); ForceBodyVisible = ((BaseUnityPlugin)this).Config.Bind("Visibility", "ForceBodyVisible", true, "Force the local player and non-head renderers visible while first-person mode is active."); HeadHideModeConfig = ((BaseUnityPlugin)this).Config.Bind("Visibility", "HeadHideMode", HeadHideModeOption.BoneShrink, "Optional fallback for head clipping. RendererDisable hides matched renderers. BoneShrink scales matched head bones down."); EnableDebugLogs = ((BaseUnityPlugin)this).Config.Bind("Debug", "EnableDebugLogs", false, "Enable extra logs for camera, state, visibility, and cleanup behavior."); LogRendererNames = ((BaseUnityPlugin)this).Config.Bind("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 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(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) : ""); 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(); } } }