Decompiled source of CrewRow v0.1.5

CrewRow.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using CrewRow.Config;
using CrewRow.Core;
using CrewRow.Features;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: IgnoresAccessChecksTo("assembly_utils")]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: AssemblyCompany("valheimcrew")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Los tripulantes de un barco reman junto al timonel para ganar velocidad con viento en contra.")]
[assembly: AssemblyFileVersion("0.1.5.0")]
[assembly: AssemblyInformationalVersion("0.1.5+32c3d8c668d6b2ff2442afbf04b3f12bfceded99")]
[assembly: AssemblyProduct("CrewRow")]
[assembly: AssemblyTitle("CrewRow")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.5.0")]
[module: UnverifiableCode]
[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 CrewRow
{
	[BepInPlugin("com.valheimcrew.crewrow", "CrewRow", "0.1.5")]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.valheimcrew.crewrow";

		public const string PluginName = "CrewRow";

		public const string PluginVersion = "0.1.5";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			PluginConfig.Bind(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("com.valheimcrew.crewrow");
			ApplyPatches();
			WarnAboutConflicts();
			Log.LogInfo((object)"CrewRow 0.1.5 cargado.");
		}

		private void ApplyPatches()
		{
			PatchGroup("aviso de remada", typeof(Ship_Start_Patch));
			PatchGroup("empuje de la tripulacion", typeof(Ship_CustomFixedUpdate_Patch));
			PatchGroup("ciclo de vida", typeof(Game_Logout_Patch));
		}

		private void PatchGroup(string feature, params Type[] patchTypes)
		{
			foreach (Type type in patchTypes)
			{
				try
				{
					_harmony.PatchAll(type);
				}
				catch (Exception ex)
				{
					Log.LogError((object)("No se pudo enganchar '" + feature + "' (" + type.Name + "). Esa parte del mod queda desactivada; probablemente el juego cambio en un parche. Detalle: " + ex.Message));
				}
			}
		}

		private void Update()
		{
			if (!((Object)(object)Player.m_localPlayer == (Object)null))
			{
				SafeTick(RowerClient.Tick, "el aviso de remada");
				SafeTick(SpeedReadout.Tick, "el indicador de velocidad");
			}
		}

		private static void SafeTick(Action tick, string what)
		{
			try
			{
				tick();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Fallo en {what}: {arg}");
			}
		}

		internal static bool IsTyping()
		{
			if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus())
			{
				return true;
			}
			if (!Console.IsVisible() && !TextInput.IsVisible())
			{
				return StoreGui.IsVisible();
			}
			return true;
		}

		private void OnDestroy()
		{
			RowBoost.Restore();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private static void WarnAboutConflicts()
		{
			string[] marcas = new string[7] { "advancedboating", "rowing", "fasterboats", "hexrowfaster", "boatspeed", "sailing", "shipspeed" };
			string[] array = Chainloader.PluginInfos.Keys.Where((string guid) => guid != "com.valheimcrew.crewrow" && marcas.Any((string m) => guid.IndexOf(m, StringComparison.OrdinalIgnoreCase) >= 0)).ToArray();
			if (array.Length != 0)
			{
				Log.LogWarning((object)("Hay otros mods cargados que tambien tocan la velocidad de los barcos: " + string.Join(", ", array) + ". Conviene dejar solo uno para que las fuerzas no se multipliquen entre si."));
			}
		}
	}
}
namespace CrewRow.Features
{
	internal static class RowBoost
	{
		private static Ship _boosted;

		private static float _original;

		private static float _applied;

		private static Ship _tickShip;

		private static int _rowersThisTick;

		private static bool _disabled;

		private static bool _disabledLogged;

		public static void Restore()
		{
			if ((Object)(object)_boosted == (Object)null)
			{
				_boosted = null;
				return;
			}
			if (_boosted.m_backwardForce == _applied)
			{
				_boosted.m_backwardForce = _original;
			}
			_boosted = null;
		}

		private static void Abort()
		{
			Restore();
			_tickShip = null;
			_rowersThisTick = 0;
		}

		public static void Begin(Ship ship)
		{
			//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_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Invalid comparison between Unknown and I4
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Invalid comparison between Unknown and I4
			Restore();
			_tickShip = null;
			_rowersThisTick = 0;
			if (_disabled || !PluginConfig.Enabled.Value || (Object)(object)ship == (Object)null)
			{
				return;
			}
			ZNetView nview = ship.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner())
			{
				return;
			}
			_tickShip = ship;
			_rowersThisTick = RowerRegistry.CountActive(ship);
			Notify(ship, _rowersThisTick);
			if (_rowersThisTick <= 0)
			{
				return;
			}
			Speed speed = ship.m_speed;
			if ((int)speed == 2 || ((int)speed == 1 && PluginConfig.ApplyInReverse.Value))
			{
				float num = Bonus(_rowersThisTick);
				if (!(num <= 0f))
				{
					_original = ship.m_backwardForce;
					_applied = _original * (1f + num);
					ship.m_backwardForce = _applied;
					_boosted = ship;
				}
			}
		}

		public static void End(Ship ship, float fixedDeltaTime)
		{
			Restore();
			bool flag = _tickShip != null && _tickShip == ship;
			int rowersThisTick = _rowersThisTick;
			_tickShip = null;
			_rowersThisTick = 0;
			if (!_disabled && flag && rowersThisTick > 0 && !((Object)(object)ship == (Object)null) && PluginConfig.Scope.Value != AlcanceDelEmpuje.SoloRemo)
			{
				ApplySailAssist(ship, fixedDeltaTime, rowersThisTick);
			}
		}

		private static void ApplySailAssist(Ship ship, float fixedDeltaTime, int rowers)
		{
			//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_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Invalid comparison between Unknown and I4
			//IL_0061: 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)
			//IL_0067: 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_0087: 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_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: 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_00a5: 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_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			Speed speed = ship.m_speed;
			if (((int)speed != 3 && (int)speed != 4) || ship.m_lastUpdateWaterForceTime != Time.time)
			{
				return;
			}
			Rigidbody body = ship.m_body;
			if (!((Object)(object)body == (Object)null))
			{
				float num = Bonus(rowers);
				if (PluginConfig.Scope.Value == AlcanceDelEmpuje.RemoYVelaEnContra)
				{
					num *= 1f - ship.GetWindAngleFactor();
				}
				if (!(num <= 0.0001f))
				{
					Vector3 forward = ((Component)ship).transform.forward;
					Vector3 val = forward * (ship.m_backwardForce * num * (1f - Mathf.Abs(ship.m_rudderValue)));
					Vector3 val2 = ((Component)ship).transform.position + forward * ship.m_stearForceOffset;
					body.AddForceAtPosition(val * (body.mass * fixedDeltaTime), val2, (ForceMode)1);
				}
			}
		}

		private static float Bonus(int rowers)
		{
			float num = (float)rowers * PluginConfig.BonusPerRower.Value;
			float value = PluginConfig.MaxBonus.Value;
			if (!(num < value))
			{
				return value;
			}
			return num;
		}

		private static void Notify(Ship ship, int rowers)
		{
			if (!RowerRegistry.CountChanged(ship, rowers))
			{
				return;
			}
			RowLog.Count(ship, rowers, 1f + Bonus(rowers));
			if (!PluginConfig.ShowNotices.Value || (Object)(object)ship.m_shipControlls == (Object)null)
			{
				return;
			}
			long user = ship.m_shipControlls.GetUser();
			if (user == 0L)
			{
				return;
			}
			Player player = Player.GetPlayer(user);
			if (!((Object)(object)player == (Object)null))
			{
				ZNetView nview = ((Character)player).m_nview;
				if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.HasOwner())
				{
					((Character)player).Message((MessageType)1, (rowers > 0) ? ("Remando: " + rowers) : "Nadie esta remando", 0, (Sprite)null, false);
				}
			}
		}

		internal static void DisableAfterFailure(string what, Exception e)
		{
			Abort();
			_disabled = true;
			if (!_disabledLogged)
			{
				_disabledLogged = true;
				Plugin.Log.LogError((object)(what + ". El empuje de la tripulacion queda desactivado hasta reiniciar el juego; los barcos siguen navegando normal. Detalle: " + e));
			}
		}
	}
	[HarmonyPatch(typeof(Ship), "CustomFixedUpdate")]
	internal static class Ship_CustomFixedUpdate_Patch
	{
		private static void Prefix(Ship __instance)
		{
			try
			{
				RowBoost.Begin(__instance);
			}
			catch (Exception e)
			{
				RowBoost.DisableAfterFailure("Fallo al preparar el empuje de la tripulacion", e);
			}
		}

		private static void Postfix(Ship __instance, float fixedDeltaTime)
		{
			try
			{
				RowBoost.End(__instance, fixedDeltaTime);
			}
			catch (Exception e)
			{
				RowBoost.DisableAfterFailure("Fallo al aplicar el empuje de la tripulacion", e);
			}
		}

		private static Exception Finalizer(Exception __exception)
		{
			RowBoost.Restore();
			return __exception;
		}
	}
	internal static class RowerClient
	{
		private const float StaminaChargeInterval = 0.5f;

		private const float ResumeStaminaFraction = 0.25f;

		private const float MinSendInterval = 0.1f;

		private const float MoveThreshold = 0.1f;

		private static bool _rowing;

		private static bool _exhausted;

		private static bool _lastSent;

		private static float _nextSend;

		private static float _lastSend;

		private static long _lastOwner;

		private static Ship _lastShip;

		private static float _staminaTimer;

		public static void Tick()
		{
			Player localPlayer = Player.m_localPlayer;
			Ship val = (((Object)(object)localPlayer == (Object)null || !PluginConfig.Enabled.Value) ? null : Ship.GetLocalShip());
			if (val != _lastShip)
			{
				SendStopTo(_lastShip, localPlayer);
				_lastShip = val;
				_lastOwner = 0L;
				_rowing = false;
				_exhausted = false;
				_lastSent = false;
				_staminaTimer = 0f;
			}
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)val == (Object)null)
			{
				SetRowing(val, rowing: false, "el barco ya no esta");
				return;
			}
			long playerID = localPlayer.GetPlayerID();
			if (CanRow(val, localPlayer, playerID, out var reason))
			{
				ReadHotkey(val);
			}
			else if (_rowing)
			{
				SetRowing(val, rowing: false, reason);
			}
			else
			{
				ExplainIfTried(localPlayer, reason);
			}
			if (_rowing)
			{
				UpdateStamina(localPlayer);
			}
			Publish(val, playerID);
		}

		private static bool IsRowingNow()
		{
			if (_rowing)
			{
				return !_exhausted;
			}
			return false;
		}

		public static void Reset()
		{
			_rowing = false;
			_exhausted = false;
			_lastSent = false;
			_nextSend = 0f;
			_lastSend = 0f;
			_lastOwner = 0L;
			_lastShip = null;
			_staminaTimer = 0f;
		}

		private static bool CanRow(Ship ship, Player me, long myID, out string reason)
		{
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			reason = null;
			ZNetView nview = ship.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid())
			{
				reason = "el barco no responde";
				return false;
			}
			if ((Object)(object)ship.m_shipControlls != (Object)null && ship.m_shipControlls.GetUser() == myID)
			{
				reason = "estas en el timon";
				return false;
			}
			if (!SpeedCounts(ship.GetSpeedSetting()))
			{
				reason = "el barco no esta remando";
				return false;
			}
			if (PluginConfig.CancelOnMove.Value && ((Vector3)(ref ((Character)me).m_moveDir)).magnitude > 0.1f)
			{
				reason = "estas en movimiento";
				return false;
			}
			return true;
		}

		private static void ExplainIfTried(Player me, string reason)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			if (reason != null && PluginConfig.ShowNotices.Value && !((Object)(object)me == (Object)null) && !Plugin.IsTyping())
			{
				KeyboardShortcut value = PluginConfig.Hotkey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown() && !((Object)(object)me.GetControlledShip() != (Object)null))
				{
					((Character)me).Message((MessageType)1, "No podes remar: " + reason, 0, (Sprite)null, false);
				}
			}
		}

		private static bool SpeedCounts(Speed speed)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Invalid comparison between Unknown and I4
			if ((int)speed == 0)
			{
				return false;
			}
			if ((int)speed == 1)
			{
				return PluginConfig.ApplyInReverse.Value;
			}
			if ((int)speed == 2)
			{
				return true;
			}
			return PluginConfig.Scope.Value != AlcanceDelEmpuje.SoloRemo;
		}

		private static void ReadHotkey(Ship ship)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: 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_0020: Unknown result type (might be due to invalid IL or missing references)
			bool flag = Plugin.IsTyping();
			KeyboardShortcut value;
			if (PluginConfig.Mode.Value == ModoDeRemada.Mantener)
			{
				int num;
				if (!flag)
				{
					value = PluginConfig.Hotkey.Value;
					num = (((KeyboardShortcut)(ref value)).IsPressed() ? 1 : 0);
				}
				else
				{
					num = 0;
				}
				bool flag2 = (byte)num != 0;
				if (flag2 != _rowing)
				{
					SetRowing(ship, flag2, null);
				}
			}
			else if (!flag)
			{
				value = PluginConfig.Hotkey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					SetRowing(ship, !_rowing, null);
				}
			}
		}

		private static void SetRowing(Ship ship, bool rowing, string reason)
		{
			if (_rowing == rowing)
			{
				return;
			}
			_rowing = rowing;
			_exhausted = false;
			_nextSend = 0f;
			RowLog.Local(ship, rowing);
			if (!PluginConfig.ShowNotices.Value)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				if (rowing)
				{
					((Character)localPlayer).Message((MessageType)1, "Estas remando", 0, (Sprite)null, false);
				}
				else
				{
					((Character)localPlayer).Message((MessageType)1, (reason == null) ? "Dejaste de remar" : ("Dejaste de remar: " + reason), 0, (Sprite)null, false);
				}
			}
		}

		private static void UpdateStamina(Player me)
		{
			float value = PluginConfig.StaminaPerSecond.Value;
			if (value <= 0f)
			{
				_exhausted = false;
				_staminaTimer = 0f;
				return;
			}
			if (_exhausted)
			{
				if (((Character)me).HaveStamina(((Character)me).GetMaxStamina() * 0.25f))
				{
					_exhausted = false;
					Say(me, "Volves a remar");
				}
				return;
			}
			_staminaTimer += Time.deltaTime;
			if (!(_staminaTimer < 0.5f))
			{
				float num = value * _staminaTimer;
				_staminaTimer = 0f;
				if (!((Character)me).HaveStamina(num))
				{
					_exhausted = true;
					Say(me, "Sin stamina: paras de remar hasta recuperar");
				}
				else
				{
					((Character)me).UseStamina(num);
				}
			}
		}

		private static void Say(Player me, string text)
		{
			if (PluginConfig.ShowNotices.Value && (Object)(object)me != (Object)null)
			{
				((Character)me).Message((MessageType)1, text, 0, (Sprite)null, false);
			}
		}

		private static void Publish(Ship ship, long myID)
		{
			ZNetView nview = ship.m_nview;
			if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.HasOwner())
			{
				return;
			}
			long owner = nview.GetZDO().GetOwner();
			bool flag = IsRowingNow();
			if (!flag && !_lastSent)
			{
				_lastOwner = owner;
				return;
			}
			float time = Time.time;
			if ((owner != _lastOwner || flag != _lastSent || !(time < _nextSend)) && !(time - _lastSend < 0.1f))
			{
				nview.InvokeRPC("CrewRow_Rowing", new object[2] { myID, flag });
				_lastSent = flag;
				_lastOwner = owner;
				_lastSend = time;
				_nextSend = time + PluginConfig.HeartbeatSeconds.Value;
			}
		}

		private static void SendStopTo(Ship ship, Player me)
		{
			if (_lastSent && !((Object)(object)ship == (Object)null) && !((Object)(object)me == (Object)null))
			{
				ZNetView nview = ship.m_nview;
				if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.HasOwner())
				{
					nview.InvokeRPC("CrewRow_Rowing", new object[2]
					{
						me.GetPlayerID(),
						false
					});
				}
			}
		}
	}
	internal static class RowerRegistry
	{
		private sealed class Budget
		{
			public float WindowStart;

			public int Used;

			public int Dropped;
		}

		private sealed class ShipState
		{
			public readonly Dictionary<long, float> Rowers = new Dictionary<long, float>();

			public readonly Dictionary<long, Budget> Budgets = new Dictionary<long, Budget>();

			public int LastNotified;
		}

		internal const string RpcName = "CrewRow_Rowing";

		private const int MaxSenders = 64;

		private static readonly Dictionary<Ship, ShipState> States = new Dictionary<Ship, ShipState>();

		private static readonly List<Ship> DeadShips = new List<Ship>(8);

		private static readonly List<long> ExpiredRowers = new List<long>(16);

		private static readonly List<long> ExpiredSenders = new List<long>(16);

		private static float _nextRejectLog;

		public static void Register(Ship ship)
		{
			if (!((Object)(object)ship == (Object)null) && !((Object)(object)ship.m_nview == (Object)null) && ship.m_nview.IsValid())
			{
				ship.m_nview.Register<long, bool>("CrewRow_Rowing", (Action<long, long, bool>)delegate(long sender, long playerID, bool rowing)
				{
					Handle(ship, sender, playerID, rowing);
				});
			}
		}

		private static void Handle(Ship ship, long sender, long playerID, bool rowing)
		{
			try
			{
				if ((Object)(object)ship == (Object)null)
				{
					return;
				}
				ZNetView nview = ship.m_nview;
				if ((Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner() || !PluginConfig.Enabled.Value || playerID == 0L || sender == 0L)
				{
					return;
				}
				if (!ship.IsPlayerInBoat(playerID))
				{
					LogRejected("no esta a bordo", sender, playerID);
					return;
				}
				Player player = Player.GetPlayer(playerID);
				if ((Object)(object)player == (Object)null || ((Character)player).GetOwner() != sender)
				{
					LogRejected("la identidad no coincide con el remitente", sender, playerID);
					return;
				}
				ShipState state = GetState(ship);
				if (TakeBudget(state, sender))
				{
					if ((Object)(object)ship.m_shipControlls != (Object)null && ship.m_shipControlls.GetUser() == playerID)
					{
						state.Rowers.Remove(playerID);
					}
					else if (rowing)
					{
						state.Rowers[playerID] = Time.time;
					}
					else
					{
						state.Rowers.Remove(playerID);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Fallo al procesar un aviso de remada: " + ex));
			}
		}

		public static int CountActive(Ship ship)
		{
			if ((Object)(object)ship == (Object)null)
			{
				return 0;
			}
			if (!States.TryGetValue(ship, out var value) || value.Rowers.Count == 0)
			{
				return 0;
			}
			float time = Time.time;
			float num = PluginConfig.StaleSeconds.Value;
			float num2 = PluginConfig.HeartbeatSeconds.Value * 2f;
			if (num < num2)
			{
				num = num2;
			}
			ExpiredRowers.Clear();
			foreach (KeyValuePair<long, float> rower in value.Rowers)
			{
				if (time - rower.Value > num || !ship.IsPlayerInBoat(rower.Key))
				{
					ExpiredRowers.Add(rower.Key);
				}
			}
			for (int i = 0; i < ExpiredRowers.Count; i++)
			{
				value.Rowers.Remove(ExpiredRowers[i]);
			}
			int value2 = PluginConfig.MaxRowers.Value;
			if (value.Rowers.Count >= value2)
			{
				return value2;
			}
			return value.Rowers.Count;
		}

		public static bool CountChanged(Ship ship, int rowers)
		{
			if ((Object)(object)ship == (Object)null || !States.TryGetValue(ship, out var value))
			{
				return false;
			}
			if (value.LastNotified == rowers)
			{
				return false;
			}
			value.LastNotified = rowers;
			return true;
		}

		public static void Reset()
		{
			States.Clear();
		}

		private static ShipState GetState(Ship ship)
		{
			if (States.TryGetValue(ship, out var value))
			{
				return value;
			}
			PruneDeadShips();
			value = new ShipState();
			States[ship] = value;
			return value;
		}

		private static void PruneDeadShips()
		{
			if (States.Count < 16)
			{
				return;
			}
			DeadShips.Clear();
			foreach (KeyValuePair<Ship, ShipState> state in States)
			{
				if ((Object)(object)state.Key == (Object)null)
				{
					DeadShips.Add(state.Key);
				}
			}
			for (int i = 0; i < DeadShips.Count; i++)
			{
				States.Remove(DeadShips[i]);
			}
		}

		private static bool TakeBudget(ShipState state, long sender)
		{
			if (!state.Budgets.TryGetValue(sender, out var value))
			{
				PruneBudgets(state);
				if (state.Budgets.Count >= 64)
				{
					return false;
				}
				value = new Budget();
				state.Budgets[sender] = value;
			}
			float time = Time.time;
			if (time - value.WindowStart >= 1f)
			{
				if (value.Dropped > 0)
				{
					RowLog.Rejected("inundacion, " + value.Dropped + " avisos descartados", sender, 0L);
				}
				value.WindowStart = time;
				value.Used = 0;
				value.Dropped = 0;
			}
			if (value.Used >= PluginConfig.MaxMessagesPerSecond.Value)
			{
				value.Dropped++;
				return false;
			}
			value.Used++;
			return true;
		}

		private static void PruneBudgets(ShipState state)
		{
			if (state.Budgets.Count < 32)
			{
				return;
			}
			float time = Time.time;
			ExpiredSenders.Clear();
			foreach (KeyValuePair<long, Budget> budget in state.Budgets)
			{
				if (time - budget.Value.WindowStart > 60f)
				{
					ExpiredSenders.Add(budget.Key);
				}
			}
			for (int i = 0; i < ExpiredSenders.Count; i++)
			{
				state.Budgets.Remove(ExpiredSenders[i]);
			}
		}

		private static void LogRejected(string reason, long sender, long playerID)
		{
			float time = Time.time;
			if (!(time < _nextRejectLog))
			{
				_nextRejectLog = time + 1f;
				RowLog.Rejected(reason, sender, playerID);
			}
		}
	}
	internal static class SpeedReadout
	{
		private const float SmoothingSeconds = 0.3f;

		private const float TeleportDistance = 50f;

		private const float MetersPerSecondToKnots = 1.943844f;

		private static Ship _tracked;

		private static Vector3 _lastPosition;

		private static float _speed;

		public static void Tick()
		{
			//IL_0059: 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)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				Reset();
				return;
			}
			Ship val = localPlayer.GetControlledShip();
			if ((Object)(object)val == (Object)null)
			{
				val = Ship.GetLocalShip();
			}
			Sample(val);
			if (PluginConfig.ShowSpeed.Value && !((Object)(object)localPlayer.GetControlledShip() == (Object)null) && !Plugin.IsTyping())
			{
				KeyboardShortcut value = PluginConfig.Hotkey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					((Character)localPlayer).Message((MessageType)1, Describe(), 0, (Sprite)null, false);
				}
			}
		}

		public static void Reset()
		{
			_tracked = null;
			_speed = 0f;
		}

		private static void Sample(Ship ship)
		{
			//IL_0015: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: 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_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: 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 ((Object)(object)ship == (Object)null)
			{
				Reset();
				return;
			}
			Vector3 position = ((Component)ship).transform.position;
			if (ship != _tracked)
			{
				_tracked = ship;
				_lastPosition = position;
				_speed = 0f;
				return;
			}
			float deltaTime = Time.deltaTime;
			if (!(deltaTime <= 0f))
			{
				Vector3 val = position - _lastPosition;
				_lastPosition = position;
				if (((Vector3)(ref val)).magnitude > 50f)
				{
					_speed = 0f;
					return;
				}
				val.y = 0f;
				Vector3 forward = ((Component)ship).transform.forward;
				forward.y = 0f;
				((Vector3)(ref forward)).Normalize();
				float num = Vector3.Dot(val / deltaTime, forward);
				_speed += (num - _speed) * (1f - Mathf.Exp((0f - deltaTime) / 0.3f));
			}
		}

		private static string Describe()
		{
			float num = _speed * 1.943844f;
			return "Velocidad: " + _speed.ToString("F1") + " m/s (" + num.ToString("F1") + " nudos)";
		}
	}
}
namespace CrewRow.Core
{
	[HarmonyPatch(typeof(Ship), "Start")]
	internal static class Ship_Start_Patch
	{
		private static void Postfix(Ship __instance)
		{
			try
			{
				RowerRegistry.Register(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("No se pudo registrar el aviso de remada en un barco. Ese barco no va a recibir remeros; el resto sigue funcionando. Detalle: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(Game), "Logout")]
	internal static class Game_Logout_Patch
	{
		private static void Prefix()
		{
			RowBoost.Restore();
			RowerRegistry.Reset();
			RowerClient.Reset();
			SpeedReadout.Reset();
		}
	}
	internal static class RowLog
	{
		public static void Local(Ship ship, bool rowing)
		{
			if (!PluginConfig.RowLog.Value)
			{
				return;
			}
			try
			{
				Plugin.Log.LogInfo((object)((rowing ? "REMANDO -> " : "PARO <- ") + Describe(ship)));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Fallo al anotar la remada local: " + ex.Message));
			}
		}

		public static void Count(Ship ship, int rowers, float factor)
		{
			if (!PluginConfig.RowLog.Value)
			{
				return;
			}
			try
			{
				Plugin.Log.LogInfo((object)("TRIPULACION " + Describe(ship) + " | remeros: " + rowers + " | empuje x" + factor.ToString("F2")));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Fallo al anotar la cuenta de remeros: " + ex.Message));
			}
		}

		public static void Rejected(string reason, long sender, long playerID)
		{
			if (!PluginConfig.RowLog.Value)
			{
				return;
			}
			try
			{
				Plugin.Log.LogInfo((object)("DESCARTADO (" + reason + ") | de " + sender + " | jugador " + playerID));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Fallo al anotar un descarte: " + ex.Message));
			}
		}

		private static string Describe(Ship ship)
		{
			//IL_0015: 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)
			if ((Object)(object)ship == (Object)null)
			{
				return "barco desconocido";
			}
			Vector3 position = ((Component)ship).transform.position;
			return ((Object)ship).name.Replace("(Clone)", "") + " (" + position.x.ToString("F0") + ", " + position.z.ToString("F0") + ")";
		}
	}
}
namespace CrewRow.Config
{
	public enum ModoDeRemada
	{
		Alternar,
		Mantener
	}
	public enum AlcanceDelEmpuje
	{
		SoloRemo,
		RemoYVelaEnContra,
		Siempre
	}
	internal static class PluginConfig
	{
		private const string General = "1 - General";

		private const string Remar = "2 - Remar";

		private const string Fuerza = "3 - Fuerza";

		private const string Red = "4 - Red";

		private const string Interfaz = "5 - Interfaz";

		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<bool> RowLog;

		public static ConfigEntry<KeyboardShortcut> Hotkey;

		public static ConfigEntry<ModoDeRemada> Mode;

		public static ConfigEntry<AlcanceDelEmpuje> Scope;

		public static ConfigEntry<bool> ApplyInReverse;

		public static ConfigEntry<bool> CancelOnMove;

		public static ConfigEntry<float> StaminaPerSecond;

		public static ConfigEntry<float> BonusPerRower;

		public static ConfigEntry<float> MaxBonus;

		public static ConfigEntry<int> MaxRowers;

		public static ConfigEntry<float> HeartbeatSeconds;

		public static ConfigEntry<float> StaleSeconds;

		public static ConfigEntry<int> MaxMessagesPerSecond;

		public static ConfigEntry<bool> ShowNotices;

		public static ConfigEntry<bool> ShowSpeed;

		public static void Bind(ConfigFile cfg)
		{
			//IL_0048: 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_00fb: Expected O, but got Unknown
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Expected O, but got Unknown
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Expected O, but got Unknown
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Expected O, but got Unknown
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d0: Expected O, but got Unknown
			//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Expected O, but got Unknown
			//IL_022b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Expected O, but got Unknown
			Enabled = cfg.Bind<bool>("1 - General", "Activado", true, "Apaga el mod entero sin desinstalarlo.");
			RowLog = cfg.Bind<bool>("1 - General", "LogDeRemada", false, "Anota en BepInEx/LogOutput.log cada vez que alguien empieza o deja de remar, y cuantos remeros cuenta el barco. Sirve para entender por que no suma velocidad; dejalo apagado en el dia a dia porque es ruidoso.");
			Hotkey = cfg.Bind<KeyboardShortcut>("2 - Remar", "Atajo", new KeyboardShortcut((KeyCode)98, Array.Empty<KeyCode>()), "Tecla para ponerse a remar. No se dispara mientras escribis en el chat, la consola o un cartel. Cambiala si choca con otro mod.");
			Mode = cfg.Bind<ModoDeRemada>("2 - Remar", "Modo", ModoDeRemada.Alternar, "Alternar: una pulsacion prende y otra apaga. Mantener: se rema solo mientras tenes la tecla apretada.");
			Scope = cfg.Bind<AlcanceDelEmpuje>("2 - Remar", "AplicarEn", AlcanceDelEmpuje.SoloRemo, "SoloRemo: la tripulacion suma unicamente en las velocidades de remo, que es lo unico que sirve con viento en contra, y la vela queda igual que en el juego base. RemoYVelaEnContra: ademas empuja con la vela izada, en proporcion a lo malo que sea el angulo del viento, asi que con viento a favor no cambia nada. Siempre: suma en todos los modos.");
			ApplyInReverse = cfg.Bind<bool>("2 - Remar", "AplicarEnReversa", true, "Si la tripulacion tambien ayuda cuando el barco va marcha atras.");
			CancelOnMove = cfg.Bind<bool>("2 - Remar", "CancelarAlMoverse", true, "Dejas de remar apenas caminas por la cubierta, y no podes empezar a remar en movimiento. Remar ocupa las dos manos: no se puede ir caminando al mismo tiempo. Apagalo si preferis seguir remando mientras te acomodas.");
			StaminaPerSecond = cfg.Bind<float>("2 - Remar", "CostoStaminaPorSegundo", 1f, new ConfigDescription("Stamina por segundo que gasta remar. Mientras remas no regeneras, asi que el gasto neto es justo este numero: con 1 y 75 de stamina se rema poco mas de un minuto seguido, bastante mas con comida. Al quedarte sin, la remada se PAUSA y vuelve sola al recuperar un cuarto de la barra, sin tener que apretar la tecla de nuevo. En 0 remar es gratis y la stamina ni se toca.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			BonusPerRower = cfg.Bind<float>("3 - Fuerza", "BonoPorRemero", 0.5f, new ConfigDescription("Cuanto suma cada remero, como fraccion del empuje de remo del barco. 0.5 = cada uno agrega medio remero extra.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 2f), Array.Empty<object>()));
			MaxBonus = cfg.Bind<float>("3 - Fuerza", "BonoMaximo", 3f, new ConfigDescription("Tope del bono sumado de toda la tripulacion. 3 = como mucho el empuje llega a 4 veces el normal. Ojo que el agua frena de forma cuadratica: 4 veces la fuerza es cerca del DOBLE de velocidad, no el cuadruple.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>()));
			MaxRowers = cfg.Bind<int>("3 - Fuerza", "MaxRemeros", 6, new ConfigDescription("Cuantos remeros se cuentan como maximo, por mas que haya mas gente arriba.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 13), Array.Empty<object>()));
			HeartbeatSeconds = cfg.Bind<float>("4 - Red", "LatidoSegundos", 0.5f, new ConfigDescription("Cada cuanto el remero le recuerda al dueno del barco que sigue remando. Es el mismo ritmo que usa el juego para mandar el timon. Subirlo baja el trafico y agranda el bache cuando cambia el dueno del barco.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.2f, 5f), Array.Empty<object>()));
			StaleSeconds = cfg.Bind<float>("4 - Red", "CaducidadSegundos", 2f, new ConfigDescription("Cuanto se le cree a un remero sin volver a escucharlo. Tiene que ser holgadamente mayor que LatidoSegundos, o los remeros van a titilar.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), Array.Empty<object>()));
			MaxMessagesPerSecond = cfg.Bind<int>("4 - Red", "MaxMensajesPorSegundo", 6, new ConfigDescription("Tope de avisos por segundo que se le aceptan a cada jugador. El motor del juego no limita los RPC, asi que el freno lo ponemos aca: lo que pase de este tope se descarta sin mirarlo.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
			ShowNotices = cfg.Bind<bool>("5 - Interfaz", "MostrarAvisos", true, "Avisa arriba a la izquierda cuando empezas o dejas de remar, y le avisa al timonel cuantos estan remando cada vez que ese numero cambia.");
			ShowSpeed = cfg.Bind<bool>("5 - Interfaz", "MostrarVelocidad", true, "Llevando el timon, la misma tecla de remar muestra a que velocidad va el barco. Para el timonel esa tecla no tiene otro uso, porque no puede remar.");
			WarnAboutMouseHotkey();
			Hotkey.SettingChanged += delegate
			{
				WarnAboutMouseHotkey();
			};
		}

		private unsafe static void WarnAboutMouseHotkey()
		{
			//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_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Invalid comparison between Unknown and I4
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Invalid comparison between Unknown and I4
			KeyboardShortcut value = Hotkey.Value;
			KeyCode mainKey = ((KeyboardShortcut)(ref value)).MainKey;
			if ((int)mainKey >= 323 && (int)mainKey <= 329)
			{
				Plugin.Log.LogWarning((object)("El atajo para remar esta puesto en " + ((object)(*(KeyCode*)(&mainKey))/*cast due to .constrained prefix*/).ToString() + ", que es un boton del mouse y se pisa con atacar o usar. Cambialo en la opcion Atajo si te molesta."));
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		internal IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}