using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BlacksmithTools; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ImmersiveFirstPerson { internal static class BlacksmithHeadHider { private sealed class BodypartEntrySnapshot { internal bool HadParts { get; set; } internal bool HadBones { get; set; } internal List? Parts { get; set; } internal List? Bones { get; set; } } private const string BodyPartControllerTypeName = "BlacksmithTools.BodyPartController"; private static readonly List EquippedHashes = new List(); private static readonly int[] HeadBoneIndexes = Util.BodyPartToBoneIndexes((bodyPart)0); private static readonly Dictionary EntrySnapshots = new Dictionary(); private static Type? _bodyPartControllerType; private static MethodInfo? _setupMethod; private static MethodInfo? _fullUpdateMethod; private static VisEquipment? _activeVisEquipment; private static string? _activeKey; private static bool _active; private static bool _dirty = true; internal static void RequestRefresh() { _dirty = true; } internal static bool TryApply(Player player, out string failureReason) { failureReason = string.Empty; if (!PlayerEquipmentAccess.TryGetVisEquipment(player, out VisEquipment visEquipment)) { return Fail("local VisEquipment was not found", out failureReason); } if ((Object)(object)_activeVisEquipment != (Object)null && (Object)(object)_activeVisEquipment != (Object)(object)visEquipment) { Restore(); } if (!CanUseBlacksmith(visEquipment, out failureReason)) { return false; } if (!TrySelectBodypartRuleKey(visEquipment, out string ruleKey, out failureReason)) { return false; } if (_active && !_dirty && (Object)(object)_activeVisEquipment == (Object)(object)visEquipment && string.Equals(_activeKey, ruleKey, StringComparison.Ordinal)) { return true; } if (_activeKey != null && !string.Equals(_activeKey, ruleKey, StringComparison.Ordinal)) { RestoreBodypartEntries(); _active = false; _activeKey = null; } RegisterHeadRule(ruleKey); if (!EnsureController(visEquipment, out Component controller, out failureReason)) { RestoreAfterFailedApply(visEquipment); return false; } if ((Object)(object)controller == (Object)null || !TriggerBodyControllerUpdate(visEquipment, controller, out failureReason)) { RestoreAfterFailedApply(visEquipment); return false; } if (!ValidateHeadHidden(visEquipment, out failureReason)) { RestoreAfterFailedApply(visEquipment); return false; } _activeVisEquipment = visEquipment; _activeKey = ruleKey; _active = true; _dirty = false; Plugin.HeadHidingDebugLog("Blacksmith head hide active using rule key '" + ruleKey + "'."); return true; } internal static void Restore() { VisEquipment activeVisEquipment = _activeVisEquipment; RestoreBodypartEntries(); if ((Object)(object)activeVisEquipment != (Object)null && TryGetController(activeVisEquipment, out Component controller) && (Object)(object)controller != (Object)null) { if (TriggerBodyControllerUpdate(activeVisEquipment, controller, out string failureReason)) { Plugin.HeadHidingDebugLog("Restored Blacksmith body mesh after first-person exit."); } else { Plugin.HeadHidingDebugLog("Blacksmith restore update failed: " + failureReason + "."); } } _activeVisEquipment = null; _activeKey = null; _active = false; _dirty = true; } private static bool CanUseBlacksmith(VisEquipment visEquipment, out string failureReason) { failureReason = string.Empty; if (Main.bodyHidingEnabled == null || !Main.bodyHidingEnabled.Value) { return Fail("Blacksmith bodypart hiding is disabled", out failureReason); } if ((Object)(object)visEquipment.m_bodyModel == (Object)null) { return Fail("VisEquipment has no body model renderer", out failureReason); } Mesh sharedMesh = visEquipment.m_bodyModel.sharedMesh; if ((Object)(object)sharedMesh == (Object)null) { return Fail("body model has no shared mesh", out failureReason); } if (!sharedMesh.isReadable) { return Fail("body model mesh is unreadable", out failureReason); } return true; } private static bool TrySelectBodypartRuleKey(VisEquipment visEquipment, out string ruleKey, out string failureReason) { ruleKey = string.Empty; failureReason = string.Empty; int helmetHash = PlayerEquipmentAccess.GetHelmetHash(visEquipment); if (helmetHash != 0 && PlayerEquipmentAccess.TryGetPrefabName(helmetHash, out ruleKey)) { return true; } PlayerEquipmentAccess.GetEquippedHashes(visEquipment, EquippedHashes); foreach (int equippedHash in EquippedHashes) { if (equippedHash == 0 || !PlayerEquipmentAccess.TryGetPrefabName(equippedHash, out ruleKey)) { continue; } return true; } return Fail("no stable equipped item prefab name was available for Blacksmith body hiding", out failureReason); } private static void RegisterHeadRule(string ruleKey) { CaptureEntrySnapshot(ruleKey); List value; List list = (BodypartSystem.bodypartSettings.TryGetValue(ruleKey, out value) ? new List(value) : new List()); if (!list.Contains((bodyPart)0)) { list.Add((bodyPart)0); } BodypartSystem.bodypartSettings[ruleKey] = list; List value2; List list2 = (BodypartSystem.bodypartSettingsAsBones.TryGetValue(ruleKey, out value2) ? new List(value2) : new List()); int[] headBoneIndexes = HeadBoneIndexes; foreach (int item in headBoneIndexes) { if (!list2.Contains(item)) { list2.Add(item); } } BodypartSystem.bodypartSettingsAsBones[ruleKey] = list2; Plugin.HeadHidingDebugLog("Registered temporary Blacksmith Head rule for '" + ruleKey + "'."); } private static void CaptureEntrySnapshot(string ruleKey) { if (!EntrySnapshots.ContainsKey(ruleKey)) { List value; bool flag = BodypartSystem.bodypartSettings.TryGetValue(ruleKey, out value); List value2; bool flag2 = BodypartSystem.bodypartSettingsAsBones.TryGetValue(ruleKey, out value2); EntrySnapshots[ruleKey] = new BodypartEntrySnapshot { HadParts = flag, HadBones = flag2, Parts = (flag ? new List(value) : null), Bones = (flag2 ? new List(value2) : null) }; } } private static bool EnsureController(VisEquipment visEquipment, out Component? controller, out string failureReason) { controller = null; failureReason = string.Empty; if (!TryResolveBodyPartControllerType(out Type controllerType, out failureReason)) { return false; } controller = ((Component)visEquipment).GetComponent(controllerType); if ((Object)(object)controller != (Object)null) { return true; } if (!CanUseBlacksmith(visEquipment, out failureReason)) { return false; } try { controller = ((Component)visEquipment).gameObject.AddComponent(controllerType); MethodInfo setupMethod = GetSetupMethod(controllerType); if (setupMethod == null) { return Fail("Blacksmith BodyPartController.Setup method was not found", out failureReason); } setupMethod.Invoke(controller, new object[1] { visEquipment }); Plugin.HeadHidingDebugLog("Attached missing Blacksmith BodyPartController to local VisEquipment."); return true; } catch (Exception ex) { return Fail("Blacksmith BodyPartController setup failed: " + ex.GetType().Name, out failureReason); } } private static bool TryGetController(VisEquipment visEquipment, out Component? controller) { controller = null; if (!TryResolveBodyPartControllerType(out Type controllerType, out string _)) { return false; } controller = (((Object)(object)visEquipment != (Object)null) ? ((Component)visEquipment).GetComponent(controllerType) : null); return (Object)(object)controller != (Object)null; } private static bool TriggerBodyControllerUpdate(VisEquipment visEquipment, Component controller, out string failureReason) { failureReason = string.Empty; try { MethodInfo fullUpdateMethod = GetFullUpdateMethod(((object)controller).GetType()); if (fullUpdateMethod == null) { return Fail("Blacksmith BodyPartController.FullUpdate method was not found", out failureReason); } fullUpdateMethod.Invoke(controller, Array.Empty()); ApplyCurrentModelMesh(visEquipment); return true; } catch (Exception ex) { return Fail("Blacksmith BodyPartController update failed: " + ex.GetType().Name, out failureReason); } } private static void ApplyCurrentModelMesh(VisEquipment visEquipment) { if ((Object)(object)visEquipment == (Object)null || (Object)(object)visEquipment.m_bodyModel == (Object)null || visEquipment.m_models == null) { return; } int modelIndex = visEquipment.GetModelIndex(); if (modelIndex >= 0 && modelIndex < visEquipment.m_models.Length) { Mesh mesh = visEquipment.m_models[modelIndex].m_mesh; if ((Object)(object)mesh != (Object)null && (Object)(object)visEquipment.m_bodyModel.sharedMesh != (Object)(object)mesh) { visEquipment.m_bodyModel.sharedMesh = mesh; } } } private static bool ValidateHeadHidden(VisEquipment visEquipment, out string failureReason) { failureReason = string.Empty; if ((Object)(object)visEquipment.m_bodyModel == (Object)null) { return Fail("body model renderer disappeared during Blacksmith validation", out failureReason); } Mesh sharedMesh = visEquipment.m_bodyModel.sharedMesh; if ((Object)(object)sharedMesh == (Object)null) { return Fail("body model mesh disappeared during Blacksmith validation", out failureReason); } if (!sharedMesh.isReadable) { return Fail("body model mesh became unreadable during Blacksmith validation", out failureReason); } if (sharedMesh.boneWeights == null || sharedMesh.boneWeights.Length == 0) { return Fail("body model mesh has no bone weights for Blacksmith validation", out failureReason); } if (MeshContainsDominantHeadTriangles(sharedMesh)) { return Fail("Blacksmith update left dominant head-weighted body triangles visible", out failureReason); } return true; } private static bool MeshContainsDominantHeadTriangles(Mesh mesh) { BoneWeight[] boneWeights = mesh.boneWeights; for (int i = 0; i < mesh.subMeshCount; i++) { int[] triangles = mesh.GetTriangles(i); for (int j = 0; j + 2 < triangles.Length; j += 3) { if (TriangleWouldBeRemovedByBlacksmith(boneWeights, triangles, j)) { return true; } } } return false; } private static bool TriangleWouldBeRemovedByBlacksmith(BoneWeight[] boneWeights, int[] triangles, int triangleIndex) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) int num = 0; for (int i = 0; i < 2; i++) { int num2 = triangles[triangleIndex + i]; if (num2 >= 0 && num2 < boneWeights.Length && HasDominantHeadBone(boneWeights[num2]) && ++num == 1) { return true; } } return false; } private static bool HasDominantHeadBone(BoneWeight boneWeight) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Max(new float[4] { ((BoneWeight)(ref boneWeight)).weight0, ((BoneWeight)(ref boneWeight)).weight1, ((BoneWeight)(ref boneWeight)).weight2, ((BoneWeight)(ref boneWeight)).weight3 }); if (num <= 0f) { return false; } for (int i = 0; i < 4; i++) { int boneIndex = GetBoneIndex(boneWeight, i); if (HeadBoneIndexes.Contains(boneIndex)) { float boneWeight2 = GetBoneWeight(boneWeight, i); if (boneWeight2 / num > 0.9f) { return true; } } } return false; } private static int GetBoneIndex(BoneWeight boneWeight, int boneSlot) { if (1 == 0) { } int result = boneSlot switch { 0 => ((BoneWeight)(ref boneWeight)).boneIndex0, 1 => ((BoneWeight)(ref boneWeight)).boneIndex1, 2 => ((BoneWeight)(ref boneWeight)).boneIndex2, 3 => ((BoneWeight)(ref boneWeight)).boneIndex3, _ => -1, }; if (1 == 0) { } return result; } private static float GetBoneWeight(BoneWeight boneWeight, int boneSlot) { if (1 == 0) { } float result = boneSlot switch { 0 => ((BoneWeight)(ref boneWeight)).weight0, 1 => ((BoneWeight)(ref boneWeight)).weight1, 2 => ((BoneWeight)(ref boneWeight)).weight2, 3 => ((BoneWeight)(ref boneWeight)).weight3, _ => 0f, }; if (1 == 0) { } return result; } private static void RestoreAfterFailedApply(VisEquipment visEquipment) { RestoreBodypartEntries(); if (TryGetController(visEquipment, out Component controller) && (Object)(object)controller != (Object)null) { TriggerBodyControllerUpdate(visEquipment, controller, out string _); } _activeVisEquipment = null; _activeKey = null; _active = false; _dirty = true; } private static void RestoreBodypartEntries() { foreach (KeyValuePair entrySnapshot in EntrySnapshots) { string key = entrySnapshot.Key; BodypartEntrySnapshot value = entrySnapshot.Value; if (value.HadParts && value.Parts != null) { BodypartSystem.bodypartSettings[key] = new List(value.Parts); } else { BodypartSystem.bodypartSettings.Remove(key); } if (value.HadBones && value.Bones != null) { BodypartSystem.bodypartSettingsAsBones[key] = new List(value.Bones); } else { BodypartSystem.bodypartSettingsAsBones.Remove(key); } } EntrySnapshots.Clear(); } private static bool TryResolveBodyPartControllerType(out Type controllerType, out string failureReason) { failureReason = string.Empty; if ((object)_bodyPartControllerType == null) { _bodyPartControllerType = typeof(BodypartSystem).Assembly.GetType("BlacksmithTools.BodyPartController", throwOnError: false); } controllerType = _bodyPartControllerType; if (controllerType == null) { return Fail("Blacksmith BodyPartController type was not found", out failureReason); } return true; } private static MethodInfo? GetSetupMethod(Type controllerType) { if ((object)_setupMethod == null) { _setupMethod = controllerType.GetMethod("Setup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } return _setupMethod; } private static MethodInfo? GetFullUpdateMethod(Type controllerType) { if ((object)_fullUpdateMethod == null) { _fullUpdateMethod = controllerType.GetMethod("FullUpdate", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } return _fullUpdateMethod; } private static bool Fail(string reason, out string failureReason) { failureReason = reason; Plugin.HeadHidingDebugLog("Blacksmith head hiding unavailable: " + reason + "."); return false; } } 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; private static readonly string[] HeldItemKeywords = new string[28] { "hand", "right", "left", "weapon", "sword", "axe", "mace", "hammer", "club", "knife", "bow", "arrow", "shield", "torch", "tool", "pickaxe", "hatchet", "battleaxe", "sledge", "spear", "atgeir", "crossbow", "buckler", "staff", "wand", "cultivator", "fishing", "itemstand" }; private static readonly string[] HeadKeywords = new string[7] { "head", "hair", "face", "jaw", "eye", "helmet", "helm" }; 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() { RestoreRendererStates(); OriginalRendererStates.Clear(); DeadRenderers.Clear(); _cachedPlayer = null; _captured = false; } internal static void RequestRefresh() { RestoreRendererStates(); OriginalRendererStates.Clear(); DeadRenderers.Clear(); _captured = false; } private static void CaptureVisibleBodyRenderers(Player player) { OriginalRendererStates.Clear(); Renderer[] componentsInChildren = ((Component)player).GetComponentsInChildren(true); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { if (!((Object)(object)val == (Object)null) && LocalPlayerRendererOwnership.IsLocalPlayerRenderer(player, val) && val.enabled && !LooksLikeHeadRenderer(val)) { OriginalRendererStates[val] = new RendererStateSnapshot(val); } } _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) && !((Object)(object)_cachedPlayer == (Object)null) && LocalPlayerRendererOwnership.IsLocalPlayerRenderer(_cachedPlayer, key) && !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 (ContainsAny(value, HeldItemKeywords)) { return false; } return ContainsAnyHeadKeyword(value); } 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 RestoreRendererStates() { if (OriginalRendererStates.Count == 0) { return; } foreach (KeyValuePair originalRendererState in OriginalRendererStates) { Renderer key = originalRendererState.Key; if (!((Object)(object)key == (Object)null)) { originalRendererState.Value.Restore(key); } } } private static bool ContainsAnyHeadKeyword(string value) { string[] headKeywords = HeadKeywords; foreach (string keyword in headKeywords) { if (ContainsKeywordTokenOrSafePrefix(value, keyword)) { return true; } } return false; } private static bool ContainsKeywordTokenOrSafePrefix(string value, string keyword) { int num = value.IndexOf(keyword, StringComparison.OrdinalIgnoreCase); while (num >= 0) { bool flag = num == 0 || !char.IsLetterOrDigit(value[num - 1]); int num2 = num + keyword.Length; bool flag2 = num2 >= value.Length || !char.IsLetterOrDigit(value[num2]); if (flag && flag2) { return true; } if (flag && keyword.Length >= 5) { return true; } num = value.IndexOf(keyword, num2, StringComparison.OrdinalIgnoreCase); } return false; } 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(); } } internal static class DodgeDirectionController { private const float DodgeQueueBodyLockSuppressSeconds = 0.75f; private static Player? _suppressedPlayer; private static float _suppressBodyLockUntil; internal static void PrepareDodge(Player player, ref Vector3 dodgeDir) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (!ShouldHandleFirstPersonDodge(player)) { return; } if (Plugin.DodgeWhereYouLook.Value) { if (TryGetFlatLookDirection(player, out var lookDirection)) { dodgeDir = lookDirection; } } else { _suppressedPlayer = player; _suppressBodyLockUntil = Time.unscaledTime + 0.75f; } } internal static bool ShouldSkipBodyYawLock(Player player) { if (!ShouldHandleFirstPersonDodge(player) || Plugin.DodgeWhereYouLook.Value) { return false; } if (((Character)player).InDodge()) { return true; } if ((Object)(object)_suppressedPlayer == (Object)(object)player && Time.unscaledTime <= _suppressBodyLockUntil) { return true; } if ((Object)(object)_suppressedPlayer == (Object)(object)player) { ClearSuppression(); } return false; } private static bool ShouldHandleFirstPersonDodge(Player player) { return (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && Plugin.EnableMod.Value && FirstPersonState.ShouldApplyCamera(player); } private static bool TryGetFlatLookDirection(Player player, out Vector3 lookDirection) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) lookDirection = ((Character)player).GetLookDir(); lookDirection.y = 0f; if (((Vector3)(ref lookDirection)).sqrMagnitude <= 0.0001f && (Object)(object)GameCamera.instance != (Object)null) { lookDirection = ((Component)GameCamera.instance).transform.forward; lookDirection.y = 0f; } if (((Vector3)(ref lookDirection)).sqrMagnitude <= 0.0001f) { return false; } ((Vector3)(ref lookDirection)).Normalize(); return true; } private static void ClearSuppression() { _suppressedPlayer = null; _suppressBodyLockUntil = 0f; } } [HarmonyPatch(typeof(Player), "Dodge")] internal static class PlayerDodgeDirectionPatch { private static void Prefix(Player __instance, ref Vector3 dodgeDir) { DodgeDirectionController.PrepareDodge(__instance, ref dodgeDir); } } [HarmonyPatch(typeof(VisEquipment), "SetHelmetEquipped")] internal static class VisEquipmentSetHelmetEquippedPatch { private static void Prefix(VisEquipment __instance) { HelmetShadowController.CaptureHelmetChangeStart(__instance); } private static void Postfix(VisEquipment __instance, bool __result) { HelmetShadowController.CaptureHelmetChangeEnd(__instance, __result); if (__result && PlayerEquipmentAccess.IsLocalPlayerVisEquipment(__instance)) { BodyVisibilityController.RequestRefresh(); HeadVisibilityController.RequestRefresh(); } } } [HarmonyPatch(typeof(VisEquipment), "SetChestEquipped")] internal static class VisEquipmentSetChestEquippedPatch { private static void Postfix(VisEquipment __instance, bool __result) { if (__result && PlayerEquipmentAccess.IsLocalPlayerVisEquipment(__instance)) { BodyVisibilityController.RequestRefresh(); HeadVisibilityController.RequestRefresh(); } } } [HarmonyPatch(typeof(VisEquipment), "SetShoulderEquipped")] internal static class VisEquipmentSetShoulderEquippedPatch { private static void Postfix(VisEquipment __instance, bool __result) { if (__result && PlayerEquipmentAccess.IsLocalPlayerVisEquipment(__instance)) { BodyVisibilityController.RequestRefresh(); HeadVisibilityController.RequestRefresh(); } } } [HarmonyPatch(typeof(VisEquipment), "SetLegEquipped")] internal static class VisEquipmentSetLegEquippedPatch { private static void Postfix(VisEquipment __instance, bool __result) { if (__result && PlayerEquipmentAccess.IsLocalPlayerVisEquipment(__instance)) { BodyVisibilityController.RequestRefresh(); HeadVisibilityController.RequestRefresh(); } } } [HarmonyPatch(typeof(VisEquipment), "SetModel")] internal static class VisEquipmentSetModelPatch { private static void Postfix(VisEquipment __instance) { if (PlayerEquipmentAccess.IsLocalPlayerVisEquipment(__instance)) { BodyVisibilityController.RequestRefresh(); HeadVisibilityController.RequestRefresh(); } } } [HarmonyPatch(typeof(Player), "OnDestroy")] internal static class PlayerOnDestroyVisibilityPatch { private static void Prefix(Player __instance) { if ((Object)(object)__instance != (Object)null && (Object)(object)__instance == (Object)(object)Player.m_localPlayer) { FirstPersonState.ForceInactive(); } } } [HarmonyPatch(typeof(GameCamera))] [HarmonyPatch("UpdateCamera")] internal static class GameCameraUpdatePatch { private static void Postfix(GameCamera __instance) { FirstPersonCamera.Update(__instance); } } internal static class FirstPersonCamera { private const float HeadBobFilterSpeed = 3f; private static readonly string[] CameraEffectTypeNameFragments = new string[8] { "PostProcessLayer", "PostProcessVolume", "Bloom", "DepthOfField", "MotionBlur", "ScreenSpaceAmbientOcclusion", "SunShafts", "Vignette" }; private static readonly FieldInfo? CameraField = AccessTools.Field(typeof(GameCamera), "m_camera"); private static readonly MethodInfo? IsCrouchingMethod = FindInstanceMethod(typeof(Player), "IsCrouching") ?? FindInstanceMethod(typeof(Character), "IsCrouching"); private static readonly MethodInfo? InSneakMethod = FindInstanceMethod(typeof(Player), "InSneak") ?? FindInstanceMethod(typeof(Character), "InSneak"); 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 Camera? _savedCamera; private static Player? _cachedAnchorPlayer; private static Transform? _cachedHeadAnchor; private static float _originalFov; private static float _originalNearClip; private static bool _originalUseOcclusionCulling; private static bool _savedOriginals; private static bool _loggedRenderingState; private static float _originalShadowDistance; private static int _originalShadowCascades; private static readonly Dictionary CameraEffectStates = new Dictionary(); private static Player? _filteredHeadPlayer; private static Vector3 _filteredLocalHeadPosition; private static bool _hasFilteredHeadPosition; private static Player? _attachedCameraPlayer; private static Vector3 _attachedLocalCameraPosition; private static bool _hasAttachedCameraPosition; private static float _currentShoulderPeekOffset; private static int _toggledShoulderPeekDirection; internal static void Update(GameCamera gameCamera) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)gameCamera == (Object)null) { ResetShoulderPeek(); return; } Camera val = GetCamera(gameCamera) ?? Camera.main; if ((Object)(object)val == (Object)null) { ResetShoulderPeek(); return; } _lastCamera = val; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || !FirstPersonState.ShouldApplyCamera(localPlayer)) { RestoreCamera(val); ResetHeadBobFilter(); ResetAttachedCameraLock(); ResetShoulderPeek(); RestoreLocalVisibilityForSuppressedCamera(); return; } Quaternion rotation = ((Component)gameCamera).transform.rotation; bool flag = ShouldLockAttachedCamera(localPlayer); if (!flag) { ResetAttachedCameraLock(); } if (Plugin.LockBodyToCamera.Value && !flag && !DodgeDirectionController.ShouldSkipBodyYawLock(localPlayer)) { LockBodyYawToCamera(localPlayer, rotation); } SaveOriginalCameraValues(val); ApplyFirstPersonCamera(gameCamera, val, localPlayer, rotation, flag); LocalPlayerVisibilityOverride.ForceVisible(localPlayer); BodyVisibilityController.Update(localPlayer); HeadVisibilityController.Update(localPlayer); } internal static void RestoreLastCamera() { if ((Object)(object)_lastCamera != (Object)null) { RestoreCamera(_lastCamera); } ResetHeadBobFilter(); ResetAttachedCameraLock(); ResetShoulderPeek(); 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 MethodInfo? FindInstanceMethod(Type type, string name) { return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } private static void SaveOriginalCameraValues(Camera camera) { if (!_savedOriginals || !((Object)(object)_savedCamera == (Object)(object)camera)) { if (_savedOriginals && (Object)(object)_savedCamera != (Object)null) { RestoreCamera(_savedCamera); } _savedCamera = camera; _originalFov = camera.fieldOfView; _originalNearClip = camera.nearClipPlane; _originalUseOcclusionCulling = camera.useOcclusionCulling; _originalShadowDistance = QualitySettings.shadowDistance; _originalShadowCascades = QualitySettings.shadowCascades; _savedOriginals = true; LogRenderingState(); } } private static void LogRenderingState() { if (!_loggedRenderingState) { _loggedRenderingState = true; Plugin.Log.LogInfo((object)$"First-person rendering state: view distance unchanged; LOD unchanged; originalNearClip={_originalNearClip:0.###}; requestedNearClip={Plugin.NearClip.Value:0.###}; originalShadowDistance={_originalShadowDistance:0.###}; shadowDistanceCap={Plugin.FirstPersonShadowDistance.Value:0.###}; originalShadowCascades={_originalShadowCascades}; shadowCascadesCap={Plugin.FirstPersonShadowCascades.Value}; originalOcclusionCulling={_originalUseOcclusionCulling}; firstPersonOcclusionCulling=false; cameraEffectsDisabled={Plugin.DisableCameraEffects.Value}."); } } private static void RestoreLocalVisibilityForSuppressedCamera() { if (FirstPersonState.Active) { BodyVisibilityController.Reset(); HeadVisibilityController.ForceVisible(); } } private static bool ShouldLockAttachedCamera(Player player) { return Plugin.LockCameraWhileAttached.Value && ((Character)player).IsAttached(); } private static void LockBodyYawToCamera(Player player, Quaternion cameraRotation) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0084: 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, bool lockAttachedCamera) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //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_0070: 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) //IL_0076: 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) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_011e: 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_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: 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_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) if (lockAttachedCamera) { ApplyAttachedCamera(gameCamera, camera, player, vanillaCameraRotation); return; } Transform cameraAnchor = GetCameraAnchor(player); bool flag = (Object)(object)cameraAnchor != (Object)null && Plugin.UseHeadTrackedAnchor.Value && (Object)(object)cameraAnchor != (Object)(object)((Character)player).m_eye; bool flag2 = IsCrouchingOrSneaking(player); Vector3 headBobScaledAnchorPosition = GetHeadBobScaledAnchorPosition(player, cameraAnchor, flag); headBobScaledAnchorPosition += Vector3.up * Plugin.CameraVerticalOffset.Value; Vector3 val = vanillaCameraRotation * Vector3.forward; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude > 0.0001f) { headBobScaledAnchorPosition += ((Vector3)(ref val)).normalized * Plugin.CameraForwardOffset.Value; } float num = Mathf.Clamp01(Vector3.Dot(vanillaCameraRotation * Vector3.forward, Vector3.down)); if (num > 0f) { if (((Vector3)(ref val)).sqrMagnitude > 0.0001f) { headBobScaledAnchorPosition += ((Vector3)(ref val)).normalized * Plugin.DownLookExtraForwardOffset.Value * num; } headBobScaledAnchorPosition += Vector3.up * Plugin.DownLookExtraVerticalOffset.Value * num; } if (!flag && flag2) { headBobScaledAnchorPosition += Vector3.up * Plugin.CrouchVerticalOffset.Value; } headBobScaledAnchorPosition = ApplyShoulderPeek(headBobScaledAnchorPosition, vanillaCameraRotation); ApplyCameraState(gameCamera, camera, headBobScaledAnchorPosition, vanillaCameraRotation); } private static void ApplyAttachedCamera(GameCamera gameCamera, Camera camera, Player player, Quaternion vanillaCameraRotation) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002d: 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) ResetHeadBobFilter(); Quaternion rotation = ((Component)player).transform.rotation; Quaternion limitedAttachedCameraRotation = GetLimitedAttachedCameraRotation(rotation, vanillaCameraRotation); Vector3 attachedCameraPosition = GetAttachedCameraPosition(player, rotation); attachedCameraPosition = ApplyShoulderPeek(attachedCameraPosition, limitedAttachedCameraRotation); ApplyCameraState(gameCamera, camera, attachedCameraPosition, limitedAttachedCameraRotation); } private static Vector3 ApplyShoulderPeek(Vector3 eyePosition, Quaternion cameraRotation) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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) if (!Plugin.EnableShoulderPeek.Value) { ResetShoulderPeek(); return eyePosition; } int shoulderPeekDirection = GetShoulderPeekDirection(); float targetOffset = (float)shoulderPeekDirection * Mathf.Max(0f, Plugin.ShoulderPeekOffset.Value); _currentShoulderPeekOffset = SmoothShoulderPeekOffset(targetOffset); if (Mathf.Abs(_currentShoulderPeekOffset) <= 0.0001f) { return eyePosition; } return eyePosition + cameraRotation * Vector3.right * _currentShoulderPeekOffset; } private static int GetShoulderPeekDirection() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) bool keyDown = Input.GetKeyDown(Plugin.PeekLeftKey.Value); bool keyDown2 = Input.GetKeyDown(Plugin.PeekRightKey.Value); bool key = Input.GetKey(Plugin.PeekLeftKey.Value); bool key2 = Input.GetKey(Plugin.PeekRightKey.Value); if (Plugin.ShoulderPeekMode.Value == ShoulderPeekMode.Toggle) { if (keyDown) { _toggledShoulderPeekDirection = ((_toggledShoulderPeekDirection != -1) ? (-1) : 0); } if (keyDown2) { _toggledShoulderPeekDirection = ((_toggledShoulderPeekDirection != 1) ? 1 : 0); } return _toggledShoulderPeekDirection; } if (key && !key2) { return -1; } if (key2 && !key) { return 1; } return 0; } private static float SmoothShoulderPeekOffset(float targetOffset) { float num = Mathf.Max(0f, Plugin.ShoulderPeekSpeed.Value); if (num <= 0f) { return targetOffset; } float num2 = Mathf.Min(Time.unscaledDeltaTime, 0.05f); float num3 = 1f - Mathf.Exp((0f - num) * num2); return Mathf.Lerp(_currentShoulderPeekOffset, targetOffset, num3); } private static void ResetShoulderPeek() { _toggledShoulderPeekDirection = 0; _currentShoulderPeekOffset = 0f; } private static Vector3 GetAttachedCameraPosition(Player player, Quaternion bodyRotation) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0078: 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_008c: 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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_attachedCameraPlayer != (Object)(object)player) { ResetAttachedCameraLock(); } if (!_hasAttachedCameraPosition) { _attachedCameraPlayer = player; _attachedLocalCameraPosition = CaptureAttachedLocalCameraPosition(player, bodyRotation); _hasAttachedCameraPosition = true; Plugin.DebugLog($"Captured attached camera local position: {_attachedLocalCameraPosition}"); } Vector3 attachedLocalCameraPosition = _attachedLocalCameraPosition; attachedLocalCameraPosition += Vector3.up * Plugin.AttachedCameraExtraVerticalOffset.Value; attachedLocalCameraPosition += Vector3.forward * Plugin.AttachedCameraExtraForwardOffset.Value; return ((Component)player).transform.TransformPoint(attachedLocalCameraPosition); } private static Vector3 CaptureAttachedLocalCameraPosition(Player player, Quaternion bodyRotation) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) Transform cameraAnchor = GetCameraAnchor(player); Vector3 val = (((Object)(object)cameraAnchor != (Object)null) ? cameraAnchor.position : GetFallbackEyePosition(player)); Vector3 val2 = bodyRotation * Vector3.forward; val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude > 0.0001f) { val += ((Vector3)(ref val2)).normalized * Plugin.CameraForwardOffset.Value; } val += Vector3.up * Plugin.CameraVerticalOffset.Value; return ((Component)player).transform.InverseTransformPoint(val); } private static Vector3 GetFallbackEyePosition(Player player) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)((Character)player).m_eye != (Object)null) { return ((Character)player).m_eye.position; } return ((Component)player).transform.position + Vector3.up * 1.6f; } private static Quaternion GetLimitedAttachedCameraRotation(Quaternion bodyRotation, Quaternion vanillaCameraRotation) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: 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_006f: 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) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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) Quaternion val = Quaternion.Inverse(bodyRotation) * vanillaCameraRotation; Vector3 val2 = NormalizeEuler(((Quaternion)(ref val)).eulerAngles); float num = Mathf.Clamp(Plugin.AttachedCameraMaxYaw.Value, 0f, 180f); float num2 = Mathf.Clamp(Plugin.AttachedCameraMaxPitch.Value, 1f, 89f); float num3 = Mathf.Clamp(val2.y, 0f - num, num); float num4 = Mathf.Clamp(val2.x, 0f - num2, num2); return bodyRotation * Quaternion.Euler(num4, num3, 0f); } private static Vector3 NormalizeEuler(Vector3 euler) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) return new Vector3(NormalizeAngle(euler.x), NormalizeAngle(euler.y), NormalizeAngle(euler.z)); } private static float NormalizeAngle(float angle) { angle %= 360f; if (angle > 180f) { angle -= 360f; } else if (angle < -180f) { angle += 360f; } return angle; } private static Vector3 GetHeadBobScaledAnchorPosition(Player player, Transform? anchor, bool hasHeadAnchor) { //IL_0016: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_010e: 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_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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_008e: 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_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00df: 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_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)anchor == (Object)null) { ResetHeadBobFilter(); return GetFallbackEyePosition(player); } if (!hasHeadAnchor) { ResetHeadBobFilter(); return anchor.position; } float num = Mathf.Clamp01(Plugin.HeadBobAmount.Value); Vector3 val = ((Component)player).transform.InverseTransformPoint(anchor.position); if ((Object)(object)_filteredHeadPlayer != (Object)(object)player) { ResetHeadBobFilter(); } if (!_hasFilteredHeadPosition) { _filteredHeadPlayer = player; _filteredLocalHeadPosition = val; _hasFilteredHeadPosition = true; } float num2 = 1f - Mathf.Exp(-3f * Time.unscaledDeltaTime); _filteredLocalHeadPosition = Vector3.Lerp(_filteredLocalHeadPosition, val, num2); if (num >= 0.999f) { return anchor.position; } Vector3 val2 = val - _filteredLocalHeadPosition; Vector3 val3 = _filteredLocalHeadPosition + val2 * num; return ((Component)player).transform.TransformPoint(val3); } private static Transform GetCameraAnchor(Player player) { if (!Plugin.UseHeadTrackedAnchor.Value) { return ((Object)(object)((Character)player).m_eye != (Object)null) ? ((Character)player).m_eye : ((Component)player).transform; } 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; } return ((Object)(object)((Character)player).m_eye != (Object)null) ? ((Character)player).m_eye : ((Component)player).transform; } private static Transform? FindBestHeadAnchor(Player player) { Transform[] componentsInChildren = ((Component)player).GetComponentsInChildren(true); Transform val = null; int num = int.MinValue; Transform[] array = componentsInChildren; foreach (Transform val2 in array) { if (!((Object)(object)val2 == (Object)null)) { int num2 = ScoreHeadAnchor(val2); if (num2 > num) { num = num2; val = val2; } } } return (num > 0) ? val : null; } 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 && CrouchField.GetValue(player) is bool result) { return result; } 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) || 1 == 0) { return false; } value = flag; return true; } private static void ApplyCameraState(GameCamera gameCamera, Camera camera, Vector3 desiredPosition, Quaternion desiredRotation) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) ((Component)gameCamera).transform.position = desiredPosition; ((Component)gameCamera).transform.rotation = desiredRotation; if ((Object)(object)((Component)camera).transform != (Object)(object)((Component)gameCamera).transform) { ((Component)camera).transform.position = desiredPosition; ((Component)camera).transform.rotation = desiredRotation; } if (Plugin.UseCustomFov.Value) { camera.fieldOfView = Mathf.Clamp(Plugin.Fov.Value, 40f, 120f); } else { camera.fieldOfView = _originalFov; } ApplyFirstPersonRendering(camera); } private static void ApplyFirstPersonRendering(Camera camera) { camera.nearClipPlane = Mathf.Clamp(Plugin.NearClip.Value, 0.005f, 0.5f); camera.useOcclusionCulling = false; ApplyShadowOverrides(); ApplyCameraEffectOverrides(camera); } private static void ApplyShadowOverrides() { if (Plugin.FirstPersonShadowDistance.Value >= 0f) { float num = Mathf.Max(0f, Plugin.FirstPersonShadowDistance.Value); QualitySettings.shadowDistance = Mathf.Min(_originalShadowDistance, num); } if (Plugin.FirstPersonShadowCascades.Value >= 0) { int num2 = NormalizeShadowCascades(Plugin.FirstPersonShadowCascades.Value); QualitySettings.shadowCascades = Mathf.Min(_originalShadowCascades, num2); } } private static int NormalizeShadowCascades(int value) { if (value <= 0) { return 0; } if (value <= 2) { return 2; } return 4; } private static void ApplyCameraEffectOverrides(Camera camera) { if (!Plugin.DisableCameraEffects.Value) { RestoreCameraEffectStates(); return; } Behaviour[] components = ((Component)camera).GetComponents(); foreach (Behaviour val in components) { if (!((Object)(object)val == (Object)null) && IsCameraEffectComponent(val)) { if (!CameraEffectStates.ContainsKey(val)) { CameraEffectStates.Add(val, val.enabled); } val.enabled = false; } } } private static bool IsCameraEffectComponent(Behaviour component) { Type type = ((object)component).GetType(); string text = type.FullName ?? type.Name; if (string.Equals(text, "UnityEngine.Rendering.Volume", StringComparison.Ordinal)) { return true; } string[] cameraEffectTypeNameFragments = CameraEffectTypeNameFragments; foreach (string value in cameraEffectTypeNameFragments) { if (text.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } return false; } private static void RestoreCamera(Camera camera) { if (_savedOriginals) { Camera val = (Camera)(((Object)(object)_savedCamera != (Object)null) ? ((object)_savedCamera) : ((object)camera)); if ((Object)(object)val != (Object)null) { val.fieldOfView = _originalFov; val.nearClipPlane = _originalNearClip; val.useOcclusionCulling = _originalUseOcclusionCulling; } QualitySettings.shadowDistance = _originalShadowDistance; QualitySettings.shadowCascades = _originalShadowCascades; RestoreCameraEffectStates(); _savedCamera = null; _savedOriginals = false; } } private static void RestoreCameraEffectStates() { foreach (KeyValuePair cameraEffectState in CameraEffectStates) { if ((Object)(object)cameraEffectState.Key != (Object)null) { cameraEffectState.Key.enabled = cameraEffectState.Value; } } CameraEffectStates.Clear(); } private static void ResetHeadBobFilter() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) _filteredHeadPlayer = null; _hasFilteredHeadPosition = false; _filteredLocalHeadPosition = Vector3.zero; } private static void ResetAttachedCameraLock() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) _attachedCameraPlayer = null; _hasAttachedCameraPosition = false; _attachedLocalCameraPosition = 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_00c2: 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) { return Plugin.EnableMod.Value && Active && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && IsSafePlayerState(player) && CanOverrideCurrentCameraContext(); } 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 !IsAnyBlockingUiVisible(); } private static bool CanOverrideCurrentCameraContext() { if (!Plugin.OverrideForcedThirdPerson.Value) { return !IsAnyBlockingUiVisible() && !IsPlayerAttached(); } if (IsHardCameraBlockVisible()) { return false; } return true; } private static bool IsAnyBlockingUiVisible() { return IsInputBlockingUiVisible() || IsHardCameraBlockVisible(); } private static bool IsInputBlockingUiVisible() { if (InventoryGui.IsVisible()) { return true; } return false; } private static bool IsHardCameraBlockVisible() { if (Menu.IsVisible()) { return true; } if (Minimap.IsOpen()) { return true; } return false; } private static bool IsSafePlayerState(Player player) { if (((Character)player).IsDead()) { return false; } return true; } private static bool IsPlayerAttached() { Player localPlayer = Player.m_localPlayer; return (Object)(object)localPlayer != (Object)null && ((Character)localPlayer).IsAttached(); } } internal static class HeadVisibilityController { private readonly struct TransformState { internal readonly Vector3 LocalPosition; internal readonly Vector3 LocalScale; internal TransformState(Transform transform) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) LocalPosition = transform.localPosition; LocalScale = transform.localScale; } } private const float HeadSlotClipRadius = 0.55f; private const float MaxHeadSlotExtent = 1.15f; private const float MinHeadSlotHeight = 0.9f; private const float MaxHeadSlotHeight = 2.4f; private const float MaxHeadSlotHorizontalDistance = 0.75f; private const float HeadShrinkScale = 0.001f; private const float HeadSlotCompensationScale = 999.99994f; private static readonly Vector3 HeadShrinkVector = Vector3.one * 0.001f; private static readonly Dictionary OriginalRendererStates = new Dictionary(); private static readonly Dictionary OriginalBoneScales = new Dictionary(); private static readonly Dictionary OriginalHeadSlotStates = new Dictionary(); private static readonly HashSet DesiredRenderers = new HashSet(); private static readonly List CachedHeadBones = new List(); private static readonly List ManagedHelmetRenderers = new List(); private static readonly List RenderersToRemove = new List(); private static readonly List BonesToRemove = new List(); private static readonly List HeadSlotTransformsToRemove = new List(); private static Player? _cachedPlayer; private static bool _active; private static bool _headBoneCacheInitialized; private static float _nextRefreshTime; private static string? _lastFallbackReason; private static readonly string[] HeadSlotKeywords = new string[18] { "head", "hair", "beard", "face", "jaw", "eye", "brow", "mouth", "nose", "teeth", "helmet", "helm", "hat", "hood", "circlet", "crown", "headgear", "padded" }; private static readonly string[] HeadSlotHierarchyKeywords = new string[8] { "attach_helmet", "attach_helm", "attach_head", "helmet", "helm", "hair", "beard", "headgear" }; private static readonly string[] HeadBoneRejectKeywords = new string[10] { "helmet", "helm", "hat", "hood", "hair", "beard", "circlet", "crown", "headgear", "padded" }; private static readonly string[] EquipmentSkeletonKeywords = new string[6] { "attach_skin", "attach_armor", "attach_helmet", "attach_helm", "attach_shoulder", "attach_back" }; private static readonly string[] HeldItemKeywords = new string[28] { "hand", "right", "left", "weapon", "sword", "axe", "mace", "hammer", "club", "knife", "bow", "arrow", "shield", "torch", "tool", "pickaxe", "hatchet", "battleaxe", "sledge", "spear", "atgeir", "crossbow", "buckler", "staff", "wand", "cultivator", "fishing", "itemstand" }; private static readonly string[] BodyKeywords = new string[14] { "body", "torso", "chest", "arm", "leg", "foot", "feet", "hand", "player", "character", "human", "male", "female", "skin" }; internal static void Update(Player player) { if (!IsLocalPlayer(player)) { ForceVisible(); return; } bool shouldHide = Plugin.EnableMod.Value && FirstPersonState.Active && Plugin.HideHead.Value; Apply(player, shouldHide); } internal static void ForceVisible() { BlacksmithHeadHider.Restore(); HelmetShadowController.Restore(); RestoreHeadSlotStates(); RestoreBoneScales(); RestoreRendererStates(); ResetCache(); } internal static void RequestRefresh() { _nextRefreshTime = 0f; _headBoneCacheInitialized = false; BlacksmithHeadHider.RequestRefresh(); HelmetShadowController.RequestRefresh(); } private static void Apply(Player player, bool shouldHide) { if (!IsLocalPlayer(player)) { ForceVisible(); return; } 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(); OriginalHeadSlotStates.Clear(); DesiredRenderers.Clear(); CachedHeadBones.Clear(); _active = true; _nextRefreshTime = 0f; } HelmetShadowController.Update(player, shouldHide: true); bool flag = Time.unscaledTime >= _nextRefreshTime; if (flag) { RefreshRendererCache(player); _nextRefreshTime = Time.unscaledTime + Mathf.Clamp(Plugin.VisibilityRefreshInterval.Value, 0.1f, 10f); } HideCachedRenderersShadowsOnly(); HelmetShadowController.Update(player, shouldHide: true); if (ShouldUseBlacksmith(player, out string fallbackReason)) { RestoreHeadSlotStates(); RestoreBoneScales(); _lastFallbackReason = null; return; } if (!ShouldUseShrinkFallback(fallbackReason)) { RestoreHeadSlotStates(); RestoreBoneScales(); return; } BlacksmithHeadHider.Restore(); LogFallbackUse(fallbackReason); if (flag || !_headBoneCacheInitialized) { RefreshHeadBoneCache(player); } ApplyCachedHeadBoneScale(); CompensateHeadSlotTransforms(); } private static bool ShouldUseBlacksmith(Player player, out string fallbackReason) { fallbackReason = string.Empty; if (Plugin.HeadHidingMode.Value == HeadHidingMode.ShrinkFallback) { fallbackReason = "ShrinkFallback mode is selected"; return false; } if (BlacksmithHeadHider.TryApply(player, out fallbackReason)) { return true; } if (Plugin.HeadHidingMode.Value == HeadHidingMode.BlacksmithTools) { Plugin.HeadHidingDebugLog("BlacksmithTools mode did not hide the head: " + fallbackReason + "."); return false; } return false; } private static bool ShouldUseShrinkFallback(string fallbackReason) { return Plugin.HeadHidingMode.Value == HeadHidingMode.ShrinkFallback || (Plugin.HeadHidingMode.Value == HeadHidingMode.Auto && !string.IsNullOrEmpty(fallbackReason)); } private static void LogFallbackUse(string fallbackReason) { string text = (string.IsNullOrEmpty(fallbackReason) ? "Blacksmith head hiding was unavailable" : fallbackReason); if (!string.Equals(_lastFallbackReason, text, StringComparison.Ordinal)) { _lastFallbackReason = text; Plugin.Log.LogWarning((object)("Using first-person head shrink fallback because " + text + ".")); } } private static void RefreshRendererCache(Player player) { //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) RestoreHeadSlotStates(); RemoveDestroyedRenderers(); RemoveDestroyedHeadSlotTransforms(); DesiredRenderers.Clear(); Renderer[] componentsInChildren = ((Component)player).GetComponentsInChildren(true); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { if (!((Object)(object)val == (Object)null) && IsRendererOwnedByPlayer(player, val) && !HelmetShadowController.IsManagedRenderer(val) && ShouldHideHeadSlotRenderer(player, val)) { DesiredRenderers.Add(val); } } RenderersToRemove.Clear(); foreach (KeyValuePair originalRendererState in OriginalRendererStates) { Renderer key = originalRendererState.Key; if ((Object)(object)key == (Object)null || !DesiredRenderers.Contains(key)) { RenderersToRemove.Add(key); } } foreach (Renderer item in RenderersToRemove) { if (item != null) { RestoreRenderer(item); OriginalRendererStates.Remove(item); } } RenderersToRemove.Clear(); foreach (Renderer desiredRenderer in DesiredRenderers) { if (!OriginalRendererStates.ContainsKey(desiredRenderer)) { OriginalRendererStates.Add(desiredRenderer, new RendererStateSnapshot(desiredRenderer)); string arg = BuildRendererDescriptor(desiredRenderer); Bounds bounds = desiredRenderer.bounds; Plugin.DebugLog($"First-person head-slot visibility matched renderer: {arg} | bounds={((Bounds)(ref bounds)).size}"); } } } private static bool ShouldHideHeadSlotRenderer(Player player, Renderer renderer) { if (!IsRendererOwnedByPlayer(player, renderer)) { return false; } string text = BuildRendererDescriptor(renderer); if (LooksLikeHeldItem(text)) { return false; } if (LooksLikeFullBodyRenderer(renderer, text)) { return false; } bool flag = ContainsAnyHeadSlotKeyword(text); bool flag2 = LooksLikeHeadSlotHierarchy(renderer, text); bool flag3 = IsSmallRendererNearHeadOrCamera(player, renderer); if (flag2 && (flag || flag3)) { return true; } return flag && flag3; } private static bool IsSmallRendererNearHeadOrCamera(Player player, Renderer renderer) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_0060: 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_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: 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_00ec: Unknown result type (might be due to invalid IL or missing references) Bounds bounds = renderer.bounds; Vector3 size = ((Bounds)(ref bounds)).size; float num = Mathf.Max(new float[3] { size.x, size.y, size.z }); if (num > 1.15f) { return false; } Vector3 val = ((Component)player).transform.InverseTransformPoint(((Bounds)(ref bounds)).center); float y = val.y; if (y < 0.9f || y > 2.4f) { return false; } Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(val.x, val.z); if (((Vector2)(ref val2)).magnitude > 0.75f) { return false; } Camera main = Camera.main; if ((Object)(object)main == (Object)null) { return false; } Vector3 val3 = ((Bounds)(ref bounds)).ClosestPoint(((Component)main).transform.position); return Vector3.Distance(val3, ((Component)main).transform.position) <= 0.55f; } private static string BuildRendererDescriptor(Renderer renderer) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(((Object)renderer).name).Append(' '); if ((Object)(object)((Component)renderer).transform != (Object)null) { stringBuilder.Append(RendererScanner.GetPath(((Component)renderer).transform)).Append(' '); } SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((renderer is SkinnedMeshRenderer) ? renderer : null); if (val != null) { if ((Object)(object)val.sharedMesh != (Object)null) { stringBuilder.Append(((Object)val.sharedMesh).name).Append(' '); } if ((Object)(object)val.rootBone != (Object)null) { stringBuilder.Append(RendererScanner.GetPath(val.rootBone)).Append(' '); } } Material[] sharedMaterials = renderer.sharedMaterials; if (sharedMaterials != null) { Material[] array = sharedMaterials; foreach (Material val2 in array) { if ((Object)(object)val2 != (Object)null) { stringBuilder.Append(((Object)val2).name).Append(' '); } } } return stringBuilder.ToString().ToLowerInvariant(); } private static bool LooksLikeHeadSlotHierarchy(Renderer renderer, string descriptor) { if ((Object)(object)renderer == (Object)null) { return false; } if (ContainsAny(descriptor, HeadSlotHierarchyKeywords)) { return true; } if (IsTransformUnderLikelyHeadBone(((Component)renderer).transform, includeSelf: false)) { return true; } SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((renderer is SkinnedMeshRenderer) ? renderer : null); if (val != null) { if (IsTransformUnderLikelyHeadBone(val.rootBone, includeSelf: true)) { return true; } Transform[] bones = val.bones; if (bones != null) { Transform[] array = bones; foreach (Transform transform in array) { if (IsTransformUnderLikelyHeadBone(transform, includeSelf: true)) { return true; } } } } return false; } private static bool IsTransformUnderLikelyHeadBone(Transform? transform, bool includeSelf) { Transform val = (Transform)(includeSelf ? ((object)transform) : ((object)((transform != null) ? transform.parent : null))); while ((Object)(object)val != (Object)null) { if (IsLikelyCharacterHeadBoneName(((Object)val).name)) { return true; } val = val.parent; } return false; } private static bool IsLikelyCharacterHeadBoneName(string name) { if (name.Equals("head", StringComparison.OrdinalIgnoreCase) || name.Equals("bip_head", StringComparison.OrdinalIgnoreCase) || name.Equals("bip01 head", StringComparison.OrdinalIgnoreCase) || name.Equals("bip01head", StringComparison.OrdinalIgnoreCase) || name.Equals("bip001 head", StringComparison.OrdinalIgnoreCase) || name.Equals("bip001head", StringComparison.OrdinalIgnoreCase)) { return true; } return name.Length > 4 && name.StartsWith("head", StringComparison.OrdinalIgnoreCase) && char.IsDigit(name[4]); } private static bool LooksLikeHeldItem(string descriptor) { return ContainsAny(descriptor, HeldItemKeywords); } private static bool LooksLikeFullBodyRenderer(Renderer renderer, string descriptor) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) Bounds bounds = renderer.bounds; Vector3 size = ((Bounds)(ref bounds)).size; float num = Mathf.Max(new float[3] { size.x, size.y, size.z }); bool flag = ContainsAny(descriptor, BodyKeywords) && !ContainsAnyHeadSlotKeyword(descriptor); bool flag2 = size.y > 1.2f && num > 1.4f; return flag || flag2; } 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 bool ContainsAnyHeadSlotKeyword(string value) { string[] headSlotKeywords = HeadSlotKeywords; foreach (string keyword in headSlotKeywords) { if (ContainsKeywordTokenOrSafePrefix(value, keyword)) { return true; } } return false; } private static bool ContainsAnyHeadBoneRejectKeyword(string value) { string[] headBoneRejectKeywords = HeadBoneRejectKeywords; foreach (string keyword in headBoneRejectKeywords) { if (ContainsKeywordTokenOrSafePrefix(value, keyword)) { return true; } } return false; } private static bool ContainsKeywordTokenOrSafePrefix(string value, string keyword) { int num = value.IndexOf(keyword, StringComparison.OrdinalIgnoreCase); while (num >= 0) { bool flag = num == 0 || !char.IsLetterOrDigit(value[num - 1]); int num2 = num + keyword.Length; bool flag2 = num2 >= value.Length || !char.IsLetterOrDigit(value[num2]); if (flag && flag2) { return true; } if (flag && keyword.Length >= 5) { return true; } num = value.IndexOf(keyword, num2, StringComparison.OrdinalIgnoreCase); } return false; } private static void HideCachedRenderersShadowsOnly() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Invalid comparison between Unknown and I4 foreach (Renderer key in OriginalRendererStates.Keys) { if (!((Object)(object)key == (Object)null) && IsCachedLocalRenderer(key)) { if (!key.enabled) { key.enabled = true; } if ((int)key.shadowCastingMode != 3) { key.shadowCastingMode = (ShadowCastingMode)3; } } } } private static void RefreshHeadBoneCache(Player player) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) RemoveDestroyedBones(); _headBoneCacheInitialized = true; Transform[] componentsInChildren = ((Component)player).GetComponentsInChildren(true); Transform[] array = componentsInChildren; foreach (Transform val in array) { if (!((Object)(object)val == (Object)null) && IsHeadBone(val) && IsTransformOwnedByPlayer(player, val) && !OriginalBoneScales.ContainsKey(val)) { OriginalBoneScales.Add(val, val.localScale); CachedHeadBones.Add(val); Plugin.DebugLog("Shrinking local head bone for first-person view: " + RendererScanner.GetPath(val)); } } } private static void ApplyCachedHeadBoneScale() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) RemoveDestroyedBones(); foreach (Transform cachedHeadBone in CachedHeadBones) { if (!((Object)(object)cachedHeadBone == (Object)null) && IsCachedLocalTransform(cachedHeadBone) && cachedHeadBone.localScale != HeadShrinkVector) { cachedHeadBone.localScale = HeadShrinkVector; } } } private static bool IsHeadBone(Transform transform) { string value = (((Object)transform).name + " " + RendererScanner.GetPath(transform)).ToLowerInvariant(); if (ContainsAny(value, EquipmentSkeletonKeywords)) { return false; } if (ContainsAny(value, HeldItemKeywords)) { return false; } if (ContainsAnyHeadBoneRejectKeyword(value)) { return false; } string name = ((Object)transform).name; return IsLikelyCharacterHeadBoneName(name) || ContainsKeywordTokenOrSafePrefix(name, "head"); } private static void CompensateHeadSlotTransforms() { foreach (Renderer key in OriginalRendererStates.Keys) { if (!((Object)(object)key == (Object)null) && IsCachedLocalRenderer(key)) { CompensateTransformIfUnderShrunkHead(((Component)key).transform); SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((key is SkinnedMeshRenderer) ? key : null); if (val != null && (Object)(object)val.rootBone != (Object)null) { CompensateTransformIfUnderShrunkHead(val.rootBone); } } } HelmetShadowController.CopyManagedRenderers(ManagedHelmetRenderers); foreach (Renderer managedHelmetRenderer in ManagedHelmetRenderers) { if (!((Object)(object)managedHelmetRenderer == (Object)null)) { CompensateTransformIfUnderShrunkHead(((Component)managedHelmetRenderer).transform); SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((managedHelmetRenderer is SkinnedMeshRenderer) ? managedHelmetRenderer : null); if (val2 != null && (Object)(object)val2.rootBone != (Object)null) { CompensateTransformIfUnderShrunkHead(val2.rootBone); } } } RemoveDestroyedHeadSlotTransforms(); } private static void CompensateTransformIfUnderShrunkHead(Transform transform) { if (IsCachedLocalTransform(transform)) { Transform val = FindCompensationRootUnderShrunkHead(transform); if (!((Object)(object)val == (Object)null)) { CompensateHeadSlotTransform(val, compensatePosition: true, "position and scale"); } } } private static void CompensateHeadSlotTransform(Transform transform, bool compensatePosition, string mode) { //IL_006f: 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) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: 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_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_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if (IsCachedLocalTransform(transform)) { if (!OriginalHeadSlotStates.ContainsKey(transform)) { OriginalHeadSlotStates.Add(transform, new TransformState(transform)); Plugin.DebugLog("Compensating head-slot transform " + mode + ": " + RendererScanner.GetPath(transform)); } TransformState transformState = OriginalHeadSlotStates[transform]; Vector3 val = (compensatePosition ? (transformState.LocalPosition * 999.99994f) : transformState.LocalPosition); Vector3 val2 = transformState.LocalScale * 999.99994f; if (transform.localPosition != val) { transform.localPosition = val; } if (transform.localScale != val2) { transform.localScale = val2; } } } private static Transform? FindCompensationRootUnderShrunkHead(Transform transform) { if ((Object)(object)transform == (Object)null || !IsCachedLocalTransform(transform) || OriginalBoneScales.ContainsKey(transform)) { return null; } Transform val = transform; while ((Object)(object)val.parent != (Object)null) { if (OriginalBoneScales.ContainsKey(val.parent)) { return val; } val = val.parent; } return null; } private static void RestoreRendererStates() { if (OriginalRendererStates.Count == 0) { return; } foreach (KeyValuePair originalRendererState in OriginalRendererStates) { Renderer key = originalRendererState.Key; if (!((Object)(object)key == (Object)null)) { originalRendererState.Value.Restore(key); } } OriginalRendererStates.Clear(); RenderersToRemove.Clear(); _nextRefreshTime = 0f; } private static void RestoreRenderer(Renderer renderer) { if (!((Object)(object)renderer == (Object)null) && OriginalRendererStates.TryGetValue(renderer, out var value)) { value.Restore(renderer); } } private static void RestoreBoneScales() { //IL_0045: 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(); CachedHeadBones.Clear(); BonesToRemove.Clear(); } private static void RestoreHeadSlotStates() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (OriginalHeadSlotStates.Count == 0) { return; } foreach (KeyValuePair originalHeadSlotState in OriginalHeadSlotStates) { Transform key = originalHeadSlotState.Key; if (!((Object)(object)key == (Object)null)) { key.localPosition = originalHeadSlotState.Value.LocalPosition; key.localScale = originalHeadSlotState.Value.LocalScale; } } OriginalHeadSlotStates.Clear(); HeadSlotTransformsToRemove.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(); for (int num = CachedHeadBones.Count - 1; num >= 0; num--) { Transform val = CachedHeadBones[num]; if (!((Object)(object)val != (Object)null)) { CachedHeadBones.RemoveAt(num); if (val != null) { OriginalBoneScales.Remove(val); } } } 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); CachedHeadBones.Remove(item); } } BonesToRemove.Clear(); } private static void RemoveDestroyedHeadSlotTransforms() { HeadSlotTransformsToRemove.Clear(); foreach (Transform key in OriginalHeadSlotStates.Keys) { if ((Object)(object)key == (Object)null) { HeadSlotTransformsToRemove.Add(key); } } foreach (Transform item in HeadSlotTransformsToRemove) { if (item != null) { OriginalHeadSlotStates.Remove(item); } } HeadSlotTransformsToRemove.Clear(); } private static bool IsLocalPlayer(Player player) { return (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer; } private static bool IsRendererOwnedByPlayer(Player player, Renderer renderer) { return LocalPlayerRendererOwnership.IsLocalPlayerRenderer(player, renderer); } private static bool IsTransformOwnedByPlayer(Player player, Transform transform) { return LocalPlayerRendererOwnership.IsLocalPlayerTransform(player, transform); } private static bool IsCachedLocalRenderer(Renderer renderer) { return (Object)(object)_cachedPlayer != (Object)null && IsRendererOwnedByPlayer(_cachedPlayer, renderer); } private static bool IsCachedLocalTransform(Transform transform) { return (Object)(object)_cachedPlayer != (Object)null && IsTransformOwnedByPlayer(_cachedPlayer, transform); } private static void ResetCache() { _cachedPlayer = null; _active = false; _headBoneCacheInitialized = false; _nextRefreshTime = 0f; OriginalRendererStates.Clear(); OriginalBoneScales.Clear(); OriginalHeadSlotStates.Clear(); DesiredRenderers.Clear(); CachedHeadBones.Clear(); ManagedHelmetRenderers.Clear(); RenderersToRemove.Clear(); BonesToRemove.Clear(); HeadSlotTransformsToRemove.Clear(); _lastFallbackReason = null; } } internal static class HelmetShadowController { private const float MaxHelmetRendererExtent = 1.35f; private const float MinHelmetRendererHeight = 0.85f; private const float MaxHelmetRendererHeight = 2.45f; private const float MaxHelmetHorizontalDistance = 0.9f; private static readonly FieldInfo? HelmetItemInstanceField = AccessTools.Field(typeof(VisEquipment), "m_helmetItemInstance"); private static readonly Dictionary OriginalRendererStates = new Dictionary(); private static readonly Dictionary> HelmetChangeSnapshots = new Dictionary>(); private static readonly HashSet DesiredRenderers = new HashSet(); private static readonly HashSet ObservedHelmetRenderers = new HashSet(); private static readonly HashSet WarnedUnsafeRenderers = new HashSet(); private static readonly HashSet HelmetPrefabFingerprints = new HashSet(); private static readonly List RenderersToRemove = new List(); private static readonly List SnapshotsToRemove = new List(); private static Player? _cachedPlayer; private static bool _active; private static bool _dirty = true; private static float _nextRefreshTime; private static readonly string[] FullBodyKeywords = new string[14] { "body", "torso", "chest", "arm", "leg", "foot", "feet", "hand", "player", "character", "human", "male", "female", "skin" }; private static readonly string[] HeadSlotKeywords = new string[10] { "head", "helmet", "helm", "hat", "hood", "hair", "beard", "circlet", "crown", "headgear" }; internal static void RequestRefresh() { _dirty = true; _nextRefreshTime = 0f; } internal static void Update(Player player, bool shouldHide) { if (!shouldHide || (Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer) { Restore(); return; } if ((Object)(object)_cachedPlayer != (Object)null && (Object)(object)_cachedPlayer != (Object)(object)player) { Restore(); } _cachedPlayer = player; if (!_active) { OriginalRendererStates.Clear(); DesiredRenderers.Clear(); _active = true; _dirty = true; _nextRefreshTime = 0f; } if (_dirty || Time.unscaledTime >= _nextRefreshTime) { RefreshRendererCache(player); _dirty = false; _nextRefreshTime = Time.unscaledTime + Mathf.Clamp(Plugin.VisibilityRefreshInterval.Value, 0.1f, 10f); } ApplyShadowsOnly(); } internal static void Restore() { RestoreRendererStates(); DesiredRenderers.Clear(); ObservedHelmetRenderers.Clear(); HelmetChangeSnapshots.Clear(); WarnedUnsafeRenderers.Clear(); HelmetPrefabFingerprints.Clear(); RenderersToRemove.Clear(); SnapshotsToRemove.Clear(); _cachedPlayer = null; _active = false; _dirty = true; _nextRefreshTime = 0f; } internal static bool IsManagedRenderer(Renderer renderer) { return (Object)(object)renderer != (Object)null && (DesiredRenderers.Contains(renderer) || OriginalRendererStates.ContainsKey(renderer)); } internal static void CopyManagedRenderers(List target) { target.Clear(); RemoveDestroyedRenderers(); foreach (Renderer desiredRenderer in DesiredRenderers) { if ((Object)(object)desiredRenderer != (Object)null) { target.Add(desiredRenderer); } } } internal static void CaptureHelmetChangeStart(VisEquipment visEquipment) { if (ShouldObserveHelmetChange(visEquipment)) { HashSet value = new HashSet(((Component)visEquipment).GetComponentsInChildren(true)); HelmetChangeSnapshots[visEquipment] = value; } } internal static void CaptureHelmetChangeEnd(VisEquipment visEquipment, bool changed) { if ((Object)(object)visEquipment == (Object)null || !HelmetChangeSnapshots.TryGetValue(visEquipment, out HashSet value)) { return; } HelmetChangeSnapshots.Remove(visEquipment); if (!changed || !PlayerEquipmentAccess.IsLocalPlayerVisEquipment(visEquipment)) { RequestRefresh(); return; } Renderer[] componentsInChildren = ((Component)visEquipment).GetComponentsInChildren(true); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { if (!((Object)(object)val == (Object)null) && !value.Contains(val) && IsSafeHelmetRenderer(visEquipment, val, confirmedHelmetSource: true, "helmet equip delta")) { ObservedHelmetRenderers.Add(val); } } RequestRefresh(); } private static bool ShouldObserveHelmetChange(VisEquipment visEquipment) { return (Object)(object)visEquipment != (Object)null && FirstPersonState.Active && PlayerEquipmentAccess.IsLocalPlayerVisEquipment(visEquipment); } private static void RefreshRendererCache(Player player) { RemoveDestroyedRenderers(); RemoveDestroyedObservedRenderers(); RemoveDeadSnapshots(); DesiredRenderers.Clear(); if (PlayerEquipmentAccess.TryGetVisEquipment(player, out VisEquipment visEquipment)) { AddHelmetInstanceRenderers(visEquipment); AddObservedHelmetRenderers(visEquipment); AddHelmetJointRenderers(visEquipment); AddPrefabMatchedHelmetRenderers(player, visEquipment); RestoreNoLongerDesiredRenderers(); CaptureNewDesiredRendererStates(); } } private static void AddHelmetInstanceRenderers(VisEquipment visEquipment) { if (HelmetItemInstanceField == null) { return; } object value = HelmetItemInstanceField.GetValue(visEquipment); GameObject val = (GameObject)((value is GameObject) ? value : null); if (val == null) { return; } Renderer[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Renderer val2 in componentsInChildren) { if (IsSafeHelmetRenderer(visEquipment, val2, confirmedHelmetSource: true, "helmet item instance")) { DesiredRenderers.Add(val2); } } } private static void AddObservedHelmetRenderers(VisEquipment visEquipment) { foreach (Renderer observedHelmetRenderer in ObservedHelmetRenderers) { if (IsSafeHelmetRenderer(visEquipment, observedHelmetRenderer, confirmedHelmetSource: true, "observed helmet renderer")) { DesiredRenderers.Add(observedHelmetRenderer); } } } private static void AddHelmetJointRenderers(VisEquipment visEquipment) { if ((Object)(object)visEquipment.m_helmet == (Object)null) { return; } Renderer[] componentsInChildren = ((Component)visEquipment.m_helmet).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (IsSafeHelmetRenderer(visEquipment, val, confirmedHelmetSource: true, "helmet joint hierarchy")) { DesiredRenderers.Add(val); } } } private static void AddPrefabMatchedHelmetRenderers(Player player, VisEquipment visEquipment) { int helmetHash = PlayerEquipmentAccess.GetHelmetHash(visEquipment); if (helmetHash == 0) { return; } BuildHelmetPrefabFingerprints(helmetHash); if (HelmetPrefabFingerprints.Count == 0) { return; } Renderer[] componentsInChildren = ((Component)player).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && !DesiredRenderers.Contains(val) && HelmetPrefabFingerprints.Contains(BuildRendererFingerprint(val)) && IsRendererNearPlayerHead(player, val) && IsSafeHelmetRenderer(visEquipment, val, confirmedHelmetSource: false, "helmet prefab fingerprint")) { DesiredRenderers.Add(val); } } } private static void BuildHelmetPrefabFingerprints(int helmetHash) { HelmetPrefabFingerprints.Clear(); ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return; } GameObject itemPrefab = instance.GetItemPrefab(helmetHash); if ((Object)(object)itemPrefab == (Object)null) { return; } Renderer[] componentsInChildren = itemPrefab.GetComponentsInChildren(true); foreach (Renderer renderer in componentsInChildren) { string text = BuildRendererFingerprint(renderer); if (!string.IsNullOrEmpty(text)) { HelmetPrefabFingerprints.Add(text); } } } private static bool IsSafeHelmetRenderer(VisEquipment visEquipment, Renderer renderer, bool confirmedHelmetSource, string source) { if ((Object)(object)renderer == (Object)null || !IsRendererOwnedByLocalPlayer(renderer)) { return false; } if ((Object)(object)visEquipment.m_bodyModel != (Object)null && (Object)(object)renderer == (Object)(object)visEquipment.m_bodyModel) { WarnUnsafeRenderer(renderer, source, "it is the player body renderer"); return false; } string descriptor = BuildRendererDescriptor(renderer); if (LooksLikeFullBodyRenderer(renderer, descriptor)) { WarnUnsafeRenderer(renderer, source, "it appears to contain full body geometry"); return false; } if (!confirmedHelmetSource && !IsRendererNearPlayerHead(Player.m_localPlayer, renderer)) { return false; } return true; } private static bool IsRendererOwnedByLocalPlayer(Renderer renderer) { Player localPlayer = Player.m_localPlayer; return (Object)(object)localPlayer != (Object)null && LocalPlayerRendererOwnership.IsLocalPlayerRenderer(localPlayer, renderer); } private static bool IsRendererNearPlayerHead(Player player, Renderer renderer) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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_004b: 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_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || (Object)(object)renderer == (Object)null) { return false; } Bounds bounds = renderer.bounds; Vector3 size = ((Bounds)(ref bounds)).size; float num = Mathf.Max(new float[3] { size.x, size.y, size.z }); if (num > 1.35f) { return false; } Vector3 val = ((Component)player).transform.InverseTransformPoint(((Bounds)(ref bounds)).center); if (val.y < 0.85f || val.y > 2.45f) { return false; } Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(val.x, val.z); return ((Vector2)(ref val2)).magnitude <= 0.9f; } private static bool LooksLikeFullBodyRenderer(Renderer renderer, string descriptor) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) Bounds bounds = renderer.bounds; Vector3 size = ((Bounds)(ref bounds)).size; float num = Mathf.Max(new float[3] { size.x, size.y, size.z }); bool flag = ContainsAny(descriptor, FullBodyKeywords); bool flag2 = ContainsAny(descriptor, HeadSlotKeywords); bool flag3 = size.y > 1.2f && num > 1.5f; return (flag && !flag2) || flag3; } private static string BuildRendererDescriptor(Renderer renderer) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(((Object)renderer).name).Append(' '); if ((Object)(object)((Component)renderer).transform != (Object)null) { stringBuilder.Append(RendererScanner.GetPath(((Component)renderer).transform)).Append(' '); } SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((renderer is SkinnedMeshRenderer) ? renderer : null); if (val != null) { if ((Object)(object)val.sharedMesh != (Object)null) { stringBuilder.Append(((Object)val.sharedMesh).name).Append(' '); } if ((Object)(object)val.rootBone != (Object)null) { stringBuilder.Append(RendererScanner.GetPath(val.rootBone)).Append(' '); } } Material[] sharedMaterials = renderer.sharedMaterials; foreach (Material val2 in sharedMaterials) { if ((Object)(object)val2 != (Object)null) { stringBuilder.Append(((Object)val2).name).Append(' '); } } return stringBuilder.ToString().ToLowerInvariant(); } private static string BuildRendererFingerprint(Renderer renderer) { if ((Object)(object)renderer == (Object)null) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(); SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((renderer is SkinnedMeshRenderer) ? renderer : null); MeshFilter val2 = default(MeshFilter); if (val != null && (Object)(object)val.sharedMesh != (Object)null) { stringBuilder.Append(((Object)val.sharedMesh).name); } else if (((Component)renderer).TryGetComponent(ref val2) && (Object)(object)val2.sharedMesh != (Object)null) { stringBuilder.Append(((Object)val2.sharedMesh).name); } stringBuilder.Append('|'); Material[] sharedMaterials = renderer.sharedMaterials; foreach (Material val3 in sharedMaterials) { if ((Object)(object)val3 != (Object)null) { stringBuilder.Append(((Object)val3).name).Append(';'); } } return stringBuilder.ToString(); } 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 WarnUnsafeRenderer(Renderer renderer, string source, string reason) { if (!((Object)(object)renderer == (Object)null) && !WarnedUnsafeRenderers.Contains(renderer)) { WarnedUnsafeRenderers.Add(renderer); Plugin.Log.LogWarning((object)("Skipped helmet ShadowsOnly renderer from " + source + " because " + reason + ": " + BuildRendererDescriptor(renderer))); } } private static void RestoreNoLongerDesiredRenderers() { RenderersToRemove.Clear(); foreach (Renderer key in OriginalRendererStates.Keys) { if ((Object)(object)key == (Object)null || !DesiredRenderers.Contains(key)) { RenderersToRemove.Add(key); } } foreach (Renderer item in RenderersToRemove) { if (item != null) { RestoreRenderer(item); OriginalRendererStates.Remove(item); } } RenderersToRemove.Clear(); } private static void CaptureNewDesiredRendererStates() { foreach (Renderer desiredRenderer in DesiredRenderers) { if (!((Object)(object)desiredRenderer == (Object)null) && !OriginalRendererStates.ContainsKey(desiredRenderer)) { OriginalRendererStates.Add(desiredRenderer, new RendererStateSnapshot(desiredRenderer)); Plugin.HeadHidingDebugLog("Helmet ShadowsOnly matched renderer: " + BuildRendererDescriptor(desiredRenderer)); } } } private static void ApplyShadowsOnly() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Invalid comparison between Unknown and I4 foreach (Renderer key in OriginalRendererStates.Keys) { if (!((Object)(object)key == (Object)null) && IsRendererOwnedByLocalPlayer(key)) { if (!key.enabled) { key.enabled = true; } if ((int)key.shadowCastingMode != 3) { key.shadowCastingMode = (ShadowCastingMode)3; } } } } private static void RestoreRendererStates() { if (OriginalRendererStates.Count == 0) { return; } foreach (KeyValuePair originalRendererState in OriginalRendererStates) { Renderer key = originalRendererState.Key; if (!((Object)(object)key == (Object)null)) { RestoreRenderer(key); } } OriginalRendererStates.Clear(); } private static void RestoreRenderer(Renderer renderer) { if (!((Object)(object)renderer == (Object)null) && OriginalRendererStates.TryGetValue(renderer, out var value)) { value.Restore(renderer); } } 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(); foreach (Renderer desiredRenderer in DesiredRenderers) { if ((Object)(object)desiredRenderer == (Object)null) { RenderersToRemove.Add(desiredRenderer); } } foreach (Renderer item2 in RenderersToRemove) { if (item2 != null) { DesiredRenderers.Remove(item2); } } RenderersToRemove.Clear(); } private static void RemoveDestroyedObservedRenderers() { RenderersToRemove.Clear(); foreach (Renderer observedHelmetRenderer in ObservedHelmetRenderers) { if ((Object)(object)observedHelmetRenderer == (Object)null) { RenderersToRemove.Add(observedHelmetRenderer); } } foreach (Renderer item in RenderersToRemove) { if (item != null) { ObservedHelmetRenderers.Remove(item); } } RenderersToRemove.Clear(); } private static void RemoveDeadSnapshots() { SnapshotsToRemove.Clear(); foreach (VisEquipment key in HelmetChangeSnapshots.Keys) { if ((Object)(object)key == (Object)null) { SnapshotsToRemove.Add(key); } } foreach (VisEquipment item in SnapshotsToRemove) { if (item != null) { HelmetChangeSnapshots.Remove(item); } } SnapshotsToRemove.Clear(); } } internal static class LocalPlayerRendererOwnership { internal static bool IsLocalPlayerRenderer(Player player, Renderer renderer) { if ((Object)(object)renderer == (Object)null || (Object)(object)((Component)renderer).transform == (Object)null) { return false; } if (!IsLocalPlayerTransform(player, ((Component)renderer).transform)) { return false; } return !HasForeignWorldOwner(player, ((Component)renderer).transform); } internal static bool IsLocalPlayerTransform(Player player, Transform transform) { return (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && (Object)(object)((Component)player).transform != (Object)null && (Object)(object)transform != (Object)null && ((Object)(object)transform == (Object)(object)((Component)player).transform || transform.IsChildOf(((Component)player).transform)); } private static bool HasForeignWorldOwner(Player player, Transform transform) { return HasForeignOwner(player, transform) || HasForeignOwner(player, transform) || HasForeignOwner(player, transform) || HasForeignOwner(player, transform) || HasForeignOwner(player, transform) || HasForeignOwner(player, transform) || HasForeignOwner(player, transform) || HasForeignOwner(player, transform) || HasForeignOwner(player, transform); } private static bool HasForeignOwner(Player player, Transform transform) where T : Component { T componentInParent = ((Component)transform).GetComponentInParent(); return (Object)(object)componentInParent != (Object)null && !IsLocalPlayerTransform(player, ((Component)componentInParent).transform); } } internal static class LocalPlayerVisibilityOverride { private static readonly MethodInfo? PlayerSetVisibleMethod = FindInstanceMethod(typeof(Player), "SetVisible") ?? FindInstanceMethod(typeof(Character), "SetVisible"); 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 = FindInstanceMethod(type, "SetVisible"); } if (_cachedVisEquipmentSetVisibleMethod == null) { return; } try { _cachedVisEquipmentSetVisibleMethod.Invoke(value, new object[1] { true }); } catch (Exception ex) { Plugin.DebugLog("VisEquipment visibility override failed: " + ex.GetType().Name); } } private static MethodInfo? FindInstanceMethod(Type type, string name) { return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } } internal static class PlayerEquipmentAccess { private static readonly FieldInfo? VisEquipmentField = AccessTools.Field(typeof(Humanoid), "m_visEquipment") ?? AccessTools.Field(typeof(Character), "m_visEquipment"); private static readonly FieldInfo? HelmetHashField = AccessTools.Field(typeof(VisEquipment), "m_currentHelmetItemHash"); private static readonly FieldInfo?[] EquippedHashFields = new FieldInfo[12] { AccessTools.Field(typeof(VisEquipment), "m_currentHelmetItemHash"), AccessTools.Field(typeof(VisEquipment), "m_currentChestItemHash"), AccessTools.Field(typeof(VisEquipment), "m_currentLegItemHash"), AccessTools.Field(typeof(VisEquipment), "m_currentShoulderItemHash"), AccessTools.Field(typeof(VisEquipment), "m_currentUtilityItemHash"), AccessTools.Field(typeof(VisEquipment), "m_currentTrinketItemHash"), AccessTools.Field(typeof(VisEquipment), "m_currentLeftItemHash"), AccessTools.Field(typeof(VisEquipment), "m_currentRightItemHash"), AccessTools.Field(typeof(VisEquipment), "m_currentLeftBackItemHash"), AccessTools.Field(typeof(VisEquipment), "m_currentRightBackItemHash"), AccessTools.Field(typeof(VisEquipment), "m_currentBeardItemHash"), AccessTools.Field(typeof(VisEquipment), "m_currentHairItemHash") }; internal static bool TryGetVisEquipment(Player player, out VisEquipment visEquipment) { visEquipment = null; if ((Object)(object)player == (Object)null || VisEquipmentField == null) { return false; } object value = VisEquipmentField.GetValue(player); VisEquipment val = (VisEquipment)((value is VisEquipment) ? value : null); if (val == null) { return false; } visEquipment = val; return true; } internal static bool IsLocalPlayerVisEquipment(VisEquipment visEquipment) { if ((Object)(object)visEquipment == (Object)null) { return false; } Player val = ((Component)visEquipment).GetComponent(); if ((Object)(object)val == (Object)null) { val = ((Component)visEquipment).GetComponentInParent(); } return (Object)(object)val != (Object)null && (Object)(object)val == (Object)(object)Player.m_localPlayer; } internal static int GetHelmetHash(VisEquipment visEquipment) { if ((Object)(object)visEquipment == (Object)null || HelmetHashField == null) { return 0; } return (HelmetHashField.GetValue(visEquipment) is int num) ? num : 0; } internal static void GetEquippedHashes(VisEquipment visEquipment, List hashes) { hashes.Clear(); if ((Object)(object)visEquipment == (Object)null) { return; } FieldInfo[] equippedHashFields = EquippedHashFields; foreach (FieldInfo fieldInfo in equippedHashFields) { if (!(fieldInfo == null) && fieldInfo.GetValue(visEquipment) is int num && num != 0 && !hashes.Contains(num)) { hashes.Add(num); } } } internal static bool TryGetPrefabName(int itemHash, out string prefabName) { prefabName = string.Empty; ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || itemHash == 0) { return false; } GameObject itemPrefab = instance.GetItemPrefab(itemHash); if ((Object)(object)itemPrefab == (Object)null) { return false; } prefabName = ((Object)itemPrefab).name; return !string.IsNullOrEmpty(prefabName); } } [HarmonyPatch(typeof(Player))] [HarmonyPatch("Update")] internal static class PlayerUpdatePatch { private static void Postfix(Player __instance) { FirstPersonState.Update(__instance); } } public enum ShoulderPeekMode { Hold, Toggle } public enum HeadHidingMode { BlacksmithTools, Auto, ShrinkFallback } [BepInPlugin("com.geronimo.valheim.immersivefirstperson", "Immersive First Person", "1.4.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [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.4.1"; public const string BlacksmithToolsPluginGuid = "GoldenJude_BlacksmithTools"; internal static ManualLogSource Log; internal static ConfigEntry EnableMod; internal static ConfigEntry ToggleFirstPersonKey; internal static ConfigEntry DefaultToFirstPerson; internal static ConfigEntry DodgeWhereYouLook; internal static ConfigEntry OverrideForcedThirdPerson; internal static ConfigEntry LockCameraWhileAttached; internal static ConfigEntry AttachedCameraExtraVerticalOffset; internal static ConfigEntry AttachedCameraExtraForwardOffset; internal static ConfigEntry AttachedCameraMaxYaw; internal static ConfigEntry AttachedCameraMaxPitch; 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 HeadBobAmount; internal static ConfigEntry LockBodyToCamera; internal static ConfigEntry BodyRotationFollowSpeed; internal static ConfigEntry EnableShoulderPeek; internal static ConfigEntry ShoulderPeekMode; internal static ConfigEntry PeekLeftKey; internal static ConfigEntry PeekRightKey; internal static ConfigEntry ShoulderPeekOffset; internal static ConfigEntry ShoulderPeekSpeed; internal static ConfigEntry FirstPersonShadowDistance; internal static ConfigEntry FirstPersonShadowCascades; internal static ConfigEntry UseOcclusionCulling; internal static ConfigEntry DisableCameraEffects; internal static ConfigEntry HideHead; internal static ConfigEntry HeadHidingMode; internal static ConfigEntry ForceBodyVisible; internal static ConfigEntry VisibilityRefreshInterval; internal static ConfigEntry EnableDebugLogs; internal static ConfigEntry LogHeadHidingDebug; internal static ConfigEntry LogRendererNames; private Harmony _harmony = null; private void Awake() { //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Expected O, but got Unknown //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Expected O, but got Unknown //IL_02fc: Unknown result type (might be due to invalid IL or missing references) //IL_0306: Expected O, but got Unknown //IL_044d: Unknown result type (might be due to invalid IL or missing references) //IL_0457: Expected O, but got Unknown //IL_047e: Unknown result type (might be due to invalid IL or missing references) //IL_0488: Expected O, but got Unknown //IL_055b: Unknown result type (might be due to invalid IL or missing references) //IL_0565: Expected O, but got Unknown //IL_05d0: Unknown result type (might be due to invalid IL or missing references) //IL_05da: 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."); DodgeWhereYouLook = ((BaseUnityPlugin)this).Config.Bind("Input", "Dodge Where You Look", false, "When enabled, dodge direction follows the first-person camera/look direction. When disabled, dodge direction follows vanilla movement input, allowing S to dodge backward without looking backward."); OverrideForcedThirdPerson = ((BaseUnityPlugin)this).Config.Bind("Camera Overrides", "OverrideForcedThirdPerson", true, "Keep first-person mode active during gameplay interactions that normally force third person, such as inventory, crafting, ships, hold fast, and attached states."); LockCameraWhileAttached = ((BaseUnityPlugin)this).Config.Bind("Camera Overrides", "LockCameraWhileAttached", true, "Lock the first-person camera to a captured head-level body offset while attached to seats, ships, hold-fast points, and similar attach points. Reduces attachment jitter and rubberbanding."); AttachedCameraExtraVerticalOffset = ((BaseUnityPlugin)this).Config.Bind("Camera Overrides", "AttachedCameraExtraVerticalOffset", 0f, "Extra vertical offset added to the captured head-level camera position while attached."); AttachedCameraExtraForwardOffset = ((BaseUnityPlugin)this).Config.Bind("Camera Overrides", "AttachedCameraExtraForwardOffset", 0.08f, "Extra forward offset added to the captured head-level camera position while attached."); AttachedCameraMaxYaw = ((BaseUnityPlugin)this).Config.Bind("Camera Overrides", "AttachedCameraMaxYaw", 80f, new ConfigDescription("Maximum left/right camera yaw from the attached body direction.", (AcceptableValueBase)(object)new AcceptableValueRange(20f, 180f), Array.Empty())); AttachedCameraMaxPitch = ((BaseUnityPlugin)this).Config.Bind("Camera Overrides", "AttachedCameraMaxPitch", 55f, new ConfigDescription("Maximum up/down camera pitch while attached.", (AcceptableValueBase)(object)new AcceptableValueRange(20f, 89f), Array.Empty())); 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 or head bob is reduced."); 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."); HeadBobAmount = ((BaseUnityPlugin)this).Config.Bind("Camera Motion", "HeadBobAmount", 0.5f, new ConfigDescription("Controls how much fast animation-based head motion affects the first-person camera. 0 keeps only filtered head tracking. 1 uses full tracked head motion.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); 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."); EnableShoulderPeek = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "EnableShoulderPeek", true, "Enable or disable first-person shoulder peek."); ShoulderPeekMode = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "ShoulderPeekMode", ImmersiveFirstPerson.ShoulderPeekMode.Hold, "Shoulder peek input mode. Valid values: Hold, Toggle."); PeekLeftKey = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "PeekLeftKey", (KeyCode)326, "Key used to peek left."); PeekRightKey = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "PeekRightKey", (KeyCode)327, "Key used to peek right."); ShoulderPeekOffset = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "ShoulderPeekOffset", 0.28f, "Sideways camera offset used when shoulder peeking."); ShoulderPeekSpeed = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "ShoulderPeekSpeed", 12f, "How quickly the camera moves into and out of shoulder peek."); FirstPersonShadowDistance = ((BaseUnityPlugin)this).Config.Bind("Graphics", "FirstPersonShadowDistance", 30f, new ConfigDescription("Maximum shadow draw distance while first-person mode is active. Set to -1 to keep the game's current shadow distance.", (AcceptableValueBase)(object)new AcceptableValueRange(-1f, 500f), Array.Empty())); FirstPersonShadowCascades = ((BaseUnityPlugin)this).Config.Bind("Graphics", "FirstPersonShadowCascades", 0, new ConfigDescription("Maximum shadow cascade count while first-person mode is active. Set to -1 to keep the game's current cascade count. Values are normalized to 0, 2, or 4.", (AcceptableValueBase)(object)new AcceptableValueRange(-1, 4), Array.Empty())); UseOcclusionCulling = ((BaseUnityPlugin)this).Config.Bind("Graphics", "UseOcclusionCulling", false, "Legacy compatibility setting. First-person mode now keeps camera occlusion culling disabled so world foliage and props remain visible."); DisableCameraEffects = ((BaseUnityPlugin)this).Config.Bind("Graphics", "DisableCameraEffects", false, "Disable known camera post-processing components while first-person mode is active, then restore them when leaving first person."); HideHead = ((BaseUnityPlugin)this).Config.Bind("Visibility", "HideHead", false, "Hide the local player's head and head-slot equipment from the first-person camera while preserving shadows."); HeadHidingMode = ((BaseUnityPlugin)this).Config.Bind("Visibility", "HeadHidingMode", ImmersiveFirstPerson.HeadHidingMode.Auto, "Head hiding backend. Auto uses Blacksmith's Tools first and falls back to head shrink when validation fails. BlacksmithTools only uses Blacksmith's Tools. ShrinkFallback uses the legacy head bone shrink path."); ForceBodyVisible = ((BaseUnityPlugin)this).Config.Bind("Visibility", "ForceBodyVisible", true, "Force the local player body and held items visible while first-person mode is active."); VisibilityRefreshInterval = ((BaseUnityPlugin)this).Config.Bind("Visibility", "VisibilityRefreshInterval", 1f, new ConfigDescription("Seconds between head-slot and head-bone refresh scans while HideHead is enabled. Lower values detect equipment changes sooner and cost more CPU.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 10f), Array.Empty())); EnableDebugLogs = ((BaseUnityPlugin)this).Config.Bind("Debug", "EnableDebugLogs", false, "Enable extra logs for camera, state, visibility, and cleanup behavior."); LogHeadHidingDebug = ((BaseUnityPlugin)this).Config.Bind("Debug", "LogHeadHidingDebug", false, "Log detailed Blacksmith head hiding and helmet shadow decisions."); 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.4.1 loaded."); LogOptimizationPolicy(); } private static void LogOptimizationPolicy() { Log.LogInfo((object)$"Optimization policy: view distance unchanged; LOD unchanged; shadowDistanceCap={FirstPersonShadowDistance.Value}; shadowCascadesCap={FirstPersonShadowCascades.Value}; firstPersonOcclusionCulling=false; disableCameraEffects={DisableCameraEffects.Value}; visibilityRefreshInterval={VisibilityRefreshInterval.Value}s."); } internal static void DebugLog(string message) { ConfigEntry enableDebugLogs = EnableDebugLogs; if (enableDebugLogs != null && enableDebugLogs.Value) { Log.LogInfo((object)("[Debug] " + message)); } } internal static void HeadHidingDebugLog(string message) { ConfigEntry logHeadHidingDebug = LogHeadHidingDebug; if (logHeadHidingDebug == null || !logHeadHidingDebug.Value) { ConfigEntry enableDebugLogs = EnableDebugLogs; if (enableDebugLogs == null || !enableDebugLogs.Value) { return; } } Log.LogInfo((object)("[HeadHiding] " + 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(); } } internal readonly struct RendererStateSnapshot { private readonly bool _enabled; private readonly ShadowCastingMode _shadowCastingMode; private readonly bool _receiveShadows; private readonly bool _forceRenderingOff; private readonly int _layer; private readonly Material[] _sharedMaterials; internal RendererStateSnapshot(Renderer renderer) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) _enabled = renderer.enabled; _shadowCastingMode = renderer.shadowCastingMode; _receiveShadows = renderer.receiveShadows; _forceRenderingOff = renderer.forceRenderingOff; _layer = ((Component)renderer).gameObject.layer; _sharedMaterials = renderer.sharedMaterials ?? Array.Empty(); } internal void Restore(Renderer renderer) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)renderer == (Object)null)) { renderer.enabled = _enabled; renderer.shadowCastingMode = _shadowCastingMode; renderer.receiveShadows = _receiveShadows; renderer.forceRenderingOff = _forceRenderingOff; renderer.sharedMaterials = _sharedMaterials; if ((Object)(object)((Component)renderer).gameObject != (Object)null) { ((Component)renderer).gameObject.layer = _layer; } } } } }