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.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("ImmersiveBuildCamera")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ImmersiveBuildCamera")] [assembly: AssemblyTitle("ImmersiveBuildCamera")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ImmersiveBuildCamera { 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 { get { if (Active) { return GetShoulderDirection() != 0; } return false; } } internal static void Update(Player player) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) 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(); if (Plugin.EnablePrecisionMovement.Value && Input.GetKeyDown(Plugin.TogglePrecisionMovementKey.Value)) { SetPrecisionMovement(!PrecisionMovementActive); } } } internal static int GetShoulderDirection() { if (!Active) { return 0; } if (Plugin.ToggleShoulderPeek.Value) { return _toggledShoulderDirection; } return GetHeldShoulderDirection(); } private static void UpdateShoulderPeekState() { //IL_0018: 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) if (!Plugin.ToggleShoulderPeek.Value) { _toggledShoulderDirection = 0; return; } bool keyDown = Input.GetKeyDown(Plugin.LeftShoulderKey.Value); bool keyDown2 = Input.GetKeyDown(Plugin.RightShoulderKey.Value); if (keyDown && keyDown2) { _toggledShoulderDirection = 0; } else if (keyDown) { ToggleShoulderDirection(-1); } else if (keyDown2) { ToggleShoulderDirection(1); } } private static void ToggleShoulderDirection(int direction) { if (_toggledShoulderDirection == direction) { _toggledShoulderDirection = 0; } else { _toggledShoulderDirection = direction; } } private static int GetHeldShoulderDirection() { //IL_0005: 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) 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) { PrecisionMovementActive = Plugin.EnablePrecisionMovement.Value && Plugin.PrecisionMovementDefaultOn.Value; } else { PrecisionMovementActive = false; _toggledShoulderDirection = 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 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); } 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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //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_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_0059: 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_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: 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_006f: 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_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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: 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) //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) 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; int shoulderDirection = BuildCameraState.GetShoulderDirection(); if (shoulderDirection != 0) { float num = Plugin.ShoulderOffsetX.Value * (float)shoulderDirection; val2 += val.right * num; val2 += val.up * Plugin.ShoulderOffsetY.Value; val2 -= val.forward * Plugin.ShoulderDistance.Value; val2 = ResolveCameraCollision(position, val2); } ((Component)gameCamera).transform.position = val2; ((Component)gameCamera).transform.rotation = rotation; camera.fieldOfView = Plugin.BuildFov.Value; camera.nearClipPlane = Plugin.NearClip.Value; } } private static Vector3 ResolveCameraCollision(Vector3 anchorPosition, Vector3 desiredPosition) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_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_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_0021: 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_0037: 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_0068: 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_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_0048: 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; } } } [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); foreach (Renderer val in componentsInChildren) { 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", "0.2.3")] [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 = "0.2.3"; internal static ManualLogSource Log; internal static ConfigEntry ToggleCameraKey; internal static ConfigEntry TogglePrecisionMovementKey; internal static ConfigEntry LeftShoulderKey; internal static ConfigEntry RightShoulderKey; internal static ConfigEntry BuildFov; internal static ConfigEntry NearClip; 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; private Harmony _harmony; private void Awake() { //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0221: 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."); 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."); ShoulderOffsetX = ((BaseUnityPlugin)this).Config.Bind("Shoulder Peek", "ShoulderOffsetX", 0.75f, "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.5f, "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."); _harmony = new Harmony("com.geronimo.valheim.immersivebuildcamera"); _harmony.PatchAll(); PrecisionMovementPatches.Apply(_harmony); Log.LogInfo((object)"Immersive Build Camera loaded."); } private void OnDestroy() { PlayerRendererVisibility.ForceVisible(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal static class PrecisionMovementPatches { internal static void Apply(Harmony harmony) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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_0047: 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_0052: 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; } } } }