Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ImmersiveFirstPerson v1.2.1
BepInEx/plugins/ImmersiveFirstPerson/ImmersiveFirstPerson.dll
Decompiled 20 minutes agousing 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(); } } }