141 lines
5.7 KiB
C#
141 lines
5.7 KiB
C#
|
|
namespace SpaceGame.Api.Simulation.Core;
|
|
|
|
public sealed class SimulationEngine
|
|
{
|
|
private readonly OrbitalSimulationOptions _orbitalSimulation;
|
|
private readonly OrbitalStateUpdater _orbitalStateUpdater;
|
|
private readonly InfrastructureSimulationService _infrastructureSimulation;
|
|
private readonly CommanderPlanningService _commanderPlanning;
|
|
private readonly StationSimulationService _stationSimulation;
|
|
private readonly StationLifecycleService _stationLifecycle;
|
|
private readonly ShipControlService _shipControl;
|
|
private readonly ShipTaskExecutionService _shipTaskExecution;
|
|
private readonly SimulationProjectionService _projection;
|
|
|
|
public SimulationEngine(OrbitalSimulationOptions? orbitalSimulation = null)
|
|
{
|
|
_orbitalSimulation = orbitalSimulation ?? new OrbitalSimulationOptions();
|
|
_orbitalStateUpdater = new OrbitalStateUpdater(_orbitalSimulation);
|
|
_infrastructureSimulation = new InfrastructureSimulationService();
|
|
_commanderPlanning = new CommanderPlanningService();
|
|
_stationSimulation = new StationSimulationService();
|
|
_stationLifecycle = new StationLifecycleService(_stationSimulation);
|
|
_shipControl = new ShipControlService();
|
|
_shipTaskExecution = new ShipTaskExecutionService();
|
|
_projection = new SimulationProjectionService(_orbitalSimulation);
|
|
}
|
|
|
|
public WorldDelta Tick(SimulationWorld world, float deltaSeconds, long sequence)
|
|
{
|
|
var nowUtc = DateTimeOffset.UtcNow;
|
|
var events = new List<SimulationEventRecord>();
|
|
|
|
world.OrbitalTimeSeconds += deltaSeconds * _orbitalSimulation.SimulatedSecondsPerRealSecond;
|
|
|
|
_orbitalStateUpdater.Update(world);
|
|
_infrastructureSimulation.UpdateClaims(world, events);
|
|
_infrastructureSimulation.UpdateConstructionSites(world, events);
|
|
_commanderPlanning.UpdateCommanders(this, world, deltaSeconds, events);
|
|
_stationLifecycle.UpdateStations(world, deltaSeconds, events);
|
|
|
|
foreach (var ship in world.Ships.ToList())
|
|
{
|
|
if (ship.Health <= 0f)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var previousPosition = ship.Position;
|
|
var previousState = ship.State;
|
|
var previousBehavior = ship.DefaultBehavior.Kind;
|
|
var previousTask = ship.ControllerTask.Kind;
|
|
|
|
_shipControl.RefreshControlLayers(ship, world);
|
|
_shipControl.PlanControllerTask(this, ship, world);
|
|
|
|
var controllerEvent = _shipTaskExecution.UpdateControllerTask(ship, world, deltaSeconds);
|
|
|
|
_shipControl.AdvanceControlState(this, ship, world, controllerEvent);
|
|
ship.Velocity = ship.Position.Subtract(previousPosition).Divide(deltaSeconds);
|
|
_shipControl.TrackHistory(ship, controllerEvent);
|
|
_shipControl.EmitShipStateEvents(ship, previousState, previousBehavior, previousTask, controllerEvent, events);
|
|
}
|
|
|
|
_orbitalStateUpdater.SyncSpatialState(world);
|
|
CleanupDestroyedEntities(world, events);
|
|
world.GeneratedAtUtc = nowUtc;
|
|
|
|
return _projection.BuildDelta(world, sequence, events);
|
|
}
|
|
|
|
public WorldSnapshot BuildSnapshot(SimulationWorld world, long sequence) =>
|
|
_projection.BuildSnapshot(world, sequence);
|
|
|
|
public void PrimeDeltaBaseline(SimulationWorld world) =>
|
|
_projection.PrimeDeltaBaseline(world);
|
|
|
|
internal void PlanResourceHarvest(ShipRuntime ship, SimulationWorld world, string resourceItemId, string requiredModule) =>
|
|
_shipControl.PlanResourceHarvest(ship, world, resourceItemId, requiredModule);
|
|
|
|
internal void PlanStationConstruction(ShipRuntime ship, SimulationWorld world) =>
|
|
_shipControl.PlanStationConstruction(ship, world);
|
|
|
|
internal void PlanAttackTarget(ShipRuntime ship, SimulationWorld world) =>
|
|
_shipControl.PlanAttackTarget(ship, world);
|
|
|
|
internal void PlanTransportHaul(ShipRuntime ship, SimulationWorld world) =>
|
|
_shipControl.PlanTransportHaul(ship, world);
|
|
|
|
internal static float GetShipCargoAmount(ShipRuntime ship) =>
|
|
SimulationRuntimeSupport.GetShipCargoAmount(ship);
|
|
|
|
private static void CleanupDestroyedEntities(SimulationWorld world, ICollection<SimulationEventRecord> events)
|
|
{
|
|
foreach (var ship in world.Ships.Where(candidate => candidate.Health <= 0f).ToList())
|
|
{
|
|
world.Ships.Remove(ship);
|
|
if (ship.DockedStationId is not null && world.Stations.FirstOrDefault(station => station.Id == ship.DockedStationId) is { } dockedStation)
|
|
{
|
|
dockedStation.DockedShipIds.Remove(ship.Id);
|
|
dockedStation.DockingPadAssignments.Remove(ship.AssignedDockingPadIndex ?? -1);
|
|
}
|
|
|
|
if (world.Factions.FirstOrDefault(candidate => candidate.Id == ship.FactionId) is { } faction)
|
|
{
|
|
faction.ShipsLost += 1;
|
|
}
|
|
|
|
if (ship.CommanderId is not null && world.Commanders.FirstOrDefault(candidate => candidate.Id == ship.CommanderId) is { } commander)
|
|
{
|
|
commander.IsAlive = false;
|
|
}
|
|
|
|
events.Add(new SimulationEventRecord("ship", ship.Id, "destroyed", $"{ship.Definition.Label} was destroyed.", DateTimeOffset.UtcNow));
|
|
}
|
|
|
|
foreach (var station in world.Stations.Where(candidate => candidate.Health <= 0f).ToList())
|
|
{
|
|
world.Stations.Remove(station);
|
|
|
|
if (station.CelestialId is not null && world.Celestials.FirstOrDefault(candidate => candidate.Id == station.CelestialId) is { } celestial)
|
|
{
|
|
celestial.OccupyingStructureId = null;
|
|
}
|
|
|
|
foreach (var claim in world.Claims.Where(candidate => candidate.CelestialId == station.CelestialId))
|
|
{
|
|
claim.Health = 0f;
|
|
claim.State = ClaimStateKinds.Destroyed;
|
|
}
|
|
|
|
foreach (var site in world.ConstructionSites.Where(candidate => candidate.StationId == station.Id))
|
|
{
|
|
site.State = ConstructionSiteStateKinds.Destroyed;
|
|
}
|
|
|
|
events.Add(new SimulationEventRecord("station", station.Id, "destroyed", $"{station.Label} was destroyed.", DateTimeOffset.UtcNow));
|
|
}
|
|
}
|
|
}
|