refactor(backend): modularize simulation engine
This commit is contained in:
84
apps/backend/Simulation/Engine/SimulationEngine.cs
Normal file
84
apps/backend/Simulation/Engine/SimulationEngine.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using SpaceGame.Api.Contracts;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
using SpaceGame.Api.Simulation.Support;
|
||||
using SpaceGame.Api.Simulation.Systems;
|
||||
|
||||
namespace SpaceGame.Api.Simulation.Engine;
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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 static float GetShipCargoAmount(ShipRuntime ship) =>
|
||||
SimulationRuntimeSupport.GetShipCargoAmount(ship);
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using SpaceGame.Simulation.Api.Contracts;
|
||||
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
{
|
||||
private readonly OrbitalSimulationOptions _orbitalSimulation;
|
||||
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) => engine.UpdateCommanders(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) => 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,
|
||||
BuildCelestialDeltas(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);
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
using SpaceGame.Simulation.Api.Contracts;
|
||||
using SpaceGame.Simulation.Api.Data;
|
||||
using SpaceGame.Api.Data;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
namespace SpaceGame.Api.Simulation.Support;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
internal static class SimulationRuntimeSupport
|
||||
{
|
||||
private static bool HasShipCapabilities(ShipDefinition definition, params string[] capabilities) =>
|
||||
internal static bool HasShipCapabilities(ShipDefinition definition, params string[] capabilities) =>
|
||||
capabilities.All(cap => definition.Capabilities.Contains(cap, StringComparer.Ordinal));
|
||||
|
||||
private static int CountStationModules(StationRuntime station, string moduleId) =>
|
||||
internal static int CountStationModules(StationRuntime station, string moduleId) =>
|
||||
station.Modules.Count(module => string.Equals(module.ModuleId, moduleId, StringComparison.Ordinal));
|
||||
|
||||
private static void AddStationModule(SimulationWorld world, StationRuntime station, string moduleId)
|
||||
internal static void AddStationModule(SimulationWorld world, StationRuntime station, string moduleId)
|
||||
{
|
||||
if (!world.ModuleDefinitions.TryGetValue(moduleId, out var definition))
|
||||
{
|
||||
@@ -28,7 +28,7 @@ public sealed partial class SimulationEngine
|
||||
station.Radius = GetStationRadius(world, station);
|
||||
}
|
||||
|
||||
private static float GetStationRadius(SimulationWorld world, StationRuntime station)
|
||||
internal static float GetStationRadius(SimulationWorld world, StationRuntime station)
|
||||
{
|
||||
var totalArea = station.Modules
|
||||
.Select(module => world.ModuleDefinitions.TryGetValue(module.ModuleId, out var definition) ? definition.Radius * definition.Radius : 0f)
|
||||
@@ -36,7 +36,7 @@ public sealed partial class SimulationEngine
|
||||
return MathF.Max(24f, MathF.Sqrt(MathF.Max(totalArea, 1f)));
|
||||
}
|
||||
|
||||
private static float GetStationStorageCapacity(StationRuntime station, string storageClass)
|
||||
internal static float GetStationStorageCapacity(StationRuntime station, string storageClass)
|
||||
{
|
||||
var baseCapacity = storageClass switch
|
||||
{
|
||||
@@ -60,13 +60,13 @@ public sealed partial class SimulationEngine
|
||||
return baseCapacity + moduleCapacity;
|
||||
}
|
||||
|
||||
private static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
||||
internal static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
||||
modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal));
|
||||
|
||||
private static float GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
|
||||
internal static float GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
|
||||
inventory.TryGetValue(itemId, out var amount) ? amount : 0f;
|
||||
|
||||
private static void AddInventory(IDictionary<string, float> inventory, string itemId, float amount)
|
||||
internal static void AddInventory(IDictionary<string, float> inventory, string itemId, float amount)
|
||||
{
|
||||
if (amount <= 0f)
|
||||
{
|
||||
@@ -76,7 +76,7 @@ public sealed partial class SimulationEngine
|
||||
inventory[itemId] = GetInventoryAmount((IReadOnlyDictionary<string, float>)inventory, itemId) + amount;
|
||||
}
|
||||
|
||||
private static float RemoveInventory(IDictionary<string, float> inventory, string itemId, float amount)
|
||||
internal static float RemoveInventory(IDictionary<string, float> inventory, string itemId, float amount)
|
||||
{
|
||||
var current = GetInventoryAmount((IReadOnlyDictionary<string, float>)inventory, itemId);
|
||||
var removed = MathF.Min(current, amount);
|
||||
@@ -93,18 +93,18 @@ public sealed partial class SimulationEngine
|
||||
return removed;
|
||||
}
|
||||
|
||||
private static bool HasStationModules(StationRuntime station, params string[] modules) =>
|
||||
internal static bool HasStationModules(StationRuntime station, params string[] modules) =>
|
||||
modules.All(moduleId => station.Modules.Any(candidate => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal)));
|
||||
|
||||
private static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node, SimulationWorld world) =>
|
||||
internal static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node, SimulationWorld world) =>
|
||||
HasShipCapabilities(ship.Definition, "mining")
|
||||
&& world.ItemDefinitions.TryGetValue(node.ItemId, out var item)
|
||||
&& string.Equals(item.CargoKind, ship.Definition.CargoKind, StringComparison.Ordinal);
|
||||
|
||||
private static bool CanBuildClaimBeacon(ShipRuntime ship) =>
|
||||
internal static bool CanBuildClaimBeacon(ShipRuntime ship) =>
|
||||
string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal);
|
||||
|
||||
private static float ComputeWorkforceRatio(float population, float workforceRequired)
|
||||
internal static float ComputeWorkforceRatio(float population, float workforceRequired)
|
||||
{
|
||||
if (workforceRequired <= 0.01f)
|
||||
{
|
||||
@@ -115,7 +115,7 @@ public sealed partial class SimulationEngine
|
||||
return 0.1f + (0.9f * staffedRatio);
|
||||
}
|
||||
|
||||
private static string? GetStorageRequirement(string storageClass) =>
|
||||
internal static string? GetStorageRequirement(string storageClass) =>
|
||||
storageClass switch
|
||||
{
|
||||
"solid" => "module_arg_stor_solid_m_01",
|
||||
@@ -123,7 +123,7 @@ public sealed partial class SimulationEngine
|
||||
_ => null,
|
||||
};
|
||||
|
||||
private static float TryAddStationInventory(SimulationWorld world, StationRuntime station, string itemId, float amount)
|
||||
internal static float TryAddStationInventory(SimulationWorld world, StationRuntime station, string itemId, float amount)
|
||||
{
|
||||
if (amount <= 0f || !world.ItemDefinitions.TryGetValue(itemId, out var itemDefinition))
|
||||
{
|
||||
@@ -156,15 +156,15 @@ public sealed partial class SimulationEngine
|
||||
return accepted;
|
||||
}
|
||||
|
||||
private static bool CanStartModuleConstruction(StationRuntime station, ModuleRecipeDefinition recipe) =>
|
||||
internal static bool CanStartModuleConstruction(StationRuntime station, ModuleRecipeDefinition recipe) =>
|
||||
recipe.Inputs.All(input => GetInventoryAmount(station.Inventory, input.ItemId) + 0.001f >= input.Amount);
|
||||
|
||||
private static ConstructionSiteRuntime? GetConstructionSiteForStation(SimulationWorld world, string stationId) =>
|
||||
internal static ConstructionSiteRuntime? GetConstructionSiteForStation(SimulationWorld world, string stationId) =>
|
||||
world.ConstructionSites.FirstOrDefault(site =>
|
||||
string.Equals(site.StationId, stationId, StringComparison.Ordinal)
|
||||
&& site.State is not ConstructionSiteStateKinds.Completed and not ConstructionSiteStateKinds.Destroyed);
|
||||
|
||||
private static float GetConstructionDeliveredAmount(SimulationWorld world, ConstructionSiteRuntime site, string itemId)
|
||||
internal static float GetConstructionDeliveredAmount(SimulationWorld world, ConstructionSiteRuntime site, string itemId)
|
||||
{
|
||||
if (site.StationId is not null
|
||||
&& world.Stations.FirstOrDefault(candidate => candidate.Id == site.StationId) is { } station)
|
||||
@@ -175,6 +175,9 @@ public sealed partial class SimulationEngine
|
||||
return GetInventoryAmount(site.DeliveredItems, itemId);
|
||||
}
|
||||
|
||||
private static bool IsConstructionSiteReady(SimulationWorld world, ConstructionSiteRuntime site) =>
|
||||
internal static bool IsConstructionSiteReady(SimulationWorld world, ConstructionSiteRuntime site) =>
|
||||
site.RequiredItems.All(entry => GetConstructionDeliveredAmount(world, site, entry.Key) + 0.001f >= entry.Value);
|
||||
|
||||
internal static float GetShipCargoAmount(ShipRuntime ship) =>
|
||||
ship.Inventory.Values.Sum();
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using SpaceGame.Simulation.Api.Contracts;
|
||||
using SpaceGame.Api.Contracts;
|
||||
using SpaceGame.Api.Simulation.AI;
|
||||
using SpaceGame.Api.Simulation.Engine;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
using static SpaceGame.Api.Simulation.Support.SimulationRuntimeSupport;
|
||||
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
namespace SpaceGame.Api.Simulation.Systems;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
internal sealed class CommanderPlanningService
|
||||
{
|
||||
private const float FactionCommanderReplanInterval = 10f;
|
||||
private const float ShipCommanderReplanInterval = 5f;
|
||||
@@ -28,7 +32,7 @@ public sealed partial class SimulationEngine
|
||||
|
||||
private static readonly GoapGoal<ShipPlanningState> _shipGoal = new AssignObjectiveGoal();
|
||||
|
||||
private void UpdateCommanders(SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
internal void UpdateCommanders(SimulationEngine engine, SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
// Faction commanders run first so their directives are available to ship commanders in the same tick.
|
||||
foreach (var commander in world.Commanders)
|
||||
@@ -39,7 +43,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
|
||||
TickCommander(commander, deltaSeconds);
|
||||
UpdateFactionCommander(world, commander);
|
||||
UpdateFactionCommander(engine, world, commander);
|
||||
}
|
||||
|
||||
foreach (var commander in world.Commanders)
|
||||
@@ -50,7 +54,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
|
||||
TickCommander(commander, deltaSeconds);
|
||||
UpdateShipCommander(world, commander);
|
||||
UpdateShipCommander(engine, world, commander);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +66,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFactionCommander(SimulationWorld world, CommanderRuntime commander)
|
||||
private void UpdateFactionCommander(SimulationEngine engine, SimulationWorld world, CommanderRuntime commander)
|
||||
{
|
||||
if (commander.ReplanTimer > 0f && !commander.NeedsReplan)
|
||||
{
|
||||
@@ -91,11 +95,11 @@ public sealed partial class SimulationEngine
|
||||
foreach (var (goal, _) in rankedGoals.Take(3))
|
||||
{
|
||||
var plan = _factionPlanner.Plan(state, goal, actions);
|
||||
plan?.CurrentAction?.Execute(this, world, commander);
|
||||
plan?.CurrentAction?.Execute(engine, world, commander);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateShipCommander(SimulationWorld world, CommanderRuntime commander)
|
||||
private void UpdateShipCommander(SimulationEngine engine, SimulationWorld world, CommanderRuntime commander)
|
||||
{
|
||||
if (commander.ReplanTimer > 0f && !commander.NeedsReplan)
|
||||
{
|
||||
@@ -117,7 +121,7 @@ public sealed partial class SimulationEngine
|
||||
{
|
||||
commander.ActiveGoalName = _shipGoal.Name;
|
||||
commander.ActiveActionName = action.Name;
|
||||
action.Execute(this, world, commander);
|
||||
action.Execute(engine, world, commander);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,8 +143,8 @@ public sealed partial class SimulationEngine
|
||||
ConstructorShipCount = world.Ships.Count(s =>
|
||||
s.FactionId == factionId &&
|
||||
string.Equals(s.Definition.Kind, "construction", StringComparison.Ordinal)),
|
||||
ControlledSystemCount = GetFactionControlledSystemsCount(world, factionId),
|
||||
TargetSystemCount = Math.Max(1, Math.Min(StrategicControlTargetSystems, world.Systems.Count)),
|
||||
ControlledSystemCount = StationSimulationService.GetFactionControlledSystemsCount(world, factionId),
|
||||
TargetSystemCount = Math.Max(1, Math.Min(StationSimulationService.StrategicControlTargetSystems, world.Systems.Count)),
|
||||
HasShipFactory = stations.Any(s => s.InstalledModules.Contains("module_gen_build_l_01", StringComparer.Ordinal)),
|
||||
OreStockpile = stations.Sum(s => GetInventoryAmount(s.Inventory, "ore")),
|
||||
RefinedMetalsStockpile = stations.Sum(s => GetInventoryAmount(s.Inventory, "refinedmetals")),
|
||||
@@ -1,11 +1,13 @@
|
||||
using SpaceGame.Simulation.Api.Contracts;
|
||||
using SpaceGame.Simulation.Api.Data;
|
||||
using SpaceGame.Api.Contracts;
|
||||
using SpaceGame.Api.Data;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
using static SpaceGame.Api.Simulation.Support.SimulationRuntimeSupport;
|
||||
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
namespace SpaceGame.Api.Simulation.Systems;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
internal sealed class InfrastructureSimulationService
|
||||
{
|
||||
private static void UpdateClaims(SimulationWorld world, ICollection<SimulationEventRecord> events)
|
||||
internal void UpdateClaims(SimulationWorld world, ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
foreach (var claim in world.Claims)
|
||||
{
|
||||
@@ -33,7 +35,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateConstructionSites(SimulationWorld world, ICollection<SimulationEventRecord> events)
|
||||
internal void UpdateConstructionSites(SimulationWorld world, ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
foreach (var site in world.ConstructionSites)
|
||||
{
|
||||
@@ -76,7 +78,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryEnsureModuleConstructionStarted(StationRuntime station, ModuleRecipeDefinition recipe, string shipId)
|
||||
internal static bool TryEnsureModuleConstructionStarted(StationRuntime station, ModuleRecipeDefinition recipe, string shipId)
|
||||
{
|
||||
if (station.ActiveConstruction is not null)
|
||||
{
|
||||
@@ -104,7 +106,7 @@ public sealed partial class SimulationEngine
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string? GetNextStationModuleToBuild(StationRuntime station, SimulationWorld world)
|
||||
internal static string? GetNextStationModuleToBuild(StationRuntime station, SimulationWorld world)
|
||||
{
|
||||
// Expand storage before it becomes a bottleneck
|
||||
const float StorageExpansionThreshold = 0.85f;
|
||||
@@ -133,7 +135,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
var priorities = GetFactionExpansionPressure(world, station.FactionId) > 0f
|
||||
var priorities = StationSimulationService.GetFactionExpansionPressure(world, station.FactionId) > 0f
|
||||
? new (string ModuleId, int TargetCount)[]
|
||||
{
|
||||
("module_gen_prod_refinedmetals_01", 1),
|
||||
@@ -169,7 +171,7 @@ public sealed partial class SimulationEngine
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void PrepareNextConstructionSiteStep(SimulationWorld world, StationRuntime station, ConstructionSiteRuntime site)
|
||||
internal static void PrepareNextConstructionSiteStep(SimulationWorld world, StationRuntime station, ConstructionSiteRuntime site)
|
||||
{
|
||||
var nextModuleId = GetNextStationModuleToBuild(station, world);
|
||||
foreach (var orderId in site.MarketOrderIds)
|
||||
@@ -224,10 +226,10 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetDockingPadCount(StationRuntime station) =>
|
||||
internal static int GetDockingPadCount(StationRuntime station) =>
|
||||
CountModules(station.InstalledModules, "module_arg_dock_m_01_lowtech") * 2;
|
||||
|
||||
private static int? ReserveDockingPad(StationRuntime station, string shipId)
|
||||
internal static int? ReserveDockingPad(StationRuntime station, string shipId)
|
||||
{
|
||||
if (station.DockingPadAssignments.FirstOrDefault(entry => string.Equals(entry.Value, shipId, StringComparison.Ordinal)) is var existing
|
||||
&& !string.IsNullOrEmpty(existing.Value))
|
||||
@@ -250,7 +252,7 @@ public sealed partial class SimulationEngine
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void ReleaseDockingPad(StationRuntime station, string shipId)
|
||||
internal static void ReleaseDockingPad(StationRuntime station, string shipId)
|
||||
{
|
||||
var assignment = station.DockingPadAssignments.FirstOrDefault(entry => string.Equals(entry.Value, shipId, StringComparison.Ordinal));
|
||||
if (!string.IsNullOrEmpty(assignment.Value))
|
||||
@@ -259,7 +261,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector3 GetDockingPadPosition(StationRuntime station, int padIndex)
|
||||
internal static Vector3 GetDockingPadPosition(StationRuntime station, int padIndex)
|
||||
{
|
||||
var padCount = Math.Max(1, GetDockingPadCount(station));
|
||||
var angle = ((MathF.PI * 2f) / padCount) * padIndex;
|
||||
@@ -270,7 +272,7 @@ public sealed partial class SimulationEngine
|
||||
station.Position.Z + (MathF.Sin(angle) * radius));
|
||||
}
|
||||
|
||||
private static Vector3 GetDockingHoldPosition(StationRuntime station, string shipId)
|
||||
internal static Vector3 GetDockingHoldPosition(StationRuntime station, string shipId)
|
||||
{
|
||||
var hash = Math.Abs(shipId.GetHashCode(StringComparison.Ordinal));
|
||||
var angle = (hash % 360) * (MathF.PI / 180f);
|
||||
@@ -281,7 +283,7 @@ public sealed partial class SimulationEngine
|
||||
station.Position.Z + (MathF.Sin(angle) * radius));
|
||||
}
|
||||
|
||||
private static Vector3 GetUndockTargetPosition(StationRuntime station, int? padIndex, float distance)
|
||||
internal static Vector3 GetUndockTargetPosition(StationRuntime station, int? padIndex, float distance)
|
||||
{
|
||||
if (padIndex is null)
|
||||
{
|
||||
@@ -304,12 +306,12 @@ public sealed partial class SimulationEngine
|
||||
pad.Z + (dz * scale));
|
||||
}
|
||||
|
||||
private static Vector3 GetShipDockedPosition(ShipRuntime ship, StationRuntime station) =>
|
||||
internal static Vector3 GetShipDockedPosition(ShipRuntime ship, StationRuntime station) =>
|
||||
ship.AssignedDockingPadIndex is int padIndex
|
||||
? GetDockingPadPosition(station, padIndex)
|
||||
: station.Position;
|
||||
|
||||
private static Vector3 GetConstructionHoldPosition(StationRuntime station, string shipId)
|
||||
internal static Vector3 GetConstructionHoldPosition(StationRuntime station, string shipId)
|
||||
{
|
||||
var hash = Math.Abs(shipId.GetHashCode(StringComparison.Ordinal));
|
||||
var angle = (hash % 360) * (MathF.PI / 180f);
|
||||
@@ -320,7 +322,7 @@ public sealed partial class SimulationEngine
|
||||
station.Position.Z + (MathF.Sin(angle) * radius));
|
||||
}
|
||||
|
||||
private static Vector3 GetResourceHoldPosition(Vector3 nodePosition, string shipId, float radius)
|
||||
internal static Vector3 GetResourceHoldPosition(Vector3 nodePosition, string shipId, float radius)
|
||||
{
|
||||
var hash = Math.Abs(shipId.GetHashCode(StringComparison.Ordinal));
|
||||
var angle = (hash % 360) * (MathF.PI / 180f);
|
||||
@@ -1,9 +1,18 @@
|
||||
using SpaceGame.Simulation.Api.Data;
|
||||
using SpaceGame.Api.Data;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
using static SpaceGame.Api.Simulation.Systems.InfrastructureSimulationService;
|
||||
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
namespace SpaceGame.Api.Simulation.Systems;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
internal sealed class OrbitalStateUpdater
|
||||
{
|
||||
private readonly OrbitalSimulationOptions _orbitalSimulation;
|
||||
|
||||
internal OrbitalStateUpdater(OrbitalSimulationOptions orbitalSimulation)
|
||||
{
|
||||
_orbitalSimulation = orbitalSimulation;
|
||||
}
|
||||
|
||||
private static Vector3 ComputePlanetPosition(PlanetDefinition planet, float timeSeconds)
|
||||
{
|
||||
var eccentricity = Math.Clamp(planet.OrbitEccentricity, 0f, 0.85f);
|
||||
@@ -153,7 +162,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateOrbitalState(SimulationWorld world)
|
||||
internal void Update(SimulationWorld world)
|
||||
{
|
||||
var worldTimeSeconds = (float)world.OrbitalTimeSeconds;
|
||||
var celestialsById = world.Celestials.ToDictionary(c => c.Id, StringComparer.Ordinal);
|
||||
@@ -248,7 +257,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private static void SyncSpatialState(SimulationWorld world)
|
||||
internal void SyncSpatialState(SimulationWorld world)
|
||||
{
|
||||
foreach (var ship in world.Ships)
|
||||
{
|
||||
@@ -1,9 +1,16 @@
|
||||
using SpaceGame.Simulation.Api.Data;
|
||||
using SpaceGame.Api.Contracts;
|
||||
using SpaceGame.Api.Simulation.AI;
|
||||
using SpaceGame.Api.Simulation.Engine;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
using static SpaceGame.Api.Simulation.Systems.InfrastructureSimulationService;
|
||||
using static SpaceGame.Api.Simulation.Support.SimulationRuntimeSupport;
|
||||
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
namespace SpaceGame.Api.Simulation.Systems;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
internal sealed class ShipControlService
|
||||
{
|
||||
private static readonly ShipBehaviorStateMachine _shipBehaviorStateMachine = ShipBehaviorStateMachine.CreateDefault();
|
||||
|
||||
private static CommanderRuntime? GetShipCommander(SimulationWorld world, ShipRuntime ship) =>
|
||||
ship.CommanderId is null
|
||||
? null
|
||||
@@ -91,7 +98,7 @@ public sealed partial class SimulationEngine
|
||||
commander.ActiveTask.Threshold = ship.ControllerTask.Threshold;
|
||||
}
|
||||
|
||||
private void RefreshControlLayers(ShipRuntime ship, SimulationWorld world)
|
||||
internal void RefreshControlLayers(ShipRuntime ship, SimulationWorld world)
|
||||
{
|
||||
var commander = GetShipCommander(world, ship);
|
||||
if (commander is not null)
|
||||
@@ -114,7 +121,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private void PlanControllerTask(ShipRuntime ship, SimulationWorld world)
|
||||
internal void PlanControllerTask(SimulationEngine engine, ShipRuntime ship, SimulationWorld world)
|
||||
{
|
||||
var commander = GetShipCommander(world, ship);
|
||||
if (ship.Order is not null)
|
||||
@@ -133,7 +140,7 @@ public sealed partial class SimulationEngine
|
||||
return;
|
||||
}
|
||||
|
||||
_shipBehaviorStateMachine.Plan(this, ship, world);
|
||||
_shipBehaviorStateMachine.Plan(engine, ship, world);
|
||||
SyncCommanderTask(commander, ship.ControllerTask);
|
||||
}
|
||||
|
||||
@@ -436,7 +443,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private void AdvanceControlState(ShipRuntime ship, SimulationWorld world, string controllerEvent)
|
||||
internal void AdvanceControlState(SimulationEngine engine, ShipRuntime ship, SimulationWorld world, string controllerEvent)
|
||||
{
|
||||
var commander = GetShipCommander(world, ship);
|
||||
if (ship.Order is not null && controllerEvent == "arrived")
|
||||
@@ -458,7 +465,7 @@ public sealed partial class SimulationEngine
|
||||
return;
|
||||
}
|
||||
|
||||
_shipBehaviorStateMachine.ApplyEvent(this, ship, world, controllerEvent);
|
||||
_shipBehaviorStateMachine.ApplyEvent(engine, ship, world, controllerEvent);
|
||||
if (commander is not null)
|
||||
{
|
||||
SyncShipToCommander(ship, commander);
|
||||
@@ -469,7 +476,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private static void TrackHistory(ShipRuntime ship, string controllerEvent)
|
||||
internal void TrackHistory(ShipRuntime ship, string controllerEvent)
|
||||
{
|
||||
var signature = $"{ship.State.ToContractValue()}|{ship.DefaultBehavior.Kind}|{ship.ControllerTask.Kind.ToContractValue()}|{ship.ControllerTask.TargetSystemId}|{ship.ControllerTask.TargetEntityId}|{GetShipCargoAmount(ship):0.0}|{controllerEvent}";
|
||||
if (signature == ship.LastSignature)
|
||||
@@ -489,7 +496,38 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private static ControllerTaskRuntime CreateIdleTask(float threshold) =>
|
||||
internal void EmitShipStateEvents(
|
||||
ShipRuntime ship,
|
||||
ShipState previousState,
|
||||
string previousBehavior,
|
||||
ControllerTaskKind previousTask,
|
||||
string controllerEvent,
|
||||
ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
var occurredAtUtc = DateTimeOffset.UtcNow;
|
||||
|
||||
if (previousState != ship.State)
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, "state-changed", $"{ship.Definition.Label} {previousState.ToContractValue()} -> {ship.State.ToContractValue()}", occurredAtUtc));
|
||||
}
|
||||
|
||||
if (previousBehavior != ship.DefaultBehavior.Kind)
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, "behavior-changed", $"{ship.Definition.Label} behavior {previousBehavior} -> {ship.DefaultBehavior.Kind}", occurredAtUtc));
|
||||
}
|
||||
|
||||
if (previousTask != ship.ControllerTask.Kind)
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, "task-changed", $"{ship.Definition.Label} task {previousTask.ToContractValue()} -> {ship.ControllerTask.Kind.ToContractValue()}", occurredAtUtc));
|
||||
}
|
||||
|
||||
if (controllerEvent != "none")
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, controllerEvent, $"{ship.Definition.Label} {controllerEvent}", occurredAtUtc));
|
||||
}
|
||||
}
|
||||
|
||||
internal static ControllerTaskRuntime CreateIdleTask(float threshold) =>
|
||||
new()
|
||||
{
|
||||
Kind = ControllerTaskKind.Idle,
|
||||
@@ -1,6 +1,11 @@
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
using SpaceGame.Api.Simulation.Support;
|
||||
using static SpaceGame.Api.Simulation.Systems.InfrastructureSimulationService;
|
||||
using static SpaceGame.Api.Simulation.Support.SimulationRuntimeSupport;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
namespace SpaceGame.Api.Simulation.Systems;
|
||||
|
||||
internal sealed partial class ShipTaskExecutionService
|
||||
{
|
||||
private static bool AdvanceTimedAction(ShipRuntime ship, float deltaSeconds, float requiredSeconds)
|
||||
{
|
||||
@@ -26,7 +31,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
|
||||
internal static float GetShipCargoAmount(ShipRuntime ship) =>
|
||||
ship.Inventory.Values.Sum();
|
||||
SimulationRuntimeSupport.GetShipCargoAmount(ship);
|
||||
|
||||
private string UpdateExtract(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
|
||||
{
|
||||
@@ -448,6 +453,6 @@ public sealed partial class SimulationEngine
|
||||
return "undocked";
|
||||
}
|
||||
|
||||
private static float GetRemainingConstructionDelivery(SimulationWorld world, ConstructionSiteRuntime site) =>
|
||||
internal static float GetRemainingConstructionDelivery(SimulationWorld world, ConstructionSiteRuntime site) =>
|
||||
site.RequiredItems.Sum(required => MathF.Max(0f, required.Value - GetConstructionDeliveredAmount(world, site, required.Key)));
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
using static SpaceGame.Api.Simulation.Systems.InfrastructureSimulationService;
|
||||
using static SpaceGame.Api.Simulation.Support.SimulationRuntimeSupport;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
namespace SpaceGame.Api.Simulation.Systems;
|
||||
|
||||
internal sealed partial class ShipTaskExecutionService
|
||||
{
|
||||
private const float WarpEngageDistanceKilometers = 250_000f;
|
||||
|
||||
@@ -14,7 +18,7 @@ public sealed partial class SimulationEngine
|
||||
world.Systems.FirstOrDefault(candidate => string.Equals(candidate.Definition.Id, systemId, StringComparison.Ordinal))?.Position
|
||||
?? Vector3.Zero;
|
||||
|
||||
private string UpdateControllerTask(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
|
||||
internal string UpdateControllerTask(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
|
||||
{
|
||||
var task = ship.ControllerTask;
|
||||
return task.Kind switch
|
||||
@@ -1,9 +1,39 @@
|
||||
using SpaceGame.Simulation.Api.Contracts;
|
||||
using SpaceGame.Api.Contracts;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
using static SpaceGame.Api.Simulation.Systems.InfrastructureSimulationService;
|
||||
using static SpaceGame.Api.Simulation.Systems.StationSimulationService;
|
||||
using static SpaceGame.Api.Simulation.Support.SimulationRuntimeSupport;
|
||||
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
namespace SpaceGame.Api.Simulation.Systems;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
internal sealed class SimulationProjectionService
|
||||
{
|
||||
private readonly OrbitalSimulationOptions _orbitalSimulation;
|
||||
|
||||
internal SimulationProjectionService(OrbitalSimulationOptions orbitalSimulation)
|
||||
{
|
||||
_orbitalSimulation = orbitalSimulation;
|
||||
}
|
||||
|
||||
internal WorldDelta BuildDelta(SimulationWorld world, long sequence, IReadOnlyList<SimulationEventRecord> events) =>
|
||||
new(
|
||||
sequence,
|
||||
world.TickIntervalMs,
|
||||
world.OrbitalTimeSeconds,
|
||||
new OrbitalSimulationSnapshot(_orbitalSimulation.SimulatedSecondsPerRealSecond),
|
||||
world.GeneratedAtUtc,
|
||||
false,
|
||||
events,
|
||||
BuildCelestialDeltas(world),
|
||||
BuildNodeDeltas(world),
|
||||
BuildStationDeltas(world),
|
||||
BuildClaimDeltas(world),
|
||||
BuildConstructionSiteDeltas(world),
|
||||
BuildMarketOrderDeltas(world),
|
||||
BuildPolicyDeltas(world),
|
||||
BuildShipDeltas(world),
|
||||
BuildFactionDeltas(world));
|
||||
|
||||
public WorldSnapshot BuildSnapshot(SimulationWorld world, long sequence)
|
||||
{
|
||||
PrimeDeltaBaseline(world);
|
||||
@@ -472,7 +502,7 @@ public sealed partial class SimulationEngine
|
||||
ship.TrackedActionKey ?? "none",
|
||||
ship.TrackedActionTotal.ToString("0.###"),
|
||||
ship.ControllerTask.TargetEntityId is not null && world.ConstructionSites.FirstOrDefault(site => site.Id == ship.ControllerTask.TargetEntityId) is { } site
|
||||
? GetRemainingConstructionDelivery(world, site).ToString("0.###")
|
||||
? ShipTaskExecutionService.GetRemainingConstructionDelivery(world, site).ToString("0.###")
|
||||
: "0",
|
||||
ship.Health.ToString("0.###"),
|
||||
ship.ActionTimer.ToString("0.###"));
|
||||
@@ -689,7 +719,7 @@ public sealed partial class SimulationEngine
|
||||
? null
|
||||
: world.ConstructionSites.FirstOrDefault(site => site.Id == ship.ControllerTask.TargetEntityId) is not { } site
|
||||
? null
|
||||
: CreateShipRemainingActionProgress("Deliver materials", ship.TrackedActionTotal, GetRemainingConstructionDelivery(world, site)),
|
||||
: CreateShipRemainingActionProgress("Deliver materials", ship.TrackedActionTotal, ShipTaskExecutionService.GetRemainingConstructionDelivery(world, site)),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@@ -782,36 +812,5 @@ public sealed partial class SimulationEngine
|
||||
state.Transit.ArrivalDueAtUtc,
|
||||
state.Transit.Progress));
|
||||
|
||||
private static void EmitShipStateEvents(
|
||||
ShipRuntime ship,
|
||||
ShipState previousState,
|
||||
string previousBehavior,
|
||||
ControllerTaskKind previousTask,
|
||||
string controllerEvent,
|
||||
ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
var occurredAtUtc = DateTimeOffset.UtcNow;
|
||||
|
||||
if (previousState != ship.State)
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, "state-changed", $"{ship.Definition.Label} {previousState.ToContractValue()} -> {ship.State.ToContractValue()}", occurredAtUtc));
|
||||
}
|
||||
|
||||
if (previousBehavior != ship.DefaultBehavior.Kind)
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, "behavior-changed", $"{ship.Definition.Label} behavior {previousBehavior} -> {ship.DefaultBehavior.Kind}", occurredAtUtc));
|
||||
}
|
||||
|
||||
if (previousTask != ship.ControllerTask.Kind)
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, "task-changed", $"{ship.Definition.Label} task {previousTask.ToContractValue()} -> {ship.ControllerTask.Kind.ToContractValue()}", occurredAtUtc));
|
||||
}
|
||||
|
||||
if (controllerEvent != "none")
|
||||
{
|
||||
events.Add(new SimulationEventRecord("ship", ship.Id, controllerEvent, $"{ship.Definition.Label} {controllerEvent}", occurredAtUtc));
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector3Dto ToDto(Vector3 value) => new(value.X, value.Y, value.Z);
|
||||
}
|
||||
@@ -1,20 +1,31 @@
|
||||
using SpaceGame.Simulation.Api.Data;
|
||||
using SpaceGame.Simulation.Api.Contracts;
|
||||
using SpaceGame.Api.Data;
|
||||
using SpaceGame.Api.Contracts;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
using static SpaceGame.Api.Simulation.Systems.ShipControlService;
|
||||
using static SpaceGame.Api.Simulation.Support.SimulationRuntimeSupport;
|
||||
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
namespace SpaceGame.Api.Simulation.Systems;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
internal sealed class StationLifecycleService
|
||||
{
|
||||
private const int StrategicControlTargetSystems = 5;
|
||||
private const float WaterConsumptionPerWorkerPerSecond = 0.004f;
|
||||
private const float PopulationGrowthPerSecond = 0.012f;
|
||||
private const float PopulationAttritionPerSecond = 0.018f;
|
||||
private readonly StationSimulationService _stationSimulation;
|
||||
|
||||
private void UpdateStations(SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
internal StationLifecycleService(StationSimulationService stationSimulation)
|
||||
{
|
||||
_stationSimulation = stationSimulation;
|
||||
}
|
||||
|
||||
internal void UpdateStations(SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
var factionPopulation = new Dictionary<string, float>(StringComparer.Ordinal);
|
||||
foreach (var station in world.Stations)
|
||||
{
|
||||
UpdateStationPopulation(station, deltaSeconds, events);
|
||||
ReviewStationMarketOrders(world, station);
|
||||
RunStationProduction(world, station, deltaSeconds, events);
|
||||
_stationSimulation.ReviewStationMarketOrders(world, station);
|
||||
_stationSimulation.RunStationProduction(world, station, deltaSeconds, events);
|
||||
factionPopulation[station.FactionId] = GetInventoryAmount(factionPopulation, station.FactionId) + station.Population;
|
||||
}
|
||||
|
||||
@@ -54,7 +65,7 @@ public sealed partial class SimulationEngine
|
||||
station.WorkforceEffectiveRatio = ComputeWorkforceRatio(station.Population, station.WorkforceRequired);
|
||||
}
|
||||
|
||||
private float CompleteShipRecipe(SimulationWorld world, StationRuntime station, RecipeDefinition recipe, ICollection<SimulationEventRecord> events)
|
||||
internal static float CompleteShipRecipe(SimulationWorld world, StationRuntime station, RecipeDefinition recipe, ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
if (recipe.ShipOutputId is null || !world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var definition))
|
||||
{
|
||||
@@ -1,11 +1,16 @@
|
||||
using SpaceGame.Simulation.Api.Data;
|
||||
using SpaceGame.Simulation.Api.Contracts;
|
||||
using SpaceGame.Api.Data;
|
||||
using SpaceGame.Api.Contracts;
|
||||
using SpaceGame.Api.Simulation.Model;
|
||||
using static SpaceGame.Api.Simulation.Systems.CommanderPlanningService;
|
||||
using static SpaceGame.Api.Simulation.Support.SimulationRuntimeSupport;
|
||||
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
namespace SpaceGame.Api.Simulation.Systems;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
internal sealed class StationSimulationService
|
||||
{
|
||||
private void ReviewStationMarketOrders(SimulationWorld world, StationRuntime station)
|
||||
internal const int StrategicControlTargetSystems = 5;
|
||||
|
||||
internal void ReviewStationMarketOrders(SimulationWorld world, StationRuntime station)
|
||||
{
|
||||
if (station.CommanderId is null)
|
||||
{
|
||||
@@ -34,7 +39,7 @@ public sealed partial class SimulationEngine
|
||||
ReconcileStationMarketOrders(world, station, desiredOrders);
|
||||
}
|
||||
|
||||
private void RunStationProduction(SimulationWorld world, StationRuntime station, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
internal void RunStationProduction(SimulationWorld world, StationRuntime station, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
var faction = world.Factions.FirstOrDefault(candidate => candidate.Id == station.FactionId);
|
||||
foreach (var laneKey in GetStationProductionLanes(world, station))
|
||||
@@ -60,7 +65,7 @@ public sealed partial class SimulationEngine
|
||||
|
||||
if (recipe.ShipOutputId is not null)
|
||||
{
|
||||
produced += CompleteShipRecipe(world, station, recipe, events);
|
||||
produced += StationLifecycleService.CompleteShipRecipe(world, station, recipe, events);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -83,7 +88,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetStationProductionLanes(SimulationWorld world, StationRuntime station)
|
||||
internal static IEnumerable<string> GetStationProductionLanes(SimulationWorld world, StationRuntime station)
|
||||
{
|
||||
foreach (var moduleId in station.InstalledModules.Distinct(StringComparer.Ordinal))
|
||||
{
|
||||
@@ -101,10 +106,10 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private static float GetStationProductionTimer(StationRuntime station, string laneKey) =>
|
||||
internal static float GetStationProductionTimer(StationRuntime station, string laneKey) =>
|
||||
station.ProductionLaneTimers.TryGetValue(laneKey, out var timer) ? timer : 0f;
|
||||
|
||||
private static RecipeDefinition? SelectProductionRecipe(SimulationWorld world, StationRuntime station, string laneKey) =>
|
||||
internal static RecipeDefinition? SelectProductionRecipe(SimulationWorld world, StationRuntime station, string laneKey) =>
|
||||
world.Recipes.Values
|
||||
.Where(recipe => RecipeAppliesToStation(station, recipe) && string.Equals(GetStationProductionLaneKey(world, recipe), laneKey, StringComparison.Ordinal))
|
||||
.OrderByDescending(recipe => GetStationRecipePriority(world, station, recipe))
|
||||
@@ -315,7 +320,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
}
|
||||
|
||||
private static float GetFactionExpansionPressure(SimulationWorld world, string factionId)
|
||||
internal static float GetFactionExpansionPressure(SimulationWorld world, string factionId)
|
||||
{
|
||||
var targetSystems = Math.Max(1, Math.Min(StrategicControlTargetSystems, world.Systems.Count));
|
||||
var controlledSystems = GetFactionControlledSystemsCount(world, factionId);
|
||||
@@ -323,7 +328,7 @@ public sealed partial class SimulationEngine
|
||||
return Math.Clamp(deficit / (float)targetSystems, 0f, 1f);
|
||||
}
|
||||
|
||||
private static int GetFactionControlledSystemsCount(SimulationWorld world, string factionId)
|
||||
internal static int GetFactionControlledSystemsCount(SimulationWorld world, string factionId)
|
||||
{
|
||||
return world.Systems.Count(system => FactionControlsSystem(world, factionId, system.Definition.Id));
|
||||
}
|
||||
Reference in New Issue
Block a user