using System; using System.Diagnostics; using System.Linq; 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(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("SirRomey")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 SirRomey")] [assembly: AssemblyDescription("Locks the wind to a fixed angle off your ship's heading while you are at the helm with sails up. Client-side; wind strength and ship stats are untouched.")] [assembly: AssemblyFileVersion("1.0.2.0")] [assembly: AssemblyInformationalVersion("1.0.2+35929e141fb8d0470c70ff1e563129af6bc5b10a")] [assembly: AssemblyProduct("HelmWind")] [assembly: AssemblyTitle("HelmWind")] [assembly: AssemblyVersion("1.0.2.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 HelmWind { public enum SailMode { Off, DeadAstern, Offset } [BepInPlugin("SirRomey.HelmWind", "HelmWind", "1.0.2")] public class Plugin : BaseUnityPlugin { public const string GUID = "SirRomey.HelmWind"; public const string ModName = "HelmWind"; public const string ModVersion = "1.0.2"; internal static ManualLogSource Log; private static ConfigEntry _enabled; private static ConfigEntry _offset; private static ConfigEntry _toggleKey; private static SailMode _mode = SailMode.Offset; private static bool _active; private static bool _focusCheckStoodDown; private Harmony _harmony; private static readonly KeyCode[] BlockingModifiers; public static bool Enabled { get { if (_enabled != null) { return _enabled.Value; } return false; } } public static SailMode Mode => _mode; public static float OffsetDegrees => Mathf.Clamp(_offset.Value, -90f, 90f); public static float ActiveOffsetDegrees { get { if (_mode != SailMode.DeadAstern) { return OffsetDegrees; } return 0f; } } private void Awake() { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0082: 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) //IL_009c: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_0152: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; _enabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Master switch. With this off the wind is never touched, whatever mode is showing."); _offset = ((BaseUnityPlugin)this).Config.Bind("General", "Offset", 60f, new ConfigDescription("Angle in degrees between the wind and the ship's heading, used by Offset mode. The sign picks the side the wind comes over: negative is one side, positive the other. Valheim's sail physics is symmetric about the centreline, so the sign changes which way you heel and nothing else - only the magnitude affects speed. Sail force peaks near 63 degrees, which is why the default is 60.", (AcceptableValueBase)(object)new AcceptableValueRange(-90f, 90f), Array.Empty())); _toggleKey = ((BaseUnityPlugin)this).Config.Bind("General", "ToggleKey", new KeyboardShortcut((KeyCode)289, Array.Empty()), new ConfigDescription("Cycles Off, then DeadAstern, then Offset, then back to Off. Modifiers work, so LeftControl+F8 and the like are valid. Ignored while the console, chat, or a text prompt has the keyboard.", (AcceptableValueBase)null, Array.Empty())); _harmony = new Harmony("SirRomey.HelmWind"); if (AccessTools.Method(typeof(EnvMan), "SetTargetWind", new Type[2] { typeof(Vector3), typeof(float) }, (Type[])null) == null) { Log.LogError((object)"could not find EnvMan.SetTargetWind(Vector3, float) - game update? wind override is off"); return; } _harmony.PatchAll(typeof(WindPatch)); _active = true; Log.LogInfo((object)string.Format("loaded v{0}, mode {1}, offset {2:0.#} deg, key {3}", "1.0.2", _mode, OffsetDegrees, _toggleKey.Value)); } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void Update() { if (_active && Enabled && !TypingSomewhere() && ToggleKeyPressed()) { CycleMode(); } } private static bool ToggleKeyPressed() { //IL_0005: 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_000d: 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_005a: 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_0066: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = _toggleKey.Value; if ((int)((KeyboardShortcut)(ref value)).MainKey == 0 || !Input.GetKeyDown(((KeyboardShortcut)(ref value)).MainKey)) { return false; } KeyCode[] array = ((KeyboardShortcut)(ref value)).Modifiers.ToArray(); KeyCode[] array2 = array; for (int i = 0; i < array2.Length; i++) { if (!Input.GetKey(array2[i])) { return false; } } array2 = BlockingModifiers; foreach (KeyCode val in array2) { if (Input.GetKey(val) && Array.IndexOf(array, val) < 0) { return false; } } return true; } private static bool TypingSomewhere() { if (_focusCheckStoodDown) { return false; } try { if (Console.IsVisible() || TextInput.IsVisible()) { return true; } return (Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus(); } catch (Exception arg) { _focusCheckStoodDown = true; Log.LogError((object)$"focus check threw, treating as not typing from here on: {arg}"); return false; } } private static void CycleMode() { string text; switch (_mode) { case SailMode.Off: _mode = SailMode.DeadAstern; text = "HelmWind: dead astern"; break; case SailMode.DeadAstern: _mode = SailMode.Offset; text = $"HelmWind: offset {OffsetDegrees:0.#} deg"; break; default: _mode = SailMode.Off; text = "HelmWind: off, vanilla wind"; break; } Log.LogInfo((object)$"mode {_mode}"); if ((Object)(object)Player.m_localPlayer != (Object)null) { ((Character)Player.m_localPlayer).Message((MessageType)1, text, 0, (Sprite)null, false); } } static Plugin() { KeyCode[] array = new KeyCode[6]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); BlockingModifiers = (KeyCode[])(object)array; } } [HarmonyPatch(typeof(EnvMan), "SetTargetWind")] internal static class WindPatch { private const float MinHeadingMagnitude = 0.0001f; private static bool _stoodDown; private static bool Prefix(ref Vector3 dir) { //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) if (_stoodDown) { return true; } try { if (TryGetHeadingWind(out var wind)) { dir = wind; } } catch (Exception arg) { _stoodDown = true; Plugin.Log.LogError((object)$"wind override threw, standing down for this session: {arg}"); } return true; } private static bool TryGetHeadingWind(out Vector3 wind) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: 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_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) wind = Vector3.zero; if (!Plugin.Enabled || Plugin.Mode == SailMode.Off) { return false; } EnvMan instance = EnvMan.instance; if ((Object)(object)instance == (Object)null || instance.m_debugWind) { return false; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return false; } Ship controlledShip = localPlayer.GetControlledShip(); if ((Object)(object)controlledShip == (Object)null) { return false; } if (!controlledShip.IsSailUp() || !controlledShip.IsOwner()) { return false; } Vector3 forward = ((Component)controlledShip).transform.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 9.999999E-09f) { return false; } wind = Quaternion.AngleAxis(Plugin.ActiveOffsetDegrees, Vector3.up) * ((Vector3)(ref forward)).normalized; return true; } } }