using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Extensions; using Jotunn.Utils; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace TommyMods.TommysShipSpeed; [NetworkCompatibility(/*Could not decode attribute arguments.*/)] [BepInPlugin("tommy.valheim.shipspeed", "Tommy's Ship Speed", "1.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class ShipSpeedPlugin : BaseUnityPlugin { public const string PluginGuid = "tommy.valheim.shipspeed"; public const string PluginName = "Tommy's Ship Speed"; public const string PluginVersion = "1.1.0"; internal static ConfigEntry Enabled; internal static ConfigEntry DebugLogging; internal static ConfigEntry PropulsionRampSeconds; internal static ShipConfig Raft; internal static ShipConfig Karve; internal static ShipConfig Longship; internal static ShipConfig Drakkar; private Harmony _harmony; internal static ManualLogSource Log; private static readonly MethodInfo GetAllPlayersMethod = AccessTools.Method(typeof(Player), "GetAllPlayers", (Type[])null, (Type[])null); private static readonly MethodInfo GetControlledShipMethod = AccessTools.Method(typeof(Player), "GetControlledShip", (Type[])null, (Type[])null); private static readonly FieldInfo PlayerAttachPointField = AccessTools.Field(typeof(Player), "m_attachPoint"); private void Awake() { //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Enabled = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, "General", "Enabled", true, "Master switch for the mod.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); DebugLogging = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, "General", "DebugLogging", false, "Write ship mode, prefab and rower-count information to the BepInEx log.", false, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); PropulsionRampSeconds = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, "General", "PropulsionRampSeconds", 0.35f, "Smoothly ramps modded propulsion when starting/changing speed to prevent a one-tick launch/jump. 0 disables smoothing.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), (Action)null, (ConfigurationManagerAttributes)null); Raft = CreateShipConfig("Raft", 1); Karve = CreateShipConfig("Karve", 2); Longship = CreateShipConfig("Longship", 6); Drakkar = CreateShipConfig("Drakkar", 8); _harmony = new Harmony("tommy.valheim.shipspeed"); _harmony.PatchAll(typeof(ShipSpeedPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Tommy's Ship Speed 1.1.0 loaded for Valheim 1.0."); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Supported vanilla prefabs: Raft, Karve, VikingShip, VikingShip_Ashlands."); } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } } private ShipConfig CreateShipConfig(string section, int defaultMaxRowers) { ShipConfig shipConfig = new ShipConfig(); shipConfig.HalfSailMultiplier = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, section, "HalfSailSpeedMultiplier", 1f, "Multiplier applied to wind/sail propulsion while using half sail.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 10f), (Action)null, (ConfigurationManagerAttributes)null); shipConfig.FullSailMultiplier = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, section, "FullSailSpeedMultiplier", 1f, "Multiplier applied to wind/sail propulsion while using full sail.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 10f), (Action)null, (ConfigurationManagerAttributes)null); shipConfig.ForwardRowingMultiplier = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, section, "ForwardRowingMultiplier", 1f, "Base propulsion multiplier while paddling/rowing forward.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 20f), (Action)null, (ConfigurationManagerAttributes)null); shipConfig.ReverseRowingMultiplier = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, section, "ReverseRowingMultiplier", 1f, "Base propulsion multiplier while paddling/rowing in reverse.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 20f), (Action)null, (ConfigurationManagerAttributes)null); shipConfig.PerRowerBonus = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, section, "PerOccupiedRowerBonus", 0.25f, "Extra rowing propulsion per additional seated/attached rower. 0.25 means +25% per rower.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), (Action)null, (ConfigurationManagerAttributes)null); shipConfig.MaxRowers = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, section, "MaxRowers", defaultMaxRowers, "Maximum number of additional seated rowers that can contribute propulsion on this ship.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange(0, 32), (Action)null, (ConfigurationManagerAttributes)null); shipConfig.RowersAffectSailing = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, section, "RowersAffectSailing", false, "If enabled, occupied rowers also multiply half/full sail propulsion. Disabled by default.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); shipConfig.AlwaysWindAtBack = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, section, "AlwaysWindAtBack", false, "If enabled, while the local player is actively sailing this ship at half/full sail, Valheim's reported wind direction is forced behind that ship. This changes the sailing wind and HUD wind indicator on that player's client.", true, (int?)null, (AcceptableValueBase)null, (Action)null, (ConfigurationManagerAttributes)null); shipConfig.TurningSpeedMultiplier = ConfigFileExtensions.BindConfig(((BaseUnityPlugin)this).Config, section, "TurningSpeedMultiplier", 1f, "Multiplier for steering/rudder response and turning force on this ship. 1 = vanilla, 2 = twice as responsive, 0.5 = half speed.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 20f), (Action)null, (ConfigurationManagerAttributes)null); return shipConfig; } internal static ShipConfig GetConfigForShip(Ship ship) { if ((Object)(object)ship == (Object)null || (Object)(object)((Component)ship).gameObject == (Object)null) { return null; } string text = ((Object)((Component)ship).gameObject).name ?? string.Empty; int num = text.IndexOf("(Clone)", StringComparison.OrdinalIgnoreCase); if (num >= 0) { text = text.Substring(0, num); } text = text.Trim(); if (string.Equals(text, "Raft", StringComparison.OrdinalIgnoreCase)) { return Raft; } if (string.Equals(text, "Karve", StringComparison.OrdinalIgnoreCase)) { return Karve; } if (string.Equals(text, "VikingShip", StringComparison.OrdinalIgnoreCase)) { return Longship; } if (string.Equals(text, "VikingShip_Ashlands", StringComparison.OrdinalIgnoreCase)) { return Drakkar; } return null; } internal static int CountActiveRowers(Ship ship, int max) { if ((Object)(object)ship == (Object)null || max <= 0 || GetAllPlayersMethod == null || PlayerAttachPointField == null) { return 0; } IEnumerable enumerable; try { enumerable = GetAllPlayersMethod.Invoke(null, null) as IEnumerable; } catch { return 0; } if (enumerable == null) { return 0; } int num = 0; Transform transform = ((Component)ship).transform; foreach (object item in enumerable) { Player val = (Player)((item is Player) ? item : null); if ((Object)(object)val == (Object)null) { continue; } Transform val2; try { object? value = PlayerAttachPointField.GetValue(val); val2 = (Transform)((value is Transform) ? value : null); } catch { continue; } if ((Object)(object)val2 == (Object)null || !IsTransformUnderShip(val2, transform)) { continue; } if (GetControlledShipMethod != null) { try { object objA = GetControlledShipMethod.Invoke(val, null); if (object.ReferenceEquals(objA, ship)) { continue; } } catch { } } num++; if (num >= max) { break; } } return num; } private static bool IsTransformUnderShip(Transform child, Transform shipTransform) { Transform val = child; for (int i = 0; i < 24; i++) { if (!((Object)(object)val != (Object)null)) { break; } if ((Object)(object)val == (Object)(object)shipTransform) { return true; } val = val.parent; } return false; } internal static bool IsSailing(Ship ship) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ship == (Object)null) { return false; } string a = ((object)ship.GetSpeedSetting()).ToString(); if (!string.Equals(a, "Half", StringComparison.OrdinalIgnoreCase)) { return string.Equals(a, "Full", StringComparison.OrdinalIgnoreCase); } return true; } internal static bool ShouldUseTailwind(Ship ship) { if (Enabled == null || !Enabled.Value || (Object)(object)ship == (Object)null || !IsSailing(ship)) { return false; } ShipConfig configForShip = GetConfigForShip(ship); if (configForShip != null && configForShip.AlwaysWindAtBack != null) { return configForShip.AlwaysWindAtBack.Value; } return false; } internal static void Debug(string text) { if (DebugLogging != null && DebugLogging.Value && Log != null) { Log.LogInfo((object)text); } } } internal sealed class ShipConfig { internal ConfigEntry HalfSailMultiplier; internal ConfigEntry FullSailMultiplier; internal ConfigEntry ForwardRowingMultiplier; internal ConfigEntry ReverseRowingMultiplier; internal ConfigEntry PerRowerBonus; internal ConfigEntry MaxRowers; internal ConfigEntry RowersAffectSailing; internal ConfigEntry AlwaysWindAtBack; internal ConfigEntry TurningSpeedMultiplier; } [HarmonyPatch(typeof(Ship), "CustomFixedUpdate", new Type[] { typeof(float) })] internal static class ShipCustomFixedUpdatePatch { private sealed class RampState { internal string Mode = "Stop"; internal float Sail = 1f; internal float Row = 1f; internal int LastSeenFrame; } internal sealed class State { internal bool Modified; internal float SailForceFactor; internal float Force; internal float BackwardForce; internal bool TurningModified; internal float SteerVelocityForceFactor; internal float SteerForce; internal float RudderSpeed; } private static readonly FieldInfo ForceField = AccessTools.Field(typeof(Ship), "m_force"); private static readonly FieldInfo NViewField = AccessTools.Field(typeof(Ship), "m_nview"); private static readonly FieldInfo SteerVelocityForceFactorField = AccessTools.Field(typeof(Ship), "m_stearVelForceFactor"); private static readonly FieldInfo SteerForceField = AccessTools.Field(typeof(Ship), "m_stearForce"); private static readonly FieldInfo RudderSpeedField = AccessTools.Field(typeof(Ship), "m_rudderSpeed"); private static readonly Dictionary RampStates = new Dictionary(); private static int _lastRampCleanupFrame; private static float SmoothMultiplier(float current, float target, float dt) { float num = ((ShipSpeedPlugin.PropulsionRampSeconds != null) ? ShipSpeedPlugin.PropulsionRampSeconds.Value : 0f); if (num <= 0.001f) { return target; } float num2 = Math.Abs(target - 1f) * Math.Max(0f, dt) / num; if (num2 < 0.001f) { num2 = 0.001f; } return Mathf.MoveTowards(current, target, num2); } private static RampState GetRampState(Ship ship, string mode) { int instanceID = ((Object)ship).GetInstanceID(); if (!RampStates.TryGetValue(instanceID, out var value)) { value = new RampState(); RampStates[instanceID] = value; } value.LastSeenFrame = Time.frameCount; if (string.Equals(value.Mode, "Stop", StringComparison.OrdinalIgnoreCase) && !string.Equals(mode, "Stop", StringComparison.OrdinalIgnoreCase)) { value.Sail = 1f; value.Row = 1f; } if (string.Equals(mode, "Stop", StringComparison.OrdinalIgnoreCase)) { value.Sail = 1f; value.Row = 1f; } value.Mode = mode; if (Time.frameCount - _lastRampCleanupFrame > 3600) { _lastRampCleanupFrame = Time.frameCount; List list = null; foreach (KeyValuePair rampState in RampStates) { if (Time.frameCount - rampState.Value.LastSeenFrame > 7200) { if (list == null) { list = new List(); } list.Add(rampState.Key); } } if (list != null) { foreach (int item in list) { RampStates.Remove(item); } } } return value; } private static ZNetView GetNView(Ship ship) { if ((Object)(object)ship == (Object)null) { return null; } if (NViewField != null) { object? value = NViewField.GetValue(ship); ZNetView val = (ZNetView)((value is ZNetView) ? value : null); if ((Object)(object)val != (Object)null) { return val; } } return ((Component)ship).GetComponent(); } private static void Prefix(Ship __instance, float __0, out State __state) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) __state = new State(); if (ShipSpeedPlugin.Enabled == null || !ShipSpeedPlugin.Enabled.Value) { return; } ZNetView nView = GetNView(__instance); if ((Object)(object)__instance == (Object)null || (Object)(object)nView == (Object)null || !nView.IsValid() || !nView.IsOwner()) { return; } ShipConfig configForShip = ShipSpeedPlugin.GetConfigForShip(__instance); if (configForShip == null) { return; } string text = ((object)__instance.GetSpeedSetting()).ToString(); int num = ShipSpeedPlugin.CountActiveRowers(__instance, configForShip.MaxRowers.Value); float num2 = 1f + (float)num * configForShip.PerRowerBonus.Value; float num3 = 1f; float num4 = 1f; bool flag = false; bool flag2 = false; if (string.Equals(text, "Half", StringComparison.OrdinalIgnoreCase)) { num3 = configForShip.HalfSailMultiplier.Value; if (configForShip.RowersAffectSailing.Value) { num3 *= num2; } flag = true; } else if (string.Equals(text, "Full", StringComparison.OrdinalIgnoreCase)) { num3 = configForShip.FullSailMultiplier.Value; if (configForShip.RowersAffectSailing.Value) { num3 *= num2; } flag = true; } else if (string.Equals(text, "Slow", StringComparison.OrdinalIgnoreCase)) { num4 = configForShip.ForwardRowingMultiplier.Value * num2; flag2 = true; } else if (string.Equals(text, "Back", StringComparison.OrdinalIgnoreCase)) { num4 = configForShip.ReverseRowingMultiplier.Value * num2; flag2 = true; } RampState rampState = GetRampState(__instance, text); if (flag) { rampState.Sail = SmoothMultiplier(rampState.Sail, num3, __0); num3 = rampState.Sail; } else { rampState.Sail = 1f; } if (flag2) { rampState.Row = SmoothMultiplier(rampState.Row, num4, __0); num4 = rampState.Row; } else { rampState.Row = 1f; } float num5 = ((configForShip.TurningSpeedMultiplier != null) ? configForShip.TurningSpeedMultiplier.Value : 1f); bool flag3 = Math.Abs(num5 - 1f) > 0.0001f; if (!flag && !flag2 && !flag3) { return; } __state.SailForceFactor = __instance.m_sailForceFactor; __state.Force = ((ForceField != null) ? ((float)ForceField.GetValue(__instance)) : 0f); __state.BackwardForce = __instance.m_backwardForce; __state.Modified = true; if (flag3) { __state.TurningModified = true; if (SteerVelocityForceFactorField != null) { __state.SteerVelocityForceFactor = (float)SteerVelocityForceFactorField.GetValue(__instance); SteerVelocityForceFactorField.SetValue(__instance, __state.SteerVelocityForceFactor * num5); } if (SteerForceField != null) { __state.SteerForce = (float)SteerForceField.GetValue(__instance); SteerForceField.SetValue(__instance, __state.SteerForce * num5); } if (RudderSpeedField != null) { __state.RudderSpeed = (float)RudderSpeedField.GetValue(__instance); RudderSpeedField.SetValue(__instance, __state.RudderSpeed * num5); } } if (flag) { __instance.m_sailForceFactor = __state.SailForceFactor * num3; } if (flag2) { if (ForceField != null) { ForceField.SetValue(__instance, __state.Force * num4); } __instance.m_backwardForce = __state.BackwardForce * num4; } ShipSpeedPlugin.Debug("Ship=" + ((Object)((Component)__instance).gameObject).name + " mode=" + text + " rowers=" + num + " crewX=" + num2.ToString("0.00") + " sailX=" + num3.ToString("0.00") + " rowX=" + num4.ToString("0.00") + " turnX=" + num5.ToString("0.00")); } private static void Postfix(Ship __instance, State __state) { if ((Object)(object)__instance == (Object)null || __state == null || !__state.Modified) { return; } __instance.m_sailForceFactor = __state.SailForceFactor; if (ForceField != null) { ForceField.SetValue(__instance, __state.Force); } __instance.m_backwardForce = __state.BackwardForce; if (__state.TurningModified) { if (SteerVelocityForceFactorField != null) { SteerVelocityForceFactorField.SetValue(__instance, __state.SteerVelocityForceFactor); } if (SteerForceField != null) { SteerForceField.SetValue(__instance, __state.SteerForce); } if (RudderSpeedField != null) { RudderSpeedField.SetValue(__instance, __state.RudderSpeed); } } } } [HarmonyPatch(typeof(EnvMan), "GetWindDir")] internal static class SailingWindDirectionPatch { private static readonly MethodInfo GetControlledShipMethod = AccessTools.Method(typeof(Player), "GetControlledShip", (Type[])null, (Type[])null); private static Ship GetLocallyControlledSailingShip() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || GetControlledShipMethod == null) { return null; } Ship val = null; try { object? obj = GetControlledShipMethod.Invoke(localPlayer, null); val = (Ship)((obj is Ship) ? obj : null); } catch { return null; } if ((Object)(object)val == (Object)null || !ShipSpeedPlugin.ShouldUseTailwind(val)) { return null; } return val; } private static void Postfix(ref Vector3 __result) { //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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) Ship locallyControlledSailingShip = GetLocallyControlledSailingShip(); if (!((Object)(object)locallyControlledSailingShip == (Object)null)) { Vector3 forward = ((Component)locallyControlledSailingShip).transform.forward; forward.y = 0f; if (!(((Vector3)(ref forward)).sqrMagnitude <= 0.0001f)) { __result = ((Vector3)(ref forward)).normalized; } } } }