using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Numerics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("TidehaulNets.Core")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("TidehaulNets.Core")] [assembly: AssemblyTitle("TidehaulNets.Core")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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] [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 TidehaulNets.Core { public readonly struct NetBuoyPose { public Vector3 Center { get; } public Vector3 Attachment { get; } internal NetBuoyPose(Vector3 center, Vector3 attachment) { Center = center; Attachment = attachment; } } public static class NetBuoyTether { public const float MaximumLength = 0.8f; public static NetBuoyPose Resolve(Vector3 netAttachment, Vector3 buoyAttachmentOffset, Vector3 lateral, float waterHeight, float bob) { if (!NetMotionPlanner.IsFinite(netAttachment) || !NetMotionPlanner.IsFinite(buoyAttachmentOffset) || !NetMotionPlanner.IsFinite(lateral) || float.IsNaN(waterHeight) || float.IsInfinity(waterHeight) || float.IsNaN(bob) || float.IsInfinity(bob) || Math.Abs(bob) > 0.1f) { throw new ArgumentOutOfRangeException("netAttachment"); } Vector3 vector = new Vector3(netAttachment.X + lateral.X, waterHeight + 0.16f + bob, netAttachment.Z + lateral.Z) + buoyAttachmentOffset - netAttachment; if (vector.LengthSquared() > 0.64000005f) { vector = Vector3.Normalize(vector) * 0.8f; } Vector3 vector2 = netAttachment + vector; return new NetBuoyPose(vector2 - buoyAttachmentOffset, vector2); } } public enum NetPhase { Stowed, Lowering, Soaking, Hauling, Ready } public enum NetActionError { StaleRevision, RevisionOverflow, AlreadyInstalled, NotInstalled, WrongPhase, InvalidArgument, PendingCatch, CatchNotDue, WrongCatchOpportunity, CapacityReached, AlreadyPaused, NotPaused } public sealed class NetActionException : InvalidOperationException { public NetActionError Error { get; } public NetActionException(NetActionError error, string message) : base(message) { Error = error; } } public sealed class NetCycleConfig { public const int MaximumCapacity = 64; public const double MaximumDurationSeconds = 86400.0; public int Capacity { get; } public double LoweringSeconds { get; } public double CatchIntervalSeconds { get; } public double HaulingSeconds { get; } public NetCycleConfig(int capacity = 3, double loweringSeconds = 8.0, double catchIntervalSeconds = 14.0, double haulingSeconds = 24.0) { if (capacity < 1 || capacity > 64) { throw new ArgumentOutOfRangeException("capacity"); } Capacity = capacity; LoweringSeconds = ValidateDuration(loweringSeconds, "loweringSeconds"); CatchIntervalSeconds = ValidateDuration(catchIntervalSeconds, "catchIntervalSeconds"); HaulingSeconds = ValidateDuration(haulingSeconds, "haulingSeconds"); } private static double ValidateDuration(double value, string name) { if (!NetCycle.IsFinite(value) || value <= 0.0 || value > 86400.0) { throw new ArgumentOutOfRangeException(name); } return value; } } public sealed class NetCatchTicket { public const double MaximumWeightFactor = 1000.0; public byte ItemId { get; } public double WeightFactor { get; } public bool IsShiny { get; } public NetCatchTicket(byte itemId, double weightFactor, bool isShiny = false) { if (!NetCycle.IsFinite(weightFactor) || weightFactor <= 0.0 || weightFactor > 1000.0) { throw new ArgumentOutOfRangeException("weightFactor"); } ItemId = itemId; WeightFactor = weightFactor; IsShiny = isShiny; } } public sealed class NetAdvanceResult { public double ConsumedSeconds { get; } public double UnconsumedSeconds { get; } public bool CatchDue { get; } internal NetAdvanceResult(double consumedSeconds, double unconsumedSeconds, bool catchDue) { ConsumedSeconds = consumedSeconds; UnconsumedSeconds = unconsumedSeconds; CatchDue = catchDue; } } public sealed class NetCycle { public const int CurrentSchemaVersion = 1; public const int MaximumIdLength = 128; private readonly List _tickets = new List(); private string? _activeOperatorId; private bool _haulPaused = true; private double _loweringElapsed; private double _soakElapsed; private double _haulElapsed; private ulong _opportunitySequence; public NetCycleConfig Config { get; private set; } public bool Installed { get; private set; } public NetPhase Phase { get; private set; } public string? DeploymentId { get; private set; } public ulong Revision { get; private set; } public bool CatchDue { get; private set; } public ulong CatchOpportunity { get { if (!CatchDue) { return 0uL; } return _opportunitySequence; } } public int TicketCount => _tickets.Count; public bool HaulPaused { get { if (Phase == NetPhase.Hauling) { return _haulPaused; } return false; } } public double DeploymentAmount => Phase switch { NetPhase.Lowering => _loweringElapsed / Config.LoweringSeconds, NetPhase.Soaking => 1.0, NetPhase.Hauling => 1.0 - _haulElapsed / Config.HaulingSeconds, _ => 0.0, }; public NetCycle() : this(new NetCycleConfig()) { } public NetCycle(NetCycleConfig config) { Config = config ?? throw new ArgumentNullException("config"); Phase = NetPhase.Stowed; } public void Install(ulong expectedRevision) { CheckRevision(expectedRevision); if (Installed) { throw ActionError(NetActionError.AlreadyInstalled, "The net is already installed."); } EnsureRevisionCanAdvance(); Installed = true; AdvanceRevision(); } public void Pack(ulong expectedRevision) { CheckRevision(expectedRevision); RequireInstalled(); RequirePhase(NetPhase.Stowed); if (_tickets.Count != 0) { throw ActionError(NetActionError.WrongPhase, "A net holding tickets cannot be packed."); } EnsureRevisionCanAdvance(); Installed = false; AdvanceRevision(); } public void Upgrade(ulong expectedRevision, NetCycleConfig configuration) { CheckRevision(expectedRevision); RequireInstalled(); RequirePhase(NetPhase.Stowed); if (configuration == null) { throw new ArgumentNullException("configuration"); } if (_tickets.Count != 0) { throw ActionError(NetActionError.PendingCatch, "Unload the catch before upgrading the net."); } EnsureRevisionCanAdvance(); Config = configuration; AdvanceRevision(); } public void BeginLowering(ulong expectedRevision, string deploymentId) { CheckRevision(expectedRevision); RequireInstalled(); RequirePhase(NetPhase.Stowed); ValidateId(deploymentId, "deploymentId"); EnsureRevisionCanAdvance(); DeploymentId = deploymentId; Phase = NetPhase.Lowering; ResetCycleValues(); AdvanceRevision(); } public NetAdvanceResult Advance(ulong expectedRevision, double elapsedSeconds, bool waterSuitable, bool boatMoving, string? activeOperatorId, bool activeOperatorNearby) { CheckRevision(expectedRevision); RequireInstalled(); if (Phase != NetPhase.Lowering && Phase != NetPhase.Soaking && Phase != NetPhase.Hauling) { throw ActionError(NetActionError.WrongPhase, "Only a deployed net can advance."); } if (!IsFinite(elapsedSeconds) || elapsedSeconds <= 0.0) { throw ActionError(NetActionError.InvalidArgument, "Elapsed seconds must be finite and positive."); } EnsureRevisionCanAdvance(); double num = 0.0; num = ((Phase == NetPhase.Lowering) ? AdvanceLowering(elapsedSeconds, waterSuitable, boatMoving) : ((Phase != NetPhase.Soaking) ? AdvanceHauling(elapsedSeconds, activeOperatorId, activeOperatorNearby) : AdvanceSoaking(elapsedSeconds, waterSuitable, boatMoving))); AdvanceRevision(); return new NetAdvanceResult(elapsedSeconds - num, num, CatchDue); } public void AcceptValidatedCatch(ulong expectedRevision, ulong catchOpportunity, NetCatchTicket ticket) { CheckRevision(expectedRevision); RequireInstalled(); RequirePhase(NetPhase.Soaking); RequireCatchOpportunity(catchOpportunity); if (ticket == null) { throw ActionError(NetActionError.InvalidArgument, "A validated ticket is required."); } if (_tickets.Count >= Config.Capacity) { throw ActionError(NetActionError.CapacityReached, "The net is full."); } EnsureRevisionCanAdvance(); _tickets.Add(CopyTicket(ticket)); CatchDue = false; _soakElapsed = 0.0; AdvanceRevision(); } public void SkipCatch(ulong expectedRevision, ulong catchOpportunity) { CheckRevision(expectedRevision); RequireInstalled(); RequirePhase(NetPhase.Soaking); RequireCatchOpportunity(catchOpportunity); EnsureRevisionCanAdvance(); CatchDue = false; _soakElapsed = 0.0; AdvanceRevision(); } public void BeginHaul(ulong expectedRevision, string operatorId) { CheckRevision(expectedRevision); RequireInstalled(); if (Phase != NetPhase.Soaking && Phase != NetPhase.Lowering) { throw ActionError(NetActionError.WrongPhase, "Only a lowering or soaking net can begin retrieval."); } if (CatchDue) { throw ActionError(NetActionError.PendingCatch, "Resolve the pending catch before hauling."); } ValidateId(operatorId, "operatorId"); EnsureRevisionCanAdvance(); double deploymentAmount = DeploymentAmount; _loweringElapsed = Config.LoweringSeconds; _haulElapsed = (1.0 - deploymentAmount) * Config.HaulingSeconds; Phase = ((_haulElapsed >= Config.HaulingSeconds) ? NetPhase.Ready : NetPhase.Hauling); _activeOperatorId = operatorId; _haulPaused = false; if (Phase == NetPhase.Ready) { PauseHaulInternal(); } AdvanceRevision(); } public void PauseHaul(ulong expectedRevision, string operatorId) { CheckRevision(expectedRevision); RequireInstalled(); RequirePhase(NetPhase.Hauling); if (_haulPaused) { throw ActionError(NetActionError.AlreadyPaused, "The haul is already paused."); } ValidateId(operatorId, "operatorId"); if (!string.Equals(_activeOperatorId, operatorId, StringComparison.Ordinal)) { throw ActionError(NetActionError.InvalidArgument, "Only the active operator can pause this haul."); } EnsureRevisionCanAdvance(); PauseHaulInternal(); AdvanceRevision(); } public void ResumeHaul(ulong expectedRevision, string operatorId) { CheckRevision(expectedRevision); RequireInstalled(); RequirePhase(NetPhase.Hauling); if (!_haulPaused) { throw ActionError(NetActionError.NotPaused, "The haul is already active."); } ValidateId(operatorId, "operatorId"); EnsureRevisionCanAdvance(); _activeOperatorId = operatorId; _haulPaused = false; AdvanceRevision(); } public ReadOnlyCollection Unload(ulong expectedRevision) { CheckRevision(expectedRevision); RequireInstalled(); RequirePhase(NetPhase.Ready); EnsureRevisionCanAdvance(); NetCatchTicket[] array = new NetCatchTicket[_tickets.Count]; for (int i = 0; i < _tickets.Count; i++) { array[i] = CopyTicket(_tickets[i]); } Phase = NetPhase.Stowed; DeploymentId = null; ResetCycleValues(); AdvanceRevision(); return Array.AsReadOnly(array); } public NetCycleSnapshot CreateSnapshot() { List list = new List(_tickets.Count); foreach (NetCatchTicket ticket in _tickets) { list.Add(new NetCatchTicketState { ItemId = ticket.ItemId, WeightFactor = ticket.WeightFactor, IsShiny = ticket.IsShiny }); } return new NetCycleSnapshot { SchemaVersion = 1, Config = new NetCycleConfigState { Capacity = Config.Capacity, LoweringSeconds = Config.LoweringSeconds, CatchIntervalSeconds = Config.CatchIntervalSeconds, HaulingSeconds = Config.HaulingSeconds }, Installed = Installed, Phase = Phase, DeploymentId = DeploymentId, Revision = Revision, LoweringElapsedSeconds = _loweringElapsed, SoakElapsedSeconds = _soakElapsed, HaulElapsedSeconds = _haulElapsed, CatchDue = CatchDue, OpportunitySequence = _opportunitySequence, HaulPaused = (Phase != NetPhase.Hauling || _haulPaused), Tickets = list }; } public static NetCycle Restore(NetCycleSnapshot snapshot) { ValidateSnapshotShape(snapshot); NetCycleConfigState config = snapshot.Config; NetCycle netCycle = new NetCycle(new NetCycleConfig(config.Capacity.GetValueOrDefault(), config.LoweringSeconds.GetValueOrDefault(), config.CatchIntervalSeconds.GetValueOrDefault(), config.HaulingSeconds.GetValueOrDefault())) { Installed = (snapshot.Installed == true), Phase = snapshot.Phase.GetValueOrDefault(), DeploymentId = snapshot.DeploymentId, Revision = snapshot.Revision.GetValueOrDefault(), _loweringElapsed = snapshot.LoweringElapsedSeconds.GetValueOrDefault(), _soakElapsed = snapshot.SoakElapsedSeconds.GetValueOrDefault(), _haulElapsed = snapshot.HaulElapsedSeconds.GetValueOrDefault(), CatchDue = (snapshot.CatchDue == true), _opportunitySequence = snapshot.OpportunitySequence.GetValueOrDefault(), _haulPaused = (snapshot.HaulPaused == true), _activeOperatorId = null }; foreach (NetCatchTicketState ticket in snapshot.Tickets) { if (ticket == null || !ticket.ItemId.HasValue || !ticket.WeightFactor.HasValue || !ticket.IsShiny.HasValue) { throw new ArgumentException("Every saved ticket must contain every field.", "snapshot"); } netCycle._tickets.Add(new NetCatchTicket(ticket.ItemId.Value, ticket.WeightFactor.Value, ticket.IsShiny.Value)); } netCycle.ValidateRestoredState(); if (netCycle.Phase == NetPhase.Hauling) { netCycle._haulPaused = true; } return netCycle; } internal static bool IsFinite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } private double AdvanceLowering(double seconds, bool waterSuitable, bool boatMoving) { if (!waterSuitable || boatMoving) { return 0.0; } double num = Config.LoweringSeconds - _loweringElapsed; if (seconds < num) { _loweringElapsed += seconds; return 0.0; } _loweringElapsed = Config.LoweringSeconds; Phase = NetPhase.Soaking; double num2 = seconds - num; if (!(num2 > 0.0)) { return 0.0; } return AdvanceSoaking(num2, waterSuitable, boatMoving); } private double AdvanceSoaking(double seconds, bool waterSuitable, bool boatMoving) { if (CatchDue) { return seconds; } if (_tickets.Count >= Config.Capacity || !waterSuitable || boatMoving) { return 0.0; } double num = Config.CatchIntervalSeconds - _soakElapsed; if (seconds < num) { _soakElapsed += seconds; return 0.0; } if (_opportunitySequence == ulong.MaxValue) { throw ActionError(NetActionError.RevisionOverflow, "The catch opportunity sequence is exhausted."); } _soakElapsed = Config.CatchIntervalSeconds; _opportunitySequence++; CatchDue = true; return seconds - num; } private double AdvanceHauling(double seconds, string? operatorId, bool operatorNearby) { if (_haulPaused) { return 0.0; } if (!activeOperatorIsValid(operatorId, operatorNearby)) { PauseHaulInternal(); return 0.0; } double num = Config.HaulingSeconds - _haulElapsed; if (seconds < num) { _haulElapsed += seconds; return 0.0; } _haulElapsed = Config.HaulingSeconds; Phase = NetPhase.Ready; PauseHaulInternal(); return seconds - num; } private bool activeOperatorIsValid(string? operatorId, bool nearby) { if (nearby && operatorId != null) { return string.Equals(_activeOperatorId, operatorId, StringComparison.Ordinal); } return false; } private void RequireCatchOpportunity(ulong catchOpportunity) { if (!CatchDue) { throw ActionError(NetActionError.CatchNotDue, "No catch is due."); } if (catchOpportunity != _opportunitySequence) { throw ActionError(NetActionError.WrongCatchOpportunity, "The catch opportunity is stale."); } } private void CheckRevision(ulong expectedRevision) { if (expectedRevision != Revision) { throw ActionError(NetActionError.StaleRevision, "The action revision is stale."); } } private void EnsureRevisionCanAdvance() { if (Revision == ulong.MaxValue) { throw ActionError(NetActionError.RevisionOverflow, "The action revision is exhausted."); } } private void AdvanceRevision() { Revision++; } private void RequireInstalled() { if (!Installed) { throw ActionError(NetActionError.NotInstalled, "The net is not installed."); } } private void RequirePhase(NetPhase phase) { if (Phase != phase) { throw ActionError(NetActionError.WrongPhase, $"Expected {phase}; current phase is {Phase}."); } } private static void ValidateId(string value, string name) { if (string.IsNullOrWhiteSpace(value) || value.Length > 128) { throw ActionError(NetActionError.InvalidArgument, $"{name} must be nonblank and at most {128} characters."); } } private static NetActionException ActionError(NetActionError error, string message) { return new NetActionException(error, message); } private static NetCatchTicket CopyTicket(NetCatchTicket ticket) { return new NetCatchTicket(ticket.ItemId, ticket.WeightFactor, ticket.IsShiny); } private void PauseHaulInternal() { _haulPaused = true; _activeOperatorId = null; } private void ResetCycleValues() { _tickets.Clear(); _loweringElapsed = 0.0; _soakElapsed = 0.0; _haulElapsed = 0.0; _opportunitySequence = 0uL; CatchDue = false; PauseHaulInternal(); } private static void ValidateSnapshotShape(NetCycleSnapshot snapshot) { if (snapshot == null) { throw new ArgumentNullException("snapshot"); } if (!snapshot.SchemaVersion.HasValue || snapshot.SchemaVersion.Value != 1) { throw new ArgumentException("The net snapshot schema is missing or unsupported.", "snapshot"); } if (snapshot.Config == null || !snapshot.Config.Capacity.HasValue || !snapshot.Config.LoweringSeconds.HasValue || !snapshot.Config.CatchIntervalSeconds.HasValue || !snapshot.Config.HaulingSeconds.HasValue) { throw new ArgumentException("The net configuration is incomplete.", "snapshot"); } if (!snapshot.Installed.HasValue || !snapshot.Phase.HasValue || !snapshot.Revision.HasValue || !snapshot.LoweringElapsedSeconds.HasValue || !snapshot.SoakElapsedSeconds.HasValue || !snapshot.HaulElapsedSeconds.HasValue || !snapshot.CatchDue.HasValue || !snapshot.OpportunitySequence.HasValue || !snapshot.HaulPaused.HasValue || snapshot.Tickets == null) { throw new ArgumentException("The net snapshot is incomplete.", "snapshot"); } if (!Enum.IsDefined(typeof(NetPhase), snapshot.Phase.Value)) { throw new ArgumentException("The net phase is invalid.", "snapshot"); } } private void ValidateRestoredState() { if (_tickets.Count > Config.Capacity) { throw new ArgumentException("The saved net exceeds its capacity."); } if (!ValidElapsed(_loweringElapsed, Config.LoweringSeconds) || !ValidElapsed(_soakElapsed, Config.CatchIntervalSeconds) || !ValidElapsed(_haulElapsed, Config.HaulingSeconds)) { throw new ArgumentException("A saved timer is outside its valid range."); } bool flag = !string.IsNullOrWhiteSpace(DeploymentId) && DeploymentId.Length <= 128; if (!Installed) { if (Phase != NetPhase.Stowed) { throw new ArgumentException("An uninstalled net must be stowed."); } ValidateEmptyStowedState(); return; } switch (Phase) { case NetPhase.Stowed: ValidateEmptyStowedState(); break; case NetPhase.Lowering: if (!flag || _loweringElapsed >= Config.LoweringSeconds || _soakElapsed != 0.0 || _haulElapsed != 0.0 || CatchDue || _opportunitySequence != 0L || _tickets.Count != 0 || !_haulPaused) { throw new ArgumentException("The lowering state is inconsistent."); } break; case NetPhase.Soaking: if (!flag || _loweringElapsed != Config.LoweringSeconds || _haulElapsed != 0.0 || !_haulPaused || (CatchDue && (_soakElapsed != Config.CatchIntervalSeconds || _opportunitySequence == 0L)) || (!CatchDue && _soakElapsed >= Config.CatchIntervalSeconds) || (_tickets.Count >= Config.Capacity && CatchDue)) { throw new ArgumentException("The soaking state is inconsistent."); } break; case NetPhase.Hauling: if (!flag || _loweringElapsed != Config.LoweringSeconds || _haulElapsed >= Config.HaulingSeconds || CatchDue || _soakElapsed >= Config.CatchIntervalSeconds) { throw new ArgumentException("The hauling state is inconsistent."); } break; case NetPhase.Ready: if (!flag || _loweringElapsed != Config.LoweringSeconds || _haulElapsed != Config.HaulingSeconds || CatchDue || _soakElapsed >= Config.CatchIntervalSeconds || !_haulPaused) { throw new ArgumentException("The ready state is inconsistent."); } break; default: throw new ArgumentException("The net phase is invalid."); } } private void ValidateEmptyStowedState() { if (DeploymentId != null || _tickets.Count != 0 || _loweringElapsed != 0.0 || _soakElapsed != 0.0 || _haulElapsed != 0.0 || CatchDue || _opportunitySequence != 0L || !_haulPaused) { throw new ArgumentException("The stowed state is inconsistent."); } } private static bool ValidElapsed(double value, double maximum) { if (IsFinite(value) && value >= 0.0) { return value <= maximum; } return false; } } public sealed class NetCycleSnapshot { public int? SchemaVersion { get; set; } public NetCycleConfigState? Config { get; set; } public bool? Installed { get; set; } public NetPhase? Phase { get; set; } public string? DeploymentId { get; set; } public ulong? Revision { get; set; } public double? LoweringElapsedSeconds { get; set; } public double? SoakElapsedSeconds { get; set; } public double? HaulElapsedSeconds { get; set; } public bool? CatchDue { get; set; } public ulong? OpportunitySequence { get; set; } public bool? HaulPaused { get; set; } public List? Tickets { get; set; } } public sealed class NetCycleConfigState { public int? Capacity { get; set; } public double? LoweringSeconds { get; set; } public double? CatchIntervalSeconds { get; set; } public double? HaulingSeconds { get; set; } } public sealed class NetCatchTicketState { public byte? ItemId { get; set; } public double? WeightFactor { get; set; } public bool? IsShiny { get; set; } } public sealed class NetEquipmentTier { public int Level { get; } public string Title { get; } public int Cost { get; } public int NativeIsland { get; } public int Capacity { get; } public double HaulingSeconds { get; } internal NetEquipmentTier(int level, string title, int cost, int nativeIsland, int capacity, double haulingSeconds) { Level = level; Title = title; Cost = cost; NativeIsland = nativeIsland; Capacity = capacity; HaulingSeconds = haulingSeconds; } public NetCycleConfig Configuration() { return new NetCycleConfig(Capacity, 8.0, 14.0, HaulingSeconds); } } public static class NetEquipment { public const int MaximumTier = 3; private static readonly NetEquipmentTier[] tiers = new NetEquipmentTier[3] { new NetEquipmentTier(1, "Tidehaul Net Winch", 1200, 3, 3, 24.0), new NetEquipmentTier(2, "Geared Net Winch", 2000, 4, 3, 16.0), new NetEquipmentTier(3, "Reinforced Net", 3500, 5, 5, 16.0) }; public static NetEquipmentTier Tier(int level) { if (level < 1 || level > 3) { throw new ArgumentOutOfRangeException("level"); } return tiers[level - 1]; } public static string? DrivingBlock(bool installed, NetPhase phase) { if (!Enum.IsDefined(typeof(NetPhase), phase)) { throw new ArgumentOutOfRangeException("phase"); } if (!installed || phase == NetPhase.Stowed) { return null; } return "Haul, unload and stow the net before driving the boat."; } public static string? PurchaseBlock(int ownedTier, int requestedTier, int shopMaximumTier, bool boatUnlocked, NetPhase phase, int money) { if (ownedTier < 0 || ownedTier > 3 || shopMaximumTier < 1 || shopMaximumTier > 3 || money < 0 || !Enum.IsDefined(typeof(NetPhase), phase)) { throw new ArgumentOutOfRangeException("ownedTier"); } if (requestedTier < 1 || requestedTier > 3) { return "That net upgrade is unavailable."; } if (!boatUnlocked) { return "Unlock the crew's boat before buying equipment."; } if (requestedTier <= ownedTier) { return "The crew already owns this net upgrade."; } if (requestedTier != ownedTier + 1) { return "Buy the preceding net upgrade first."; } if (requestedTier > shopMaximumTier) { return "This dock does not stock that net upgrade."; } if (phase != NetPhase.Stowed) { return "Haul and unload the net before upgrading it."; } if (money < Tier(requestedTier).Cost) { return "The crew cannot afford this net upgrade."; } return null; } public static int RestoreTier(int schemaVersion, int? savedTier, NetCycleSnapshot snapshot) { NetCycle netCycle = NetCycle.Restore(snapshot); int num; if (schemaVersion == 1 && !savedTier.HasValue) { num = (netCycle.Installed ? 1 : 0); } else { if (schemaVersion != 2 || !savedTier.HasValue) { throw new InvalidDataException("The saved net equipment schema is unsupported or incomplete."); } num = savedTier.Value; } if (num < 0 || num > 3 || netCycle.Installed != (num != 0)) { throw new InvalidDataException("The saved net tier does not match its installation."); } NetCycleConfig netCycleConfig = Tier(Math.Max(1, num)).Configuration(); if (netCycle.Config.Capacity != netCycleConfig.Capacity || netCycle.Config.LoweringSeconds != netCycleConfig.LoweringSeconds || netCycle.Config.CatchIntervalSeconds != netCycleConfig.CatchIntervalSeconds || netCycle.Config.HaulingSeconds != netCycleConfig.HaulingSeconds) { throw new InvalidDataException("The saved net tuning does not match its purchased tier."); } return num; } } public enum NetMotionStage { AboveRailTransfer, OutboardDescent } public readonly struct NetMotionBounds { public Vector3 Min { get; } public Vector3 Max { get; } public NetMotionBounds(Vector3 min, Vector3 max) { if (!NetMotionPlanner.IsFinite(min) || !NetMotionPlanner.IsFinite(max) || min.X >= max.X || min.Y >= max.Y || min.Z >= max.Z) { throw new ArgumentOutOfRangeException("min"); } Min = min; Max = max; } } public readonly struct NetMotionPose { public Vector3 Position { get; } public NetMotionStage Stage { get; } public float SlewRadians { get; } internal NetMotionPose(Vector3 position, NetMotionStage stage, float slewRadians = 0f) { Position = position; Stage = stage; SlewRadians = slewRadians; } } public sealed class NetMotionPlanner { public const float TransferFraction = 0.22f; public const float VerticalClearance = 0.12f; public const float HorizontalClearance = 0.08f; public const float HoistRopeLength = 0.3f; public static NetMotionBounds AuthoredNetBounds { get; } = new NetMotionBounds(new Vector3(-0.727f, -0.775f, -0.624f), new Vector3(0.727f, 0.562f, 0.624f)); public Vector3 Delivery { get; } public Vector3 Pivot { get; } public float DeliveryYaw { get; } public Vector3 OutboardClear { get; } public Vector3 Submerged { get; } public float RailX { get; } public float RailTop { get; } public int OutboardSign { get; } public NetMotionBounds Bounds { get; } public NetMotionPlanner(Vector3 pivot, float deliveryYaw, Vector3 outboardClear, Vector3 submerged, float railX, float railTop, int outboardSign, NetMotionBounds bounds) { if (!IsFinite(pivot) || !Finite(deliveryYaw) || Math.Abs(deliveryYaw) < 0.05f || (double)Math.Abs(deliveryYaw) > Math.PI || !IsFinite(outboardClear) || !IsFinite(submerged) || !Finite(railX) || !Finite(railTop) || (outboardSign != -1 && outboardSign != 1)) { throw new ArgumentOutOfRangeException("pivot"); } if (bounds.Min.X >= bounds.Max.X || bounds.Min.Y >= bounds.Max.Y || bounds.Min.Z >= bounds.Max.Z) { throw new ArgumentOutOfRangeException("bounds"); } if (Math.Abs(outboardClear.X - submerged.X) > 0.001f || Math.Abs(outboardClear.Z - submerged.Z) > 0.001f || submerged.Y >= outboardClear.Y) { throw new ArgumentException("The submerged leg must descend vertically from the outboard clearance point."); } float num = ((outboardSign < 0) ? (outboardClear.X + bounds.Max.X) : (outboardClear.X + bounds.Min.X)); float num2 = (float)outboardSign * (num - railX); if (num2 < 0.08f) { throw new ArgumentException("The vertical hoist leg does not keep the whole net outboard of the rail " + $"(inner edge {num:F3} m, rail {railX:F3} m, clearance {num2:F3} m, " + $"required {0.08f:F3} m)."); } Vector3 vector = SwingPosition(pivot, outboardClear, deliveryYaw); float num3 = outboardClear.Y + bounds.Min.Y; float num4 = railTop + 0.12f; if (num3 < num4) { throw new ArgumentException("The transfer leg does not lift the whole net above the rail " + $"(lowest point {num3:F3} m, rail top {railTop:F3} m, required {num4:F3} m)."); } if ((float)outboardSign * (vector.X - outboardClear.X) >= 0f) { throw new ArgumentException("The delivery point must be inboard of the vertical hoist leg."); } Delivery = vector; Pivot = pivot; DeliveryYaw = deliveryYaw; OutboardClear = outboardClear; Submerged = submerged; RailX = railX; RailTop = railTop; OutboardSign = outboardSign; Bounds = bounds; } public NetMotionPose Sample(float deploymentAmount) { if (!Finite(deploymentAmount) || deploymentAmount < 0f || deploymentAmount > 1f) { throw new ArgumentOutOfRangeException("deploymentAmount"); } if (deploymentAmount <= 0.22f) { float num = DeliveryYaw * (1f - Smooth(deploymentAmount / 0.22f)); return new NetMotionPose(SwingPosition(Pivot, OutboardClear, num), NetMotionStage.AboveRailTransfer, num); } float amount = Smooth((deploymentAmount - 0.22f) / 0.78f); return new NetMotionPose(Vector3.Lerp(OutboardClear, Submerged, amount), NetMotionStage.OutboardDescent); } public static Vector3 SwingPosition(Vector3 pivot, Vector3 outboardClear, float yaw) { if (!IsFinite(pivot) || !IsFinite(outboardClear) || !Finite(yaw)) { throw new ArgumentOutOfRangeException("pivot"); } return pivot + Vector3.Transform(outboardClear - pivot, Quaternion.CreateFromAxisAngle(Vector3.UnitY, yaw)); } public Vector3 RopeEnd(float deploymentAmount, Vector3 hoistOffset) { if (!IsFinite(hoistOffset)) { throw new ArgumentOutOfRangeException("hoistOffset"); } return Sample(deploymentAmount).Position + hoistOffset; } public NetMotionBounds BoundsAt(float deploymentAmount) { Vector3 position = Sample(deploymentAmount).Position; return new NetMotionBounds(position + Bounds.Min, position + Bounds.Max); } public bool ClearsRail(float deploymentAmount) { NetMotionPose netMotionPose = Sample(deploymentAmount); if (netMotionPose.Stage == NetMotionStage.AboveRailTransfer) { return netMotionPose.Position.Y + Bounds.Min.Y >= RailTop + 0.12f - 0.0001f; } float num = ((OutboardSign < 0) ? (netMotionPose.Position.X + Bounds.Max.X) : (netMotionPose.Position.X + Bounds.Min.X)); return (float)OutboardSign * (num - RailX) >= 0.0799f; } public static Vector3 Transform(Vector3 local, Vector3 origin, Quaternion rotation) { if (!IsFinite(local) || !IsFinite(origin) || !Finite(rotation.X) || !Finite(rotation.Y) || !Finite(rotation.Z) || !Finite(rotation.W) || rotation.LengthSquared() < 0.999f || rotation.LengthSquared() > 1.001f) { throw new ArgumentOutOfRangeException("rotation"); } return origin + Vector3.Transform(local, rotation); } private static float Smooth(float value) { return value * value * (3f - 2f * value); } internal static bool IsFinite(Vector3 value) { if (Finite(value.X) && Finite(value.Y)) { return Finite(value.Z); } return false; } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } public static class NetSaveRevision { public static bool Accept(string key, string version, string fingerprint, IReadOnlyDictionary code) { if (key != "tidehaul_nets" || code == null || code.Count != 2 || !code.TryGetValue("TidehaulNets", out string value) || !code.TryGetValue("TidehaulNets.Core", out string value2)) { return false; } return version switch { "0.1.0" => fingerprint == "f0de0fe65fd588acf8471443a7a9c06bb371dfb8125be35a660f6ea37cc970cd" && value == "39DF9CA789BF9639121A1A60A6561DA85CD772E19FD2ACCD76A1EF71880A69D3" && value2 == "1F8F4001A943D96462FAC6A617E91628C83613CA061F1A36B73133FF1F081068", "0.1.1" => fingerprint == "3aa86b4683cb0bfc87a350089f958dba237732515a2e21f23c61b34cc34c6671" && value == "4BFE658F9FA056F8FF01D4735937C72023E33B63CDE6C1FB47080A5A1419FCE6" && value2 == "033D34E5896B7B9C4B32066A6E83C4309C03A00B177E8A7FB26F0A861C13C29D", "0.2.0" => fingerprint == "59337ff742ba6d1973a677422696654746c57bb482b7f9347da60abf98c9a40f" && value == "3729C46B01F282BA81B8F259382EDEE411DF2522471816F882473C9F021D1B9D" && value2 == "47776D90AD676C10306EF49D8D760ABFE3D9EFFE7EE64DA4D88037AB10C7B11A", "0.2.1" => fingerprint == "e4e0c2fa2cbbe1f0c6d97f7caa6bb7dc135d5d371cbeee38406b17b86d988594" && value == "19841BDB2B5E61FDFEDF439511697891D929519B5BD93C44326D095FCD22D435" && value2 == "F1C98231E13D91BB344326EA9E4DB50B9CBFFA8AC5E26278140A2FBEBC32098D", "0.2.2" => fingerprint == "31b1eb65a5a499e3f93a0e359bcbaff52886dfb60d6ef93a9ea55b4ab5514a05" && value == "BCCBE61F8C70F1B75D100FC01B07877F24A22B950F35115B81FAB56BD0598B7A" && value2 == "8CC9BCD351418FABEB3C8B1B6772CD55EBFF34714F17121C1F7DE38C3BAB71BA", "0.2.3" => fingerprint == "0780c25c76f291b844a7f3a9ced066b7eec55a215977ea7ad514d8a76e7c60b1" && ((value == "A6E1D77CF3D25F5AE1070457DC8C5BCACFF221F2FBBFB8BF817199061997ABA0" && value2 == "185061B08F6C4BDF2BF0E7B40247522A70BE7227A0791DF11FB2B2281D740213") || (value == "5E9EEFF81308069B5E4A4E6E42ABE58FBF1990BBFB6C0284019C9FE8D634E7CB" && value2 == "8EBF58544EDDAD9931B9718345C362165DBFEFE081CCE15E3148867D27F08A28")), _ => false, }; } } }