301 lines
13 KiB
C#
301 lines
13 KiB
C#
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";
|
|
}
|
|
}
|