Refactor simulation and viewer architecture
This commit is contained in:
276
apps/backend/Simulation/SimulationEngine.MovementSystem.cs
Normal file
276
apps/backend/Simulation/SimulationEngine.MovementSystem.cs
Normal file
@@ -0,0 +1,276 @@
|
||||
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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user