namespace SpaceGame.Simulation.Api.Simulation; public sealed partial class SimulationEngine { private string UpdateControllerTask(ShipRuntime ship, SimulationWorld world, float deltaSeconds) { var task = ship.ControllerTask; return task.Kind switch { ControllerTaskKind.Idle => UpdateIdle(ship, world, deltaSeconds), ControllerTaskKind.Travel => UpdateTravel(ship, world, deltaSeconds), ControllerTaskKind.Extract => UpdateExtract(ship, world, deltaSeconds), ControllerTaskKind.Dock => UpdateDock(ship, world, deltaSeconds), ControllerTaskKind.Load => UpdateLoadCargo(ship, world, deltaSeconds), ControllerTaskKind.Unload => UpdateUnload(ship, world, deltaSeconds), ControllerTaskKind.Refuel => UpdateRefuel(ship, world, deltaSeconds), ControllerTaskKind.DeliverConstruction => UpdateDeliverConstruction(ship, world, deltaSeconds), ControllerTaskKind.BuildConstructionSite => UpdateBuildConstructionSite(ship, world, deltaSeconds), ControllerTaskKind.LoadWorkers => UpdateLoadWorkers(ship, world, deltaSeconds), ControllerTaskKind.UnloadWorkers => UpdateUnloadWorkers(ship, world, deltaSeconds), ControllerTaskKind.ConstructModule => UpdateConstructModule(ship, world, deltaSeconds), ControllerTaskKind.Undock => UpdateUndock(ship, world, deltaSeconds), _ => UpdateIdle(ship, world, deltaSeconds), }; } private static string UpdateIdle(ShipRuntime ship, SimulationWorld world, float deltaSeconds) { TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds); ship.State = ShipState.Idle; ship.TargetPosition = ship.Position; return "none"; } private string UpdateTravel(ShipRuntime ship, SimulationWorld world, float deltaSeconds) { var task = ship.ControllerTask; if (task.TargetPosition is null || task.TargetSystemId is null) { ship.State = ShipState.Idle; ship.TargetPosition = ship.Position; return "none"; } var targetPosition = task.TargetPosition.Value; var targetNode = ResolveTravelTargetNode(world, task, targetPosition); ship.TargetPosition = targetPosition; if (ship.SystemId != task.TargetSystemId) { var destinationEntryNode = ResolveSystemEntryNode(world, task.TargetSystemId); var destinationEntryPosition = destinationEntryNode?.Position ?? Vector3.Zero; return UpdateFtlTransit(ship, world, deltaSeconds, task.TargetSystemId, destinationEntryPosition, destinationEntryNode); } var currentNode = ResolveCurrentNode(world, ship); if (targetNode is not null && currentNode is not null && !string.Equals(currentNode.Id, targetNode.Id, StringComparison.Ordinal)) { return UpdateWarpTransit(ship, world, deltaSeconds, targetPosition, targetNode); } return UpdateLocalTravel(ship, world, deltaSeconds, task.TargetSystemId, targetPosition, targetNode, task.Threshold); } private static NodeRuntime? ResolveTravelTargetNode(SimulationWorld world, ControllerTaskRuntime task, Vector3 targetPosition) { if (!string.IsNullOrWhiteSpace(task.TargetEntityId)) { var station = world.Stations.FirstOrDefault(candidate => candidate.Id == task.TargetEntityId); if (station?.NodeId is not null) { return world.SpatialNodes.FirstOrDefault(candidate => candidate.Id == station.NodeId); } var node = world.SpatialNodes.FirstOrDefault(candidate => candidate.Id == task.TargetEntityId); if (node is not null) { return node; } } return world.SpatialNodes .Where(candidate => task.TargetSystemId is null || candidate.SystemId == task.TargetSystemId) .OrderBy(candidate => candidate.Position.DistanceTo(targetPosition)) .FirstOrDefault(); } private static NodeRuntime? ResolveCurrentNode(SimulationWorld world, ShipRuntime ship) { if (ship.SpatialState.CurrentNodeId is not null) { return world.SpatialNodes.FirstOrDefault(candidate => candidate.Id == ship.SpatialState.CurrentNodeId); } return world.SpatialNodes .Where(candidate => candidate.SystemId == ship.SystemId) .OrderBy(candidate => candidate.Position.DistanceTo(ship.Position)) .FirstOrDefault(); } private static NodeRuntime? ResolveSystemEntryNode(SimulationWorld world, string systemId) => world.SpatialNodes.FirstOrDefault(candidate => candidate.SystemId == systemId && candidate.Kind == SpatialNodeKind.Star); private string UpdateLocalTravel( ShipRuntime ship, SimulationWorld world, float deltaSeconds, string targetSystemId, Vector3 targetPosition, NodeRuntime? targetNode, float threshold) { var distance = ship.Position.DistanceTo(targetPosition); ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace; ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight; ship.SpatialState.Transit = null; ship.SpatialState.DestinationNodeId = targetNode?.Id; if (distance <= threshold) { ship.ActionTimer = 0f; TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds); ship.Position = targetPosition; ship.TargetPosition = ship.Position; ship.SystemId = targetSystemId; ship.SpatialState.CurrentNodeId = targetNode?.Id; ship.SpatialState.CurrentBubbleId = targetNode?.BubbleId; ship.State = ShipState.Arriving; return "arrived"; } if (!TryConsumeShipEnergy(ship, world.Balance.Energy.MoveDrain * deltaSeconds)) { ship.State = ShipState.CapacitorStarved; ship.TargetPosition = ship.Position; return "none"; } ship.ActionTimer = 0f; ship.State = ShipState.LocalFlight; ship.Position = ship.Position.MoveToward(targetPosition, ship.Definition.Speed * deltaSeconds); return "none"; } private string UpdateWarpTransit(ShipRuntime ship, SimulationWorld world, float deltaSeconds, Vector3 targetPosition, NodeRuntime targetNode) { var transit = ship.SpatialState.Transit; if (transit is null || transit.Regime != MovementRegimeKinds.Warp || transit.DestinationNodeId != targetNode.Id) { transit = new ShipTransitRuntime { Regime = MovementRegimeKinds.Warp, OriginNodeId = ship.SpatialState.CurrentNodeId, DestinationNodeId = targetNode.Id, StartedAtUtc = world.GeneratedAtUtc, }; ship.SpatialState.Transit = transit; } ship.SpatialState.SpaceLayer = SpaceLayerKinds.SystemSpace; ship.SpatialState.MovementRegime = MovementRegimeKinds.Warp; ship.SpatialState.CurrentNodeId = null; ship.SpatialState.CurrentBubbleId = null; ship.SpatialState.DestinationNodeId = targetNode.Id; var spoolDuration = MathF.Max(0.4f, ship.Definition.SpoolTime * 0.5f); if (ship.State != ShipState.Warping) { if (ship.State != ShipState.SpoolingWarp) { ship.ActionTimer = 0f; } if (!TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds)) { ship.State = ShipState.CapacitorStarved; ship.TargetPosition = ship.Position; return "none"; } ship.State = ShipState.SpoolingWarp; if (!AdvanceTimedAction(ship, deltaSeconds, spoolDuration)) { return "none"; } ship.State = ShipState.Warping; } if (!TryConsumeShipEnergy(ship, world.Balance.Energy.WarpDrain * deltaSeconds)) { ship.State = ShipState.CapacitorStarved; ship.TargetPosition = ship.Position; return "none"; } var totalDistance = MathF.Max(0.001f, transit.OriginNodeId is null ? ship.Position.DistanceTo(targetPosition) : (world.SpatialNodes.FirstOrDefault(candidate => candidate.Id == transit.OriginNodeId)?.Position.DistanceTo(targetPosition) ?? ship.Position.DistanceTo(targetPosition))); ship.Position = ship.Position.MoveToward(targetPosition, ship.Definition.Speed * deltaSeconds); transit.Progress = MathF.Min(1f, 1f - (ship.Position.DistanceTo(targetPosition) / totalDistance)); return ship.Position.DistanceTo(targetPosition) <= 18f ? CompleteTransitArrival(ship, targetNode.SystemId, targetPosition, targetNode) : "none"; } private string UpdateFtlTransit(ShipRuntime ship, SimulationWorld world, float deltaSeconds, string targetSystemId, Vector3 targetPosition, NodeRuntime? targetNode) { var destinationNodeId = targetNode?.Id; var transit = ship.SpatialState.Transit; if (transit is null || transit.Regime != MovementRegimeKinds.FtlTransit || transit.DestinationNodeId != destinationNodeId) { transit = new ShipTransitRuntime { Regime = MovementRegimeKinds.FtlTransit, OriginNodeId = ship.SpatialState.CurrentNodeId, DestinationNodeId = destinationNodeId, StartedAtUtc = world.GeneratedAtUtc, }; ship.SpatialState.Transit = transit; } ship.SpatialState.SpaceLayer = SpaceLayerKinds.GalaxySpace; ship.SpatialState.MovementRegime = MovementRegimeKinds.FtlTransit; ship.SpatialState.CurrentNodeId = null; ship.SpatialState.CurrentBubbleId = null; ship.SpatialState.DestinationNodeId = destinationNodeId; if (ship.State != ShipState.Ftl) { if (ship.State != ShipState.SpoolingFtl) { ship.ActionTimer = 0f; } if (!TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds)) { ship.State = ShipState.CapacitorStarved; ship.TargetPosition = ship.Position; return "none"; } ship.State = ShipState.SpoolingFtl; if (!AdvanceTimedAction(ship, deltaSeconds, ship.Definition.SpoolTime)) { return "none"; } ship.State = ShipState.Ftl; } if (!TryConsumeShipEnergy(ship, world.Balance.Energy.WarpDrain * deltaSeconds)) { ship.State = ShipState.CapacitorStarved; ship.TargetPosition = ship.Position; return "none"; } var totalDistance = MathF.Max(0.001f, ship.Position.DistanceTo(targetPosition)); ship.Position = ship.Position.MoveToward(targetPosition, ship.Definition.FtlSpeed * deltaSeconds); transit.Progress = MathF.Min(1f, 1f - (ship.Position.DistanceTo(targetPosition) / totalDistance)); return ship.Position.DistanceTo(targetPosition) <= 24f ? CompleteSystemEntryArrival(ship, targetSystemId, targetPosition, targetNode) : "none"; } private static string CompleteTransitArrival(ShipRuntime ship, string targetSystemId, Vector3 targetPosition, NodeRuntime? targetNode) { ship.ActionTimer = 0f; ship.Position = targetPosition; ship.TargetPosition = targetPosition; ship.SystemId = targetSystemId; ship.SpatialState.Transit = null; ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace; ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight; ship.SpatialState.CurrentNodeId = targetNode?.Id; ship.SpatialState.CurrentBubbleId = targetNode?.BubbleId; ship.SpatialState.DestinationNodeId = targetNode?.Id; ship.State = ShipState.Arriving; return "arrived"; } private static string CompleteSystemEntryArrival(ShipRuntime ship, string targetSystemId, Vector3 targetPosition, NodeRuntime? targetNode) { ship.ActionTimer = 0f; ship.Position = targetPosition; ship.TargetPosition = targetPosition; ship.SystemId = targetSystemId; ship.SpatialState.Transit = null; ship.SpatialState.SpaceLayer = SpaceLayerKinds.LocalSpace; ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight; ship.SpatialState.CurrentNodeId = targetNode?.Id; ship.SpatialState.CurrentBubbleId = targetNode?.BubbleId; ship.SpatialState.DestinationNodeId = targetNode?.Id; ship.State = ShipState.Arriving; return "none"; } }