Files
space-game/apps/backend/Simulation/SimulationEngine.cs

110 lines
4.5 KiB
C#

using SpaceGame.Simulation.Api.Contracts;
namespace SpaceGame.Simulation.Api.Simulation;
public sealed partial class SimulationEngine
{
private readonly OrbitalSimulationOptions _orbitalSimulation;
private const float ShipFuelToEnergyRatio = 12f;
private const float StationFuelToEnergyRatio = 18f;
private const float CapacitorEnergyPerModule = 120f;
private const float StationEnergyPerPowerCore = 480f;
private const float ShipFuelPerReactor = 100f;
private const float StationFuelPerTank = 500f;
private const float WaterConsumptionPerWorkerPerSecond = 0.004f;
private const float PopulationGrowthPerSecond = 0.012f;
private const float PopulationAttritionPerSecond = 0.018f;
private static readonly ShipBehaviorStateMachine _shipBehaviorStateMachine = ShipBehaviorStateMachine.CreateDefault();
private static readonly IReadOnlyList<WorldUpdateStep> _worldUpdatePipeline =
[
new((engine, world, deltaSeconds, nowUtc, events) => engine.UpdateOrbitalState(world)),
new((engine, world, deltaSeconds, nowUtc, events) => UpdateClaims(world, events)),
new((engine, world, deltaSeconds, nowUtc, events) => UpdateConstructionSites(world, events)),
new((engine, world, deltaSeconds, nowUtc, events) => UpdateStationPower(world, deltaSeconds, events)),
new((engine, world, deltaSeconds, nowUtc, events) => engine.UpdateStations(world, deltaSeconds, events)),
];
private static readonly IReadOnlyList<ShipUpdateStep> _shipUpdatePipeline =
[
new((engine, ship, world, deltaSeconds, events) => UpdateShipPower(ship, world, deltaSeconds, events)),
new((engine, ship, world, deltaSeconds, events) => engine.RefreshControlLayers(ship, world)),
new((engine, ship, world, deltaSeconds, events) => engine.PlanControllerTask(ship, world)),
];
public SimulationEngine(OrbitalSimulationOptions? orbitalSimulation = null)
{
_orbitalSimulation = orbitalSimulation ?? new OrbitalSimulationOptions();
}
public WorldDelta Tick(SimulationWorld world, float deltaSeconds, long sequence)
{
var events = new List<SimulationEventRecord>();
var nowUtc = DateTimeOffset.UtcNow;
world.OrbitalTimeSeconds += deltaSeconds * _orbitalSimulation.SimulatedSecondsPerRealSecond;
foreach (var step in _worldUpdatePipeline)
{
step.Execute(this, world, deltaSeconds, nowUtc, events);
}
foreach (var ship in world.Ships)
{
var previousPosition = ship.Position;
var previousState = ship.State;
var previousBehavior = ship.DefaultBehavior.Kind;
var previousTask = ship.ControllerTask.Kind;
foreach (var step in _shipUpdatePipeline)
{
step.Execute(this, ship, world, deltaSeconds, events);
}
var controllerEvent = UpdateControllerTask(ship, world, deltaSeconds);
AdvanceControlState(ship, world, controllerEvent);
ship.Velocity = ship.Position.Subtract(previousPosition).Divide(deltaSeconds);
TrackHistory(ship, controllerEvent);
EmitShipStateEvents(ship, previousState, previousBehavior, previousTask, controllerEvent, events);
}
SyncSpatialState(world);
world.GeneratedAtUtc = nowUtc;
return new WorldDelta(
sequence,
world.TickIntervalMs,
world.OrbitalTimeSeconds,
new OrbitalSimulationSnapshot(_orbitalSimulation.SimulatedSecondsPerRealSecond),
world.GeneratedAtUtc,
false,
events,
BuildSpatialNodeDeltas(world),
BuildLocalBubbleDeltas(world),
BuildNodeDeltas(world),
BuildStationDeltas(world),
BuildClaimDeltas(world),
BuildConstructionSiteDeltas(world),
BuildMarketOrderDeltas(world),
BuildPolicyDeltas(world),
BuildShipDeltas(world),
BuildFactionDeltas(world));
}
private delegate void WorldUpdateStepAction(
SimulationEngine engine,
SimulationWorld world,
float deltaSeconds,
DateTimeOffset nowUtc,
List<SimulationEventRecord> events);
private delegate void ShipUpdateStepAction(
SimulationEngine engine,
ShipRuntime ship,
SimulationWorld world,
float deltaSeconds,
List<SimulationEventRecord> events);
private sealed record WorldUpdateStep(WorldUpdateStepAction Execute);
private sealed record ShipUpdateStep(ShipUpdateStepAction Execute);
}