using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("ImmersiveBuildCamera")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ImmersiveBuildCamera")] [assembly: AssemblyTitle("ImmersiveBuildCamera")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ImmersiveBuildCamera { internal static class BuildCameraDistance { private const float HeldInputStepsPerSecond = 20f; private static bool _initialized; internal static float Current { get; private set; } internal static void ResetForSession() { if (!Plugin.RememberScrollDistance.Value || !_initialized) { Current = Clamp(Plugin.DefaultBuildCameraDistance.Value); _initialized = true; Plugin.DebugLog($"Camera distance reset to {Current:0.00}."); } } internal static void UpdateFromInput() { //IL_003a: 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) float num = Mathf.Max(0f, Plugin.ScrollDistanceStep.Value); if (!(num <= 0f)) { float num2 = num * 20f * Time.unscaledDeltaTime; if (Input.GetKey(Plugin.CameraDistanceCloserKey.Value)) { AddDistance(0f - num2); } if (Input.GetKey(Plugin.CameraDistanceFartherKey.Value)) { AddDistance(num2); } } } private static void AddDistance(float delta) { float num = Clamp(Current + delta); if (!Mathf.Approximately(Current, num)) { Current = num; Plugin.DebugLog($"Camera distance set to {Current:0.00}."); } } private static float Clamp(float value) { float num = Mathf.Min(Plugin.MinBuildCameraDistance.Value, Plugin.MaxBuildCameraDistance.Value); float num2 = Mathf.Max(Plugin.MinBuildCameraDistance.Value, Plugin.MaxBuildCameraDistance.Value); return Mathf.Clamp(value, num, num2); } } internal static class BuildCameraState { private static int _toggledShoulderDirection; private static readonly FieldInfo? RightItemField = AccessTools.Field(typeof(Humanoid), "m_rightItem"); internal static bool Active { get; private set; } internal static bool PrecisionMovementActive { get; private set; } internal static bool ShoulderPeekActive => Active && GetShoulderDirection() != 0; internal static void Update(Player player) { //IL_003f: 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) if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer) { return; } if (!CanUseImmersiveCamera(player)) { SetActive(active: false); return; } if (Input.GetKeyDown(Plugin.ToggleCameraKey.Value)) { SetActive(!Active); } if (Active) { UpdateShoulderPeekState(); BuildCameraDistance.UpdateFromInput(); if (Plugin.EnablePrecisionMovement.Value && Input.GetKeyDown(Plugin.TogglePrecisionMovementKey.Value)) { SetPrecisionMovement(!PrecisionMovementActive); } } } internal static void ForceInactive() { SetActive(active: false); } internal static int GetShoulderDirection() { if (!Active) { return 0; } if (Plugin.ToggleShoulderPeek.Value) { return _toggledShoulderDirection; } return GetHeldShoulderDirection(); } private static void UpdateShoulderPeekState() { //IL_0020: 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) if (!Plugin.ToggleShoulderPeek.Value) { _toggledShoulderDirection = 0; return; } bool keyDown = Input.GetKeyDown(Plugin.LeftShoulderKey.Value); bool keyDown2 = Input.GetKeyDown(Plugin.RightShoulderKey.Value); if (keyDown && keyDown2) { SetShoulderDirection(0); } else if (keyDown) { ToggleShoulderDirection(-1); } else if (keyDown2) { ToggleShoulderDirection(1); } } private static void ToggleShoulderDirection(int direction) { if (_toggledShoulderDirection == direction) { SetShoulderDirection(0); } else { SetShoulderDirection(direction); } } private static void SetShoulderDirection(int direction) { if (_toggledShoulderDirection != direction) { _toggledShoulderDirection = direction; if (direction < 0) { Plugin.DebugLog("Shoulder peek set to left."); } else if (direction > 0) { Plugin.DebugLog("Shoulder peek set to right."); } else { Plugin.DebugLog("Shoulder peek centered."); } } } private static int GetHeldShoulderDirection() { //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) bool key = Input.GetKey(Plugin.LeftShoulderKey.Value); bool key2 = Input.GetKey(Plugin.RightShoulderKey.Value); if (key && !key2) { return -1; } if (key2 && !key) { return 1; } return 0; } private static void SetActive(bool active) { if (Active != active) { Active = active; if (active) { BuildCameraDistance.ResetForSession(); PrecisionMovementActive = Plugin.EnablePrecisionMovement.Value && Plugin.PrecisionMovementDefaultOn.Value; } else { PrecisionMovementActive = false; SetShoulderDirection(0); PlayerRendererVisibility.ForceVisible(); } Plugin.Log.LogInfo((object)(active ? ("Immersive build camera active. Precision movement: " + (PrecisionMovementActive ? "on" : "off") + ".") : "Immersive build camera inactive.")); } } private static void SetPrecisionMovement(bool active) { if (PrecisionMovementActive != active) { PrecisionMovementActive = active; Plugin.Log.LogInfo((object)(active ? "Precision movement active." : "Precision movement inactive.")); } } private static bool CanUseImmersiveCamera(Player player) { if (!IsSafePlayerState(player)) { return false; } if (!HasBuildTool(player)) { return false; } return true; } private static bool HasBuildTool(Player player) { if (RightItemField == null) { Plugin.Log.LogWarning((object)"Could not find Humanoid.m_rightItem."); return false; } object? value = RightItemField.GetValue(player); ItemData val = (ItemData)((value is ItemData) ? value : null); if (val == null) { return false; } if (val.m_shared == null) { return false; } return (Object)(object)val.m_shared.m_buildPieces != (Object)null; } private static bool IsSafePlayerState(Player player) { if (((Character)player).IsDead()) { return false; } if (((Character)player).IsAttached()) { return false; } if (((Character)player).IsSwimming()) { return false; } if (InventoryGui.IsVisible()) { return false; } if (Menu.IsVisible()) { return false; } if (Minimap.IsOpen()) { return false; } return true; } } [HarmonyPatch(typeof(GameCamera))] [HarmonyPatch("UpdateCamera")] internal static class GameCameraUpdatePatch { private static readonly FieldInfo? CameraField = AccessTools.Field(typeof(GameCamera), "m_camera"); private static float _originalFov; private static float _originalNearClip; private static bool _savedOriginals; private static Vector3 _smoothPosition; private static Quaternion _smoothRotation = Quaternion.identity; private static bool _hasSmoothTransform; private static int _cachedCollisionMask = -1; private static void Postfix(GameCamera __instance) { if ((Object)(object)__instance == (Object)null) { return; } Camera val = GetCamera(__instance) ?? Camera.main; if (!((Object)(object)val == (Object)null)) { if (!_savedOriginals) { _originalFov = val.fieldOfView; _originalNearClip = val.nearClipPlane; _savedOriginals = true; } if (!BuildCameraState.Active) { RestoreCamera(val); ResetSmoothing(); } else { ApplyImmersiveBuildCamera(__instance, val); } } } private static Camera? GetCamera(GameCamera gameCamera) { if (CameraField == null) { return null; } object? value = CameraField.GetValue(gameCamera); return (Camera?)((value is Camera) ? value : null); } private static void ApplyImmersiveBuildCamera(GameCamera gameCamera, Camera camera) { //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_003d: 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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: 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_0069: 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_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_009e: 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_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: 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_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: 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_00eb: 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) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0107: 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) Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { Transform val = (((Object)(object)((Character)localPlayer).m_eye != (Object)null) ? ((Character)localPlayer).m_eye : ((Component)localPlayer).transform); Vector3 position = val.position; Vector3 val2 = position; Quaternion rotation = val.rotation; float num = Mathf.Max(0f, BuildCameraDistance.Current); if (num > 0.001f) { val2 -= val.forward * num; } int shoulderDirection = BuildCameraState.GetShoulderDirection(); if (shoulderDirection != 0) { float num2 = Plugin.ShoulderOffsetX.Value * (float)shoulderDirection; val2 += val.right * num2; val2 += val.up * Plugin.ShoulderOffsetY.Value; val2 -= val.forward * Plugin.ShoulderDistance.Value; val2 = ResolveCameraCollision(position, val2); } else if (num > 0.001f) { val2 = ResolveCameraCollision(position, val2); } ApplySmoothedTransform(gameCamera, val2, rotation); camera.fieldOfView = Plugin.BuildFov.Value; camera.nearClipPlane = Plugin.NearClip.Value; } } private static void ApplySmoothedTransform(GameCamera gameCamera, Vector3 desiredPosition, Quaternion desiredRotation) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //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_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) if (!_hasSmoothTransform) { _smoothPosition = ((Component)gameCamera).transform.position; _smoothRotation = ((Component)gameCamera).transform.rotation; _hasSmoothTransform = true; } float num = Mathf.Max(0f, Plugin.CameraTransitionSpeed.Value); float num2 = ((num <= 0f) ? 1f : (1f - Mathf.Exp((0f - num) * Time.unscaledDeltaTime))); _smoothPosition = Vector3.Lerp(_smoothPosition, desiredPosition, num2); _smoothRotation = Quaternion.Slerp(_smoothRotation, desiredRotation, num2); ((Component)gameCamera).transform.position = _smoothPosition; ((Component)gameCamera).transform.rotation = _smoothRotation; } private static Vector3 ResolveCameraCollision(Vector3 anchorPosition, Vector3 desiredPosition) { //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_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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_005e: 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_0093: Unknown result type (might be due to invalid IL or missing references) Vector3 val = desiredPosition - anchorPosition; float magnitude = ((Vector3)(ref val)).magnitude; if (magnitude <= 0.001f) { return desiredPosition; } Vector3 val2 = val / magnitude; RaycastHit val3 = default(RaycastHit); if (!Physics.SphereCast(anchorPosition, Mathf.Max(0.01f, Plugin.CollisionRadius.Value), val2, ref val3, magnitude, GetCollisionMask(), (QueryTriggerInteraction)1)) { return desiredPosition; } float num = Mathf.Max(0f, ((RaycastHit)(ref val3)).distance - Plugin.CollisionRadius.Value); return anchorPosition + val2 * num; } private static int GetCollisionMask() { if (_cachedCollisionMask != -1) { return _cachedCollisionMask; } int num = LayerMask.GetMask(new string[5] { "Default", "static_solid", "terrain", "piece", "piece_nonsolid" }); if (num == 0) { num = -5; Plugin.Log.LogWarning((object)"Could not resolve Valheim-specific collision layers. Falling back to Physics.DefaultRaycastLayers."); } _cachedCollisionMask = num; return _cachedCollisionMask; } private static void RestoreCamera(Camera camera) { if (_savedOriginals) { camera.fieldOfView = _originalFov; camera.nearClipPlane = _originalNearClip; } } private static void ResetSmoothing() { //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_0011: 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) _hasSmoothTransform = false; _smoothPosition = Vector3.zero; _smoothRotation = Quaternion.identity; } } [HarmonyPatch(typeof(Player))] [HarmonyPatch("Update")] internal static class PlayerUpdatePatch { private static void Postfix(Player __instance) { BuildCameraState.Update(__instance); PlayerRendererVisibility.Update(__instance); } } internal static class PlayerRendererVisibility { private static readonly Dictionary OriginalRendererStates = new Dictionary(); private static readonly List DeadRenderers = new List(); private static readonly FieldInfo? PlacementGhostField = AccessTools.Field(typeof(Player), "m_placementGhost"); private static Player? _cachedPlayer; private static bool _hidden; private static float _nextRefreshTime; internal static void Update(Player player) { if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { bool shouldHide = Plugin.HideLocalPlayerWhenImmersive.Value && BuildCameraState.Active && !BuildCameraState.ShoulderPeekActive; Apply(player, shouldHide); } } internal static void ForceVisible() { RestoreRendererStates(); ResetCache(); } private static void Apply(Player player, bool shouldHide) { if ((Object)(object)player == (Object)null) { ForceVisible(); return; } if ((Object)(object)_cachedPlayer != (Object)null && (Object)(object)_cachedPlayer != (Object)(object)player) { ForceVisible(); } _cachedPlayer = player; if (shouldHide) { HidePlayerRenderers(player); } else { RestoreRendererStates(); } } private static void HidePlayerRenderers(Player player) { if (!_hidden) { OriginalRendererStates.Clear(); _hidden = true; _nextRefreshTime = 0f; } if (OriginalRendererStates.Count == 0 || Time.unscaledTime >= _nextRefreshTime) { RefreshRendererCache(player); _nextRefreshTime = Time.unscaledTime + 0.25f; } foreach (Renderer key in OriginalRendererStates.Keys) { if (!((Object)(object)key == (Object)null) && key.enabled) { key.enabled = false; } } } private static void RefreshRendererCache(Player player) { RemoveDestroyedRenderers(); Renderer[] componentsInChildren = ((Component)player).GetComponentsInChildren(true); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { if (!((Object)(object)val == (Object)null) && !ShouldSkipRenderer(player, val) && !OriginalRendererStates.ContainsKey(val)) { OriginalRendererStates.Add(val, val.enabled); } } } private static bool ShouldSkipRenderer(Player player, Renderer renderer) { GameObject placementGhost = GetPlacementGhost(player); if ((Object)(object)placementGhost != (Object)null && (Object)(object)((Component)renderer).transform != (Object)null && ((Component)renderer).transform.IsChildOf(placementGhost.transform)) { return true; } return false; } private static GameObject? GetPlacementGhost(Player player) { if (PlacementGhostField == null) { return null; } object? value = PlacementGhostField.GetValue(player); return (GameObject?)((value is GameObject) ? value : null); } private static void RestoreRendererStates() { if (!_hidden && OriginalRendererStates.Count == 0) { return; } foreach (KeyValuePair originalRendererState in OriginalRendererStates) { Renderer key = originalRendererState.Key; if (!((Object)(object)key == (Object)null)) { key.enabled = originalRendererState.Value; } } OriginalRendererStates.Clear(); DeadRenderers.Clear(); _hidden = false; _nextRefreshTime = 0f; } 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) { OriginalRendererStates.Remove(deadRenderer); } DeadRenderers.Clear(); } private static void ResetCache() { _cachedPlayer = null; _hidden = false; _nextRefreshTime = 0f; OriginalRendererStates.Clear(); DeadRenderers.Clear(); } } [BepInPlugin("com.geronimo.valheim.immersivebuildcamera", "Immersive Build Camera", "1.1.0")] [BepInProcess("valheim.exe")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.geronimo.valheim.immersivebuildcamera"; public const string PluginName = "Immersive Build Camera"; public const string PluginVersion = "1.1.0"; internal static ManualLogSource Log; internal static ConfigEntry ToggleCameraKey; internal static ConfigEntry TogglePrecisionMovementKey; internal static ConfigEntry LeftShoulderKey; internal static ConfigEntry RightShoulderKey; internal static ConfigEntry CameraDistanceCloserKey; internal static ConfigEntry CameraDistanceFartherKey; internal static ConfigEntry BuildFov; internal static ConfigEntry NearClip; internal static ConfigEntry CameraTransitionSpeed; internal static ConfigEntry EnableScrollDistanceAdjust; internal static ConfigEntry DefaultBuildCameraDistance; internal static ConfigEntry MinBuildCameraDistance; internal static ConfigEntry MaxBuildCameraDistance; internal static ConfigEntry ScrollDistanceStep; internal static ConfigEntry RememberScrollDistance; internal static ConfigEntry ShoulderOffsetX; internal static ConfigEntry ShoulderOffsetY; internal static ConfigEntry ShoulderDistance; internal static ConfigEntry CollisionRadius; internal static ConfigEntry ToggleShoulderPeek; internal static ConfigEntry EnablePrecisionMovement; internal static ConfigEntry PrecisionMovementDefaultOn; internal static ConfigEntry PrecisionMoveMultiplier; internal static ConfigEntry HideLocalPlayerWhenImmersive; internal static ConfigEntry EnableDebugLogs; private Harmony _harmony = null; private void Awake() { //IL_0374: Unknown result type (might be due to invalid IL or missing references) //IL_037e: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ToggleCameraKey = ((BaseUnityPlugin)this).Config.Bind("Input", "ToggleCameraKey", (KeyCode)308, "Press this while using a build tool to toggle immersive build camera."); TogglePrecisionMovementKey = ((BaseUnityPlugin)this).Config.Bind("Input", "TogglePrecisionMovementKey", (KeyCode)306, "Press this while immersive build camera is active to toggle slow precision movement."); LeftShoulderKey = ((BaseUnityPlugin)this).Config.Bind("Input", "LeftShoulderKey", (KeyCode)113, "Hold or press this while immersive build camera is active to peek left, depending on ToggleShoulderPeek."); RightShoulderKey = ((BaseUnityPlugin)this).Config.Bind("Input", "RightShoulderKey", (KeyCode)101, "Hold or press this while immersive build camera is active to peek right, depending on ToggleShoulderPeek."); CameraDistanceCloserKey = ((BaseUnityPlugin)this).Config.Bind("Input", "CameraDistanceCloserKey", (KeyCode)280, "Press this while immersive build camera is active to move the camera closer."); CameraDistanceFartherKey = ((BaseUnityPlugin)this).Config.Bind("Input", "CameraDistanceFartherKey", (KeyCode)281, "Press this while immersive build camera is active to move the camera farther away."); BuildFov = ((BaseUnityPlugin)this).Config.Bind("Camera", "BuildFov", 68f, "Field of view while immersive build camera is active."); NearClip = ((BaseUnityPlugin)this).Config.Bind("Camera", "NearClip", 0.04f, "Near clipping plane while immersive build camera is active."); CameraTransitionSpeed = ((BaseUnityPlugin)this).Config.Bind("Camera", "CameraTransitionSpeed", 12f, "How quickly the immersive build camera moves toward its target. Set to 0 for instant movement."); EnableScrollDistanceAdjust = ((BaseUnityPlugin)this).Config.Bind("Camera Distance", "EnableScrollDistanceAdjust", false, "Legacy opt-in. If true, mouse wheel also adjusts camera distance, but this can conflict with Valheim build piece rotation."); DefaultBuildCameraDistance = ((BaseUnityPlugin)this).Config.Bind("Camera Distance", "DefaultBuildCameraDistance", 0f, "Starting backward camera distance from the player's eye when immersive build camera turns on."); MinBuildCameraDistance = ((BaseUnityPlugin)this).Config.Bind("Camera Distance", "MinBuildCameraDistance", 0f, "Closest allowed camera distance."); MaxBuildCameraDistance = ((BaseUnityPlugin)this).Config.Bind("Camera Distance", "MaxBuildCameraDistance", 1.25f, "Farthest allowed camera distance."); ScrollDistanceStep = ((BaseUnityPlugin)this).Config.Bind("Camera Distance", "ScrollDistanceStep", 0.05f, "Distance changed per camera distance key press or mouse wheel notch."); RememberScrollDistance = ((BaseUnityPlugin)this).Config.Bind("Camera Distance", "RememberScrollDistance", false, "If true, keeps the adjusted camera distance between immersive build camera sessions."); ShoulderOffsetX = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "ShoulderOffsetX", 0.9f, "Horizontal shoulder offset. Higher values make shoulder peek more useful but more likely to hit collision."); ShoulderOffsetY = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "ShoulderOffsetY", 0.06f, "Vertical shoulder offset."); ShoulderDistance = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "ShoulderDistance", 0.55f, "Backward shoulder camera distance."); CollisionRadius = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "CollisionRadius", 0.1f, "Sphere radius used to prevent shoulder peek camera clipping into objects."); ToggleShoulderPeek = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "ToggleShoulderPeek", false, "If false, shoulder peek keys must be held. If true, shoulder peek keys toggle left, right, or centered."); EnablePrecisionMovement = ((BaseUnityPlugin)this).Config.Bind("Movement", "EnablePrecisionMovement", true, "Allow slow precision movement while immersive build camera is active."); PrecisionMovementDefaultOn = ((BaseUnityPlugin)this).Config.Bind("Movement", "PrecisionMovementDefaultOn", true, "Whether slow precision movement starts enabled whenever immersive build camera is toggled on."); PrecisionMoveMultiplier = ((BaseUnityPlugin)this).Config.Bind("Movement", "PrecisionMoveMultiplier", 0.35f, "Movement input multiplier when precision movement is enabled. Lower means slower."); HideLocalPlayerWhenImmersive = ((BaseUnityPlugin)this).Config.Bind("Local Visibility", "HideLocalPlayerWhenImmersive", true, "Hide only the local player's renderers while immersive build camera is active and shoulder peek is not being used."); EnableDebugLogs = ((BaseUnityPlugin)this).Config.Bind("Debug", "EnableDebugLogs", false, "Enable extra logging for camera, shoulder peek, precision movement, visibility, and cleanup state changes."); _harmony = new Harmony("com.geronimo.valheim.immersivebuildcamera"); _harmony.PatchAll(); PrecisionMovementPatches.Apply(_harmony); Log.LogInfo((object)"Immersive Build Camera 1.1.0 loaded."); } internal static void DebugLog(string message) { ConfigEntry enableDebugLogs = EnableDebugLogs; if (enableDebugLogs != null && enableDebugLogs.Value) { Log.LogInfo((object)("[Debug] " + message)); } } private void OnDestroy() { BuildCameraState.ForceInactive(); PlayerRendererVisibility.ForceVisible(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal static class PrecisionMovementPatches { internal static void Apply(Harmony harmony) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(Player), "SetControls", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(PrecisionMovementPatches), "PrefixSetControls", (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogWarning((object)"Could not find Player.SetControls. Precision movement patch skipped."); return; } if (methodInfo2 == null) { Plugin.Log.LogWarning((object)"Could not find precision movement prefix."); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"Patched Player.SetControls for precision movement."); } private static void PrefixSetControls(Player __instance, ref Vector3 movedir, ref bool run, ref bool autoRun) { //IL_005f: 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) if (Plugin.EnablePrecisionMovement.Value && BuildCameraState.Active && BuildCameraState.PrecisionMovementActive && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { float num = Mathf.Clamp(Plugin.PrecisionMoveMultiplier.Value, 0.05f, 1f); movedir *= num; run = false; autoRun = false; } } } }