277 lines
11 KiB
C#
277 lines
11 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
|
|
{
|
|
"idle" => UpdateIdle(ship, world, deltaSeconds),
|
|
"travel" => UpdateTravel(ship, world, deltaSeconds),
|
|
"extract" => UpdateExtract(ship, world, deltaSeconds),
|
|
"dock" => UpdateDock(ship, world, deltaSeconds),
|
|
"unload" => UpdateUnload(ship, world, deltaSeconds),
|
|
"refuel" => UpdateRefuel(ship, world, deltaSeconds),
|
|
"deliver-construction" => UpdateDeliverConstruction(ship, world, deltaSeconds),
|
|
"build-construction-site" => UpdateBuildConstructionSite(ship, world, deltaSeconds),
|
|
"load-workers" => UpdateLoadWorkers(ship, world, deltaSeconds),
|
|
"unload-workers" => UpdateUnloadWorkers(ship, world, deltaSeconds),
|
|
"construct-module" => UpdateConstructModule(ship, world, deltaSeconds),
|
|
"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 = "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 = "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)
|
|
{
|
|
return UpdateFtlTransit(ship, world, deltaSeconds, task.TargetSystemId, targetPosition, targetNode);
|
|
}
|
|
|
|
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 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 = "arriving";
|
|
return "arrived";
|
|
}
|
|
|
|
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.MoveDrain * deltaSeconds))
|
|
{
|
|
ship.State = "power-starved";
|
|
ship.TargetPosition = ship.Position;
|
|
return "none";
|
|
}
|
|
|
|
ship.ActionTimer = 0f;
|
|
ship.State = "local-flight";
|
|
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 != "warping")
|
|
{
|
|
if (ship.State != "spooling-warp")
|
|
{
|
|
ship.ActionTimer = 0f;
|
|
}
|
|
|
|
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds))
|
|
{
|
|
ship.State = "power-starved";
|
|
ship.TargetPosition = ship.Position;
|
|
return "none";
|
|
}
|
|
|
|
ship.State = "spooling-warp";
|
|
if (!AdvanceTimedAction(ship, deltaSeconds, spoolDuration))
|
|
{
|
|
return "none";
|
|
}
|
|
|
|
ship.State = "warping";
|
|
}
|
|
|
|
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.WarpDrain * deltaSeconds))
|
|
{
|
|
ship.State = "power-starved";
|
|
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 != "ftl")
|
|
{
|
|
if (ship.State != "spooling-ftl")
|
|
{
|
|
ship.ActionTimer = 0f;
|
|
}
|
|
|
|
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds))
|
|
{
|
|
ship.State = "power-starved";
|
|
ship.TargetPosition = ship.Position;
|
|
return "none";
|
|
}
|
|
|
|
ship.State = "spooling-ftl";
|
|
if (!AdvanceTimedAction(ship, deltaSeconds, ship.Definition.SpoolTime))
|
|
{
|
|
return "none";
|
|
}
|
|
|
|
ship.State = "ftl";
|
|
}
|
|
|
|
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.WarpDrain * deltaSeconds))
|
|
{
|
|
ship.State = "power-starved";
|
|
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
|
|
? CompleteTransitArrival(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 = "arriving";
|
|
return "arrived";
|
|
}
|
|
}
|