Decompiled source of Tommys Ship Speed v1.1.0

plugins\TommysShipSpeed\TommysShipSpeed.dll

Decompiled 19 hours ago
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<bool> Enabled;

	internal static ConfigEntry<bool> DebugLogging;

	internal static ConfigEntry<float> 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<bool>(((BaseUnityPlugin)this).Config, "General", "Enabled", true, "Master switch for the mod.", true, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
		DebugLogging = ConfigFileExtensions.BindConfig<bool>(((BaseUnityPlugin)this).Config, "General", "DebugLogging", false, "Write ship mode, prefab and rower-count information to the BepInEx log.", false, (int?)null, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
		PropulsionRampSeconds = ConfigFileExtensions.BindConfig<float>(((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<float>(0f, 10f), (Action<ConfigEntryBase>)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<float>(((BaseUnityPlugin)this).Config, section, "HalfSailSpeedMultiplier", 1f, "Multiplier applied to wind/sail propulsion while using half sail.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 10f), (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
		shipConfig.FullSailMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, section, "FullSailSpeedMultiplier", 1f, "Multiplier applied to wind/sail propulsion while using full sail.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 10f), (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
		shipConfig.ForwardRowingMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, section, "ForwardRowingMultiplier", 1f, "Base propulsion multiplier while paddling/rowing forward.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 20f), (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
		shipConfig.ReverseRowingMultiplier = ConfigFileExtensions.BindConfig<float>(((BaseUnityPlugin)this).Config, section, "ReverseRowingMultiplier", 1f, "Base propulsion multiplier while paddling/rowing in reverse.", true, (int?)null, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 20f), (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
		shipConfig.PerRowerBonus = ConfigFileExtensions.BindConfig<float>(((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<float>(0f, 5f), (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
		shipConfig.MaxRowers = ConfigFileExtensions.BindConfig<int>(((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<int>(0, 32), (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
		shipConfig.RowersAffectSailing = ConfigFileExtensions.BindConfig<bool>(((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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
		shipConfig.AlwaysWindAtBack = ConfigFileExtensions.BindConfig<bool>(((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<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null);
		shipConfig.TurningSpeedMultiplier = ConfigFileExtensions.BindConfig<float>(((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<float>(0.05f, 20f), (Action<ConfigEntryBase>)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<float> HalfSailMultiplier;

	internal ConfigEntry<float> FullSailMultiplier;

	internal ConfigEntry<float> ForwardRowingMultiplier;

	internal ConfigEntry<float> ReverseRowingMultiplier;

	internal ConfigEntry<float> PerRowerBonus;

	internal ConfigEntry<int> MaxRowers;

	internal ConfigEntry<bool> RowersAffectSailing;

	internal ConfigEntry<bool> AlwaysWindAtBack;

	internal ConfigEntry<float> 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<int, RampState> RampStates = new Dictionary<int, RampState>();

	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<int> list = null;
			foreach (KeyValuePair<int, RampState> rampState in RampStates)
			{
				if (Time.frameCount - rampState.Value.LastSeenFrame > 7200)
				{
					if (list == null)
					{
						list = new List<int>();
					}
					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<ZNetView>();
	}

	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;
			}
		}
	}
}