feat: rework modules, items and fuel
This commit is contained in:
@@ -17,10 +17,6 @@ public sealed record StationSnapshot(
|
|||||||
int DockedShips,
|
int DockedShips,
|
||||||
IReadOnlyList<string> DockedShipIds,
|
IReadOnlyList<string> DockedShipIds,
|
||||||
int DockingPads,
|
int DockingPads,
|
||||||
float FuelStored,
|
|
||||||
float FuelCapacity,
|
|
||||||
float EnergyStored,
|
|
||||||
float EnergyCapacity,
|
|
||||||
IReadOnlyList<StationActionProgressSnapshot> CurrentProcesses,
|
IReadOnlyList<StationActionProgressSnapshot> CurrentProcesses,
|
||||||
IReadOnlyList<InventoryEntry> Inventory,
|
IReadOnlyList<InventoryEntry> Inventory,
|
||||||
string FactionId,
|
string FactionId,
|
||||||
@@ -30,6 +26,7 @@ public sealed record StationSnapshot(
|
|||||||
float PopulationCapacity,
|
float PopulationCapacity,
|
||||||
float WorkforceRequired,
|
float WorkforceRequired,
|
||||||
float WorkforceEffectiveRatio,
|
float WorkforceEffectiveRatio,
|
||||||
|
IReadOnlyList<StationStorageUsageSnapshot> StorageUsage,
|
||||||
IReadOnlyList<string> InstalledModules,
|
IReadOnlyList<string> InstalledModules,
|
||||||
IReadOnlyList<string> MarketOrderIds);
|
IReadOnlyList<string> MarketOrderIds);
|
||||||
|
|
||||||
@@ -46,10 +43,6 @@ public sealed record StationDelta(
|
|||||||
int DockedShips,
|
int DockedShips,
|
||||||
IReadOnlyList<string> DockedShipIds,
|
IReadOnlyList<string> DockedShipIds,
|
||||||
int DockingPads,
|
int DockingPads,
|
||||||
float FuelStored,
|
|
||||||
float FuelCapacity,
|
|
||||||
float EnergyStored,
|
|
||||||
float EnergyCapacity,
|
|
||||||
IReadOnlyList<StationActionProgressSnapshot> CurrentProcesses,
|
IReadOnlyList<StationActionProgressSnapshot> CurrentProcesses,
|
||||||
IReadOnlyList<InventoryEntry> Inventory,
|
IReadOnlyList<InventoryEntry> Inventory,
|
||||||
string FactionId,
|
string FactionId,
|
||||||
@@ -59,6 +52,7 @@ public sealed record StationDelta(
|
|||||||
float PopulationCapacity,
|
float PopulationCapacity,
|
||||||
float WorkforceRequired,
|
float WorkforceRequired,
|
||||||
float WorkforceEffectiveRatio,
|
float WorkforceEffectiveRatio,
|
||||||
|
IReadOnlyList<StationStorageUsageSnapshot> StorageUsage,
|
||||||
IReadOnlyList<string> InstalledModules,
|
IReadOnlyList<string> InstalledModules,
|
||||||
IReadOnlyList<string> MarketOrderIds);
|
IReadOnlyList<string> MarketOrderIds);
|
||||||
|
|
||||||
@@ -67,6 +61,11 @@ public sealed record StationActionProgressSnapshot(
|
|||||||
string Label,
|
string Label,
|
||||||
float Progress);
|
float Progress);
|
||||||
|
|
||||||
|
public sealed record StationStorageUsageSnapshot(
|
||||||
|
string StorageClass,
|
||||||
|
float Used,
|
||||||
|
float Capacity);
|
||||||
|
|
||||||
public sealed record ClaimSnapshot(
|
public sealed record ClaimSnapshot(
|
||||||
string Id,
|
string Id,
|
||||||
string FactionId,
|
string FactionId,
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ public sealed record ShipSnapshot(
|
|||||||
float CargoCapacity,
|
float CargoCapacity,
|
||||||
string? CargoItemId,
|
string? CargoItemId,
|
||||||
float WorkerPopulation,
|
float WorkerPopulation,
|
||||||
float EnergyStored,
|
|
||||||
float TravelSpeed,
|
float TravelSpeed,
|
||||||
string TravelSpeedUnit,
|
string TravelSpeedUnit,
|
||||||
IReadOnlyList<InventoryEntry> Inventory,
|
IReadOnlyList<InventoryEntry> Inventory,
|
||||||
@@ -52,7 +51,6 @@ public sealed record ShipDelta(
|
|||||||
float CargoCapacity,
|
float CargoCapacity,
|
||||||
string? CargoItemId,
|
string? CargoItemId,
|
||||||
float WorkerPopulation,
|
float WorkerPopulation,
|
||||||
float EnergyStored,
|
|
||||||
float TravelSpeed,
|
float TravelSpeed,
|
||||||
string TravelSpeedUnit,
|
string TravelSpeedUnit,
|
||||||
IReadOnlyList<InventoryEntry> Inventory,
|
IReadOnlyList<InventoryEntry> Inventory,
|
||||||
|
|||||||
@@ -1,5 +1,18 @@
|
|||||||
namespace SpaceGame.Simulation.Api.Data;
|
namespace SpaceGame.Simulation.Api.Data;
|
||||||
|
|
||||||
|
public sealed class ConstructionDefinition
|
||||||
|
{
|
||||||
|
public string? RecipeId { get; set; }
|
||||||
|
public string FacilityCategory { get; set; } = "station";
|
||||||
|
public List<string> RequiredModules { get; set; } = [];
|
||||||
|
public List<RecipeInputDefinition> Requirements { get; set; } = [];
|
||||||
|
public float CycleTime { get; set; }
|
||||||
|
public float BatchSize { get; set; } = 1f;
|
||||||
|
public float ProductsPerHour { get; set; }
|
||||||
|
public float MaxEfficiency { get; set; } = 1f;
|
||||||
|
public int Priority { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class BalanceDefinition
|
public sealed class BalanceDefinition
|
||||||
{
|
{
|
||||||
public float YPlane { get; set; }
|
public float YPlane { get; set; }
|
||||||
@@ -10,16 +23,6 @@ public sealed class BalanceDefinition
|
|||||||
public float DockingDuration { get; set; }
|
public float DockingDuration { get; set; }
|
||||||
public float UndockingDuration { get; set; }
|
public float UndockingDuration { get; set; }
|
||||||
public float UndockDistance { get; set; }
|
public float UndockDistance { get; set; }
|
||||||
public EnergyBalanceDefinition Energy { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class EnergyBalanceDefinition
|
|
||||||
{
|
|
||||||
public float IdleDrain { get; set; }
|
|
||||||
public float MoveDrain { get; set; }
|
|
||||||
public float WarpDrain { get; set; }
|
|
||||||
public float ShipRechargeRate { get; set; }
|
|
||||||
public float StationSolarCharge { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class SolarSystemDefinition
|
public sealed class SolarSystemDefinition
|
||||||
@@ -62,9 +65,18 @@ public sealed class ResourceNodeDefinition
|
|||||||
public sealed class ItemDefinition
|
public sealed class ItemDefinition
|
||||||
{
|
{
|
||||||
public required string Id { get; set; }
|
public required string Id { get; set; }
|
||||||
public required string Label { get; set; }
|
public required string Name { get; set; }
|
||||||
public required string Storage { get; set; }
|
public string Description { get; set; } = string.Empty;
|
||||||
public string Summary { get; set; } = string.Empty;
|
public string Type { get; set; } = "material";
|
||||||
|
public required string CargoKind { get; set; }
|
||||||
|
public float Volume { get; set; } = 1f;
|
||||||
|
public ConstructionDefinition? Construction { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class RecipeOutputDefinition
|
||||||
|
{
|
||||||
|
public required string ItemId { get; set; }
|
||||||
|
public float Amount { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RecipeInputDefinition
|
public sealed class RecipeInputDefinition
|
||||||
@@ -73,6 +85,25 @@ public sealed class RecipeInputDefinition
|
|||||||
public float Amount { get; set; }
|
public float Amount { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class ModuleConstructionDefinition
|
||||||
|
{
|
||||||
|
public required List<RecipeInputDefinition> Requirements { get; set; }
|
||||||
|
public float ProductionTime { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ModuleDefinition
|
||||||
|
{
|
||||||
|
public required string Id { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
public required string Type { get; set; }
|
||||||
|
public string? Product { get; set; }
|
||||||
|
public float Radius { get; set; } = 12f;
|
||||||
|
public float Hull { get; set; } = 100f;
|
||||||
|
public float WorkforceNeeded { get; set; }
|
||||||
|
public ModuleConstructionDefinition? Construction { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class ModuleRecipeDefinition
|
public sealed class ModuleRecipeDefinition
|
||||||
{
|
{
|
||||||
public required string ModuleId { get; set; }
|
public required string ModuleId { get; set; }
|
||||||
@@ -80,12 +111,6 @@ public sealed class ModuleRecipeDefinition
|
|||||||
public required List<RecipeInputDefinition> Inputs { get; set; }
|
public required List<RecipeInputDefinition> Inputs { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RecipeOutputDefinition
|
|
||||||
{
|
|
||||||
public required string ItemId { get; set; }
|
|
||||||
public float Amount { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class RecipeDefinition
|
public sealed class RecipeDefinition
|
||||||
{
|
{
|
||||||
public required string Id { get; set; }
|
public required string Id { get; set; }
|
||||||
@@ -136,18 +161,7 @@ public sealed class ShipDefinition
|
|||||||
public float Size { get; set; }
|
public float Size { get; set; }
|
||||||
public float MaxHealth { get; set; }
|
public float MaxHealth { get; set; }
|
||||||
public List<string> Modules { get; set; } = [];
|
public List<string> Modules { get; set; } = [];
|
||||||
}
|
public ConstructionDefinition? Construction { get; set; }
|
||||||
|
|
||||||
public sealed class ConstructibleDefinition
|
|
||||||
{
|
|
||||||
public required string Id { get; set; }
|
|
||||||
public required string Label { get; set; }
|
|
||||||
public required string Category { get; set; }
|
|
||||||
public required string Color { get; set; }
|
|
||||||
public float Radius { get; set; }
|
|
||||||
public int DockingCapacity { get; set; }
|
|
||||||
public Dictionary<string, float> Storage { get; set; } = new(StringComparer.Ordinal);
|
|
||||||
public List<string> Modules { get; set; } = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ScenarioDefinition
|
public sealed class ScenarioDefinition
|
||||||
@@ -160,8 +174,10 @@ public sealed class ScenarioDefinition
|
|||||||
|
|
||||||
public sealed class InitialStationDefinition
|
public sealed class InitialStationDefinition
|
||||||
{
|
{
|
||||||
public required string ConstructibleId { get; set; }
|
|
||||||
public required string SystemId { get; set; }
|
public required string SystemId { get; set; }
|
||||||
|
public string Label { get; set; } = "Orbital Station";
|
||||||
|
public string Color { get; set; } = "#8df0d2";
|
||||||
|
public List<string> StartingModules { get; set; } = [];
|
||||||
public string? FactionId { get; set; }
|
public string? FactionId { get; set; }
|
||||||
public int? PlanetIndex { get; set; }
|
public int? PlanetIndex { get; set; }
|
||||||
public int? LagrangeSide { get; set; }
|
public int? LagrangeSide { get; set; }
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ internal sealed class ShipBehaviorStateMachine
|
|||||||
idleState,
|
idleState,
|
||||||
new PatrolShipBehaviorState(),
|
new PatrolShipBehaviorState(),
|
||||||
new ResourceHarvestShipBehaviorState("auto-mine", "ore", "mining-turret"),
|
new ResourceHarvestShipBehaviorState("auto-mine", "ore", "mining-turret"),
|
||||||
new ResourceHarvestShipBehaviorState("auto-harvest-gas", "gas", "gas-extractor"),
|
|
||||||
new EnergySupplyShipBehaviorState(),
|
|
||||||
new ConstructStationShipBehaviorState(),
|
new ConstructStationShipBehaviorState(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -90,13 +90,7 @@ internal sealed class ResourceHarvestShipBehaviorState : IShipBehaviorState
|
|||||||
ship.DefaultBehavior.Phase = "dock";
|
ship.DefaultBehavior.Phase = "dock";
|
||||||
break;
|
break;
|
||||||
case ("dock", "docked"):
|
case ("dock", "docked"):
|
||||||
ship.DefaultBehavior.Phase = SimulationEngine.GetShipCargoAmount(ship) > 0.01f ? "unload" : "refuel";
|
ship.DefaultBehavior.Phase = "unload";
|
||||||
break;
|
|
||||||
case ("unload", "unloaded"):
|
|
||||||
ship.DefaultBehavior.Phase = "refuel";
|
|
||||||
break;
|
|
||||||
case ("refuel", "refueled"):
|
|
||||||
ship.DefaultBehavior.Phase = "undock";
|
|
||||||
break;
|
break;
|
||||||
case ("undock", "undocked"):
|
case ("undock", "undocked"):
|
||||||
ship.DefaultBehavior.Phase = "travel-to-node";
|
ship.DefaultBehavior.Phase = "travel-to-node";
|
||||||
@@ -118,9 +112,6 @@ internal sealed class ConstructStationShipBehaviorState : IShipBehaviorState
|
|||||||
switch (ship.DefaultBehavior.Phase, controllerEvent)
|
switch (ship.DefaultBehavior.Phase, controllerEvent)
|
||||||
{
|
{
|
||||||
case ("travel-to-station", "arrived"):
|
case ("travel-to-station", "arrived"):
|
||||||
ship.DefaultBehavior.Phase = SimulationEngine.NeedsRefuel(ship, world) ? "refuel" : "deliver-to-site";
|
|
||||||
break;
|
|
||||||
case ("refuel", "refueled"):
|
|
||||||
ship.DefaultBehavior.Phase = "deliver-to-site";
|
ship.DefaultBehavior.Phase = "deliver-to-site";
|
||||||
break;
|
break;
|
||||||
case ("deliver-to-site", "construction-delivered"):
|
case ("deliver-to-site", "construction-delivered"):
|
||||||
@@ -134,37 +125,3 @@ internal sealed class ConstructStationShipBehaviorState : IShipBehaviorState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class EnergySupplyShipBehaviorState : IShipBehaviorState
|
|
||||||
{
|
|
||||||
public string Kind => "auto-supply-energy";
|
|
||||||
|
|
||||||
public void Plan(SimulationEngine engine, ShipRuntime ship, SimulationWorld world) =>
|
|
||||||
engine.PlanEnergySupply(ship, world);
|
|
||||||
|
|
||||||
public void ApplyEvent(SimulationEngine engine, ShipRuntime ship, SimulationWorld world, string controllerEvent)
|
|
||||||
{
|
|
||||||
switch (ship.DefaultBehavior.Phase, controllerEvent)
|
|
||||||
{
|
|
||||||
case ("travel-to-source", "arrived"):
|
|
||||||
case ("travel-to-destination", "arrived"):
|
|
||||||
ship.DefaultBehavior.Phase = "dock";
|
|
||||||
break;
|
|
||||||
case ("dock", "docked"):
|
|
||||||
ship.DefaultBehavior.Phase = SimulationEngine.GetShipCargoAmount(ship) > 0.01f ? "unload" : "load";
|
|
||||||
break;
|
|
||||||
case ("load", "loaded"):
|
|
||||||
ship.DefaultBehavior.Phase = SimulationEngine.NeedsRefuel(ship, world) ? "refuel" : "undock";
|
|
||||||
break;
|
|
||||||
case ("unload", "unloaded"):
|
|
||||||
ship.DefaultBehavior.Phase = SimulationEngine.NeedsRefuel(ship, world) ? "refuel" : "undock";
|
|
||||||
break;
|
|
||||||
case ("refuel", "refueled"):
|
|
||||||
ship.DefaultBehavior.Phase = "undock";
|
|
||||||
break;
|
|
||||||
case ("undock", "undocked"):
|
|
||||||
ship.DefaultBehavior.Phase = SimulationEngine.GetShipCargoAmount(ship) > 0.01f ? "travel-to-destination" : "travel-to-source";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ public sealed class ShipRuntime
|
|||||||
public float ActionTimer { get; set; }
|
public float ActionTimer { get; set; }
|
||||||
public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
|
public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
|
||||||
public float WorkerPopulation { get; set; }
|
public float WorkerPopulation { get; set; }
|
||||||
public float EnergyStored { get; set; }
|
public string DockedStationId { get; set; }
|
||||||
public string? DockedStationId { get; set; }
|
|
||||||
public int? AssignedDockingPadIndex { get; set; }
|
public int? AssignedDockingPadIndex { get; set; }
|
||||||
public string? CommanderId { get; set; }
|
public string? CommanderId { get; set; }
|
||||||
public string? PolicySetId { get; set; }
|
public string? PolicySetId { get; set; }
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ public enum ShipState
|
|||||||
{
|
{
|
||||||
Idle,
|
Idle,
|
||||||
Arriving,
|
Arriving,
|
||||||
CapacitorStarved,
|
|
||||||
LocalFlight,
|
LocalFlight,
|
||||||
SpoolingWarp,
|
SpoolingWarp,
|
||||||
Warping,
|
Warping,
|
||||||
@@ -45,7 +44,6 @@ public enum ShipState
|
|||||||
Transferring,
|
Transferring,
|
||||||
Loading,
|
Loading,
|
||||||
Unloading,
|
Unloading,
|
||||||
Refueling,
|
|
||||||
WaitingMaterials,
|
WaitingMaterials,
|
||||||
ConstructionBlocked,
|
ConstructionBlocked,
|
||||||
Constructing,
|
Constructing,
|
||||||
@@ -62,7 +60,6 @@ public enum ControllerTaskKind
|
|||||||
Dock,
|
Dock,
|
||||||
Load,
|
Load,
|
||||||
Unload,
|
Unload,
|
||||||
Refuel,
|
|
||||||
DeliverConstruction,
|
DeliverConstruction,
|
||||||
BuildConstructionSite,
|
BuildConstructionSite,
|
||||||
LoadWorkers,
|
LoadWorkers,
|
||||||
@@ -197,7 +194,6 @@ public static class SimulationEnumMappings
|
|||||||
{
|
{
|
||||||
ShipState.Idle => "idle",
|
ShipState.Idle => "idle",
|
||||||
ShipState.Arriving => "arriving",
|
ShipState.Arriving => "arriving",
|
||||||
ShipState.CapacitorStarved => "capacitor-starved",
|
|
||||||
ShipState.LocalFlight => "local-flight",
|
ShipState.LocalFlight => "local-flight",
|
||||||
ShipState.SpoolingWarp => "spooling-warp",
|
ShipState.SpoolingWarp => "spooling-warp",
|
||||||
ShipState.Warping => "warping",
|
ShipState.Warping => "warping",
|
||||||
@@ -214,7 +210,6 @@ public static class SimulationEnumMappings
|
|||||||
ShipState.Transferring => "transferring",
|
ShipState.Transferring => "transferring",
|
||||||
ShipState.Loading => "loading",
|
ShipState.Loading => "loading",
|
||||||
ShipState.Unloading => "unloading",
|
ShipState.Unloading => "unloading",
|
||||||
ShipState.Refueling => "refueling",
|
|
||||||
ShipState.WaitingMaterials => "waiting-materials",
|
ShipState.WaitingMaterials => "waiting-materials",
|
||||||
ShipState.ConstructionBlocked => "construction-blocked",
|
ShipState.ConstructionBlocked => "construction-blocked",
|
||||||
ShipState.Constructing => "constructing",
|
ShipState.Constructing => "constructing",
|
||||||
@@ -232,7 +227,6 @@ public static class SimulationEnumMappings
|
|||||||
ControllerTaskKind.Dock => "dock",
|
ControllerTaskKind.Dock => "dock",
|
||||||
ControllerTaskKind.Load => "load",
|
ControllerTaskKind.Load => "load",
|
||||||
ControllerTaskKind.Unload => "unload",
|
ControllerTaskKind.Unload => "unload",
|
||||||
ControllerTaskKind.Refuel => "refuel",
|
|
||||||
ControllerTaskKind.DeliverConstruction => "deliver-construction",
|
ControllerTaskKind.DeliverConstruction => "deliver-construction",
|
||||||
ControllerTaskKind.BuildConstructionSite => "build-construction-site",
|
ControllerTaskKind.BuildConstructionSite => "build-construction-site",
|
||||||
ControllerTaskKind.LoadWorkers => "load-workers",
|
ControllerTaskKind.LoadWorkers => "load-workers",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public sealed class SimulationWorld
|
|||||||
public required List<PolicySetRuntime> Policies { get; init; }
|
public required List<PolicySetRuntime> Policies { get; init; }
|
||||||
public required Dictionary<string, ShipDefinition> ShipDefinitions { get; init; }
|
public required Dictionary<string, ShipDefinition> ShipDefinitions { get; init; }
|
||||||
public required Dictionary<string, ItemDefinition> ItemDefinitions { get; init; }
|
public required Dictionary<string, ItemDefinition> ItemDefinitions { get; init; }
|
||||||
|
public required Dictionary<string, ModuleDefinition> ModuleDefinitions { get; init; }
|
||||||
public required Dictionary<string, ModuleRecipeDefinition> ModuleRecipes { get; init; }
|
public required Dictionary<string, ModuleRecipeDefinition> ModuleRecipes { get; init; }
|
||||||
public required Dictionary<string, RecipeDefinition> Recipes { get; init; }
|
public required Dictionary<string, RecipeDefinition> Recipes { get; init; }
|
||||||
public int TickIntervalMs { get; init; } = 200;
|
public int TickIntervalMs { get; init; } = 200;
|
||||||
|
|||||||
@@ -1,25 +1,26 @@
|
|||||||
using SpaceGame.Simulation.Api.Data;
|
|
||||||
|
|
||||||
namespace SpaceGame.Simulation.Api.Simulation;
|
namespace SpaceGame.Simulation.Api.Simulation;
|
||||||
|
|
||||||
public sealed class StationRuntime
|
public sealed class StationRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string SystemId { get; init; }
|
public required string SystemId { get; init; }
|
||||||
public required ConstructibleDefinition Definition { get; init; }
|
public required string Label { get; set; }
|
||||||
|
public string Category { get; set; } = "station";
|
||||||
|
public string Color { get; set; } = "#8df0d2";
|
||||||
public required Vector3 Position { get; set; }
|
public required Vector3 Position { get; set; }
|
||||||
|
public float Radius { get; set; } = 24f;
|
||||||
public required string FactionId { get; init; }
|
public required string FactionId { get; init; }
|
||||||
public string? NodeId { get; set; }
|
public string? NodeId { get; set; }
|
||||||
public string? BubbleId { get; set; }
|
public string? BubbleId { get; set; }
|
||||||
public string? AnchorNodeId { get; set; }
|
public string? AnchorNodeId { get; set; }
|
||||||
public string? CommanderId { get; set; }
|
public string? CommanderId { get; set; }
|
||||||
public string? PolicySetId { get; set; }
|
public string? PolicySetId { get; set; }
|
||||||
public List<string> InstalledModules { get; } = [];
|
public List<StationModuleRuntime> Modules { get; } = [];
|
||||||
|
public IEnumerable<string> InstalledModules => Modules.Select((module) => module.ModuleId);
|
||||||
public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
|
public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
|
||||||
public Dictionary<string, float> ProductionLaneTimers { get; } = new(StringComparer.Ordinal);
|
public Dictionary<string, float> ProductionLaneTimers { get; } = new(StringComparer.Ordinal);
|
||||||
public Dictionary<int, string> DockingPadAssignments { get; } = new();
|
public Dictionary<int, string> DockingPadAssignments { get; } = new();
|
||||||
public HashSet<string> MarketOrderIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> MarketOrderIds { get; } = new(StringComparer.Ordinal);
|
||||||
public float EnergyStored { get; set; }
|
|
||||||
public float Population { get; set; }
|
public float Population { get; set; }
|
||||||
public float PopulationCapacity { get; set; }
|
public float PopulationCapacity { get; set; }
|
||||||
public float WorkforceRequired { get; set; }
|
public float WorkforceRequired { get; set; }
|
||||||
@@ -31,6 +32,14 @@ public sealed class StationRuntime
|
|||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class StationModuleRuntime
|
||||||
|
{
|
||||||
|
public required string Id { get; init; }
|
||||||
|
public required string ModuleId { get; init; }
|
||||||
|
public float Health { get; set; }
|
||||||
|
public float MaxHealth { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class ModuleConstructionRuntime
|
public sealed class ModuleConstructionRuntime
|
||||||
{
|
{
|
||||||
public required string ModuleId { get; init; }
|
public required string ModuleId { get; init; }
|
||||||
|
|||||||
@@ -254,7 +254,6 @@ public sealed partial class ScenarioLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
nodes.AddRange(BuildAsteroidBeltNodes(generatedIndex, planets));
|
nodes.AddRange(BuildAsteroidBeltNodes(generatedIndex, planets));
|
||||||
nodes.AddRange(BuildGasCloudNodes(generatedIndex, planets));
|
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,46 +343,6 @@ public sealed partial class ScenarioLoader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<ResourceNodeDefinition> BuildGasCloudNodes(int generatedIndex, IReadOnlyList<PlanetDefinition> planets)
|
|
||||||
{
|
|
||||||
var gasAnchor = planets
|
|
||||||
.Where((planet) => planet.PlanetType is "gas-giant" or "ice-giant")
|
|
||||||
.OrderByDescending((planet) => planet.OrbitRadius)
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (gasAnchor is null)
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var gasAnchorIndex = 0;
|
|
||||||
for (var index = 0; index < planets.Count; index += 1)
|
|
||||||
{
|
|
||||||
if (ReferenceEquals(planets[index], gasAnchor))
|
|
||||||
{
|
|
||||||
gasAnchorIndex = index;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodeCount = 2 + (generatedIndex % 3);
|
|
||||||
var gasAmount = 1000f;
|
|
||||||
for (var index = 0; index < nodeCount; index += 1)
|
|
||||||
{
|
|
||||||
yield return new ResourceNodeDefinition
|
|
||||||
{
|
|
||||||
SourceKind = "gas-cloud",
|
|
||||||
Angle = gasAnchor.OrbitPhaseAtEpoch * (MathF.PI / 180f) + (((MathF.PI * 2f) / nodeCount) * index) + Jitter(generatedIndex, 240 + index, 0.18f),
|
|
||||||
RadiusOffset = 170000f + Jitter(generatedIndex, 260 + index, 44000f),
|
|
||||||
InclinationDegrees = Jitter(generatedIndex, 320 + index, 10f),
|
|
||||||
AnchorPlanetIndex = gasAnchorIndex,
|
|
||||||
OreAmount = gasAmount,
|
|
||||||
ItemId = "gas",
|
|
||||||
ShardCount = 10 + index,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int ResolveAsteroidAnchorPlanetIndex(IReadOnlyList<PlanetDefinition> planets)
|
private static int ResolveAsteroidAnchorPlanetIndex(IReadOnlyList<PlanetDefinition> planets)
|
||||||
{
|
{
|
||||||
if (planets.Count == 0)
|
if (planets.Count == 0)
|
||||||
@@ -566,9 +525,6 @@ public sealed partial class ScenarioLoader
|
|||||||
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 1.8f, RadiusOffset = 148000f, InclinationDegrees = -6f, AnchorPlanetIndex = 3, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 1.8f, RadiusOffset = 148000f, InclinationDegrees = -6f, AnchorPlanetIndex = 3, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
||||||
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 3.5f, RadiusOffset = 138000f, InclinationDegrees = 8f, AnchorPlanetIndex = 4, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 3.5f, RadiusOffset = 138000f, InclinationDegrees = 8f, AnchorPlanetIndex = 4, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
||||||
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 5.1f, RadiusOffset = 164000f, InclinationDegrees = -5f, AnchorPlanetIndex = 4, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
new ResourceNodeDefinition { SourceKind = "asteroid-belt", Angle = 5.1f, RadiusOffset = 164000f, InclinationDegrees = -5f, AnchorPlanetIndex = 4, OreAmount = 1000f, ItemId = "ore", ShardCount = 9 },
|
||||||
new ResourceNodeDefinition { SourceKind = "gas-cloud", Angle = 0.9f, RadiusOffset = 210000f, InclinationDegrees = 3f, AnchorPlanetIndex = 4, OreAmount = 1000f, ItemId = "gas", ShardCount = 12 },
|
|
||||||
new ResourceNodeDefinition { SourceKind = "gas-cloud", Angle = 2.7f, RadiusOffset = 228000f, InclinationDegrees = -4f, AnchorPlanetIndex = 5, OreAmount = 1000f, ItemId = "gas", ShardCount = 12 },
|
|
||||||
new ResourceNodeDefinition { SourceKind = "gas-cloud", Angle = 4.8f, RadiusOffset = 186000f, InclinationDegrees = 6f, AnchorPlanetIndex = 6, OreAmount = 1000f, ItemId = "gas", ShardCount = 10 },
|
|
||||||
],
|
],
|
||||||
Planets =
|
Planets =
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public sealed partial class ScenarioLoader
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var refineries = ownedStations
|
var refineries = ownedStations
|
||||||
.Where((station) => HasModules(station.Definition, "refinery-stack", "power-core", "liquid-tank", "gas-tank"))
|
.Where((station) => HasInstalledModules(station, "refinery-stack", "power-core", "liquid-tank"))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (refineries.Count > 0)
|
if (refineries.Count > 0)
|
||||||
@@ -86,7 +86,7 @@ public sealed partial class ScenarioLoader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var shipyard in ownedStations.Where((station) => station.Definition.Category == "shipyard"))
|
foreach (var shipyard in ownedStations.Where((station) => HasInstalledModules(station, "ship-factory")))
|
||||||
{
|
{
|
||||||
shipyard.Inventory["refined-metals"] = MathF.Max(GetInventoryAmount(shipyard.Inventory, "refined-metals"), MinimumShipyardStock);
|
shipyard.Inventory["refined-metals"] = MathF.Max(GetInventoryAmount(shipyard.Inventory, "refined-metals"), MinimumShipyardStock);
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ public sealed partial class ScenarioLoader
|
|||||||
NodeId = anchorNode.Id,
|
NodeId = anchorNode.Id,
|
||||||
BubbleId = anchorNode.BubbleId,
|
BubbleId = anchorNode.BubbleId,
|
||||||
TargetKind = "station-module",
|
TargetKind = "station-module",
|
||||||
TargetDefinitionId = station.Definition.Id,
|
TargetDefinitionId = "station",
|
||||||
BlueprintId = moduleId,
|
BlueprintId = moduleId,
|
||||||
ClaimId = claim.Id,
|
ClaimId = claim.Id,
|
||||||
StationId = station.Id,
|
StationId = station.Id,
|
||||||
@@ -213,8 +213,6 @@ public sealed partial class ScenarioLoader
|
|||||||
{
|
{
|
||||||
foreach (var (moduleId, targetCount) in new (string ModuleId, int TargetCount)[]
|
foreach (var (moduleId, targetCount) in new (string ModuleId, int TargetCount)[]
|
||||||
{
|
{
|
||||||
("gas-tank", 1),
|
|
||||||
("fuel-processor", 1),
|
|
||||||
("refinery-stack", 1),
|
("refinery-stack", 1),
|
||||||
("container-bay", 1),
|
("container-bay", 1),
|
||||||
("fabricator-array", 2),
|
("fabricator-array", 2),
|
||||||
@@ -238,7 +236,7 @@ public sealed partial class ScenarioLoader
|
|||||||
{
|
{
|
||||||
var habitatModules = CountModules(station.InstalledModules, "habitat-ring");
|
var habitatModules = CountModules(station.InstalledModules, "habitat-ring");
|
||||||
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
||||||
station.WorkforceRequired = MathF.Max(12f, station.InstalledModules.Count * 14f);
|
station.WorkforceRequired = MathF.Max(12f, station.Modules.Count * 14f);
|
||||||
station.Population = habitatModules > 0
|
station.Population = habitatModules > 0
|
||||||
? MathF.Min(station.PopulationCapacity * 0.65f, station.WorkforceRequired * 1.05f)
|
? MathF.Min(station.PopulationCapacity * 0.65f, station.WorkforceRequired * 1.05f)
|
||||||
: MathF.Min(28f, station.PopulationCapacity);
|
: MathF.Min(28f, station.PopulationCapacity);
|
||||||
@@ -391,21 +389,6 @@ public sealed partial class ScenarioLoader
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasModules(definition, "reactor-core", "capacitor-bank", "gas-extractor") && refinery is not null)
|
|
||||||
{
|
|
||||||
return CreateResourceHarvestBehavior("auto-harvest-gas", scenario.MiningDefaults.NodeSystemId, refinery.Id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(definition.Role, "transport", StringComparison.Ordinal) && refinery is not null)
|
|
||||||
{
|
|
||||||
return new DefaultBehaviorRuntime
|
|
||||||
{
|
|
||||||
Kind = "auto-supply-energy",
|
|
||||||
StationId = refinery.Id,
|
|
||||||
Phase = "travel-to-source",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (HasModules(definition, "reactor-core", "capacitor-bank", "mining-turret") && refinery is not null)
|
if (HasModules(definition, "reactor-core", "capacitor-bank", "mining-turret") && refinery is not null)
|
||||||
{
|
{
|
||||||
return CreateResourceHarvestBehavior("auto-mine", scenario.MiningDefaults.NodeSystemId, refinery.Id);
|
return CreateResourceHarvestBehavior("auto-mine", scenario.MiningDefaults.NodeSystemId, refinery.Id);
|
||||||
|
|||||||
@@ -97,15 +97,15 @@ public sealed partial class ScenarioLoader
|
|||||||
var scenario = NormalizeScenarioToAvailableSystems(
|
var scenario = NormalizeScenarioToAvailableSystems(
|
||||||
Read<ScenarioDefinition>("scenario.json"),
|
Read<ScenarioDefinition>("scenario.json"),
|
||||||
systems.Select((system) => system.Id).ToList());
|
systems.Select((system) => system.Id).ToList());
|
||||||
|
var modules = Read<List<ModuleDefinition>>("modules.json");
|
||||||
var ships = Read<List<ShipDefinition>>("ships.json");
|
var ships = Read<List<ShipDefinition>>("ships.json");
|
||||||
var constructibles = Read<List<ConstructibleDefinition>>("constructibles.json");
|
|
||||||
var items = Read<List<ItemDefinition>>("items.json");
|
var items = Read<List<ItemDefinition>>("items.json");
|
||||||
var recipes = Read<List<RecipeDefinition>>("recipes.json");
|
|
||||||
var moduleRecipes = Read<List<ModuleRecipeDefinition>>("module-recipes.json");
|
|
||||||
var balance = Read<BalanceDefinition>("balance.json");
|
var balance = Read<BalanceDefinition>("balance.json");
|
||||||
|
var recipes = BuildRecipes(items, ships);
|
||||||
|
var moduleRecipes = BuildModuleRecipes(modules);
|
||||||
|
|
||||||
|
var moduleDefinitions = modules.ToDictionary((definition) => definition.Id, StringComparer.Ordinal);
|
||||||
var shipDefinitions = ships.ToDictionary((definition) => definition.Id, StringComparer.Ordinal);
|
var shipDefinitions = ships.ToDictionary((definition) => definition.Id, StringComparer.Ordinal);
|
||||||
var constructibleDefinitions = constructibles.ToDictionary((definition) => definition.Id, StringComparer.Ordinal);
|
|
||||||
var itemDefinitions = items.ToDictionary((definition) => definition.Id, StringComparer.Ordinal);
|
var itemDefinitions = items.ToDictionary((definition) => definition.Id, StringComparer.Ordinal);
|
||||||
var recipeDefinitions = recipes.ToDictionary((definition) => definition.Id, StringComparer.Ordinal);
|
var recipeDefinitions = recipes.ToDictionary((definition) => definition.Id, StringComparer.Ordinal);
|
||||||
var moduleRecipeDefinitions = moduleRecipes.ToDictionary((definition) => definition.ModuleId, StringComparer.Ordinal);
|
var moduleRecipeDefinitions = moduleRecipes.ToDictionary((definition) => definition.ModuleId, StringComparer.Ordinal);
|
||||||
@@ -178,7 +178,7 @@ public sealed partial class ScenarioLoader
|
|||||||
var stationIdCounter = 0;
|
var stationIdCounter = 0;
|
||||||
foreach (var plan in scenario.InitialStations)
|
foreach (var plan in scenario.InitialStations)
|
||||||
{
|
{
|
||||||
if (!constructibleDefinitions.TryGetValue(plan.ConstructibleId, out var definition) || !systemsById.TryGetValue(plan.SystemId, out var system))
|
if (!systemsById.TryGetValue(plan.SystemId, out var system))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,8 @@ public sealed partial class ScenarioLoader
|
|||||||
{
|
{
|
||||||
Id = $"station-{++stationIdCounter}",
|
Id = $"station-{++stationIdCounter}",
|
||||||
SystemId = system.Definition.Id,
|
SystemId = system.Definition.Id,
|
||||||
Definition = definition,
|
Label = plan.Label,
|
||||||
|
Color = plan.Color,
|
||||||
Position = placement.Position,
|
Position = placement.Position,
|
||||||
FactionId = plan.FactionId ?? DefaultFactionId,
|
FactionId = plan.FactionId ?? DefaultFactionId,
|
||||||
};
|
};
|
||||||
@@ -214,21 +215,23 @@ public sealed partial class ScenarioLoader
|
|||||||
Id = stationBubbleId,
|
Id = stationBubbleId,
|
||||||
NodeId = stationNodeId,
|
NodeId = stationNodeId,
|
||||||
SystemId = station.SystemId,
|
SystemId = station.SystemId,
|
||||||
Radius = MathF.Max(160f, definition.Radius + 60f),
|
Radius = MathF.Max(160f, GetStationRadius(moduleDefinitions, station) + 60f),
|
||||||
});
|
});
|
||||||
localBubbles[^1].OccupantStationIds.Add(station.Id);
|
localBubbles[^1].OccupantStationIds.Add(station.Id);
|
||||||
placement.AnchorNode.OccupyingStructureId = station.Id;
|
placement.AnchorNode.OccupyingStructureId = station.Id;
|
||||||
|
|
||||||
foreach (var moduleId in definition.Modules)
|
var startingModules = plan.StartingModules.Count > 0
|
||||||
|
? plan.StartingModules
|
||||||
|
: ["dock-bay-small", "power-core", "bulk-bay", "liquid-tank"];
|
||||||
|
foreach (var moduleId in startingModules)
|
||||||
{
|
{
|
||||||
stations[^1].InstalledModules.Add(moduleId);
|
AddStationModule(stations[^1], moduleDefinitions, moduleId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var station in stations)
|
foreach (var station in stations)
|
||||||
{
|
{
|
||||||
InitializeStationPopulation(station);
|
InitializeStationPopulation(station);
|
||||||
station.Inventory["fuel"] = 240f;
|
|
||||||
station.Inventory["refined-metals"] = 120f;
|
station.Inventory["refined-metals"] = 120f;
|
||||||
if (station.Population > 0f)
|
if (station.Population > 0f)
|
||||||
{
|
{
|
||||||
@@ -277,19 +280,6 @@ public sealed partial class ScenarioLoader
|
|||||||
ControllerTask = new ControllerTaskRuntime { Kind = ControllerTaskKind.Idle, Threshold = balance.ArrivalThreshold, Status = WorkStatus.Pending },
|
ControllerTask = new ControllerTaskRuntime { Kind = ControllerTaskKind.Idle, Threshold = balance.ArrivalThreshold, Status = WorkStatus.Pending },
|
||||||
Health = definition.MaxHealth,
|
Health = definition.MaxHealth,
|
||||||
});
|
});
|
||||||
|
|
||||||
shipsRuntime[^1].Inventory["gas"] = definition.Id switch
|
|
||||||
{
|
|
||||||
_ => 0f,
|
|
||||||
};
|
|
||||||
shipsRuntime[^1].Inventory.Remove("gas");
|
|
||||||
shipsRuntime[^1].Inventory["fuel"] = definition.Id switch
|
|
||||||
{
|
|
||||||
"constructor" => 90f,
|
|
||||||
"miner" => 90f,
|
|
||||||
"gas-miner" => 90f,
|
|
||||||
_ => 120f,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,6 +310,7 @@ public sealed partial class ScenarioLoader
|
|||||||
Policies = policies,
|
Policies = policies,
|
||||||
ShipDefinitions = shipDefinitions,
|
ShipDefinitions = shipDefinitions,
|
||||||
ItemDefinitions = itemDefinitions,
|
ItemDefinitions = itemDefinitions,
|
||||||
|
ModuleDefinitions = moduleDefinitions,
|
||||||
ModuleRecipes = moduleRecipeDefinitions,
|
ModuleRecipes = moduleRecipeDefinitions,
|
||||||
Recipes = recipeDefinitions,
|
Recipes = recipeDefinitions,
|
||||||
OrbitalTimeSeconds = WorldSeed * 97d,
|
OrbitalTimeSeconds = WorldSeed * 97d,
|
||||||
@@ -356,8 +347,10 @@ public sealed partial class ScenarioLoader
|
|||||||
InitialStations = scenario.InitialStations
|
InitialStations = scenario.InitialStations
|
||||||
.Select((station) => new InitialStationDefinition
|
.Select((station) => new InitialStationDefinition
|
||||||
{
|
{
|
||||||
ConstructibleId = station.ConstructibleId,
|
|
||||||
SystemId = ResolveSystemId(station.SystemId),
|
SystemId = ResolveSystemId(station.SystemId),
|
||||||
|
Label = station.Label,
|
||||||
|
Color = station.Color,
|
||||||
|
StartingModules = station.StartingModules.ToList(),
|
||||||
FactionId = station.FactionId,
|
FactionId = station.FactionId,
|
||||||
PlanetIndex = station.PlanetIndex,
|
PlanetIndex = station.PlanetIndex,
|
||||||
LagrangeSide = station.LagrangeSide,
|
LagrangeSide = station.LagrangeSide,
|
||||||
@@ -404,15 +397,37 @@ public sealed partial class ScenarioLoader
|
|||||||
: raw;
|
: raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool HasModules(ConstructibleDefinition definition, params string[] modules) =>
|
|
||||||
modules.All((moduleId) => definition.Modules.Contains(moduleId, StringComparer.Ordinal));
|
|
||||||
|
|
||||||
private static bool HasInstalledModules(StationRuntime station, params string[] modules) =>
|
private static bool HasInstalledModules(StationRuntime station, params string[] modules) =>
|
||||||
modules.All((moduleId) => station.InstalledModules.Contains(moduleId, StringComparer.Ordinal));
|
modules.All((moduleId) => station.Modules.Any((candidate) => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal)));
|
||||||
|
|
||||||
private static bool HasModules(ShipDefinition definition, params string[] modules) =>
|
private static bool HasModules(ShipDefinition definition, params string[] modules) =>
|
||||||
modules.All((moduleId) => definition.Modules.Contains(moduleId, StringComparer.Ordinal));
|
modules.All((moduleId) => definition.Modules.Contains(moduleId, StringComparer.Ordinal));
|
||||||
|
|
||||||
|
private static void AddStationModule(StationRuntime station, IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions, string moduleId)
|
||||||
|
{
|
||||||
|
if (!moduleDefinitions.TryGetValue(moduleId, out var definition))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
station.Modules.Add(new StationModuleRuntime
|
||||||
|
{
|
||||||
|
Id = $"{station.Id}-module-{station.Modules.Count + 1}",
|
||||||
|
ModuleId = moduleId,
|
||||||
|
Health = definition.Hull,
|
||||||
|
MaxHealth = definition.Hull,
|
||||||
|
});
|
||||||
|
station.Radius = GetStationRadius(moduleDefinitions, station);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetStationRadius(IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions, StationRuntime station)
|
||||||
|
{
|
||||||
|
var totalArea = station.Modules
|
||||||
|
.Select((module) => moduleDefinitions.TryGetValue(module.ModuleId, out var definition) ? definition.Radius * definition.Radius : 0f)
|
||||||
|
.Sum();
|
||||||
|
return MathF.Max(24f, MathF.Sqrt(MathF.Max(totalArea, 1f)));
|
||||||
|
}
|
||||||
|
|
||||||
private static Vector3 Add(Vector3 left, Vector3 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
|
private static Vector3 Add(Vector3 left, Vector3 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
|
||||||
|
|
||||||
private static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
private static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
||||||
@@ -429,6 +444,89 @@ public sealed partial class ScenarioLoader
|
|||||||
return 0.1f + (0.9f * staffedRatio);
|
return 0.1f + (0.9f * staffedRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<ModuleRecipeDefinition> BuildModuleRecipes(IEnumerable<ModuleDefinition> modules) =>
|
||||||
|
modules
|
||||||
|
.Where((module) => module.Construction is not null)
|
||||||
|
.Select((module) => new ModuleRecipeDefinition
|
||||||
|
{
|
||||||
|
ModuleId = module.Id,
|
||||||
|
Duration = module.Construction!.ProductionTime,
|
||||||
|
Inputs = module.Construction.Requirements
|
||||||
|
.Select((input) => new RecipeInputDefinition
|
||||||
|
{
|
||||||
|
ItemId = input.ItemId,
|
||||||
|
Amount = input.Amount,
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
private static List<RecipeDefinition> BuildRecipes(IEnumerable<ItemDefinition> items, IEnumerable<ShipDefinition> ships)
|
||||||
|
{
|
||||||
|
var recipes = new List<RecipeDefinition>();
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (item.Construction is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
recipes.Add(new RecipeDefinition
|
||||||
|
{
|
||||||
|
Id = item.Construction.RecipeId ?? $"{item.Id}-production",
|
||||||
|
Label = item.Name,
|
||||||
|
FacilityCategory = item.Construction.FacilityCategory,
|
||||||
|
Duration = item.Construction.CycleTime,
|
||||||
|
Priority = item.Construction.Priority,
|
||||||
|
RequiredModules = item.Construction.RequiredModules.ToList(),
|
||||||
|
Inputs = item.Construction.Requirements
|
||||||
|
.Select((input) => new RecipeInputDefinition
|
||||||
|
{
|
||||||
|
ItemId = input.ItemId,
|
||||||
|
Amount = input.Amount,
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
Outputs =
|
||||||
|
[
|
||||||
|
new RecipeOutputDefinition
|
||||||
|
{
|
||||||
|
ItemId = item.Id,
|
||||||
|
Amount = item.Construction.BatchSize,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var ship in ships)
|
||||||
|
{
|
||||||
|
if (ship.Construction is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
recipes.Add(new RecipeDefinition
|
||||||
|
{
|
||||||
|
Id = ship.Construction.RecipeId ?? $"{ship.Id}-construction",
|
||||||
|
Label = $"{ship.Label} Construction",
|
||||||
|
FacilityCategory = ship.Construction.FacilityCategory,
|
||||||
|
Duration = ship.Construction.CycleTime,
|
||||||
|
Priority = ship.Construction.Priority,
|
||||||
|
RequiredModules = ship.Construction.RequiredModules.ToList(),
|
||||||
|
Inputs = ship.Construction.Requirements
|
||||||
|
.Select((input) => new RecipeInputDefinition
|
||||||
|
{
|
||||||
|
ItemId = input.ItemId,
|
||||||
|
Amount = input.Amount,
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
ShipOutputId = ship.Id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipes;
|
||||||
|
}
|
||||||
|
|
||||||
private static Vector3 Scale(Vector3 vector, float scale) => new(vector.X * scale, vector.Y * scale, vector.Z * scale);
|
private static Vector3 Scale(Vector3 vector, float scale) => new(vector.X * scale, vector.Y * scale, vector.Z * scale);
|
||||||
|
|
||||||
private static float DegreesToRadians(float degrees) => degrees * (MathF.PI / 180f);
|
private static float DegreesToRadians(float degrees) => degrees * (MathF.PI / 180f);
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ public sealed partial class SimulationEngine
|
|||||||
ControllerTaskKind.Dock => UpdateDock(ship, world, deltaSeconds),
|
ControllerTaskKind.Dock => UpdateDock(ship, world, deltaSeconds),
|
||||||
ControllerTaskKind.Load => UpdateLoadCargo(ship, world, deltaSeconds),
|
ControllerTaskKind.Load => UpdateLoadCargo(ship, world, deltaSeconds),
|
||||||
ControllerTaskKind.Unload => UpdateUnload(ship, world, deltaSeconds),
|
ControllerTaskKind.Unload => UpdateUnload(ship, world, deltaSeconds),
|
||||||
ControllerTaskKind.Refuel => UpdateRefuel(ship, world, deltaSeconds),
|
|
||||||
ControllerTaskKind.DeliverConstruction => UpdateDeliverConstruction(ship, world, deltaSeconds),
|
ControllerTaskKind.DeliverConstruction => UpdateDeliverConstruction(ship, world, deltaSeconds),
|
||||||
ControllerTaskKind.BuildConstructionSite => UpdateBuildConstructionSite(ship, world, deltaSeconds),
|
ControllerTaskKind.BuildConstructionSite => UpdateBuildConstructionSite(ship, world, deltaSeconds),
|
||||||
ControllerTaskKind.LoadWorkers => UpdateLoadWorkers(ship, world, deltaSeconds),
|
ControllerTaskKind.LoadWorkers => UpdateLoadWorkers(ship, world, deltaSeconds),
|
||||||
@@ -38,7 +37,6 @@ public sealed partial class SimulationEngine
|
|||||||
|
|
||||||
private static string UpdateIdle(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
|
private static string UpdateIdle(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
|
||||||
{
|
{
|
||||||
TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds);
|
|
||||||
ship.State = ShipState.Idle;
|
ship.State = ShipState.Idle;
|
||||||
ship.TargetPosition = ship.Position;
|
ship.TargetPosition = ship.Position;
|
||||||
return "none";
|
return "none";
|
||||||
@@ -133,7 +131,6 @@ public sealed partial class SimulationEngine
|
|||||||
if (distance <= threshold)
|
if (distance <= threshold)
|
||||||
{
|
{
|
||||||
ship.ActionTimer = 0f;
|
ship.ActionTimer = 0f;
|
||||||
TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds);
|
|
||||||
ship.Position = targetPosition;
|
ship.Position = targetPosition;
|
||||||
ship.TargetPosition = ship.Position;
|
ship.TargetPosition = ship.Position;
|
||||||
ship.SystemId = targetSystemId;
|
ship.SystemId = targetSystemId;
|
||||||
@@ -143,13 +140,6 @@ public sealed partial class SimulationEngine
|
|||||||
return "arrived";
|
return "arrived";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.MoveDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.ActionTimer = 0f;
|
ship.ActionTimer = 0f;
|
||||||
ship.State = ShipState.LocalFlight;
|
ship.State = ShipState.LocalFlight;
|
||||||
ship.Position = ship.Position.MoveToward(targetPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
|
ship.Position = ship.Position.MoveToward(targetPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
|
||||||
@@ -185,13 +175,6 @@ public sealed partial class SimulationEngine
|
|||||||
ship.ActionTimer = 0f;
|
ship.ActionTimer = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.State = ShipState.SpoolingWarp;
|
ship.State = ShipState.SpoolingWarp;
|
||||||
if (!AdvanceTimedAction(ship, deltaSeconds, spoolDuration))
|
if (!AdvanceTimedAction(ship, deltaSeconds, spoolDuration))
|
||||||
{
|
{
|
||||||
@@ -201,13 +184,6 @@ public sealed partial class SimulationEngine
|
|||||||
ship.State = ShipState.Warping;
|
ship.State = ShipState.Warping;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.WarpDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalDistance = MathF.Max(0.001f, transit.OriginNodeId is null
|
var totalDistance = MathF.Max(0.001f, transit.OriginNodeId is null
|
||||||
? ship.Position.DistanceTo(targetPosition)
|
? ship.Position.DistanceTo(targetPosition)
|
||||||
: (world.SpatialNodes.FirstOrDefault(candidate => candidate.Id == transit.OriginNodeId)?.Position.DistanceTo(targetPosition) ?? ship.Position.DistanceTo(targetPosition)));
|
: (world.SpatialNodes.FirstOrDefault(candidate => candidate.Id == transit.OriginNodeId)?.Position.DistanceTo(targetPosition) ?? ship.Position.DistanceTo(targetPosition)));
|
||||||
@@ -247,13 +223,6 @@ public sealed partial class SimulationEngine
|
|||||||
ship.ActionTimer = 0f;
|
ship.ActionTimer = 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.State = ShipState.SpoolingFtl;
|
ship.State = ShipState.SpoolingFtl;
|
||||||
if (!AdvanceTimedAction(ship, deltaSeconds, ship.Definition.SpoolTime))
|
if (!AdvanceTimedAction(ship, deltaSeconds, ship.Definition.SpoolTime))
|
||||||
{
|
{
|
||||||
@@ -263,13 +232,6 @@ public sealed partial class SimulationEngine
|
|||||||
ship.State = ShipState.Ftl;
|
ship.State = ShipState.Ftl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.WarpDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
var originSystemPosition = ResolveSystemGalaxyPosition(world, ship.SystemId);
|
var originSystemPosition = ResolveSystemGalaxyPosition(world, ship.SystemId);
|
||||||
var destinationSystemPosition = ResolveSystemGalaxyPosition(world, targetSystemId);
|
var destinationSystemPosition = ResolveSystemGalaxyPosition(world, targetSystemId);
|
||||||
var totalDistance = MathF.Max(0.001f, originSystemPosition.DistanceTo(destinationSystemPosition));
|
var totalDistance = MathF.Max(0.001f, originSystemPosition.DistanceTo(destinationSystemPosition));
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public sealed partial class SimulationEngine
|
|||||||
|
|
||||||
private static float ComputeResourceNodeOrbitSpeed(ResourceNodeRuntime node)
|
private static float ComputeResourceNodeOrbitSpeed(ResourceNodeRuntime node)
|
||||||
{
|
{
|
||||||
var baseSpeed = node.SourceKind == "gas-cloud" ? 0.16f : 0.24f;
|
var baseSpeed = 0.24f;
|
||||||
return baseSpeed / MathF.Sqrt(MathF.Max(node.OrbitRadius / 180000f, 0.45f));
|
return baseSpeed / MathF.Sqrt(MathF.Max(node.OrbitRadius / 180000f, 0.45f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ namespace SpaceGame.Simulation.Api.Simulation;
|
|||||||
|
|
||||||
public sealed partial class SimulationEngine
|
public sealed partial class SimulationEngine
|
||||||
{
|
{
|
||||||
private const float StationEnergyCellToEnergyRatio = 1f;
|
|
||||||
|
|
||||||
private static bool HasShipModules(ShipDefinition definition, params string[] modules) =>
|
private static bool HasShipModules(ShipDefinition definition, params string[] modules) =>
|
||||||
modules.All(moduleId => definition.Modules.Contains(moduleId, StringComparer.Ordinal));
|
modules.All(moduleId => definition.Modules.Contains(moduleId, StringComparer.Ordinal));
|
||||||
|
|
||||||
@@ -16,169 +14,56 @@ public sealed partial class SimulationEngine
|
|||||||
private static float GetWorkerTransportCapacity(ShipRuntime ship) =>
|
private static float GetWorkerTransportCapacity(ShipRuntime ship) =>
|
||||||
CountModules(ship.Definition.Modules, "habitat-ring") * 120f;
|
CountModules(ship.Definition.Modules, "habitat-ring") * 120f;
|
||||||
|
|
||||||
private static void UpdateStationPower(SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
private static int CountStationModules(StationRuntime station, string moduleId) =>
|
||||||
{
|
station.Modules.Count(module => string.Equals(module.ModuleId, moduleId, StringComparison.Ordinal));
|
||||||
foreach (var station in world.Stations)
|
|
||||||
{
|
|
||||||
var previousEnergy = station.EnergyStored;
|
|
||||||
GenerateStationEnergy(station, world, deltaSeconds);
|
|
||||||
|
|
||||||
if (previousEnergy > 0.01f && station.EnergyStored <= 0.01f && GetInventoryAmount(station.Inventory, "fuel") <= 0.01f)
|
private static void AddStationModule(SimulationWorld world, StationRuntime station, string moduleId)
|
||||||
{
|
{
|
||||||
events.Add(new SimulationEventRecord("station", station.Id, "power-lost", $"{station.Definition.Label} ran out of fuel and power", DateTimeOffset.UtcNow));
|
if (!world.ModuleDefinitions.TryGetValue(moduleId, out var definition))
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void UpdateShipPower(ShipRuntime ship, SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
|
||||||
{
|
{
|
||||||
var previousEnergy = ship.EnergyStored;
|
|
||||||
GenerateShipEnergy(ship, world, deltaSeconds);
|
|
||||||
|
|
||||||
if (previousEnergy > 0.01f && ship.EnergyStored <= 0.01f && GetInventoryAmount(ship.Inventory, "fuel") <= 0.01f)
|
|
||||||
{
|
|
||||||
events.Add(new SimulationEventRecord("ship", ship.Id, "power-lost", $"{ship.Definition.Label} ran out of fuel and power", DateTimeOffset.UtcNow));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GenerateStationEnergy(StationRuntime station, SimulationWorld world, float deltaSeconds)
|
|
||||||
{
|
|
||||||
var powerCores = CountModules(station.InstalledModules, "power-core");
|
|
||||||
var tanks = CountModules(station.InstalledModules, "liquid-tank");
|
|
||||||
if (powerCores <= 0 || tanks <= 0)
|
|
||||||
{
|
|
||||||
station.EnergyStored = 0f;
|
|
||||||
station.Inventory.Remove("fuel");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var energyCapacity = powerCores * StationEnergyPerPowerCore;
|
station.Modules.Add(new StationModuleRuntime
|
||||||
var fuelStored = GetInventoryAmount(station.Inventory, "fuel");
|
|
||||||
var desiredEnergy = MathF.Max(0f, energyCapacity - station.EnergyStored);
|
|
||||||
if (desiredEnergy <= 0.01f)
|
|
||||||
{
|
{
|
||||||
station.EnergyStored = MathF.Min(station.EnergyStored, energyCapacity);
|
Id = $"{station.Id}-module-{station.Modules.Count + 1}",
|
||||||
station.Inventory["fuel"] = MathF.Min(fuelStored, tanks * StationFuelPerTank);
|
ModuleId = moduleId,
|
||||||
return;
|
Health = definition.Hull,
|
||||||
|
MaxHealth = definition.Hull,
|
||||||
|
});
|
||||||
|
station.Radius = GetStationRadius(world, station);
|
||||||
}
|
}
|
||||||
|
|
||||||
var solarGenerated = MathF.Min(desiredEnergy, GetStationSolarGeneration(station, world) * deltaSeconds);
|
private static float GetStationRadius(SimulationWorld world, StationRuntime station)
|
||||||
if (solarGenerated > 0.01f)
|
|
||||||
{
|
{
|
||||||
station.EnergyStored = MathF.Min(energyCapacity, station.EnergyStored + solarGenerated);
|
var totalArea = station.Modules
|
||||||
desiredEnergy = MathF.Max(0f, energyCapacity - station.EnergyStored);
|
.Select(module => world.ModuleDefinitions.TryGetValue(module.ModuleId, out var definition) ? definition.Radius * definition.Radius : 0f)
|
||||||
|
.Sum();
|
||||||
|
return MathF.Max(24f, MathF.Sqrt(MathF.Max(totalArea, 1f)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (desiredEnergy > 0.01f && fuelStored <= 0.01f)
|
|
||||||
{
|
|
||||||
var energyCells = GetInventoryAmount(station.Inventory, "energy-cell");
|
|
||||||
if (energyCells > 0.01f)
|
|
||||||
{
|
|
||||||
var consumedCells = MathF.Min(energyCells, desiredEnergy / StationEnergyCellToEnergyRatio);
|
|
||||||
RemoveInventory(station.Inventory, "energy-cell", consumedCells);
|
|
||||||
station.EnergyStored = MathF.Min(energyCapacity, station.EnergyStored + (consumedCells * StationEnergyCellToEnergyRatio));
|
|
||||||
desiredEnergy = MathF.Max(0f, energyCapacity - station.EnergyStored);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (desiredEnergy <= 0.01f || fuelStored <= 0.01f)
|
|
||||||
{
|
|
||||||
station.EnergyStored = MathF.Min(station.EnergyStored, energyCapacity);
|
|
||||||
station.Inventory["fuel"] = MathF.Min(fuelStored, tanks * StationFuelPerTank);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var generated = MathF.Min(desiredEnergy, powerCores * 24f * deltaSeconds);
|
|
||||||
var requiredFuel = generated / StationFuelToEnergyRatio;
|
|
||||||
var consumedFuel = MathF.Min(requiredFuel, fuelStored);
|
|
||||||
var actualGenerated = consumedFuel * StationFuelToEnergyRatio;
|
|
||||||
|
|
||||||
RemoveInventory(station.Inventory, "fuel", consumedFuel);
|
|
||||||
station.EnergyStored = MathF.Min(energyCapacity, station.EnergyStored + actualGenerated);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float GetStationFuelCapacity(StationRuntime station) =>
|
|
||||||
CountModules(station.InstalledModules, "liquid-tank") * StationFuelPerTank;
|
|
||||||
|
|
||||||
private static float GetStationEnergyCapacity(StationRuntime station) =>
|
|
||||||
CountModules(station.InstalledModules, "power-core") * StationEnergyPerPowerCore;
|
|
||||||
|
|
||||||
private static float GetStationSolarGeneration(StationRuntime station, SimulationWorld world) =>
|
|
||||||
world.Balance.Energy.StationSolarCharge * (1f + CountModules(station.InstalledModules, "solar-array"));
|
|
||||||
|
|
||||||
private static float GetStationStorageCapacity(StationRuntime station, string storageClass)
|
private static float GetStationStorageCapacity(StationRuntime station, string storageClass)
|
||||||
{
|
{
|
||||||
var baseCapacity = station.Definition.Storage.TryGetValue(storageClass, out var capacity)
|
var baseCapacity = storageClass switch
|
||||||
? capacity
|
|
||||||
: 0f;
|
|
||||||
|
|
||||||
var extraBulkBays = Math.Max(0, CountModules(station.InstalledModules, "bulk-bay") - CountModules(station.Definition.Modules, "bulk-bay"));
|
|
||||||
var extraLiquidTanks = Math.Max(0, CountModules(station.InstalledModules, "liquid-tank") - CountModules(station.Definition.Modules, "liquid-tank"));
|
|
||||||
var extraGasTanks = Math.Max(0, CountModules(station.InstalledModules, "gas-tank") - CountModules(station.Definition.Modules, "gas-tank"));
|
|
||||||
var extraContainerBays = Math.Max(0, CountModules(station.InstalledModules, "container-bay") - CountModules(station.Definition.Modules, "container-bay"));
|
|
||||||
var moduleBonus = storageClass switch
|
|
||||||
{
|
{
|
||||||
"bulk-solid" => extraBulkBays * 1000f,
|
"manufactured" => 400f,
|
||||||
"bulk-liquid" => extraLiquidTanks * 500f,
|
|
||||||
"bulk-gas" => extraGasTanks * 500f,
|
|
||||||
"container" => extraContainerBays * 800f,
|
|
||||||
_ => 0f,
|
_ => 0f,
|
||||||
};
|
};
|
||||||
|
|
||||||
return baseCapacity + moduleBonus;
|
var bulkBays = CountStationModules(station, "bulk-bay");
|
||||||
}
|
var liquidTanks = CountStationModules(station, "liquid-tank");
|
||||||
|
var containerBays = CountStationModules(station, "container-bay");
|
||||||
|
|
||||||
private static void GenerateShipEnergy(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
|
var moduleCapacity = storageClass switch
|
||||||
{
|
{
|
||||||
var reactors = CountModules(ship.Definition.Modules, "reactor-core");
|
"bulk-solid" => bulkBays * 1000f,
|
||||||
var capacitors = CountModules(ship.Definition.Modules, "capacitor-bank");
|
"bulk-liquid" => liquidTanks * 500f,
|
||||||
if (reactors <= 0 || capacitors <= 0)
|
"container" => containerBays * 800f,
|
||||||
{
|
"manufactured" => containerBays * 200f,
|
||||||
ship.EnergyStored = 0f;
|
_ => 0f,
|
||||||
ship.Inventory.Remove("fuel");
|
};
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var energyCapacity = capacitors * CapacitorEnergyPerModule;
|
return baseCapacity + moduleCapacity;
|
||||||
var fuelCapacity = reactors * ShipFuelPerReactor;
|
|
||||||
var fuelStored = GetInventoryAmount(ship.Inventory, "fuel");
|
|
||||||
var desiredEnergy = MathF.Max(0f, energyCapacity - ship.EnergyStored);
|
|
||||||
if (desiredEnergy <= 0.01f || fuelStored <= 0.01f)
|
|
||||||
{
|
|
||||||
ship.EnergyStored = MathF.Min(ship.EnergyStored, energyCapacity);
|
|
||||||
ship.Inventory["fuel"] = MathF.Min(fuelStored, fuelCapacity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var generated = MathF.Min(desiredEnergy, world.Balance.Energy.ShipRechargeRate * reactors * deltaSeconds);
|
|
||||||
var requiredFuel = generated / ShipFuelToEnergyRatio;
|
|
||||||
var consumedFuel = MathF.Min(requiredFuel, fuelStored);
|
|
||||||
var actualGenerated = consumedFuel * ShipFuelToEnergyRatio;
|
|
||||||
|
|
||||||
RemoveInventory(ship.Inventory, "fuel", consumedFuel);
|
|
||||||
ship.EnergyStored = MathF.Min(energyCapacity, ship.EnergyStored + actualGenerated);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryConsumeShipEnergy(ShipRuntime ship, float amount)
|
|
||||||
{
|
|
||||||
if (ship.EnergyStored + 0.0001f < amount)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.EnergyStored = MathF.Max(0f, ship.EnergyStored - amount);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryConsumeStationEnergy(StationRuntime station, float amount)
|
|
||||||
{
|
|
||||||
if (station.EnergyStored + 0.0001f < amount)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
station.EnergyStored = MathF.Max(0f, station.EnergyStored - amount);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
private static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
||||||
@@ -215,278 +100,18 @@ public sealed partial class SimulationEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static bool HasStationModules(StationRuntime station, params string[] modules) =>
|
private static bool HasStationModules(StationRuntime station, params string[] modules) =>
|
||||||
modules.All(moduleId => station.InstalledModules.Contains(moduleId, StringComparer.Ordinal));
|
modules.All(moduleId => station.Modules.Any(candidate => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal)));
|
||||||
|
|
||||||
private static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node) =>
|
private static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node) =>
|
||||||
node.ItemId switch
|
node.ItemId switch
|
||||||
{
|
{
|
||||||
"ore" => HasShipModules(ship.Definition, "reactor-core", "capacitor-bank", "mining-turret"),
|
"ore" => HasShipModules(ship.Definition, "mining-turret"),
|
||||||
"gas" => HasShipModules(ship.Definition, "reactor-core", "capacitor-bank", "gas-extractor"),
|
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
private static bool CanBuildClaimBeacon(ShipRuntime ship) =>
|
private static bool CanBuildClaimBeacon(ShipRuntime ship) =>
|
||||||
string.Equals(ship.Definition.Role, "military", StringComparison.Ordinal);
|
string.Equals(ship.Definition.Role, "military", StringComparison.Ordinal);
|
||||||
|
|
||||||
private static float GetShipFuelCapacity(ShipRuntime ship) =>
|
|
||||||
CountModules(ship.Definition.Modules, "reactor-core") * ShipFuelPerReactor;
|
|
||||||
|
|
||||||
private static float GetShipAvailableEnergyBudget(ShipRuntime ship) =>
|
|
||||||
ship.EnergyStored + (GetInventoryAmount(ship.Inventory, "fuel") * ShipFuelToEnergyRatio);
|
|
||||||
|
|
||||||
private static float GetShipFuelReserve(ShipRuntime ship, float plannedFuel)
|
|
||||||
{
|
|
||||||
var capacity = GetShipFuelCapacity(ship);
|
|
||||||
var reserveRatio = ship.Definition.CargoItemId == "gas" ? 0.4f : 0.3f;
|
|
||||||
var reserve = MathF.Max(16f, MathF.Max(capacity * 0.18f, plannedFuel * reserveRatio));
|
|
||||||
return MathF.Min(capacity, reserve);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float EstimateFuelForEnergyDemand(ShipRuntime ship, float energyDemand) =>
|
|
||||||
MathF.Max(0f, energyDemand - ship.EnergyStored) / ShipFuelToEnergyRatio;
|
|
||||||
|
|
||||||
private static float EstimateTimedEnergyUse(SimulationWorld world, float durationSeconds, float drainPerSecond) =>
|
|
||||||
MathF.Max(0f, durationSeconds) * drainPerSecond;
|
|
||||||
|
|
||||||
private static float EstimateTravelEnergy(
|
|
||||||
ShipRuntime ship,
|
|
||||||
SimulationWorld world,
|
|
||||||
Vector3 fromPosition,
|
|
||||||
string fromSystemId,
|
|
||||||
Vector3 toPosition,
|
|
||||||
string toSystemId)
|
|
||||||
{
|
|
||||||
if (!string.Equals(fromSystemId, toSystemId, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
var destinationEntryNode = ResolveSystemEntryNode(world, toSystemId);
|
|
||||||
var destinationEntryPosition = destinationEntryNode?.Position ?? toPosition;
|
|
||||||
var originSystemPosition = ResolveSystemGalaxyPosition(world, fromSystemId);
|
|
||||||
var destinationSystemPosition = ResolveSystemGalaxyPosition(world, toSystemId);
|
|
||||||
var ftlDistance = originSystemPosition.DistanceTo(destinationSystemPosition);
|
|
||||||
var ftlDuration = ftlDistance / MathF.Max(ship.Definition.FtlSpeed, 0.01f);
|
|
||||||
return EstimateTimedEnergyUse(world, ship.Definition.SpoolTime, world.Balance.Energy.IdleDrain)
|
|
||||||
+ EstimateTimedEnergyUse(world, ftlDuration, world.Balance.Energy.WarpDrain)
|
|
||||||
+ EstimateInSystemTravelEnergy(ship, world, destinationEntryPosition, toPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
return EstimateInSystemTravelEnergy(ship, world, fromPosition, toPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float EstimateInSystemTravelEnergy(ShipRuntime ship, SimulationWorld world, Vector3 fromPosition, Vector3 toPosition)
|
|
||||||
{
|
|
||||||
var distance = fromPosition.DistanceTo(toPosition);
|
|
||||||
if (distance <= world.Balance.ArrivalThreshold)
|
|
||||||
{
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (distance <= WarpEngageDistanceKilometers)
|
|
||||||
{
|
|
||||||
var localDuration = distance / MathF.Max(GetLocalTravelSpeed(ship), 0.01f);
|
|
||||||
return EstimateTimedEnergyUse(world, localDuration, world.Balance.Energy.MoveDrain);
|
|
||||||
}
|
|
||||||
|
|
||||||
var warpSpoolDuration = MathF.Max(0.4f, ship.Definition.SpoolTime * 0.5f);
|
|
||||||
var warpDuration = distance / MathF.Max(GetWarpTravelSpeed(ship), 0.01f);
|
|
||||||
return EstimateTimedEnergyUse(world, warpSpoolDuration, world.Balance.Energy.IdleDrain)
|
|
||||||
+ EstimateTimedEnergyUse(world, warpDuration, world.Balance.Energy.WarpDrain);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float EstimateDockingEnergy(SimulationWorld world) =>
|
|
||||||
EstimateTimedEnergyUse(world, world.Balance.DockingDuration, world.Balance.Energy.MoveDrain)
|
|
||||||
+ EstimateTimedEnergyUse(world, 6f, world.Balance.Energy.IdleDrain);
|
|
||||||
|
|
||||||
private static float EstimateUndockingEnergy(SimulationWorld world) =>
|
|
||||||
EstimateTimedEnergyUse(world, world.Balance.UndockingDuration, world.Balance.Energy.MoveDrain)
|
|
||||||
+ EstimateTimedEnergyUse(world, 4f, world.Balance.Energy.IdleDrain);
|
|
||||||
|
|
||||||
private static float EstimateExtractionEnergy(ShipRuntime ship, SimulationWorld world)
|
|
||||||
{
|
|
||||||
var remainingCargo = MathF.Max(0f, ship.Definition.CargoCapacity - GetShipCargoAmount(ship));
|
|
||||||
if (remainingCargo <= 0.01f)
|
|
||||||
{
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cycles = MathF.Ceiling(remainingCargo / MathF.Max(world.Balance.MiningRate, 0.01f));
|
|
||||||
return EstimateTimedEnergyUse(world, cycles * world.Balance.MiningCycleSeconds, world.Balance.Energy.MoveDrain)
|
|
||||||
+ EstimateTimedEnergyUse(world, cycles * 1.5f, world.Balance.Energy.IdleDrain);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float EstimateConstructionEnergy(ShipRuntime ship, SimulationWorld world, StationRuntime station)
|
|
||||||
{
|
|
||||||
var holdPosition = GetConstructionHoldPosition(station, ship.Id);
|
|
||||||
var travelEnergy = EstimateTravelEnergy(ship, world, ship.Position, ship.SystemId, holdPosition, station.SystemId);
|
|
||||||
var site = GetConstructionSiteForStation(world, station.Id);
|
|
||||||
if (site is not null && site.State == ConstructionSiteStateKinds.Active && IsConstructionSiteReady(world, site))
|
|
||||||
{
|
|
||||||
if (world.ModuleRecipes.TryGetValue(site.BlueprintId ?? string.Empty, out var siteRecipe))
|
|
||||||
{
|
|
||||||
return travelEnergy + EstimateTimedEnergyUse(world, siteRecipe.Duration, world.Balance.Energy.IdleDrain);
|
|
||||||
}
|
|
||||||
|
|
||||||
return travelEnergy;
|
|
||||||
}
|
|
||||||
|
|
||||||
var moduleId = site?.BlueprintId ?? GetNextStationModuleToBuild(station, world);
|
|
||||||
if (moduleId is not null
|
|
||||||
&& world.ModuleRecipes.TryGetValue(moduleId, out var recipe)
|
|
||||||
&& CanStartModuleConstruction(station, recipe))
|
|
||||||
{
|
|
||||||
return travelEnergy + EstimateTimedEnergyUse(world, recipe.Duration, world.Balance.Energy.IdleDrain);
|
|
||||||
}
|
|
||||||
|
|
||||||
return travelEnergy;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float EstimateResourceHarvestEnergy(ShipRuntime ship, SimulationWorld world)
|
|
||||||
{
|
|
||||||
var cargoItemId = ship.Definition.CargoItemId;
|
|
||||||
if (cargoItemId is null)
|
|
||||||
{
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var requiredModule = cargoItemId == "gas" ? "gas-extractor" : "mining-turret";
|
|
||||||
var behavior = ship.DefaultBehavior;
|
|
||||||
var refinery = SelectBestBuyStation(world, ship, cargoItemId, behavior.StationId);
|
|
||||||
var node = behavior.NodeId is null
|
|
||||||
? world.Nodes
|
|
||||||
.Where(candidate =>
|
|
||||||
(behavior.AreaSystemId is null || candidate.SystemId == behavior.AreaSystemId) &&
|
|
||||||
candidate.ItemId == cargoItemId &&
|
|
||||||
candidate.OreRemaining > 0.01f)
|
|
||||||
.OrderByDescending(candidate => candidate.OreRemaining)
|
|
||||||
.FirstOrDefault()
|
|
||||||
: world.Nodes.FirstOrDefault(candidate => candidate.Id == behavior.NodeId && candidate.OreRemaining > 0.01f);
|
|
||||||
if (refinery is null || node is null || !HasShipModules(ship.Definition, "reactor-core", "capacitor-bank", requiredModule))
|
|
||||||
{
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentPosition = ship.Position;
|
|
||||||
var currentSystemId = ship.SystemId;
|
|
||||||
var energy = 0f;
|
|
||||||
var cargoAmount = GetShipCargoAmount(ship);
|
|
||||||
if (ship.DockedStationId == refinery.Id)
|
|
||||||
{
|
|
||||||
currentPosition = GetUndockTargetPosition(refinery, ship.AssignedDockingPadIndex, world.Balance.UndockDistance);
|
|
||||||
currentSystemId = refinery.SystemId;
|
|
||||||
energy += EstimateUndockingEnergy(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cargoAmount > 0.01f)
|
|
||||||
{
|
|
||||||
energy += EstimateTravelEnergy(ship, world, currentPosition, currentSystemId, refinery.Position, refinery.SystemId);
|
|
||||||
return energy + EstimateDockingEnergy(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
var holdPosition = GetResourceHoldPosition(node.Position, ship.Id, 20f);
|
|
||||||
energy += EstimateTravelEnergy(ship, world, currentPosition, currentSystemId, holdPosition, node.SystemId);
|
|
||||||
energy += EstimateExtractionEnergy(ship, world);
|
|
||||||
energy += EstimateTravelEnergy(ship, world, holdPosition, node.SystemId, refinery.Position, refinery.SystemId);
|
|
||||||
energy += EstimateDockingEnergy(world);
|
|
||||||
return energy;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float EstimateResourceReturnEnergy(ShipRuntime ship, SimulationWorld world)
|
|
||||||
{
|
|
||||||
var cargoItemId = ship.Definition.CargoItemId;
|
|
||||||
if (cargoItemId is null)
|
|
||||||
{
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var refinery = SelectBestBuyStation(world, ship, cargoItemId, ship.DefaultBehavior.StationId);
|
|
||||||
if (refinery is null)
|
|
||||||
{
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentPosition = ship.Position;
|
|
||||||
var currentSystemId = ship.SystemId;
|
|
||||||
return EstimateTravelEnergy(ship, world, currentPosition, currentSystemId, refinery.Position, refinery.SystemId)
|
|
||||||
+ EstimateDockingEnergy(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float EstimateTransportEnergy(ShipRuntime ship, SimulationWorld world)
|
|
||||||
{
|
|
||||||
var cargoItemId = ship.Definition.CargoItemId;
|
|
||||||
if (cargoItemId is null)
|
|
||||||
{
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var behavior = ship.DefaultBehavior;
|
|
||||||
var source = SelectBestSellStation(world, ship, cargoItemId, behavior.StationId);
|
|
||||||
var destination = SelectBestBuyStation(world, ship, cargoItemId, behavior.StationId);
|
|
||||||
if (source is null && destination is null)
|
|
||||||
{
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cargoAmount = GetShipCargoAmount(ship);
|
|
||||||
var currentPosition = ship.Position;
|
|
||||||
var currentSystemId = ship.SystemId;
|
|
||||||
if (ship.DockedStationId is not null)
|
|
||||||
{
|
|
||||||
var dockedStation = world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DockedStationId);
|
|
||||||
if (dockedStation is not null)
|
|
||||||
{
|
|
||||||
currentPosition = GetUndockTargetPosition(dockedStation, ship.AssignedDockingPadIndex, world.Balance.UndockDistance);
|
|
||||||
currentSystemId = dockedStation.SystemId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetStation = cargoAmount > 0.01f ? destination : source;
|
|
||||||
if (targetStation is null)
|
|
||||||
{
|
|
||||||
return ship.DockedStationId is not null ? EstimateUndockingEnergy(world) : 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var energy = ship.DockedStationId is not null ? EstimateUndockingEnergy(world) : 0f;
|
|
||||||
energy += EstimateTravelEnergy(ship, world, currentPosition, currentSystemId, targetStation.Position, targetStation.SystemId);
|
|
||||||
return energy + EstimateDockingEnergy(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float EstimateShipMissionEnergyDemand(ShipRuntime ship, SimulationWorld world) =>
|
|
||||||
ship.DefaultBehavior.Kind switch
|
|
||||||
{
|
|
||||||
"auto-mine" or "auto-harvest-gas" => EstimateResourceHarvestEnergy(ship, world),
|
|
||||||
"auto-supply-energy" => EstimateTransportEnergy(ship, world),
|
|
||||||
"construct-station" when ship.DefaultBehavior.StationId is not null
|
|
||||||
=> world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DefaultBehavior.StationId) is { } station
|
|
||||||
? EstimateConstructionEnergy(ship, world, station)
|
|
||||||
: 0f,
|
|
||||||
_ when ship.ControllerTask.TargetPosition is { } targetPosition && ship.ControllerTask.TargetSystemId is { } targetSystemId
|
|
||||||
=> EstimateTravelEnergy(ship, world, ship.Position, ship.SystemId, targetPosition, targetSystemId),
|
|
||||||
_ => 0f,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static float GetShipRefuelTarget(ShipRuntime ship, SimulationWorld world)
|
|
||||||
{
|
|
||||||
var capacity = GetShipFuelCapacity(ship);
|
|
||||||
var missionFuel = EstimateFuelForEnergyDemand(ship, EstimateShipMissionEnergyDemand(ship, world));
|
|
||||||
var reserveFuel = GetShipFuelReserve(ship, missionFuel);
|
|
||||||
return MathF.Min(capacity, missionFuel + reserveFuel);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool NeedsRefuel(ShipRuntime ship, SimulationWorld world) =>
|
|
||||||
GetInventoryAmount(ship.Inventory, "fuel") + 0.01f < GetShipRefuelTarget(ship, world);
|
|
||||||
|
|
||||||
internal static bool NeedsEmergencyReturn(ShipRuntime ship, SimulationWorld world)
|
|
||||||
{
|
|
||||||
if (ship.DefaultBehavior.Kind is not "auto-mine" and not "auto-harvest-gas")
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var returnEnergy = EstimateResourceReturnEnergy(ship, world);
|
|
||||||
var reserveFuel = GetShipFuelReserve(ship, EstimateFuelForEnergyDemand(ship, returnEnergy));
|
|
||||||
var requiredBudget = returnEnergy + (reserveFuel * ShipFuelToEnergyRatio);
|
|
||||||
return GetShipAvailableEnergyBudget(ship) + 0.01f < requiredBudget;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float ComputeWorkforceRatio(float population, float workforceRequired)
|
private static float ComputeWorkforceRatio(float population, float workforceRequired)
|
||||||
{
|
{
|
||||||
if (workforceRequired <= 0.01f)
|
if (workforceRequired <= 0.01f)
|
||||||
@@ -503,7 +128,6 @@ public sealed partial class SimulationEngine
|
|||||||
{
|
{
|
||||||
"bulk-solid" => "bulk-bay",
|
"bulk-solid" => "bulk-bay",
|
||||||
"bulk-liquid" => "liquid-tank",
|
"bulk-liquid" => "liquid-tank",
|
||||||
"bulk-gas" => "gas-tank",
|
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -514,7 +138,7 @@ public sealed partial class SimulationEngine
|
|||||||
return 0f;
|
return 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
var storageClass = itemDefinition.Storage;
|
var storageClass = itemDefinition.CargoKind;
|
||||||
var requiredModule = GetStorageRequirement(storageClass);
|
var requiredModule = GetStorageRequirement(storageClass);
|
||||||
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
||||||
{
|
{
|
||||||
@@ -528,7 +152,7 @@ public sealed partial class SimulationEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
var used = station.Inventory
|
var used = station.Inventory
|
||||||
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.Storage == storageClass)
|
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == storageClass)
|
||||||
.Sum(entry => entry.Value);
|
.Sum(entry => entry.Value);
|
||||||
var accepted = MathF.Min(amount, MathF.Max(0f, capacity - used));
|
var accepted = MathF.Min(amount, MathF.Max(0f, capacity - used));
|
||||||
if (accepted <= 0.01f)
|
if (accepted <= 0.01f)
|
||||||
|
|||||||
@@ -79,10 +79,6 @@ public sealed partial class SimulationEngine
|
|||||||
station.DockedShips,
|
station.DockedShips,
|
||||||
station.DockedShipIds,
|
station.DockedShipIds,
|
||||||
station.DockingPads,
|
station.DockingPads,
|
||||||
station.FuelStored,
|
|
||||||
station.FuelCapacity,
|
|
||||||
station.EnergyStored,
|
|
||||||
station.EnergyCapacity,
|
|
||||||
station.CurrentProcesses,
|
station.CurrentProcesses,
|
||||||
station.Inventory,
|
station.Inventory,
|
||||||
station.FactionId,
|
station.FactionId,
|
||||||
@@ -92,6 +88,7 @@ public sealed partial class SimulationEngine
|
|||||||
station.PopulationCapacity,
|
station.PopulationCapacity,
|
||||||
station.WorkforceRequired,
|
station.WorkforceRequired,
|
||||||
station.WorkforceEffectiveRatio,
|
station.WorkforceEffectiveRatio,
|
||||||
|
station.StorageUsage,
|
||||||
station.InstalledModules,
|
station.InstalledModules,
|
||||||
station.MarketOrderIds)).ToList(),
|
station.MarketOrderIds)).ToList(),
|
||||||
world.Claims.Select(ToClaimDelta).Select(claim => new ClaimSnapshot(
|
world.Claims.Select(ToClaimDelta).Select(claim => new ClaimSnapshot(
|
||||||
@@ -104,7 +101,7 @@ public sealed partial class SimulationEngine
|
|||||||
claim.Health,
|
claim.Health,
|
||||||
claim.PlacedAtUtc,
|
claim.PlacedAtUtc,
|
||||||
claim.ActivatesAtUtc)).ToList(),
|
claim.ActivatesAtUtc)).ToList(),
|
||||||
world.ConstructionSites.Select(ToConstructionSiteDelta).Select(site => new ConstructionSiteSnapshot(
|
world.ConstructionSites.Select(site => ToConstructionSiteDelta(world, site)).Select(site => new ConstructionSiteSnapshot(
|
||||||
site.Id,
|
site.Id,
|
||||||
site.FactionId,
|
site.FactionId,
|
||||||
site.SystemId,
|
site.SystemId,
|
||||||
@@ -164,7 +161,6 @@ public sealed partial class SimulationEngine
|
|||||||
ship.CargoCapacity,
|
ship.CargoCapacity,
|
||||||
ship.CargoItemId,
|
ship.CargoItemId,
|
||||||
ship.WorkerPopulation,
|
ship.WorkerPopulation,
|
||||||
ship.EnergyStored,
|
|
||||||
ship.TravelSpeed,
|
ship.TravelSpeed,
|
||||||
ship.TravelSpeedUnit,
|
ship.TravelSpeedUnit,
|
||||||
ship.Inventory,
|
ship.Inventory,
|
||||||
@@ -341,7 +337,7 @@ public sealed partial class SimulationEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
site.LastDeltaSignature = signature;
|
site.LastDeltaSignature = signature;
|
||||||
deltas.Add(ToConstructionSiteDelta(site));
|
deltas.Add(ToConstructionSiteDelta(world, site));
|
||||||
}
|
}
|
||||||
|
|
||||||
return deltas;
|
return deltas;
|
||||||
@@ -439,10 +435,6 @@ public sealed partial class SimulationEngine
|
|||||||
station.CommanderId ?? "none",
|
station.CommanderId ?? "none",
|
||||||
station.PolicySetId ?? "none",
|
station.PolicySetId ?? "none",
|
||||||
BuildInventorySignature(station.Inventory),
|
BuildInventorySignature(station.Inventory),
|
||||||
GetInventoryAmount(station.Inventory, "fuel").ToString("0.###"),
|
|
||||||
GetStationFuelCapacity(station).ToString("0.###"),
|
|
||||||
station.EnergyStored.ToString("0.###"),
|
|
||||||
GetStationEnergyCapacity(station).ToString("0.###"),
|
|
||||||
string.Join(",", processes.Select(process => $"{process.Lane}:{process.Label}:{process.Progress:0.###}")),
|
string.Join(",", processes.Select(process => $"{process.Lane}:{process.Label}:{process.Progress:0.###}")),
|
||||||
string.Join(",", station.DockedShipIds.OrderBy(id => id, StringComparer.Ordinal)),
|
string.Join(",", station.DockedShipIds.OrderBy(id => id, StringComparer.Ordinal)),
|
||||||
station.DockingPadAssignments.Count.ToString(),
|
station.DockingPadAssignments.Count.ToString(),
|
||||||
@@ -501,13 +493,11 @@ public sealed partial class SimulationEngine
|
|||||||
ship.SpatialState.Transit?.DestinationNodeId ?? "none",
|
ship.SpatialState.Transit?.DestinationNodeId ?? "none",
|
||||||
ship.SpatialState.Transit?.Progress.ToString("0.###") ?? "0",
|
ship.SpatialState.Transit?.Progress.ToString("0.###") ?? "0",
|
||||||
GetShipCargoAmount(ship).ToString("0.###"),
|
GetShipCargoAmount(ship).ToString("0.###"),
|
||||||
GetInventoryAmount(ship.Inventory, "fuel").ToString("0.###"),
|
|
||||||
ship.TrackedActionKey ?? "none",
|
ship.TrackedActionKey ?? "none",
|
||||||
ship.TrackedActionTotal.ToString("0.###"),
|
ship.TrackedActionTotal.ToString("0.###"),
|
||||||
ship.ControllerTask.TargetEntityId is not null && world.ConstructionSites.FirstOrDefault(site => site.Id == ship.ControllerTask.TargetEntityId) is { } site
|
ship.ControllerTask.TargetEntityId is not null && world.ConstructionSites.FirstOrDefault(site => site.Id == ship.ControllerTask.TargetEntityId) is { } site
|
||||||
? GetRemainingConstructionDelivery(world, site).ToString("0.###")
|
? GetRemainingConstructionDelivery(world, site).ToString("0.###")
|
||||||
: "0",
|
: "0",
|
||||||
ship.EnergyStored.ToString("0.###"),
|
|
||||||
ship.Health.ToString("0.###"),
|
ship.Health.ToString("0.###"),
|
||||||
ship.ActionTimer.ToString("0.###"));
|
ship.ActionTimer.ToString("0.###"));
|
||||||
|
|
||||||
@@ -553,21 +543,17 @@ public sealed partial class SimulationEngine
|
|||||||
|
|
||||||
private static StationDelta ToStationDelta(SimulationWorld world, StationRuntime station) => new(
|
private static StationDelta ToStationDelta(SimulationWorld world, StationRuntime station) => new(
|
||||||
station.Id,
|
station.Id,
|
||||||
station.Definition.Label,
|
station.Label,
|
||||||
station.Definition.Category,
|
station.Category,
|
||||||
station.SystemId,
|
station.SystemId,
|
||||||
ToDto(station.Position),
|
ToDto(station.Position),
|
||||||
station.NodeId,
|
station.NodeId,
|
||||||
station.BubbleId,
|
station.BubbleId,
|
||||||
station.AnchorNodeId,
|
station.AnchorNodeId,
|
||||||
station.Definition.Color,
|
station.Color,
|
||||||
station.DockedShipIds.Count,
|
station.DockedShipIds.Count,
|
||||||
station.DockedShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
station.DockedShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||||
GetDockingPadCount(station),
|
GetDockingPadCount(station),
|
||||||
GetInventoryAmount(station.Inventory, "fuel"),
|
|
||||||
GetStationFuelCapacity(station),
|
|
||||||
station.EnergyStored,
|
|
||||||
GetStationEnergyCapacity(station),
|
|
||||||
ToStationActionProgressSnapshots(world, station),
|
ToStationActionProgressSnapshots(world, station),
|
||||||
ToInventoryEntries(station.Inventory),
|
ToInventoryEntries(station.Inventory),
|
||||||
station.FactionId,
|
station.FactionId,
|
||||||
@@ -577,6 +563,7 @@ public sealed partial class SimulationEngine
|
|||||||
station.PopulationCapacity,
|
station.PopulationCapacity,
|
||||||
station.WorkforceRequired,
|
station.WorkforceRequired,
|
||||||
station.WorkforceEffectiveRatio,
|
station.WorkforceEffectiveRatio,
|
||||||
|
ToStationStorageUsageSnapshots(world, station),
|
||||||
station.InstalledModules.OrderBy(moduleId => moduleId, StringComparer.Ordinal).ToList(),
|
station.InstalledModules.OrderBy(moduleId => moduleId, StringComparer.Ordinal).ToList(),
|
||||||
station.MarketOrderIds.OrderBy(orderId => orderId, StringComparer.Ordinal).ToList());
|
station.MarketOrderIds.OrderBy(orderId => orderId, StringComparer.Ordinal).ToList());
|
||||||
|
|
||||||
@@ -586,7 +573,7 @@ public sealed partial class SimulationEngine
|
|||||||
{
|
{
|
||||||
var recipe = SelectProductionRecipe(world, station, laneKey);
|
var recipe = SelectProductionRecipe(world, station, laneKey);
|
||||||
var timer = GetStationProductionTimer(station, laneKey);
|
var timer = GetStationProductionTimer(station, laneKey);
|
||||||
return recipe is null || station.EnergyStored <= 0.01f || timer <= 0.01f
|
return recipe is null || timer <= 0.01f
|
||||||
? null
|
? null
|
||||||
: new StationActionProgressSnapshot(
|
: new StationActionProgressSnapshot(
|
||||||
laneKey,
|
laneKey,
|
||||||
@@ -597,6 +584,20 @@ public sealed partial class SimulationEngine
|
|||||||
.Cast<StationActionProgressSnapshot>()
|
.Cast<StationActionProgressSnapshot>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
private static IReadOnlyList<StationStorageUsageSnapshot> ToStationStorageUsageSnapshots(SimulationWorld world, StationRuntime station)
|
||||||
|
{
|
||||||
|
string[] storageClasses = ["bulk-solid", "bulk-liquid", "container", "manufactured"];
|
||||||
|
return storageClasses
|
||||||
|
.Select(storageClass => new StationStorageUsageSnapshot(
|
||||||
|
storageClass,
|
||||||
|
station.Inventory
|
||||||
|
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == storageClass)
|
||||||
|
.Sum(entry => entry.Value),
|
||||||
|
GetStationStorageCapacity(station, storageClass)))
|
||||||
|
.Where(snapshot => snapshot.Capacity > 0.01f)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private static ClaimDelta ToClaimDelta(ClaimRuntime claim) => new(
|
private static ClaimDelta ToClaimDelta(ClaimRuntime claim) => new(
|
||||||
claim.Id,
|
claim.Id,
|
||||||
claim.FactionId,
|
claim.FactionId,
|
||||||
@@ -608,7 +609,7 @@ public sealed partial class SimulationEngine
|
|||||||
claim.PlacedAtUtc,
|
claim.PlacedAtUtc,
|
||||||
claim.ActivatesAtUtc);
|
claim.ActivatesAtUtc);
|
||||||
|
|
||||||
private static ConstructionSiteDelta ToConstructionSiteDelta(ConstructionSiteRuntime site) => new(
|
private static ConstructionSiteDelta ToConstructionSiteDelta(SimulationWorld world, ConstructionSiteRuntime site) => new(
|
||||||
site.Id,
|
site.Id,
|
||||||
site.FactionId,
|
site.FactionId,
|
||||||
site.SystemId,
|
site.SystemId,
|
||||||
@@ -620,13 +621,25 @@ public sealed partial class SimulationEngine
|
|||||||
site.ClaimId,
|
site.ClaimId,
|
||||||
site.StationId,
|
site.StationId,
|
||||||
site.State,
|
site.State,
|
||||||
site.Progress,
|
GetConstructionSiteProgress(world, site),
|
||||||
ToInventoryEntries(site.Inventory),
|
ToInventoryEntries(site.Inventory),
|
||||||
ToInventoryEntries(site.RequiredItems),
|
ToInventoryEntries(site.RequiredItems),
|
||||||
ToInventoryEntries(site.DeliveredItems),
|
ToInventoryEntries(site.DeliveredItems),
|
||||||
site.AssignedConstructorShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
site.AssignedConstructorShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||||
site.MarketOrderIds.OrderBy(id => id, StringComparer.Ordinal).ToList());
|
site.MarketOrderIds.OrderBy(id => id, StringComparer.Ordinal).ToList());
|
||||||
|
|
||||||
|
private static float GetConstructionSiteProgress(SimulationWorld world, ConstructionSiteRuntime site)
|
||||||
|
{
|
||||||
|
if (site.BlueprintId is not null
|
||||||
|
&& world.ModuleRecipes.TryGetValue(site.BlueprintId, out var recipe)
|
||||||
|
&& recipe.Duration > 0.01f)
|
||||||
|
{
|
||||||
|
return Math.Clamp(site.Progress / recipe.Duration, 0f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.Clamp(site.Progress, 0f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
private static MarketOrderDelta ToMarketOrderDelta(MarketOrderRuntime order) => new(
|
private static MarketOrderDelta ToMarketOrderDelta(MarketOrderRuntime order) => new(
|
||||||
order.Id,
|
order.Id,
|
||||||
order.FactionId,
|
order.FactionId,
|
||||||
@@ -671,7 +684,6 @@ public sealed partial class SimulationEngine
|
|||||||
ship.Definition.CargoCapacity,
|
ship.Definition.CargoCapacity,
|
||||||
ship.Definition.CargoItemId,
|
ship.Definition.CargoItemId,
|
||||||
ship.WorkerPopulation,
|
ship.WorkerPopulation,
|
||||||
ship.EnergyStored,
|
|
||||||
ToShipTravelSpeed(ship).Speed,
|
ToShipTravelSpeed(ship).Speed,
|
||||||
ToShipTravelSpeed(ship).Unit,
|
ToShipTravelSpeed(ship).Unit,
|
||||||
ToInventoryEntries(ship.Inventory),
|
ToInventoryEntries(ship.Inventory),
|
||||||
@@ -693,10 +705,6 @@ public sealed partial class SimulationEngine
|
|||||||
ShipState.Docking => CreateShipActionProgress("Docking", ship.ActionTimer, MathF.Max(world.Balance.DockingDuration, 0.1f)),
|
ShipState.Docking => CreateShipActionProgress("Docking", ship.ActionTimer, MathF.Max(world.Balance.DockingDuration, 0.1f)),
|
||||||
ShipState.Undocking => CreateShipActionProgress("Undocking", ship.ActionTimer, MathF.Max(world.Balance.UndockingDuration, 0.1f)),
|
ShipState.Undocking => CreateShipActionProgress("Undocking", ship.ActionTimer, MathF.Max(world.Balance.UndockingDuration, 0.1f)),
|
||||||
ShipState.Transferring => CreateShipRemainingActionProgress("Transfer", ship.TrackedActionTotal, GetShipCargoAmount(ship)),
|
ShipState.Transferring => CreateShipRemainingActionProgress("Transfer", ship.TrackedActionTotal, GetShipCargoAmount(ship)),
|
||||||
ShipState.Refueling => CreateShipRemainingActionProgress(
|
|
||||||
"Refuel",
|
|
||||||
ship.TrackedActionTotal,
|
|
||||||
MathF.Max(0f, GetShipRefuelTarget(ship, world) - GetInventoryAmount(ship.Inventory, "fuel"))),
|
|
||||||
ShipState.Loading => CreateShipRemainingActionProgress(
|
ShipState.Loading => CreateShipRemainingActionProgress(
|
||||||
"Load workers",
|
"Load workers",
|
||||||
ship.TrackedActionTotal,
|
ship.TrackedActionTotal,
|
||||||
|
|||||||
@@ -109,8 +109,6 @@ public sealed partial class SimulationEngine
|
|||||||
var priorities = GetFactionExpansionPressure(world, station.FactionId) > 0f
|
var priorities = GetFactionExpansionPressure(world, station.FactionId) > 0f
|
||||||
? new (string ModuleId, int TargetCount)[]
|
? new (string ModuleId, int TargetCount)[]
|
||||||
{
|
{
|
||||||
("gas-tank", 1),
|
|
||||||
("fuel-processor", 1),
|
|
||||||
("refinery-stack", 1),
|
("refinery-stack", 1),
|
||||||
("container-bay", 1),
|
("container-bay", 1),
|
||||||
("fabricator-array", 2),
|
("fabricator-array", 2),
|
||||||
@@ -121,8 +119,6 @@ public sealed partial class SimulationEngine
|
|||||||
}
|
}
|
||||||
: new (string ModuleId, int TargetCount)[]
|
: new (string ModuleId, int TargetCount)[]
|
||||||
{
|
{
|
||||||
("gas-tank", 1),
|
|
||||||
("fuel-processor", 1),
|
|
||||||
("refinery-stack", 1),
|
("refinery-stack", 1),
|
||||||
("container-bay", 1),
|
("container-bay", 1),
|
||||||
("fabricator-array", 2),
|
("fabricator-array", 2),
|
||||||
@@ -238,7 +234,7 @@ public sealed partial class SimulationEngine
|
|||||||
{
|
{
|
||||||
var padCount = Math.Max(1, GetDockingPadCount(station));
|
var padCount = Math.Max(1, GetDockingPadCount(station));
|
||||||
var angle = ((MathF.PI * 2f) / padCount) * padIndex;
|
var angle = ((MathF.PI * 2f) / padCount) * padIndex;
|
||||||
var radius = station.Definition.Radius + 18f;
|
var radius = station.Radius + 18f;
|
||||||
return new Vector3(
|
return new Vector3(
|
||||||
station.Position.X + (MathF.Cos(angle) * radius),
|
station.Position.X + (MathF.Cos(angle) * radius),
|
||||||
station.Position.Y,
|
station.Position.Y,
|
||||||
@@ -249,7 +245,7 @@ public sealed partial class SimulationEngine
|
|||||||
{
|
{
|
||||||
var hash = Math.Abs(shipId.GetHashCode(StringComparison.Ordinal));
|
var hash = Math.Abs(shipId.GetHashCode(StringComparison.Ordinal));
|
||||||
var angle = (hash % 360) * (MathF.PI / 180f);
|
var angle = (hash % 360) * (MathF.PI / 180f);
|
||||||
var radius = station.Definition.Radius + 24f;
|
var radius = station.Radius + 24f;
|
||||||
return new Vector3(
|
return new Vector3(
|
||||||
station.Position.X + (MathF.Cos(angle) * radius),
|
station.Position.X + (MathF.Cos(angle) * radius),
|
||||||
station.Position.Y,
|
station.Position.Y,
|
||||||
@@ -288,7 +284,7 @@ public sealed partial class SimulationEngine
|
|||||||
{
|
{
|
||||||
var hash = Math.Abs(shipId.GetHashCode(StringComparison.Ordinal));
|
var hash = Math.Abs(shipId.GetHashCode(StringComparison.Ordinal));
|
||||||
var angle = (hash % 360) * (MathF.PI / 180f);
|
var angle = (hash % 360) * (MathF.PI / 180f);
|
||||||
var radius = station.Definition.Radius + 78f;
|
var radius = station.Radius + 78f;
|
||||||
return new Vector3(
|
return new Vector3(
|
||||||
station.Position.X + (MathF.Cos(angle) * radius),
|
station.Position.X + (MathF.Cos(angle) * radius),
|
||||||
station.Position.Y,
|
station.Position.Y,
|
||||||
|
|||||||
@@ -56,25 +56,12 @@ public sealed partial class SimulationEngine
|
|||||||
if (distance > task.Threshold)
|
if (distance > task.Threshold)
|
||||||
{
|
{
|
||||||
ship.ActionTimer = 0f;
|
ship.ActionTimer = 0f;
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.MoveDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.State = ShipState.MiningApproach;
|
ship.State = ShipState.MiningApproach;
|
||||||
ship.Position = ship.Position.MoveToward(task.TargetPosition.Value, GetLocalTravelSpeed(ship) * deltaSeconds);
|
ship.Position = ship.Position.MoveToward(task.TargetPosition.Value, GetLocalTravelSpeed(ship) * deltaSeconds);
|
||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.MoveDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.State = ShipState.Mining;
|
ship.State = ShipState.Mining;
|
||||||
if (!AdvanceTimedAction(ship, deltaSeconds, world.Balance.MiningCycleSeconds))
|
if (!AdvanceTimedAction(ship, deltaSeconds, world.Balance.MiningCycleSeconds))
|
||||||
{
|
{
|
||||||
@@ -121,7 +108,7 @@ public sealed partial class SimulationEngine
|
|||||||
ship.State = ShipState.AwaitingDock;
|
ship.State = ShipState.AwaitingDock;
|
||||||
ship.TargetPosition = GetDockingHoldPosition(station, ship.Id);
|
ship.TargetPosition = GetDockingHoldPosition(station, ship.Id);
|
||||||
var waitDistance = ship.Position.DistanceTo(ship.TargetPosition);
|
var waitDistance = ship.Position.DistanceTo(ship.TargetPosition);
|
||||||
if (waitDistance > 4f && TryConsumeShipEnergy(ship, world.Balance.Energy.MoveDrain * deltaSeconds))
|
if (waitDistance > 4f)
|
||||||
{
|
{
|
||||||
ship.Position = ship.Position.MoveToward(ship.TargetPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
|
ship.Position = ship.Position.MoveToward(ship.TargetPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
|
||||||
}
|
}
|
||||||
@@ -136,32 +123,12 @@ public sealed partial class SimulationEngine
|
|||||||
if (distance > 4f)
|
if (distance > 4f)
|
||||||
{
|
{
|
||||||
ship.ActionTimer = 0f;
|
ship.ActionTimer = 0f;
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.MoveDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.State = ShipState.DockingApproach;
|
ship.State = ShipState.DockingApproach;
|
||||||
ship.Position = ship.Position.MoveToward(padPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
|
ship.Position = ship.Position.MoveToward(padPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
|
||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.MoveDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryConsumeStationEnergy(station, world.Balance.Energy.IdleDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.State = ShipState.Docking;
|
ship.State = ShipState.Docking;
|
||||||
if (!AdvanceTimedAction(ship, deltaSeconds, world.Balance.DockingDuration))
|
if (!AdvanceTimedAction(ship, deltaSeconds, world.Balance.DockingDuration))
|
||||||
{
|
{
|
||||||
@@ -195,13 +162,6 @@ public sealed partial class SimulationEngine
|
|||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeStationEnergy(station, world.Balance.Energy.IdleDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.TargetPosition = GetShipDockedPosition(ship, station);
|
ship.TargetPosition = GetShipDockedPosition(ship, station);
|
||||||
ship.Position = ship.TargetPosition;
|
ship.Position = ship.TargetPosition;
|
||||||
ship.ActionTimer = 0f;
|
ship.ActionTimer = 0f;
|
||||||
@@ -245,13 +205,6 @@ public sealed partial class SimulationEngine
|
|||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeStationEnergy(station, world.Balance.Energy.IdleDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.TargetPosition = GetShipDockedPosition(ship, station);
|
ship.TargetPosition = GetShipDockedPosition(ship, station);
|
||||||
ship.Position = ship.TargetPosition;
|
ship.Position = ship.TargetPosition;
|
||||||
ship.ActionTimer = 0f;
|
ship.ActionTimer = 0f;
|
||||||
@@ -273,50 +226,6 @@ public sealed partial class SimulationEngine
|
|||||||
: "none";
|
: "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string UpdateRefuel(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
|
|
||||||
{
|
|
||||||
var station = ResolveShipSupportStation(ship, world);
|
|
||||||
if (station is null)
|
|
||||||
{
|
|
||||||
ship.State = ShipState.Idle;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportPosition = ResolveShipSupportPosition(ship, station);
|
|
||||||
if (!IsShipWithinSupportRange(ship, supportPosition, ship.ControllerTask.Threshold))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.LocalFlight;
|
|
||||||
ship.TargetPosition = supportPosition;
|
|
||||||
ship.Position = ship.Position.MoveToward(supportPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds)
|
|
||||||
|| !TryConsumeStationEnergy(station, world.Balance.Energy.IdleDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.TargetPosition = supportPosition;
|
|
||||||
ship.Position = ship.TargetPosition;
|
|
||||||
ship.ActionTimer = 0f;
|
|
||||||
ship.State = ShipState.Refueling;
|
|
||||||
var refuelTarget = GetShipRefuelTarget(ship, world);
|
|
||||||
BeginTrackedAction(ship, "refueling", MathF.Max(0f, refuelTarget - GetInventoryAmount(ship.Inventory, "fuel")));
|
|
||||||
var transfer = MathF.Min(world.Balance.TransferRate * deltaSeconds, refuelTarget - GetInventoryAmount(ship.Inventory, "fuel"));
|
|
||||||
var moved = MathF.Min(transfer, GetInventoryAmount(station.Inventory, "fuel"));
|
|
||||||
if (moved > 0.01f)
|
|
||||||
{
|
|
||||||
RemoveInventory(station.Inventory, "fuel", moved);
|
|
||||||
AddInventory(ship.Inventory, "fuel", moved);
|
|
||||||
}
|
|
||||||
|
|
||||||
return !NeedsRefuel(ship, world) ? "refueled" : "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
private string UpdateConstructModule(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
|
private string UpdateConstructModule(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
|
||||||
{
|
{
|
||||||
var station = ResolveShipSupportStation(ship, world);
|
var station = ResolveShipSupportStation(ship, world);
|
||||||
@@ -344,14 +253,6 @@ public sealed partial class SimulationEngine
|
|||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds)
|
|
||||||
|| !TryConsumeStationEnergy(station, world.Balance.Energy.MoveDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryEnsureModuleConstructionStarted(station, recipe, ship.Id))
|
if (!TryEnsureModuleConstructionStarted(station, recipe, ship.Id))
|
||||||
{
|
{
|
||||||
ship.ActionTimer = 0f;
|
ship.ActionTimer = 0f;
|
||||||
@@ -377,7 +278,7 @@ public sealed partial class SimulationEngine
|
|||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
station.InstalledModules.Add(station.ActiveConstruction.ModuleId);
|
AddStationModule(world, station, station.ActiveConstruction.ModuleId);
|
||||||
station.ActiveConstruction = null;
|
station.ActiveConstruction = null;
|
||||||
return "module-constructed";
|
return "module-constructed";
|
||||||
}
|
}
|
||||||
@@ -409,14 +310,6 @@ public sealed partial class SimulationEngine
|
|||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds)
|
|
||||||
|| !TryConsumeStationEnergy(station, world.Balance.Energy.IdleDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.TargetPosition = supportPosition;
|
ship.TargetPosition = supportPosition;
|
||||||
ship.Position = ship.TargetPosition;
|
ship.Position = ship.TargetPosition;
|
||||||
ship.ActionTimer = 0f;
|
ship.ActionTimer = 0f;
|
||||||
@@ -487,14 +380,6 @@ public sealed partial class SimulationEngine
|
|||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds)
|
|
||||||
|| !TryConsumeStationEnergy(station, world.Balance.Energy.MoveDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.TargetPosition = supportPosition;
|
ship.TargetPosition = supportPosition;
|
||||||
ship.Position = ship.TargetPosition;
|
ship.Position = ship.TargetPosition;
|
||||||
ship.ActionTimer = 0f;
|
ship.ActionTimer = 0f;
|
||||||
@@ -506,7 +391,7 @@ public sealed partial class SimulationEngine
|
|||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
station.InstalledModules.Add(site.BlueprintId);
|
AddStationModule(world, station, site.BlueprintId);
|
||||||
PrepareNextConstructionSiteStep(world, station, site);
|
PrepareNextConstructionSiteStep(world, station, site);
|
||||||
return "site-constructed";
|
return "site-constructed";
|
||||||
}
|
}
|
||||||
@@ -601,19 +486,6 @@ public sealed partial class SimulationEngine
|
|||||||
? task.TargetPosition.Value
|
? task.TargetPosition.Value
|
||||||
: GetUndockTargetPosition(station, ship.AssignedDockingPadIndex, world.Balance.UndockDistance);
|
: GetUndockTargetPosition(station, ship.AssignedDockingPadIndex, world.Balance.UndockDistance);
|
||||||
ship.TargetPosition = undockTarget;
|
ship.TargetPosition = undockTarget;
|
||||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.MoveDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (station is not null && !TryConsumeStationEnergy(station, world.Balance.Energy.IdleDrain * deltaSeconds))
|
|
||||||
{
|
|
||||||
ship.State = ShipState.CapacitorStarved;
|
|
||||||
ship.TargetPosition = ship.Position;
|
|
||||||
return "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
ship.State = ShipState.Undocking;
|
ship.State = ShipState.Undocking;
|
||||||
if (!AdvanceTimedAction(ship, deltaSeconds, world.Balance.UndockingDuration))
|
if (!AdvanceTimedAction(ship, deltaSeconds, world.Balance.UndockingDuration))
|
||||||
|
|||||||
@@ -160,10 +160,6 @@ public sealed partial class SimulationEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
behavior.NodeId ??= node.Id;
|
behavior.NodeId ??= node.Id;
|
||||||
if (NeedsEmergencyReturn(ship, world) && behavior.Phase is "travel-to-node" or "extract")
|
|
||||||
{
|
|
||||||
behavior.Phase = "travel-to-station";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GetShipCargoAmount(ship) >= ship.Definition.CargoCapacity - 0.01f
|
if (GetShipCargoAmount(ship) >= ship.Definition.CargoCapacity - 0.01f
|
||||||
&& behavior.Phase is "travel-to-node" or "extract")
|
&& behavior.Phase is "travel-to-node" or "extract")
|
||||||
@@ -177,21 +173,12 @@ public sealed partial class SimulationEngine
|
|||||||
{
|
{
|
||||||
behavior.Phase = "unload";
|
behavior.Phase = "unload";
|
||||||
}
|
}
|
||||||
else if (behavior.Phase == "undock")
|
else if (behavior.Phase is "dock" or "unload")
|
||||||
{
|
|
||||||
// Keep the post-refuel departure decision stable for the current dock cycle.
|
|
||||||
behavior.Phase = "undock";
|
|
||||||
}
|
|
||||||
else if (NeedsRefuel(ship, world))
|
|
||||||
{
|
|
||||||
behavior.Phase = "refuel";
|
|
||||||
}
|
|
||||||
else if (behavior.Phase is "dock" or "unload" or "refuel")
|
|
||||||
{
|
{
|
||||||
behavior.Phase = "undock";
|
behavior.Phase = "undock";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (NeedsRefuel(ship, world) && behavior.Phase is not "travel-to-station" and not "dock" and not "travel-to-node" and not "extract")
|
else if (behavior.Phase is not "travel-to-station" and not "dock" and not "travel-to-node" and not "extract")
|
||||||
{
|
{
|
||||||
behavior.Phase = "travel-to-station";
|
behavior.Phase = "travel-to-station";
|
||||||
}
|
}
|
||||||
@@ -216,7 +203,7 @@ public sealed partial class SimulationEngine
|
|||||||
TargetEntityId = refinery.Id,
|
TargetEntityId = refinery.Id,
|
||||||
TargetSystemId = refinery.SystemId,
|
TargetSystemId = refinery.SystemId,
|
||||||
TargetPosition = refinery.Position,
|
TargetPosition = refinery.Position,
|
||||||
Threshold = refinery.Definition.Radius + 8f,
|
Threshold = refinery.Radius + 8f,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "dock":
|
case "dock":
|
||||||
@@ -226,7 +213,7 @@ public sealed partial class SimulationEngine
|
|||||||
TargetEntityId = refinery.Id,
|
TargetEntityId = refinery.Id,
|
||||||
TargetSystemId = refinery.SystemId,
|
TargetSystemId = refinery.SystemId,
|
||||||
TargetPosition = refinery.Position,
|
TargetPosition = refinery.Position,
|
||||||
Threshold = refinery.Definition.Radius + 4f,
|
Threshold = refinery.Radius + 4f,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "unload":
|
case "unload":
|
||||||
@@ -239,16 +226,6 @@ public sealed partial class SimulationEngine
|
|||||||
Threshold = 0f,
|
Threshold = 0f,
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case "refuel":
|
|
||||||
ship.ControllerTask = new ControllerTaskRuntime
|
|
||||||
{
|
|
||||||
Kind = ControllerTaskKind.Refuel,
|
|
||||||
TargetEntityId = refinery.Id,
|
|
||||||
TargetSystemId = refinery.SystemId,
|
|
||||||
TargetPosition = refinery.Position,
|
|
||||||
Threshold = 0f,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case "undock":
|
case "undock":
|
||||||
ship.ControllerTask = new ControllerTaskRuntime
|
ship.ControllerTask = new ControllerTaskRuntime
|
||||||
{
|
{
|
||||||
@@ -298,107 +275,6 @@ public sealed partial class SimulationEngine
|
|||||||
return bestOrder.Station ?? preferred;
|
return bestOrder.Station ?? preferred;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static StationRuntime? SelectBestSellStation(SimulationWorld world, ShipRuntime ship, string itemId, string? preferredStationId)
|
|
||||||
{
|
|
||||||
var preferred = preferredStationId is null
|
|
||||||
? null
|
|
||||||
: world.Stations.FirstOrDefault(station => station.Id == preferredStationId);
|
|
||||||
|
|
||||||
var bestOrder = world.MarketOrders
|
|
||||||
.Where(order =>
|
|
||||||
order.Kind == MarketOrderKinds.Sell &&
|
|
||||||
order.ConstructionSiteId is null &&
|
|
||||||
order.State != MarketOrderStateKinds.Cancelled &&
|
|
||||||
order.ItemId == itemId &&
|
|
||||||
order.RemainingAmount > 0.01f)
|
|
||||||
.Select(order => (Order: order, Station: world.Stations.FirstOrDefault(station => station.Id == order.StationId)))
|
|
||||||
.Where(entry => entry.Station is not null && GetInventoryAmount(entry.Station!.Inventory, itemId) > 0.01f)
|
|
||||||
.OrderByDescending(entry =>
|
|
||||||
{
|
|
||||||
var distancePenalty = entry.Station!.SystemId == ship.SystemId ? 0f : 0.2f;
|
|
||||||
return entry.Order.Valuation - distancePenalty;
|
|
||||||
})
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
return bestOrder.Station ?? preferred;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void PlanEnergySupply(ShipRuntime ship, SimulationWorld world)
|
|
||||||
{
|
|
||||||
var behavior = ship.DefaultBehavior;
|
|
||||||
var cargoItemId = ship.Definition.CargoItemId;
|
|
||||||
if (cargoItemId is null)
|
|
||||||
{
|
|
||||||
behavior.Kind = "idle";
|
|
||||||
ship.ControllerTask = CreateIdleTask(world.Balance.ArrivalThreshold);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cargoAmount = GetShipCargoAmount(ship);
|
|
||||||
if (cargoAmount > 0.01f)
|
|
||||||
{
|
|
||||||
var destination = SelectBestBuyStation(world, ship, cargoItemId, behavior.StationId);
|
|
||||||
if (destination is null)
|
|
||||||
{
|
|
||||||
ship.ControllerTask = CreateIdleTask(world.Balance.ArrivalThreshold);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
behavior.StationId = destination.Id;
|
|
||||||
switch (behavior.Phase)
|
|
||||||
{
|
|
||||||
case "dock":
|
|
||||||
case "unload":
|
|
||||||
case "refuel":
|
|
||||||
case "undock":
|
|
||||||
ship.ControllerTask = CreateStationSupportTask(world, ship, destination, behavior.Phase);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
behavior.Phase = "travel-to-destination";
|
|
||||||
ship.ControllerTask = new ControllerTaskRuntime
|
|
||||||
{
|
|
||||||
Kind = ControllerTaskKind.Travel,
|
|
||||||
TargetEntityId = destination.Id,
|
|
||||||
TargetSystemId = destination.SystemId,
|
|
||||||
TargetPosition = destination.Position,
|
|
||||||
Threshold = 18f,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var source = SelectBestSellStation(world, ship, cargoItemId, behavior.StationId);
|
|
||||||
if (source is null)
|
|
||||||
{
|
|
||||||
ship.ControllerTask = CreateIdleTask(world.Balance.ArrivalThreshold);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
behavior.StationId = source.Id;
|
|
||||||
switch (behavior.Phase)
|
|
||||||
{
|
|
||||||
case "dock":
|
|
||||||
case "load":
|
|
||||||
case "refuel":
|
|
||||||
case "undock":
|
|
||||||
ship.ControllerTask = CreateStationSupportTask(world, ship, source, behavior.Phase);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
behavior.Phase = "travel-to-source";
|
|
||||||
ship.ControllerTask = new ControllerTaskRuntime
|
|
||||||
{
|
|
||||||
Kind = ControllerTaskKind.Travel,
|
|
||||||
TargetEntityId = source.Id,
|
|
||||||
TargetSystemId = source.SystemId,
|
|
||||||
TargetPosition = source.Position,
|
|
||||||
Threshold = 18f,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ControllerTaskRuntime CreateStationSupportTask(SimulationWorld world, ShipRuntime ship, StationRuntime station, string? phase) =>
|
private static ControllerTaskRuntime CreateStationSupportTask(SimulationWorld world, ShipRuntime ship, StationRuntime station, string? phase) =>
|
||||||
phase switch
|
phase switch
|
||||||
{
|
{
|
||||||
@@ -426,14 +302,6 @@ public sealed partial class SimulationEngine
|
|||||||
TargetPosition = station.Position,
|
TargetPosition = station.Position,
|
||||||
Threshold = 8f,
|
Threshold = 8f,
|
||||||
},
|
},
|
||||||
"refuel" => new ControllerTaskRuntime
|
|
||||||
{
|
|
||||||
Kind = ControllerTaskKind.Refuel,
|
|
||||||
TargetEntityId = station.Id,
|
|
||||||
TargetSystemId = station.SystemId,
|
|
||||||
TargetPosition = station.Position,
|
|
||||||
Threshold = 12f,
|
|
||||||
},
|
|
||||||
"undock" => new ControllerTaskRuntime
|
"undock" => new ControllerTaskRuntime
|
||||||
{
|
{
|
||||||
Kind = ControllerTaskKind.Undock,
|
Kind = ControllerTaskKind.Undock,
|
||||||
@@ -486,11 +354,7 @@ public sealed partial class SimulationEngine
|
|||||||
|
|
||||||
if (isAtConstructionHold)
|
if (isAtConstructionHold)
|
||||||
{
|
{
|
||||||
if (NeedsRefuel(ship, world))
|
if (site is not null && site.State == ConstructionSiteStateKinds.Active && !IsConstructionSiteReady(world, site))
|
||||||
{
|
|
||||||
behavior.Phase = "refuel";
|
|
||||||
}
|
|
||||||
else if (site is not null && site.State == ConstructionSiteStateKinds.Active && !IsConstructionSiteReady(world, site))
|
|
||||||
{
|
{
|
||||||
behavior.Phase = "deliver-to-site";
|
behavior.Phase = "deliver-to-site";
|
||||||
}
|
}
|
||||||
@@ -518,16 +382,6 @@ public sealed partial class SimulationEngine
|
|||||||
|
|
||||||
switch (behavior.Phase)
|
switch (behavior.Phase)
|
||||||
{
|
{
|
||||||
case "refuel":
|
|
||||||
ship.ControllerTask = new ControllerTaskRuntime
|
|
||||||
{
|
|
||||||
Kind = ControllerTaskKind.Refuel,
|
|
||||||
TargetEntityId = station.Id,
|
|
||||||
TargetSystemId = station.SystemId,
|
|
||||||
TargetPosition = constructionHoldPosition,
|
|
||||||
Threshold = 10f,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case "construct-module":
|
case "construct-module":
|
||||||
ship.ControllerTask = new ControllerTaskRuntime
|
ship.ControllerTask = new ControllerTaskRuntime
|
||||||
{
|
{
|
||||||
@@ -649,7 +503,6 @@ public sealed partial class SimulationEngine
|
|||||||
"dock" => ControllerTaskKind.Dock,
|
"dock" => ControllerTaskKind.Dock,
|
||||||
"load" => ControllerTaskKind.Load,
|
"load" => ControllerTaskKind.Load,
|
||||||
"unload" => ControllerTaskKind.Unload,
|
"unload" => ControllerTaskKind.Unload,
|
||||||
"refuel" => ControllerTaskKind.Refuel,
|
|
||||||
"deliver-construction" => ControllerTaskKind.DeliverConstruction,
|
"deliver-construction" => ControllerTaskKind.DeliverConstruction,
|
||||||
"build-construction-site" => ControllerTaskKind.BuildConstructionSite,
|
"build-construction-site" => ControllerTaskKind.BuildConstructionSite,
|
||||||
"load-workers" => ControllerTaskKind.LoadWorkers,
|
"load-workers" => ControllerTaskKind.LoadWorkers,
|
||||||
|
|||||||
@@ -26,16 +26,15 @@ public sealed partial class SimulationEngine
|
|||||||
|
|
||||||
private void UpdateStationPopulation(StationRuntime station, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
private void UpdateStationPopulation(StationRuntime station, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||||
{
|
{
|
||||||
station.WorkforceRequired = MathF.Max(12f, station.InstalledModules.Count * 14f);
|
station.WorkforceRequired = MathF.Max(12f, station.Modules.Count * 14f);
|
||||||
|
|
||||||
var requiredWater = station.Population * WaterConsumptionPerWorkerPerSecond * deltaSeconds;
|
var requiredWater = station.Population * WaterConsumptionPerWorkerPerSecond * deltaSeconds;
|
||||||
var consumedWater = RemoveInventory(station.Inventory, "water", requiredWater);
|
var consumedWater = RemoveInventory(station.Inventory, "water", requiredWater);
|
||||||
var waterSatisfied = requiredWater <= 0.01f || consumedWater + 0.001f >= requiredWater;
|
var waterSatisfied = requiredWater <= 0.01f || consumedWater + 0.001f >= requiredWater;
|
||||||
var hasPower = station.EnergyStored > 0.01f;
|
|
||||||
var habitatModules = CountModules(station.InstalledModules, "habitat-ring");
|
var habitatModules = CountModules(station.InstalledModules, "habitat-ring");
|
||||||
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
||||||
|
|
||||||
if (waterSatisfied && hasPower)
|
if (waterSatisfied)
|
||||||
{
|
{
|
||||||
if (habitatModules > 0 && station.Population < station.PopulationCapacity)
|
if (habitatModules > 0 && station.Population < station.PopulationCapacity)
|
||||||
{
|
{
|
||||||
@@ -48,7 +47,7 @@ public sealed partial class SimulationEngine
|
|||||||
station.Population = MathF.Max(0f, station.Population - (PopulationAttritionPerSecond * deltaSeconds));
|
station.Population = MathF.Max(0f, station.Population - (PopulationAttritionPerSecond * deltaSeconds));
|
||||||
if (MathF.Floor(previous) > MathF.Floor(station.Population))
|
if (MathF.Floor(previous) > MathF.Floor(station.Population))
|
||||||
{
|
{
|
||||||
events.Add(new SimulationEventRecord("station", station.Id, "population-loss", $"{station.Definition.Label} lost population due to support shortages.", DateTimeOffset.UtcNow));
|
events.Add(new SimulationEventRecord("station", station.Id, "population-loss", $"{station.Label} lost population due to support shortages.", DateTimeOffset.UtcNow));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,31 +62,22 @@ public sealed partial class SimulationEngine
|
|||||||
}
|
}
|
||||||
|
|
||||||
var desiredOrders = new List<DesiredMarketOrder>();
|
var desiredOrders = new List<DesiredMarketOrder>();
|
||||||
var fuelReserve = MathF.Max(80f, CountModules(station.InstalledModules, "power-core") * 140f);
|
|
||||||
var energyCellReserve = HasStationModules(station, "power-core", "liquid-tank") ? MathF.Max(20f, CountModules(station.InstalledModules, "power-core") * 40f) : 0f;
|
|
||||||
var waterReserve = MathF.Max(30f, station.Population * 3f);
|
var waterReserve = MathF.Max(30f, station.Population * 3f);
|
||||||
var refinedReserve = HasStationModules(station, "fabricator-array") ? 140f : 40f;
|
var refinedReserve = HasStationModules(station, "fabricator-array") ? 140f : 40f;
|
||||||
var oreReserve = HasRefineryCapability(station) ? 180f : 0f;
|
var oreReserve = HasRefineryCapability(station) ? 180f : 0f;
|
||||||
var gasReserve = CanProcessFuel(station) ? 120f : 0f;
|
|
||||||
var shipPartsReserve = HasStationModules(station, "fabricator-array")
|
var shipPartsReserve = HasStationModules(station, "fabricator-array")
|
||||||
&& !HasStationModules(station, "component-factory", "ship-factory")
|
&& !HasStationModules(station, "component-factory", "ship-factory")
|
||||||
&& FactionNeedsMoreWarships(world, station.FactionId)
|
&& FactionNeedsMoreWarships(world, station.FactionId)
|
||||||
? 90f
|
? 90f
|
||||||
: 0f;
|
: 0f;
|
||||||
|
|
||||||
AddDemandOrder(desiredOrders, station, "fuel", fuelReserve, valuationBase: 1.2f);
|
|
||||||
AddDemandOrder(desiredOrders, station, "energy-cell", energyCellReserve, valuationBase: 1.1f);
|
|
||||||
AddDemandOrder(desiredOrders, station, "water", waterReserve, valuationBase: 1.1f);
|
AddDemandOrder(desiredOrders, station, "water", waterReserve, valuationBase: 1.1f);
|
||||||
AddDemandOrder(desiredOrders, station, "ore", oreReserve, valuationBase: 1.0f);
|
AddDemandOrder(desiredOrders, station, "ore", oreReserve, valuationBase: 1.0f);
|
||||||
AddDemandOrder(desiredOrders, station, "gas", gasReserve, valuationBase: 0.95f);
|
|
||||||
AddDemandOrder(desiredOrders, station, "refined-metals", refinedReserve, valuationBase: 1.15f);
|
AddDemandOrder(desiredOrders, station, "refined-metals", refinedReserve, valuationBase: 1.15f);
|
||||||
AddDemandOrder(desiredOrders, station, "ship-parts", shipPartsReserve, valuationBase: 1.3f);
|
AddDemandOrder(desiredOrders, station, "ship-parts", shipPartsReserve, valuationBase: 1.3f);
|
||||||
|
|
||||||
AddSupplyOrder(desiredOrders, station, "fuel", fuelReserve * 1.5f, reserveFloor: fuelReserve, valuationBase: 0.8f);
|
|
||||||
AddSupplyOrder(desiredOrders, station, "energy-cell", energyCellReserve * 1.8f, reserveFloor: energyCellReserve, valuationBase: 0.82f);
|
|
||||||
AddSupplyOrder(desiredOrders, station, "water", waterReserve * 1.5f, reserveFloor: waterReserve, valuationBase: 0.65f);
|
AddSupplyOrder(desiredOrders, station, "water", waterReserve * 1.5f, reserveFloor: waterReserve, valuationBase: 0.65f);
|
||||||
AddSupplyOrder(desiredOrders, station, "ore", oreReserve * 1.4f, reserveFloor: oreReserve, valuationBase: 0.7f);
|
AddSupplyOrder(desiredOrders, station, "ore", oreReserve * 1.4f, reserveFloor: oreReserve, valuationBase: 0.7f);
|
||||||
AddSupplyOrder(desiredOrders, station, "gas", gasReserve * 1.4f, reserveFloor: gasReserve, valuationBase: 0.72f);
|
|
||||||
AddSupplyOrder(desiredOrders, station, "refined-metals", refinedReserve * 1.4f, reserveFloor: refinedReserve, valuationBase: 0.95f);
|
AddSupplyOrder(desiredOrders, station, "refined-metals", refinedReserve * 1.4f, reserveFloor: refinedReserve, valuationBase: 0.95f);
|
||||||
|
|
||||||
ReconcileStationMarketOrders(world, station, desiredOrders);
|
ReconcileStationMarketOrders(world, station, desiredOrders);
|
||||||
@@ -99,18 +89,13 @@ public sealed partial class SimulationEngine
|
|||||||
foreach (var laneKey in GetStationProductionLanes(station))
|
foreach (var laneKey in GetStationProductionLanes(station))
|
||||||
{
|
{
|
||||||
var recipe = SelectProductionRecipe(world, station, laneKey);
|
var recipe = SelectProductionRecipe(world, station, laneKey);
|
||||||
if (recipe is null || station.EnergyStored <= 0.01f)
|
if (recipe is null)
|
||||||
{
|
{
|
||||||
station.ProductionLaneTimers[laneKey] = 0f;
|
station.ProductionLaneTimers[laneKey] = 0f;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var throughput = GetStationProductionThroughput(station, recipe);
|
var throughput = GetStationProductionThroughput(station, recipe);
|
||||||
if (!TryConsumeStationEnergy(station, world.Balance.Energy.MoveDrain * deltaSeconds * throughput))
|
|
||||||
{
|
|
||||||
station.ProductionLaneTimers[laneKey] = 0f;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var produced = 0f;
|
var produced = 0f;
|
||||||
station.ProductionLaneTimers[laneKey] = GetStationProductionTimer(station, laneKey) + (deltaSeconds * station.WorkforceEffectiveRatio * throughput);
|
station.ProductionLaneTimers[laneKey] = GetStationProductionTimer(station, laneKey) + (deltaSeconds * station.WorkforceEffectiveRatio * throughput);
|
||||||
@@ -139,7 +124,7 @@ public sealed partial class SimulationEngine
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
events.Add(new SimulationEventRecord("station", station.Id, "production-complete", $"{station.Definition.Label} completed {recipe.Label}.", DateTimeOffset.UtcNow));
|
events.Add(new SimulationEventRecord("station", station.Id, "production-complete", $"{station.Label} completed {recipe.Label}.", DateTimeOffset.UtcNow));
|
||||||
if (faction is not null)
|
if (faction is not null)
|
||||||
{
|
{
|
||||||
faction.GoodsProduced += produced;
|
faction.GoodsProduced += produced;
|
||||||
@@ -154,11 +139,6 @@ public sealed partial class SimulationEngine
|
|||||||
yield return "refinery";
|
yield return "refinery";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CountModules(station.InstalledModules, "fuel-processor") > 0)
|
|
||||||
{
|
|
||||||
yield return "fuel";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CountModules(station.InstalledModules, "fabricator-array") > 0)
|
if (CountModules(station.InstalledModules, "fabricator-array") > 0)
|
||||||
{
|
{
|
||||||
yield return "fabrication";
|
yield return "fabrication";
|
||||||
@@ -186,11 +166,6 @@ public sealed partial class SimulationEngine
|
|||||||
|
|
||||||
private static string? GetStationProductionLaneKey(RecipeDefinition recipe)
|
private static string? GetStationProductionLaneKey(RecipeDefinition recipe)
|
||||||
{
|
{
|
||||||
if (recipe.RequiredModules.Contains("fuel-processor", StringComparer.Ordinal))
|
|
||||||
{
|
|
||||||
return "fuel";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipe.RequiredModules.Contains("refinery-stack", StringComparer.Ordinal))
|
if (recipe.RequiredModules.Contains("refinery-stack", StringComparer.Ordinal))
|
||||||
{
|
{
|
||||||
return "refinery";
|
return "refinery";
|
||||||
@@ -217,13 +192,6 @@ public sealed partial class SimulationEngine
|
|||||||
private static float GetStationRecipePriority(SimulationWorld world, StationRuntime station, RecipeDefinition recipe)
|
private static float GetStationRecipePriority(SimulationWorld world, StationRuntime station, RecipeDefinition recipe)
|
||||||
{
|
{
|
||||||
var priority = (float)recipe.Priority;
|
var priority = (float)recipe.Priority;
|
||||||
var producesFuel = recipe.Outputs.Any(output => string.Equals(output.ItemId, "fuel", StringComparison.Ordinal));
|
|
||||||
if (producesFuel)
|
|
||||||
{
|
|
||||||
var fuelCapacity = MathF.Max(GetStationFuelCapacity(station), 1f);
|
|
||||||
var fuelRatio = GetInventoryAmount(station.Inventory, "fuel") / fuelCapacity;
|
|
||||||
priority += (1f - Math.Clamp(fuelRatio, 0f, 1f)) * 200f;
|
|
||||||
}
|
|
||||||
|
|
||||||
var expansionPressure = GetFactionExpansionPressure(world, station.FactionId);
|
var expansionPressure = GetFactionExpansionPressure(world, station.FactionId);
|
||||||
var fleetPressure = FactionNeedsMoreWarships(world, station.FactionId) ? 1f : 0f;
|
var fleetPressure = FactionNeedsMoreWarships(world, station.FactionId) ? 1f : 0f;
|
||||||
@@ -251,9 +219,9 @@ public sealed partial class SimulationEngine
|
|||||||
|
|
||||||
private static bool RecipeAppliesToStation(StationRuntime station, RecipeDefinition recipe)
|
private static bool RecipeAppliesToStation(StationRuntime station, RecipeDefinition recipe)
|
||||||
{
|
{
|
||||||
var categoryMatch = string.Equals(station.Definition.Category, recipe.FacilityCategory, StringComparison.Ordinal)
|
var categoryMatch = string.Equals(recipe.FacilityCategory, "station", StringComparison.Ordinal)
|
||||||
|| (string.Equals(recipe.FacilityCategory, "station", StringComparison.Ordinal)
|
|| string.Equals(recipe.FacilityCategory, "farm", StringComparison.Ordinal)
|
||||||
&& station.Definition.Category is "station" or "shipyard" or "defense" or "gate");
|
|| string.Equals(recipe.FacilityCategory, station.Category, StringComparison.Ordinal);
|
||||||
return categoryMatch && recipe.RequiredModules.All(moduleId => station.InstalledModules.Contains(moduleId, StringComparer.Ordinal));
|
return categoryMatch && recipe.RequiredModules.All(moduleId => station.InstalledModules.Contains(moduleId, StringComparer.Ordinal));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,20 +257,20 @@ public sealed partial class SimulationEngine
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var requiredModule = GetStorageRequirement(itemDefinition.Storage);
|
var requiredModule = GetStorageRequirement(itemDefinition.CargoKind);
|
||||||
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var capacity = GetStationStorageCapacity(station, itemDefinition.Storage);
|
var capacity = GetStationStorageCapacity(station, itemDefinition.CargoKind);
|
||||||
if (capacity <= 0.01f)
|
if (capacity <= 0.01f)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var used = station.Inventory
|
var used = station.Inventory
|
||||||
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.Storage == itemDefinition.Storage)
|
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == itemDefinition.CargoKind)
|
||||||
.Sum(entry => entry.Value);
|
.Sum(entry => entry.Value);
|
||||||
return used + amount <= capacity + 0.001f;
|
return used + amount <= capacity + 0.001f;
|
||||||
}
|
}
|
||||||
@@ -387,9 +355,6 @@ public sealed partial class SimulationEngine
|
|||||||
private static bool HasRefineryCapability(StationRuntime station) =>
|
private static bool HasRefineryCapability(StationRuntime station) =>
|
||||||
HasStationModules(station, "refinery-stack", "power-core", "bulk-bay");
|
HasStationModules(station, "refinery-stack", "power-core", "bulk-bay");
|
||||||
|
|
||||||
private static bool CanProcessFuel(StationRuntime station) =>
|
|
||||||
HasStationModules(station, "fuel-processor", "power-core", "gas-tank", "liquid-tank");
|
|
||||||
|
|
||||||
private float CompleteShipRecipe(SimulationWorld world, StationRuntime station, RecipeDefinition recipe, ICollection<SimulationEventRecord> events)
|
private float CompleteShipRecipe(SimulationWorld world, StationRuntime station, RecipeDefinition recipe, ICollection<SimulationEventRecord> events)
|
||||||
{
|
{
|
||||||
if (recipe.ShipOutputId is null || !world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var definition))
|
if (recipe.ShipOutputId is null || !world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var definition))
|
||||||
@@ -397,7 +362,7 @@ public sealed partial class SimulationEngine
|
|||||||
return 0f;
|
return 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
var spawnPosition = new Vector3(station.Position.X + station.Definition.Radius + 32f, station.Position.Y, station.Position.Z);
|
var spawnPosition = new Vector3(station.Position.X + GetStationRadius(world, station) + 32f, station.Position.Y, station.Position.Z);
|
||||||
var ship = new ShipRuntime
|
var ship = new ShipRuntime
|
||||||
{
|
{
|
||||||
Id = $"ship-{world.Ships.Count + 1}",
|
Id = $"ship-{world.Ships.Count + 1}",
|
||||||
@@ -412,14 +377,13 @@ public sealed partial class SimulationEngine
|
|||||||
Health = definition.MaxHealth,
|
Health = definition.MaxHealth,
|
||||||
};
|
};
|
||||||
|
|
||||||
ship.Inventory["fuel"] = 120f;
|
|
||||||
world.Ships.Add(ship);
|
world.Ships.Add(ship);
|
||||||
if (world.Factions.FirstOrDefault(candidate => candidate.Id == station.FactionId) is { } faction)
|
if (world.Factions.FirstOrDefault(candidate => candidate.Id == station.FactionId) is { } faction)
|
||||||
{
|
{
|
||||||
faction.ShipsBuilt += 1;
|
faction.ShipsBuilt += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
events.Add(new SimulationEventRecord("station", station.Id, "ship-built", $"{station.Definition.Label} launched {definition.Label}.", DateTimeOffset.UtcNow));
|
events.Add(new SimulationEventRecord("station", station.Id, "ship-built", $"{station.Label} launched {definition.Label}.", DateTimeOffset.UtcNow));
|
||||||
return 1f;
|
return 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,7 +456,7 @@ public sealed partial class SimulationEngine
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var patrolRadius = station.Definition.Radius + 90f;
|
var patrolRadius = station.Radius + 90f;
|
||||||
return new DefaultBehaviorRuntime
|
return new DefaultBehaviorRuntime
|
||||||
{
|
{
|
||||||
Kind = "patrol",
|
Kind = "patrol",
|
||||||
@@ -513,11 +477,6 @@ public sealed partial class SimulationEngine
|
|||||||
return Math.Max(1, CountModules(station.InstalledModules, "refinery-stack"));
|
return Math.Max(1, CountModules(station.InstalledModules, "refinery-stack"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recipe.RequiredModules.Contains("fuel-processor", StringComparer.Ordinal))
|
|
||||||
{
|
|
||||||
return Math.Max(1, CountModules(station.InstalledModules, "fuel-processor"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipe.RequiredModules.Contains("fabricator-array", StringComparer.Ordinal))
|
if (recipe.RequiredModules.Contains("fabricator-array", StringComparer.Ordinal))
|
||||||
{
|
{
|
||||||
return Math.Max(1, CountModules(station.InstalledModules, "fabricator-array"));
|
return Math.Max(1, CountModules(station.InstalledModules, "fabricator-array"));
|
||||||
|
|||||||
@@ -5,12 +5,6 @@ namespace SpaceGame.Simulation.Api.Simulation;
|
|||||||
public sealed partial class SimulationEngine
|
public sealed partial class SimulationEngine
|
||||||
{
|
{
|
||||||
private readonly OrbitalSimulationOptions _orbitalSimulation;
|
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 WaterConsumptionPerWorkerPerSecond = 0.004f;
|
||||||
private const float PopulationGrowthPerSecond = 0.012f;
|
private const float PopulationGrowthPerSecond = 0.012f;
|
||||||
private const float PopulationAttritionPerSecond = 0.018f;
|
private const float PopulationAttritionPerSecond = 0.018f;
|
||||||
@@ -20,12 +14,10 @@ public sealed partial class SimulationEngine
|
|||||||
new((engine, world, deltaSeconds, nowUtc, events) => engine.UpdateOrbitalState(world)),
|
new((engine, world, deltaSeconds, nowUtc, events) => engine.UpdateOrbitalState(world)),
|
||||||
new((engine, world, deltaSeconds, nowUtc, events) => UpdateClaims(world, events)),
|
new((engine, world, deltaSeconds, nowUtc, events) => UpdateClaims(world, events)),
|
||||||
new((engine, world, deltaSeconds, nowUtc, events) => UpdateConstructionSites(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)),
|
new((engine, world, deltaSeconds, nowUtc, events) => engine.UpdateStations(world, deltaSeconds, events)),
|
||||||
];
|
];
|
||||||
private static readonly IReadOnlyList<ShipUpdateStep> _shipUpdatePipeline =
|
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.RefreshControlLayers(ship, world)),
|
||||||
new((engine, ship, world, deltaSeconds, events) => engine.PlanControllerTask(ship, world)),
|
new((engine, ship, world, deltaSeconds, events) => engine.PlanControllerTask(ship, world)),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ export interface StationActionProgressSnapshot {
|
|||||||
progress: number;
|
progress: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StationStorageUsageSnapshot {
|
||||||
|
storageClass: string;
|
||||||
|
used: number;
|
||||||
|
capacity: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface StationSnapshot {
|
export interface StationSnapshot {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -19,10 +25,6 @@ export interface StationSnapshot {
|
|||||||
dockedShips: number;
|
dockedShips: number;
|
||||||
dockedShipIds: string[];
|
dockedShipIds: string[];
|
||||||
dockingPads: number;
|
dockingPads: number;
|
||||||
fuelStored: number;
|
|
||||||
fuelCapacity: number;
|
|
||||||
energyStored: number;
|
|
||||||
energyCapacity: number;
|
|
||||||
currentProcesses: StationActionProgressSnapshot[];
|
currentProcesses: StationActionProgressSnapshot[];
|
||||||
inventory: InventoryEntry[];
|
inventory: InventoryEntry[];
|
||||||
factionId: string;
|
factionId: string;
|
||||||
@@ -32,11 +34,12 @@ export interface StationSnapshot {
|
|||||||
populationCapacity: number;
|
populationCapacity: number;
|
||||||
workforceRequired: number;
|
workforceRequired: number;
|
||||||
workforceEffectiveRatio: number;
|
workforceEffectiveRatio: number;
|
||||||
|
storageUsage: StationStorageUsageSnapshot[];
|
||||||
installedModules: string[];
|
installedModules: string[];
|
||||||
marketOrderIds: string[];
|
marketOrderIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StationDelta extends StationSnapshot {}
|
export interface StationDelta extends StationSnapshot { }
|
||||||
|
|
||||||
export interface ClaimSnapshot {
|
export interface ClaimSnapshot {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -50,7 +53,7 @@ export interface ClaimSnapshot {
|
|||||||
activatesAtUtc: string;
|
activatesAtUtc: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClaimDelta extends ClaimSnapshot {}
|
export interface ClaimDelta extends ClaimSnapshot { }
|
||||||
|
|
||||||
export interface ConstructionSiteSnapshot {
|
export interface ConstructionSiteSnapshot {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -72,4 +75,4 @@ export interface ConstructionSiteSnapshot {
|
|||||||
marketOrderIds: string[];
|
marketOrderIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConstructionSiteDelta extends ConstructionSiteSnapshot {}
|
export interface ConstructionSiteDelta extends ConstructionSiteSnapshot { }
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export interface ShipSnapshot {
|
|||||||
cargoCapacity: number;
|
cargoCapacity: number;
|
||||||
cargoItemId?: string | null;
|
cargoItemId?: string | null;
|
||||||
workerPopulation: number;
|
workerPopulation: number;
|
||||||
energyStored: number;
|
|
||||||
travelSpeed: number;
|
travelSpeed: number;
|
||||||
travelSpeedUnit: string;
|
travelSpeedUnit: string;
|
||||||
inventory: InventoryEntry[];
|
inventory: InventoryEntry[];
|
||||||
@@ -32,7 +31,7 @@ export interface ShipSnapshot {
|
|||||||
spatialState: ShipSpatialStateSnapshot;
|
spatialState: ShipSpatialStateSnapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShipDelta extends ShipSnapshot {}
|
export interface ShipDelta extends ShipSnapshot { }
|
||||||
|
|
||||||
export interface ShipActionProgressSnapshot {
|
export interface ShipActionProgressSnapshot {
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export function renderFactionStrip(
|
|||||||
|
|
||||||
return ships
|
return ships
|
||||||
.map((ship) => {
|
.map((ship) => {
|
||||||
const fuel = inventoryAmount(ship.inventory, "fuel");
|
|
||||||
const cargo = ship.cargoItemId
|
const cargo = ship.cargoItemId
|
||||||
? inventoryAmount(ship.inventory, ship.cargoItemId)
|
? inventoryAmount(ship.inventory, ship.cargoItemId)
|
||||||
: 0;
|
: 0;
|
||||||
@@ -54,7 +53,7 @@ export function renderFactionStrip(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>${shipLocation.system}${shipLocation.local ? `<br>${shipLocation.local}` : ""}</p>
|
<p>${shipLocation.system}${shipLocation.local ? `<br>${shipLocation.local}` : ""}</p>
|
||||||
<p>Fuel ${fuel.toFixed(1)} · Cap ${ship.energyStored.toFixed(1)}${ship.cargoCapacity > 0 ? ` · Cargo ${cargo.toFixed(0)}` : ""}</p>
|
<p>Cargo ${cargo.toFixed(0)}</p>
|
||||||
<p>State ${shipState}</p>
|
<p>State ${shipState}</p>
|
||||||
${shipAction ? `
|
${shipAction ? `
|
||||||
<div class="ship-action-progress">
|
<div class="ship-action-progress">
|
||||||
|
|||||||
@@ -37,6 +37,94 @@ interface SystemPanelParams {
|
|||||||
cameraTargetShipId?: string;
|
cameraTargetShipId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function laneModuleId(lane: string): string | undefined {
|
||||||
|
switch (lane) {
|
||||||
|
case "refinery":
|
||||||
|
return "refinery-stack";
|
||||||
|
case "fabrication":
|
||||||
|
return "fabricator-array";
|
||||||
|
case "components":
|
||||||
|
return "component-factory";
|
||||||
|
case "shipyard":
|
||||||
|
return "ship-factory";
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatModuleListWithConstruction(
|
||||||
|
world: WorldState,
|
||||||
|
stationId: string,
|
||||||
|
installedModules: string[],
|
||||||
|
currentProcesses: { lane: string; label: string; progress: number }[],
|
||||||
|
): string {
|
||||||
|
const processByModule = new Map<string, { label: string; progress: number }[]>();
|
||||||
|
for (const process of currentProcesses) {
|
||||||
|
const moduleId = laneModuleId(process.lane);
|
||||||
|
if (!moduleId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = processByModule.get(moduleId) ?? [];
|
||||||
|
existing.push({ label: process.label, progress: process.progress });
|
||||||
|
processByModule.set(moduleId, existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderedProcessCount = new Map<string, number>();
|
||||||
|
const moduleLines = installedModules.map((moduleId) => {
|
||||||
|
const processIndex = renderedProcessCount.get(moduleId) ?? 0;
|
||||||
|
const processes = processByModule.get(moduleId) ?? [];
|
||||||
|
const process = processes[processIndex];
|
||||||
|
renderedProcessCount.set(moduleId, processIndex + 1);
|
||||||
|
if (!process) {
|
||||||
|
return moduleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${moduleId} -> ${process.label} (${Math.round(process.progress * 100)}%)`;
|
||||||
|
});
|
||||||
|
const activeSites = [...world.constructionSites.values()]
|
||||||
|
.filter((site) => site.stationId === stationId && site.state !== "completed")
|
||||||
|
.sort((left, right) => left.targetDefinitionId.localeCompare(right.targetDefinitionId));
|
||||||
|
|
||||||
|
for (const site of activeSites) {
|
||||||
|
const moduleId = site.blueprintId ?? site.targetDefinitionId;
|
||||||
|
const progress = Math.round(site.progress * 100);
|
||||||
|
const tooltip = site.requiredItems.length > 0
|
||||||
|
? site.requiredItems
|
||||||
|
.map((entry) => `${entry.itemId}: ${entry.amount.toFixed(0)} required / ${inventoryAmount(site.stationId ? (world.stations.get(site.stationId)?.inventory ?? []) : site.deliveredItems, entry.itemId).toFixed(0)} available`)
|
||||||
|
.join("\n")
|
||||||
|
: "No material requirements";
|
||||||
|
const escapedTooltip = tooltip
|
||||||
|
.replaceAll("&", "&")
|
||||||
|
.replaceAll("\"", """)
|
||||||
|
.replaceAll("<", "<")
|
||||||
|
.replaceAll(">", ">");
|
||||||
|
moduleLines.push(`<span title="${escapedTooltip}">${moduleId} (${progress}% constructing)</span>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return moduleLines.length > 0 ? moduleLines.join("<br>") : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatStorageClassLabel(storageClass: string): string {
|
||||||
|
return storageClass
|
||||||
|
.split("-")
|
||||||
|
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
||||||
|
.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatStorageUsage(storageUsage: { storageClass: string; used: number; capacity: number }[]): string {
|
||||||
|
if (storageUsage.length === 0) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
return storageUsage
|
||||||
|
.map((entry) => {
|
||||||
|
const percentUsed = entry.capacity > 0 ? Math.round((entry.used / entry.capacity) * 100) : 0;
|
||||||
|
return `${formatStorageClassLabel(entry.storageClass)} ${percentUsed}% used (${entry.used.toFixed(0)} / ${entry.capacity.toFixed(0)})`;
|
||||||
|
})
|
||||||
|
.join("<br>");
|
||||||
|
}
|
||||||
|
|
||||||
function renderSystemOwnership(world: WorldState, systemId: string): string {
|
function renderSystemOwnership(world: WorldState, systemId: string): string {
|
||||||
const claims = [...world.claims.values()].filter((claim) =>
|
const claims = [...world.claims.values()].filter((claim) =>
|
||||||
claim.systemId === systemId && claim.state !== "destroyed");
|
claim.systemId === systemId && claim.state !== "destroyed");
|
||||||
@@ -108,7 +196,6 @@ export function updateDetailPanel(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parent = describeSelectionParent(selected);
|
const parent = describeSelectionParent(selected);
|
||||||
const fuelStored = inventoryAmount(ship.inventory, "fuel");
|
|
||||||
const cargoUsed = ship.cargoItemId
|
const cargoUsed = ship.cargoItemId
|
||||||
? inventoryAmount(ship.inventory, ship.cargoItemId)
|
? inventoryAmount(ship.inventory, ship.cargoItemId)
|
||||||
: 0;
|
: 0;
|
||||||
@@ -130,7 +217,6 @@ export function updateDetailPanel(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
` : ""}
|
` : ""}
|
||||||
<p>Fuel ${fuelStored.toFixed(1)}<br>Capacitor ${ship.energyStored.toFixed(1)}</p>
|
|
||||||
<p>Cargo ${cargoLabel} ${cargoUsed.toFixed(0)} / ${ship.cargoCapacity.toFixed(0)}</p>
|
<p>Cargo ${cargoLabel} ${cargoUsed.toFixed(0)} / ${ship.cargoCapacity.toFixed(0)}</p>
|
||||||
<p>Inventory ${formatInventory(ship.inventory)}</p>
|
<p>Inventory ${formatInventory(ship.inventory)}</p>
|
||||||
<p>Speed ${formatShipSpeed(ship)}</p>
|
<p>Speed ${formatShipSpeed(ship)}</p>
|
||||||
@@ -145,17 +231,12 @@ export function updateDetailPanel(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parent = describeSelectionParent(selected);
|
const parent = describeSelectionParent(selected);
|
||||||
const installedModules = station.installedModules.length > 0
|
const moduleList = formatModuleListWithConstruction(world, station.id, station.installedModules, station.currentProcesses);
|
||||||
? station.installedModules.join("<br>")
|
|
||||||
: "none";
|
|
||||||
const activeConstruction = [...world.constructionSites.values()]
|
|
||||||
.filter((site) => site.stationId === station.id && site.state !== "completed")
|
|
||||||
.map((site) => `${site.blueprintId ?? site.targetDefinitionId} (${site.state})`)
|
|
||||||
.join("<br>") || "none";
|
|
||||||
const dockedShipLabels = station.dockedShipIds.length > 0
|
const dockedShipLabels = station.dockedShipIds.length > 0
|
||||||
? station.dockedShipIds.map((shipId) => world.ships.get(shipId)?.label ?? shipId).join("<br>")
|
? station.dockedShipIds.map((shipId) => world.ships.get(shipId)?.label ?? shipId).join("<br>")
|
||||||
: "none";
|
: "none";
|
||||||
const stationInventory = station.inventory.filter((entry) => entry.itemId !== "fuel");
|
const stationInventory = station.inventory;
|
||||||
|
const stationStorageUsage = formatStorageUsage(station.storageUsage);
|
||||||
const stationProcesses = station.currentProcesses;
|
const stationProcesses = station.currentProcesses;
|
||||||
const stationProcessingHtml = stationProcesses.length > 0
|
const stationProcessingHtml = stationProcesses.length > 0
|
||||||
? stationProcesses.map((process) => `
|
? stationProcesses.map((process) => `
|
||||||
@@ -175,14 +256,12 @@ export function updateDetailPanel(
|
|||||||
<p>${station.category} · ${station.systemId}</p>
|
<p>${station.category} · ${station.systemId}</p>
|
||||||
<p>Parent ${parent}</p>
|
<p>Parent ${parent}</p>
|
||||||
${stationProcessingHtml}
|
${stationProcessingHtml}
|
||||||
<p>Fuel ${station.fuelStored.toFixed(1)} / ${station.fuelCapacity.toFixed(1)}<br>Capacitor ${station.energyStored.toFixed(1)} / ${station.energyCapacity.toFixed(1)}</p>
|
|
||||||
<p>Docked ${station.dockedShips} / ${station.dockingPads}
|
<p>Docked ${station.dockedShips} / ${station.dockingPads}
|
||||||
<br>
|
<br>
|
||||||
${dockedShipLabels}</p>
|
${dockedShipLabels}</p>
|
||||||
<p>Modules ${installedModules}</p>
|
<p>Modules ${moduleList}</p>
|
||||||
<p>Constructing ${activeConstruction}</p>
|
<p>Storage ${stationStorageUsage}</p>
|
||||||
<p>Inventory ${formatInventory(stationInventory)}</p>
|
<p>Inventory ${formatInventory(stationInventory)}</p>
|
||||||
<p>History available in the separate history window.</p>
|
|
||||||
`;
|
`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -332,8 +332,6 @@ function describeControllerTask(taskKind: string): string {
|
|||||||
return "docking";
|
return "docking";
|
||||||
case "unload":
|
case "unload":
|
||||||
return "transfer";
|
return "transfer";
|
||||||
case "refuel":
|
|
||||||
return "refuel";
|
|
||||||
case "deliver-construction":
|
case "deliver-construction":
|
||||||
return "material delivery";
|
return "material delivery";
|
||||||
case "build-construction-site":
|
case "build-construction-site":
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ Typical outputs:
|
|||||||
- current destination node
|
- current destination node
|
||||||
- local tactical task
|
- local tactical task
|
||||||
- retreat decision
|
- retreat decision
|
||||||
- docking/refuel intent
|
- docking intent
|
||||||
- trade or delivery acceptance
|
- trade or delivery acceptance
|
||||||
|
|
||||||
## Commander Ownership
|
## Commander Ownership
|
||||||
|
|||||||
@@ -390,19 +390,6 @@ Suggested station-side workforce fields:
|
|||||||
|
|
||||||
Commanders should not be ordinary cargo items even if they are population-derived.
|
Commanders should not be ordinary cargo items even if they are population-derived.
|
||||||
|
|
||||||
## Power State
|
|
||||||
|
|
||||||
Ships and stations both need explicit operational power state.
|
|
||||||
|
|
||||||
Suggested fields:
|
|
||||||
|
|
||||||
- `fuelInventory`
|
|
||||||
- `energyStored`
|
|
||||||
- `powerOperational`
|
|
||||||
- `powerDeficitReason?`
|
|
||||||
|
|
||||||
This matters because no fuel leads to no power, and no power halts major operations.
|
|
||||||
|
|
||||||
## Inventories
|
## Inventories
|
||||||
|
|
||||||
Inventories should remain generic item maps, but hosts should also have explicit context.
|
Inventories should remain generic item maps, but hosts should also have explicit context.
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ For the implementation migration path from the current codebase to this design s
|
|||||||
- item categories
|
- item categories
|
||||||
- life-support goods
|
- life-support goods
|
||||||
- construction goods
|
- construction goods
|
||||||
- fuel-chain goods
|
|
||||||
- population-related units
|
- population-related units
|
||||||
|
|
||||||
- [WORKFORCE.md](/home/jbourdon/repos/space-game/docs/WORKFORCE.md)
|
- [WORKFORCE.md](/home/jbourdon/repos/space-game/docs/WORKFORCE.md)
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ A buy order should include, conceptually:
|
|||||||
Buy orders let a station express:
|
Buy orders let a station express:
|
||||||
|
|
||||||
- production input demand
|
- production input demand
|
||||||
- fuel shortages
|
|
||||||
- construction material shortages
|
- construction material shortages
|
||||||
- military resupply needs
|
- military resupply needs
|
||||||
|
|
||||||
@@ -138,8 +137,6 @@ The station commander should:
|
|||||||
|
|
||||||
Without a station commander, a station should not act like a healthy market participant.
|
Without a station commander, a station should not act like a healthy market participant.
|
||||||
|
|
||||||
If the station has no fuel and therefore no power, it should not continue normal market operation.
|
|
||||||
|
|
||||||
However, there is an important exception during founding or emergency intervention:
|
However, there is an important exception during founding or emergency intervention:
|
||||||
|
|
||||||
- a higher actor may force transfers toward the station or construction site even without ordinary market behavior
|
- a higher actor may force transfers toward the station or construction site even without ordinary market behavior
|
||||||
@@ -149,7 +146,7 @@ Recommended review loop:
|
|||||||
1. inspect current inventory
|
1. inspect current inventory
|
||||||
2. inspect production queues or goals
|
2. inspect production queues or goals
|
||||||
3. inspect incoming and outgoing reservations
|
3. inspect incoming and outgoing reservations
|
||||||
4. inspect fuel, defense, and construction reserves
|
4. inspect defense, and construction reserves
|
||||||
5. update buy orders
|
5. update buy orders
|
||||||
6. update sell orders
|
6. update sell orders
|
||||||
7. request logistics or strategic help if necessary
|
7. request logistics or strategic help if necessary
|
||||||
@@ -185,7 +182,7 @@ The intended economy should eventually support flows such as:
|
|||||||
1. extract raw resources
|
1. extract raw resources
|
||||||
2. move them to useful stations
|
2. move them to useful stations
|
||||||
3. refine or process them
|
3. refine or process them
|
||||||
4. consume them for fuel, production, or expansion
|
4. consume them for production or expansion
|
||||||
5. produce intermediate and advanced goods
|
5. produce intermediate and advanced goods
|
||||||
6. sell surpluses or acquire shortages through the market
|
6. sell surpluses or acquire shortages through the market
|
||||||
|
|
||||||
@@ -200,7 +197,6 @@ Logistics should emerge from market demand, not only from hardcoded behavior loo
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- a hauler sees a profitable sell-to-buy opportunity
|
- a hauler sees a profitable sell-to-buy opportunity
|
||||||
- a station commander requests urgent fuel delivery
|
|
||||||
- a faction commander subsidizes strategic resource movement
|
- a faction commander subsidizes strategic resource movement
|
||||||
|
|
||||||
This is a better long-term basis than one-off scripted “mine and deliver to this exact station” logic.
|
This is a better long-term basis than one-off scripted “mine and deliver to this exact station” logic.
|
||||||
@@ -208,7 +204,6 @@ This is a better long-term basis than one-off scripted “mine and deliver to th
|
|||||||
Traders should generally prefer the best reachable buy opportunity within their allowed operational range, subject to:
|
Traders should generally prefer the best reachable buy opportunity within their allowed operational range, subject to:
|
||||||
|
|
||||||
- travel time
|
- travel time
|
||||||
- fuel cost
|
|
||||||
- risk
|
- risk
|
||||||
- behavioral restrictions
|
- behavioral restrictions
|
||||||
- territorial or regional limits
|
- territorial or regional limits
|
||||||
@@ -236,7 +231,6 @@ The economy will work better if stations can reserve expected inventory changes.
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- incoming fuel is reserved for station power
|
|
||||||
- outbound metals are reserved for a construction project
|
- outbound metals are reserved for a construction project
|
||||||
- a hauler claims part of a sell order before pickup
|
- a hauler claims part of a sell order before pickup
|
||||||
|
|
||||||
|
|||||||
@@ -289,7 +289,6 @@ Every event should be capable of producing a concise human-readable summary.
|
|||||||
Example style:
|
Example style:
|
||||||
|
|
||||||
- `Claim at Helios IV L4 destroyed by pirates`
|
- `Claim at Helios IV L4 destroyed by pirates`
|
||||||
- `Station buy order for fuel opened`
|
|
||||||
- `Miner completed warp to refinery node`
|
- `Miner completed warp to refinery node`
|
||||||
|
|
||||||
This helps reuse the same event model for:
|
This helps reuse the same event model for:
|
||||||
|
|||||||
@@ -33,10 +33,9 @@ The intended categories are:
|
|||||||
2. processed industrial goods
|
2. processed industrial goods
|
||||||
3. life-support goods
|
3. life-support goods
|
||||||
4. civilian goods
|
4. civilian goods
|
||||||
5. fuel and power-chain goods
|
5. construction goods
|
||||||
6. construction goods
|
6. population-related units
|
||||||
7. population-related units
|
7. special logistics goods later
|
||||||
8. special logistics goods later
|
|
||||||
|
|
||||||
## Raw Resources
|
## Raw Resources
|
||||||
|
|
||||||
@@ -86,20 +85,6 @@ Current important example:
|
|||||||
|
|
||||||
These goods should matter for workforce health, quality of life, and possibly future growth modifiers.
|
These goods should matter for workforce health, quality of life, and possibly future growth modifiers.
|
||||||
|
|
||||||
## Fuel And Power-Chain Goods
|
|
||||||
|
|
||||||
These are the goods that keep ships and stations running.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
- gas as an energy-chain input
|
|
||||||
- fuel as a refined operational good
|
|
||||||
|
|
||||||
The exact chain may evolve, but the important distinction is:
|
|
||||||
|
|
||||||
- some goods are energy inputs
|
|
||||||
- some goods are operational fuels
|
|
||||||
|
|
||||||
## Construction Goods
|
## Construction Goods
|
||||||
|
|
||||||
These are the goods used to build stations and possibly ships.
|
These are the goods used to build stations and possibly ships.
|
||||||
@@ -116,7 +101,7 @@ Construction storage at a station site should create demand for these goods thro
|
|||||||
|
|
||||||
## Population-Related Units
|
## Population-Related Units
|
||||||
|
|
||||||
Population itself should be treated as a tracked resource, but not as an ordinary trade good in the same sense as metal or fuel.
|
Population itself should be treated as a tracked resource, but not as an ordinary trade good in the same sense as ore.
|
||||||
|
|
||||||
Important distinctions:
|
Important distinctions:
|
||||||
|
|
||||||
@@ -144,12 +129,6 @@ The current design implies at least these roles:
|
|||||||
- `ore`
|
- `ore`
|
||||||
- raw industrial input
|
- raw industrial input
|
||||||
|
|
||||||
- `gas`
|
|
||||||
- raw fuel-chain input
|
|
||||||
|
|
||||||
- `fuel`
|
|
||||||
- operational energy good
|
|
||||||
|
|
||||||
- `food`
|
- `food`
|
||||||
- workforce life-support
|
- workforce life-support
|
||||||
|
|
||||||
@@ -173,12 +152,11 @@ Not every item should necessarily fit in every hold type forever.
|
|||||||
|
|
||||||
Useful distinctions later may include:
|
Useful distinctions later may include:
|
||||||
|
|
||||||
- bulk industrial cargo
|
- solid storage
|
||||||
- liquid cargo
|
- liquid storage
|
||||||
- gas cargo
|
- container storage
|
||||||
- containerized finished goods
|
- passengers
|
||||||
- human transport capacity
|
- livestock
|
||||||
- livestock capacity
|
|
||||||
|
|
||||||
For now, the important rule is simply:
|
For now, the important rule is simply:
|
||||||
|
|
||||||
@@ -191,7 +169,6 @@ Items should participate in the market according to their role.
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- life-support goods generate recurring demand
|
- life-support goods generate recurring demand
|
||||||
- fuel goods generate operational demand
|
|
||||||
- construction goods generate burst demand during expansion
|
- construction goods generate burst demand during expansion
|
||||||
- industrial goods feed production chains
|
- industrial goods feed production chains
|
||||||
- worker transport supports station staffing
|
- worker transport supports station staffing
|
||||||
@@ -220,7 +197,7 @@ The following rules should remain true unless deliberately revised:
|
|||||||
|
|
||||||
- workforce depends on real support goods
|
- workforce depends on real support goods
|
||||||
- station construction depends on real construction goods
|
- station construction depends on real construction goods
|
||||||
- fuel and industrial chains are item-based
|
- industrial chains are item-based
|
||||||
- workers are movable population units
|
- workers are movable population units
|
||||||
- commanders are not ordinary trade cargo
|
- commanders are not ordinary trade cargo
|
||||||
- livestock is distinct from workers
|
- livestock is distinct from workers
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ Examples:
|
|||||||
- no docking module means no docking service
|
- no docking module means no docking service
|
||||||
- no habitat module means no population growth or human transport
|
- no habitat module means no population growth or human transport
|
||||||
- no refinery module means no refining
|
- no refinery module means no refining
|
||||||
- no fuel-processing module means no gas-to-fuel conversion
|
|
||||||
- no storage module means reduced or absent inventory capability
|
- no storage module means reduced or absent inventory capability
|
||||||
- no shipyard-related module means no ship production
|
- no shipyard-related module means no ship production
|
||||||
|
|
||||||
@@ -92,7 +91,6 @@ Likely station-side categories include:
|
|||||||
- storage
|
- storage
|
||||||
- habitat
|
- habitat
|
||||||
- refinery
|
- refinery
|
||||||
- fuel processing
|
|
||||||
- manufacturing
|
- manufacturing
|
||||||
- shipyard or construction support
|
- shipyard or construction support
|
||||||
- defense
|
- defense
|
||||||
@@ -141,7 +139,6 @@ Examples:
|
|||||||
- reactor
|
- reactor
|
||||||
- capacitor
|
- capacitor
|
||||||
- station power core
|
- station power core
|
||||||
- fuel systems
|
|
||||||
|
|
||||||
### Production Modules
|
### Production Modules
|
||||||
|
|
||||||
@@ -150,7 +147,6 @@ These convert goods into other goods or into built output.
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- refinery
|
- refinery
|
||||||
- fuel processor
|
|
||||||
- factory
|
- factory
|
||||||
- shipyard support
|
- shipyard support
|
||||||
|
|
||||||
@@ -198,7 +194,6 @@ They may require:
|
|||||||
- build time
|
- build time
|
||||||
- power
|
- power
|
||||||
- workforce
|
- workforce
|
||||||
- fuel or energy inputs
|
|
||||||
- docking or logistics support
|
- docking or logistics support
|
||||||
|
|
||||||
This should let stations and ships fail in believable ways when underbuilt or undersupplied.
|
This should let stations and ships fail in believable ways when underbuilt or undersupplied.
|
||||||
@@ -219,7 +214,6 @@ Modules should define which item flows an entity can participate in.
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- a habitat module enables population support
|
- a habitat module enables population support
|
||||||
- a fuel-processing module consumes gas and produces fuel
|
|
||||||
- a refinery consumes raw resources and produces processed goods
|
- a refinery consumes raw resources and produces processed goods
|
||||||
- a storage module determines what volume or class of goods can be held
|
- a storage module determines what volume or class of goods can be held
|
||||||
- a livestock module participates in the food chain
|
- a livestock module participates in the food chain
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ A recipe should conceptually define:
|
|||||||
- cycle time
|
- cycle time
|
||||||
- valid producing module types
|
- valid producing module types
|
||||||
- optional workforce requirement
|
- optional workforce requirement
|
||||||
- optional power or fuel requirement
|
|
||||||
|
|
||||||
Recipes should be first-class design objects, not hidden assumptions inside modules.
|
Recipes should be first-class design objects, not hidden assumptions inside modules.
|
||||||
|
|
||||||
@@ -60,7 +59,6 @@ Recipes are executed by production-capable modules.
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- refinery module
|
- refinery module
|
||||||
- fuel processing module
|
|
||||||
- factory module
|
- factory module
|
||||||
- food-chain module later
|
- food-chain module later
|
||||||
- shipyard support module
|
- shipyard support module
|
||||||
@@ -112,16 +110,6 @@ For now:
|
|||||||
|
|
||||||
This keeps the initial system consistent and simple.
|
This keeps the initial system consistent and simple.
|
||||||
|
|
||||||
## Power Interaction
|
|
||||||
|
|
||||||
Production should also respect power and fuel state.
|
|
||||||
|
|
||||||
Without power:
|
|
||||||
|
|
||||||
- production stops
|
|
||||||
|
|
||||||
This is especially important for stations because no-fuel means no-power, and no-power means no normal operation.
|
|
||||||
|
|
||||||
## Input Shortage Behavior
|
## Input Shortage Behavior
|
||||||
|
|
||||||
If inputs are missing:
|
If inputs are missing:
|
||||||
@@ -153,7 +141,6 @@ The exact recipes can evolve, but the intended shape includes chains like:
|
|||||||
|
|
||||||
2. refining or processing
|
2. refining or processing
|
||||||
- ore -> refined goods
|
- ore -> refined goods
|
||||||
- gas -> fuel
|
|
||||||
- food-loop conversions later
|
- food-loop conversions later
|
||||||
|
|
||||||
3. industrial use
|
3. industrial use
|
||||||
|
|||||||
@@ -151,23 +151,6 @@ Not:
|
|||||||
|
|
||||||
This means friendly or otherwise permitted factions may build stations within the same system, so long as they use different valid locations.
|
This means friendly or otherwise permitted factions may build stations within the same system, so long as they use different valid locations.
|
||||||
|
|
||||||
## Failure State
|
|
||||||
|
|
||||||
Without fuel there is no power.
|
|
||||||
|
|
||||||
Without power, station function collapses.
|
|
||||||
|
|
||||||
A powerless station should not continue normal market or industrial behavior.
|
|
||||||
|
|
||||||
At that point, recovery should require outside intervention such as emergency restoration, delivered fuel, or a dedicated support operation.
|
|
||||||
|
|
||||||
This also means:
|
|
||||||
|
|
||||||
- no loading
|
|
||||||
- no unloading
|
|
||||||
- no ordinary trade handling
|
|
||||||
- no ordinary production
|
|
||||||
|
|
||||||
## Services
|
## Services
|
||||||
|
|
||||||
Depending on modules and category, a station may provide:
|
Depending on modules and category, a station may provide:
|
||||||
@@ -175,11 +158,9 @@ Depending on modules and category, a station may provide:
|
|||||||
- docking
|
- docking
|
||||||
- storage
|
- storage
|
||||||
- refining
|
- refining
|
||||||
- fuel processing
|
|
||||||
- manufacturing
|
- manufacturing
|
||||||
- repair later
|
- repair
|
||||||
- fitting later
|
- fitting, rearm and resupply later
|
||||||
- rearm and resupply later
|
|
||||||
- habitats
|
- habitats
|
||||||
|
|
||||||
The exact conversion and factory behavior behind these services is described in [PRODUCTION.md](/home/jbourdon/repos/space-game/docs/PRODUCTION.md).
|
The exact conversion and factory behavior behind these services is described in [PRODUCTION.md](/home/jbourdon/repos/space-game/docs/PRODUCTION.md).
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ Goals are high-level commander intentions.
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- expand into this system
|
- expand into this system
|
||||||
- keep this station fueled
|
|
||||||
- defend this claim
|
- defend this claim
|
||||||
- protect trade in this region
|
- protect trade in this region
|
||||||
- supply this station with workers
|
- supply this station with workers
|
||||||
@@ -60,7 +59,6 @@ Examples:
|
|||||||
- dock at station
|
- dock at station
|
||||||
- claim Lagrange point
|
- claim Lagrange point
|
||||||
- build station here
|
- build station here
|
||||||
- deliver fuel
|
|
||||||
- escort this ship
|
- escort this ship
|
||||||
- defend this bubble
|
- defend this bubble
|
||||||
|
|
||||||
@@ -223,7 +221,6 @@ Examples:
|
|||||||
- deny dock request
|
- deny dock request
|
||||||
- transfer goods
|
- transfer goods
|
||||||
- request defense
|
- request defense
|
||||||
- request emergency fuel support
|
|
||||||
|
|
||||||
These may be implemented as station jobs, station operations, or station-side tasks.
|
These may be implemented as station jobs, station operations, or station-side tasks.
|
||||||
|
|
||||||
@@ -237,7 +234,7 @@ Examples:
|
|||||||
- flee to nearest allowed station
|
- flee to nearest allowed station
|
||||||
- hold position if no valid route exists
|
- hold position if no valid route exists
|
||||||
- suspend trade when no legal destination exists
|
- suspend trade when no legal destination exists
|
||||||
- wait for fuel, escort, or dock access
|
- wait for escort, or dock access
|
||||||
|
|
||||||
This prevents autonomous loops from becoming self-destructive.
|
This prevents autonomous loops from becoming self-destructive.
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ Workers consume, per worker:
|
|||||||
|
|
||||||
- food
|
- food
|
||||||
- water
|
- water
|
||||||
- energy
|
|
||||||
- consumer goods
|
- consumer goods
|
||||||
|
|
||||||
These should be understood using the item roles defined in [ITEMS.md](/home/jbourdon/repos/space-game/docs/ITEMS.md).
|
These should be understood using the item roles defined in [ITEMS.md](/home/jbourdon/repos/space-game/docs/ITEMS.md).
|
||||||
@@ -146,7 +145,6 @@ A newly founded station may begin with:
|
|||||||
|
|
||||||
It can still exist and operate at baseline efficiency, but it remains weak until supplied with:
|
It can still exist and operate at baseline efficiency, but it remains weak until supplied with:
|
||||||
|
|
||||||
- fuel
|
|
||||||
- workers
|
- workers
|
||||||
- support goods
|
- support goods
|
||||||
- eventually a station commander
|
- eventually a station commander
|
||||||
@@ -159,7 +157,6 @@ Relevant shortages include:
|
|||||||
|
|
||||||
- food shortage
|
- food shortage
|
||||||
- water shortage
|
- water shortage
|
||||||
- energy shortage
|
|
||||||
- consumer goods shortage
|
- consumer goods shortage
|
||||||
|
|
||||||
This gives logistics failure lasting demographic consequences.
|
This gives logistics failure lasting demographic consequences.
|
||||||
@@ -178,7 +175,7 @@ The following rules should remain true unless deliberately revised:
|
|||||||
|
|
||||||
- population grows only at stations for now
|
- population grows only at stations for now
|
||||||
- habitat modules are required for growth
|
- habitat modules are required for growth
|
||||||
- workers consume food, water, energy, and consumer goods
|
- workers consume food, water and consumer goods
|
||||||
- workforce affects station efficiency
|
- workforce affects station efficiency
|
||||||
- stations retain a small baseline efficiency at zero workforce
|
- stations retain a small baseline efficiency at zero workforce
|
||||||
- population can be transported between stations
|
- population can be transported between stations
|
||||||
|
|||||||
@@ -6,12 +6,5 @@
|
|||||||
"transferRate": 56,
|
"transferRate": 56,
|
||||||
"dockingDuration": 1.2,
|
"dockingDuration": 1.2,
|
||||||
"undockingDuration": 1.2,
|
"undockingDuration": 1.2,
|
||||||
"undockDistance": 42,
|
"undockDistance": 42
|
||||||
"energy": {
|
|
||||||
"idleDrain": 0.7,
|
|
||||||
"moveDrain": 1.8,
|
|
||||||
"warpDrain": 7,
|
|
||||||
"shipRechargeRate": 10,
|
|
||||||
"stationSolarCharge": 5
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "station-core",
|
|
||||||
"label": "Orbital Station",
|
|
||||||
"category": "station",
|
|
||||||
"color": "#8df0d2",
|
|
||||||
"radius": 24,
|
|
||||||
"dockingCapacity": 4,
|
|
||||||
"storage": {
|
|
||||||
"bulk-solid": 2000,
|
|
||||||
"manufactured": 1200,
|
|
||||||
"bulk-liquid": 600,
|
|
||||||
"bulk-gas": 600
|
|
||||||
},
|
|
||||||
"modules": ["dock-bay-small", "power-core", "bulk-bay", "liquid-tank"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "trade-hub",
|
|
||||||
"label": "Trade Hub",
|
|
||||||
"category": "station",
|
|
||||||
"color": "#8bd3ff",
|
|
||||||
"radius": 20,
|
|
||||||
"dockingCapacity": 4,
|
|
||||||
"storage": { "container": 1200, "manufactured": 800 },
|
|
||||||
"modules": ["habitat-ring", "container-bay"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "refinery",
|
|
||||||
"label": "Refining Station",
|
|
||||||
"category": "station",
|
|
||||||
"color": "#ffb86c",
|
|
||||||
"radius": 24,
|
|
||||||
"dockingCapacity": 3,
|
|
||||||
"storage": { "bulk-solid": 2000, "manufactured": 1000, "bulk-liquid": 400, "bulk-gas": 400 },
|
|
||||||
"modules": ["power-core", "bulk-bay", "liquid-tank", "gas-tank", "refinery-stack", "fuel-processor"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "farm-ring",
|
|
||||||
"label": "Farm Station",
|
|
||||||
"category": "farm",
|
|
||||||
"color": "#92ef8a",
|
|
||||||
"radius": 22,
|
|
||||||
"dockingCapacity": 2,
|
|
||||||
"storage": { "bulk-liquid": 600, "container": 400 },
|
|
||||||
"modules": ["habitat-ring", "fabricator-array", "container-bay"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "manufactory",
|
|
||||||
"label": "Orbital Manufactory",
|
|
||||||
"category": "station",
|
|
||||||
"color": "#8df0d2",
|
|
||||||
"radius": 24,
|
|
||||||
"dockingCapacity": 3,
|
|
||||||
"storage": { "manufactured": 2200, "container": 1600 },
|
|
||||||
"modules": ["fabricator-array", "fabricator-array", "container-bay"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "shipyard",
|
|
||||||
"label": "Orbital Shipyard",
|
|
||||||
"category": "shipyard",
|
|
||||||
"color": "#d0a2ff",
|
|
||||||
"radius": 28,
|
|
||||||
"dockingCapacity": 5,
|
|
||||||
"storage": { "manufactured": 1800, "container": 1200 },
|
|
||||||
"modules": ["component-factory", "ship-factory", "container-bay", "dock-bay-small", "power-core"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "defense-grid",
|
|
||||||
"label": "Defense Platform",
|
|
||||||
"category": "defense",
|
|
||||||
"color": "#ff7a95",
|
|
||||||
"radius": 18,
|
|
||||||
"dockingCapacity": 1,
|
|
||||||
"storage": { "manufactured": 300 },
|
|
||||||
"modules": ["turret-grid", "command-bridge"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "stargate",
|
|
||||||
"label": "Stargate",
|
|
||||||
"category": "gate",
|
|
||||||
"color": "#76f0ff",
|
|
||||||
"radius": 34,
|
|
||||||
"dockingCapacity": 0,
|
|
||||||
"storage": { "manufactured": 2400, "container": 800 },
|
|
||||||
"modules": ["ftl-core", "fabricator-array"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,206 +1,630 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "ore",
|
"id": "ore",
|
||||||
"label": "Raw Ore",
|
"name": "Raw Ore",
|
||||||
"storage": "bulk-solid",
|
"description": "Unprocessed asteroid ore used as the main industrial feedstock.",
|
||||||
"summary": "Unprocessed asteroid ore used as the main industrial feedstock."
|
"type": "resource",
|
||||||
},
|
"cargoKind": "bulk-solid",
|
||||||
{
|
"volume": 1.2
|
||||||
"id": "refined-metals",
|
|
||||||
"label": "Refined Metals",
|
|
||||||
"storage": "manufactured",
|
|
||||||
"summary": "Processed structural metals used by stations and shipyards."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "hull-sections",
|
|
||||||
"label": "Hull Sections",
|
|
||||||
"storage": "manufactured",
|
|
||||||
"summary": "Prefabricated structural assemblies for ships and stations."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ammo-crates",
|
|
||||||
"label": "Ammo Crates",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Containerized magazines for turrets, launchers, and point defense."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "naval-guns",
|
|
||||||
"label": "Naval Guns",
|
|
||||||
"storage": "manufactured",
|
|
||||||
"summary": "Shipboard turret and cannon assemblies."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ship-equipment",
|
|
||||||
"label": "Ship Equipment",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Shield emitters, avionics, cooling loops, and service kits."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ship-parts",
|
|
||||||
"label": "Ship Parts",
|
|
||||||
"storage": "manufactured",
|
|
||||||
"summary": "High-value integration kits for hull fitting and final assembly."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "command-bridge-module",
|
|
||||||
"label": "Command Bridge Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Packaged bridge and combat-information-center assembly for final ship integration."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "reactor-core-module",
|
|
||||||
"label": "Reactor Core Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Contained ship reactor package ready for installation into a hull."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "capacitor-bank-module",
|
|
||||||
"label": "Capacitor Bank Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Buffered capacitor section for propulsion, weapons, and industrial loads."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ion-drive-module",
|
|
||||||
"label": "Ion Drive Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Preassembled sublight engine unit."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ftl-core-module",
|
|
||||||
"label": "FTL Core Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Integrated FTL drive package for inter-system transit."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "gun-turret-module",
|
|
||||||
"label": "Gun Turret Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Shipboard turret mount and fire-control package."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "carrier-bay-module",
|
|
||||||
"label": "Carrier Bay Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Hangar and launch-recovery assembly for capital ship integration."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "habitat-ring-module",
|
|
||||||
"label": "Habitat Ring Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Crew habitat section packaged for large ship installation."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "bulk-bay-module",
|
|
||||||
"label": "Bulk Bay Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Industrial cargo hold segment for raw-solid hauling ships."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "container-bay-module",
|
|
||||||
"label": "Container Bay Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Freight rack segment for manufactured and palletized cargo."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "liquid-tank-module",
|
|
||||||
"label": "Liquid Tank Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Pressurized liquid storage segment for fuel and energy logistics."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "gas-tank-module",
|
|
||||||
"label": "Gas Tank Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Pressurized gas storage segment for volatile cargo hauling."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "mining-turret-module",
|
|
||||||
"label": "Mining Turret Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Ship-mounted hard-rock extraction head."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "gas-extractor-module",
|
|
||||||
"label": "Gas Extractor Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Cryogenic intake and compression package for gas harvesting ships."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "fabricator-array-module",
|
|
||||||
"label": "Fabricator Array Module",
|
|
||||||
"storage": "container",
|
|
||||||
"summary": "Mobile industrial fabrication block for constructors."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "gas",
|
|
||||||
"label": "Volatile Gas",
|
|
||||||
"storage": "bulk-gas",
|
|
||||||
"summary": "Compressed gas reserves for future chemical and fuel chains."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "fuel",
|
|
||||||
"label": "Reactor Fuel",
|
|
||||||
"storage": "bulk-liquid",
|
|
||||||
"summary": "Processed liquid fuel consumed by ships and station power systems."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "energy-cell",
|
|
||||||
"label": "Energy Cell",
|
|
||||||
"storage": "bulk-liquid",
|
|
||||||
"summary": "Charged energy reserves that can be stored, traded, and discharged into station power grids."
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "water",
|
"id": "water",
|
||||||
"label": "Water",
|
"name": "Water",
|
||||||
"storage": "bulk-liquid",
|
"description": "Life-support and agricultural input.",
|
||||||
"summary": "Life-support and agricultural input."
|
"type": "commodity",
|
||||||
|
"cargoKind": "bulk-liquid",
|
||||||
|
"volume": 1.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "water-reclamation",
|
||||||
|
"facilityCategory": "farm",
|
||||||
|
"requiredModules": ["liquid-tank", "solar-array"],
|
||||||
|
"requirements": [],
|
||||||
|
"cycleTime": 6,
|
||||||
|
"batchSize": 12,
|
||||||
|
"productsPerHour": 7200,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "refined-metals",
|
||||||
|
"name": "Refined Metals",
|
||||||
|
"description": "Processed structural metals used by stations and shipyards.",
|
||||||
|
"type": "material",
|
||||||
|
"cargoKind": "manufactured",
|
||||||
|
"volume": 1.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "ore-refining",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["refinery-stack"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "ore", "amount": 60 }
|
||||||
|
],
|
||||||
|
"cycleTime": 8,
|
||||||
|
"batchSize": 60,
|
||||||
|
"productsPerHour": 27000,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "hull-sections",
|
||||||
|
"name": "Hull Sections",
|
||||||
|
"description": "Prefabricated structural assemblies for ships and stations.",
|
||||||
|
"type": "component",
|
||||||
|
"cargoKind": "manufactured",
|
||||||
|
"volume": 1.5,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "hull-fabrication",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 70 }
|
||||||
|
],
|
||||||
|
"cycleTime": 10,
|
||||||
|
"batchSize": 35,
|
||||||
|
"productsPerHour": 12600,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 40
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ammo-crates",
|
||||||
|
"name": "Ammo Crates",
|
||||||
|
"description": "Containerized magazines for turrets, launchers, and point defense.",
|
||||||
|
"type": "component",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 1.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "ammo-fabrication",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 24 }
|
||||||
|
],
|
||||||
|
"cycleTime": 6,
|
||||||
|
"batchSize": 30,
|
||||||
|
"productsPerHour": 18000,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 34
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "naval-guns",
|
||||||
|
"name": "Naval Guns",
|
||||||
|
"description": "Shipboard turret and cannon assemblies.",
|
||||||
|
"type": "component",
|
||||||
|
"cargoKind": "manufactured",
|
||||||
|
"volume": 1.4,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "gun-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 36 }
|
||||||
|
],
|
||||||
|
"cycleTime": 9,
|
||||||
|
"batchSize": 12,
|
||||||
|
"productsPerHour": 4800,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ship-equipment",
|
||||||
|
"name": "Ship Equipment",
|
||||||
|
"description": "Shield emitters, avionics, cooling loops, and service kits.",
|
||||||
|
"type": "component",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 1.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "equipment-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 28 },
|
||||||
|
{ "itemId": "water", "amount": 8 }
|
||||||
|
],
|
||||||
|
"cycleTime": 11,
|
||||||
|
"batchSize": 18,
|
||||||
|
"productsPerHour": 5890.9,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ship-parts",
|
||||||
|
"name": "Ship Parts",
|
||||||
|
"description": "High-value integration kits for hull fitting and final assembly.",
|
||||||
|
"type": "component",
|
||||||
|
"cargoKind": "manufactured",
|
||||||
|
"volume": 1.3,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "ship-parts-integration",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "hull-sections", "amount": 24 },
|
||||||
|
{ "itemId": "naval-guns", "amount": 6 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 10 }
|
||||||
|
],
|
||||||
|
"cycleTime": 14,
|
||||||
|
"batchSize": 20,
|
||||||
|
"productsPerHour": 5142.9,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 50
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "drone-parts",
|
"id": "drone-parts",
|
||||||
"label": "Drone Parts",
|
"name": "Drone Parts",
|
||||||
"storage": "container",
|
"description": "Containerized industrial freight for construction support.",
|
||||||
"summary": "Containerized industrial freight."
|
"type": "component",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 1.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "drone-parts-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 12 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 6 }
|
||||||
|
],
|
||||||
|
"cycleTime": 7,
|
||||||
|
"batchSize": 16,
|
||||||
|
"productsPerHour": 8228.6,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "command-bridge-module",
|
||||||
|
"name": "Command Bridge Module",
|
||||||
|
"description": "Packaged bridge and combat-information-center assembly for final ship integration.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 2.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "command-bridge-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 20 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 10 }
|
||||||
|
],
|
||||||
|
"cycleTime": 9,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 400,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 52
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor-core-module",
|
||||||
|
"name": "Reactor Core Module",
|
||||||
|
"description": "Contained ship reactor package ready for installation into a hull.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 2.2,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "reactor-core-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 30 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 8 }
|
||||||
|
],
|
||||||
|
"cycleTime": 10,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 360,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 54
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "capacitor-bank-module",
|
||||||
|
"name": "Capacitor Bank Module",
|
||||||
|
"description": "Buffered capacitor section for propulsion, weapons, and industrial loads.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 1.8,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "capacitor-bank-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 18 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 4 }
|
||||||
|
],
|
||||||
|
"cycleTime": 9,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 400,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 52
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ion-drive-module",
|
||||||
|
"name": "Ion Drive Module",
|
||||||
|
"description": "Preassembled sublight engine unit.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 2.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "ion-drive-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 22 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 8 }
|
||||||
|
],
|
||||||
|
"cycleTime": 10,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 360,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 53
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ftl-core-module",
|
||||||
|
"name": "FTL Core Module",
|
||||||
|
"description": "Integrated FTL drive package for inter-system transit.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 2.4,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "ftl-core-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 34 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 14 }
|
||||||
|
],
|
||||||
|
"cycleTime": 12,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 300,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 56
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gun-turret-module",
|
||||||
|
"name": "Gun Turret Module",
|
||||||
|
"description": "Shipboard turret mount and fire-control package.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 1.6,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "gun-turret-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "naval-guns", "amount": 8 },
|
||||||
|
{ "itemId": "refined-metals", "amount": 12 }
|
||||||
|
],
|
||||||
|
"cycleTime": 8,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 450,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 58
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "carrier-bay-module",
|
||||||
|
"name": "Carrier Bay Module",
|
||||||
|
"description": "Hangar and launch-recovery assembly for capital ship integration.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 3.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "carrier-bay-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "hull-sections", "amount": 18 },
|
||||||
|
{ "itemId": "refined-metals", "amount": 18 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 10 }
|
||||||
|
],
|
||||||
|
"cycleTime": 14,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 257.1,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 40
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "habitat-ring-module",
|
||||||
|
"name": "Habitat Ring Module",
|
||||||
|
"description": "Crew habitat section packaged for large ship installation.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 2.6,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "habitat-ring-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "hull-sections", "amount": 14 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 8 },
|
||||||
|
{ "itemId": "water", "amount": 10 }
|
||||||
|
],
|
||||||
|
"cycleTime": 12,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 300,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 22
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bulk-bay-module",
|
||||||
|
"name": "Bulk Bay Module",
|
||||||
|
"description": "Industrial cargo hold segment for raw-solid hauling ships.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 2.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "bulk-bay-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 16 },
|
||||||
|
{ "itemId": "hull-sections", "amount": 10 }
|
||||||
|
],
|
||||||
|
"cycleTime": 8,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 450,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "container-bay-module",
|
||||||
|
"name": "Container Bay Module",
|
||||||
|
"description": "Freight rack segment for manufactured and palletized cargo.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 2.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "container-bay-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 12 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 4 }
|
||||||
|
],
|
||||||
|
"cycleTime": 8,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 450,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "liquid-tank-module",
|
||||||
|
"name": "Liquid Tank Module",
|
||||||
|
"description": "Pressurized liquid storage segment for water and liquid logistics.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 2.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "liquid-tank-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 14 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 4 }
|
||||||
|
],
|
||||||
|
"cycleTime": 8,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 450,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 18
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mining-turret-module",
|
||||||
|
"name": "Mining Turret Module",
|
||||||
|
"description": "Ship-mounted hard-rock extraction head.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 1.8,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "mining-turret-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 18 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 6 }
|
||||||
|
],
|
||||||
|
"cycleTime": 9,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 400,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 24
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fabricator-array-module",
|
||||||
|
"name": "Fabricator Array Module",
|
||||||
|
"description": "Mobile industrial fabrication block for constructors.",
|
||||||
|
"type": "ship-module",
|
||||||
|
"cargoKind": "container",
|
||||||
|
"volume": 2.4,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "fabricator-array-module-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["component-factory", "container-bay"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "refined-metals", "amount": 24 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 10 }
|
||||||
|
],
|
||||||
|
"cycleTime": 11,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 327.3,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 20
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "trade-hub-kit",
|
"id": "trade-hub-kit",
|
||||||
"label": "Trade Hub Kit",
|
"name": "Trade Hub Kit",
|
||||||
"storage": "manufactured",
|
"description": "Deployable prefab package for a trade hub station.",
|
||||||
"summary": "Deployable prefab package for a trade hub station."
|
"type": "kit",
|
||||||
|
"cargoKind": "manufactured",
|
||||||
|
"volume": 6.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "trade-hub-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "ship-parts", "amount": 26 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 16 },
|
||||||
|
{ "itemId": "drone-parts", "amount": 10 }
|
||||||
|
],
|
||||||
|
"cycleTime": 18,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 200,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 24
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "refinery-kit",
|
"id": "refinery-kit",
|
||||||
"label": "Refinery Kit",
|
"name": "Refinery Kit",
|
||||||
"storage": "manufactured",
|
"description": "Deployable prefab package for a refining station.",
|
||||||
"summary": "Deployable prefab package for a refining station."
|
"type": "kit",
|
||||||
|
"cargoKind": "manufactured",
|
||||||
|
"volume": 6.5,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "refinery-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "ship-parts", "amount": 32 },
|
||||||
|
{ "itemId": "hull-sections", "amount": 24 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 14 }
|
||||||
|
],
|
||||||
|
"cycleTime": 20,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 180,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 26
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "farm-ring-kit",
|
"id": "farm-ring-kit",
|
||||||
"label": "Farm Ring Kit",
|
"name": "Farm Ring Kit",
|
||||||
"storage": "manufactured",
|
"description": "Deployable prefab package for a farm station.",
|
||||||
"summary": "Deployable prefab package for a farm station."
|
"type": "kit",
|
||||||
|
"cargoKind": "manufactured",
|
||||||
|
"volume": 6.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "farm-ring-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "ship-parts", "amount": 22 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 18 },
|
||||||
|
{ "itemId": "water", "amount": 22 }
|
||||||
|
],
|
||||||
|
"cycleTime": 18,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 200,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 22
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "manufactory-kit",
|
"id": "manufactory-kit",
|
||||||
"label": "Manufactory Kit",
|
"name": "Manufactory Kit",
|
||||||
"storage": "manufactured",
|
"description": "Deployable prefab package for an orbital manufactory.",
|
||||||
"summary": "Deployable prefab package for an orbital manufactory."
|
"type": "kit",
|
||||||
|
"cargoKind": "manufactured",
|
||||||
|
"volume": 7.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "manufactory-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "ship-parts", "amount": 34 },
|
||||||
|
{ "itemId": "hull-sections", "amount": 16 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 18 }
|
||||||
|
],
|
||||||
|
"cycleTime": 22,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 163.6,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 28
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "shipyard-kit",
|
"id": "shipyard-kit",
|
||||||
"label": "Shipyard Kit",
|
"name": "Shipyard Kit",
|
||||||
"storage": "manufactured",
|
"description": "Deployable prefab package for an orbital shipyard.",
|
||||||
"summary": "Deployable prefab package for an orbital shipyard."
|
"type": "kit",
|
||||||
|
"cargoKind": "manufactured",
|
||||||
|
"volume": 8.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "shipyard-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "ship-parts", "amount": 42 },
|
||||||
|
{ "itemId": "hull-sections", "amount": 30 },
|
||||||
|
{ "itemId": "naval-guns", "amount": 10 }
|
||||||
|
],
|
||||||
|
"cycleTime": 26,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 138.5,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 30
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "defense-grid-kit",
|
"id": "defense-grid-kit",
|
||||||
"label": "Defense Grid Kit",
|
"name": "Defense Grid Kit",
|
||||||
"storage": "manufactured",
|
"description": "Deployable prefab package for a defense platform.",
|
||||||
"summary": "Deployable prefab package for a defense platform."
|
"type": "kit",
|
||||||
|
"cargoKind": "manufactured",
|
||||||
|
"volume": 7.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "defense-grid-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "ship-parts", "amount": 18 },
|
||||||
|
{ "itemId": "naval-guns", "amount": 12 },
|
||||||
|
{ "itemId": "ammo-crates", "amount": 18 }
|
||||||
|
],
|
||||||
|
"cycleTime": 16,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 225,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 20
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "stargate-kit",
|
"id": "stargate-kit",
|
||||||
"label": "Stargate Kit",
|
"name": "Stargate Kit",
|
||||||
"storage": "manufactured",
|
"description": "Deployable prefab package for a stargate structure.",
|
||||||
"summary": "Deployable prefab package for a stargate structure."
|
"type": "kit",
|
||||||
|
"cargoKind": "manufactured",
|
||||||
|
"volume": 10.0,
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "stargate-assembly",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": ["fabricator-array"],
|
||||||
|
"requirements": [
|
||||||
|
{ "itemId": "ship-parts", "amount": 60 },
|
||||||
|
{ "itemId": "hull-sections", "amount": 44 },
|
||||||
|
{ "itemId": "ship-equipment", "amount": 26 },
|
||||||
|
{ "itemId": "naval-guns", "amount": 8 }
|
||||||
|
],
|
||||||
|
"cycleTime": 34,
|
||||||
|
"batchSize": 1,
|
||||||
|
"productsPerHour": 105.9,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 36
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"moduleId": "dock-bay-small",
|
|
||||||
"duration": 12,
|
|
||||||
"inputs": [
|
|
||||||
{ "itemId": "refined-metals", "amount": 34 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleId": "gas-tank",
|
|
||||||
"duration": 10,
|
|
||||||
"inputs": [
|
|
||||||
{ "itemId": "refined-metals", "amount": 30 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleId": "container-bay",
|
|
||||||
"duration": 10,
|
|
||||||
"inputs": [
|
|
||||||
{ "itemId": "refined-metals", "amount": 26 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleId": "fuel-processor",
|
|
||||||
"duration": 14,
|
|
||||||
"inputs": [
|
|
||||||
{ "itemId": "refined-metals", "amount": 42 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleId": "refinery-stack",
|
|
||||||
"duration": 14,
|
|
||||||
"inputs": [
|
|
||||||
{ "itemId": "refined-metals", "amount": 38 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleId": "fabricator-array",
|
|
||||||
"duration": 16,
|
|
||||||
"inputs": [
|
|
||||||
{ "itemId": "refined-metals", "amount": 48 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleId": "component-factory",
|
|
||||||
"duration": 18,
|
|
||||||
"inputs": [
|
|
||||||
{ "itemId": "refined-metals", "amount": 54 },
|
|
||||||
{ "itemId": "ship-equipment", "amount": 12 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleId": "ship-factory",
|
|
||||||
"duration": 22,
|
|
||||||
"inputs": [
|
|
||||||
{ "itemId": "refined-metals", "amount": 60 },
|
|
||||||
{ "itemId": "hull-sections", "amount": 24 },
|
|
||||||
{ "itemId": "ship-equipment", "amount": 14 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleId": "solar-array",
|
|
||||||
"duration": 12,
|
|
||||||
"inputs": [
|
|
||||||
{ "itemId": "refined-metals", "amount": 28 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
247
shared/data/modules.json
Normal file
247
shared/data/modules.json
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "dock-bay-small",
|
||||||
|
"name": "Small Dock Bay",
|
||||||
|
"description": "External docking pad cluster for small and medium hulls.",
|
||||||
|
"type": "Dock",
|
||||||
|
"hull": 160,
|
||||||
|
"workforceNeeded": 10,
|
||||||
|
"construction": {
|
||||||
|
"productionTime": 12,
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "refined-metals",
|
||||||
|
"amount": 34
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "container-bay",
|
||||||
|
"name": "Container Bay",
|
||||||
|
"description": "Manufactured cargo storage and container handling racks.",
|
||||||
|
"type": "Storage",
|
||||||
|
"hull": 140,
|
||||||
|
"workforceNeeded": 8,
|
||||||
|
"construction": {
|
||||||
|
"productionTime": 10,
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "refined-metals",
|
||||||
|
"amount": 26
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bulk-bay",
|
||||||
|
"name": "Bulk Bay",
|
||||||
|
"description": "Raw solid storage and ore handling volume.",
|
||||||
|
"type": "Storage",
|
||||||
|
"hull": 140,
|
||||||
|
"workforceNeeded": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "liquid-tank",
|
||||||
|
"name": "Liquid Tank",
|
||||||
|
"description": "Liquid cargo and water tankage.",
|
||||||
|
"type": "Storage",
|
||||||
|
"hull": 130,
|
||||||
|
"workforceNeeded": 6,
|
||||||
|
"construction": {
|
||||||
|
"productionTime": 10,
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "refined-metals",
|
||||||
|
"amount": 20
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "refinery-stack",
|
||||||
|
"name": "Refinery Stack",
|
||||||
|
"description": "Heavy refining line for ore to refined metals.",
|
||||||
|
"type": "Production",
|
||||||
|
"product": "refined-metals",
|
||||||
|
"hull": 180,
|
||||||
|
"workforceNeeded": 18,
|
||||||
|
"construction": {
|
||||||
|
"productionTime": 14,
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "refined-metals",
|
||||||
|
"amount": 38
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "solar-array",
|
||||||
|
"name": "Solar Array",
|
||||||
|
"description": "Orbital solar generation and utility frame.",
|
||||||
|
"type": "Production",
|
||||||
|
"hull": 110,
|
||||||
|
"workforceNeeded": 6,
|
||||||
|
"construction": {
|
||||||
|
"productionTime": 12,
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "refined-metals",
|
||||||
|
"amount": 28
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "fabricator-array",
|
||||||
|
"name": "Fabricator Array",
|
||||||
|
"description": "General fabrication line for industrial goods and prefab kits.",
|
||||||
|
"type": "Build Module",
|
||||||
|
"hull": 200,
|
||||||
|
"workforceNeeded": 20,
|
||||||
|
"construction": {
|
||||||
|
"productionTime": 16,
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "refined-metals",
|
||||||
|
"amount": 48
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "component-factory",
|
||||||
|
"name": "Component Factory",
|
||||||
|
"description": "Assembly line for ship-grade modules and integrated components.",
|
||||||
|
"type": "Build Module",
|
||||||
|
"hull": 220,
|
||||||
|
"workforceNeeded": 24,
|
||||||
|
"construction": {
|
||||||
|
"productionTime": 18,
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "refined-metals",
|
||||||
|
"amount": 54
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ship-equipment",
|
||||||
|
"amount": 12
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ship-factory",
|
||||||
|
"name": "Ship Factory",
|
||||||
|
"description": "Slip-line and integration yard for final ship assembly.",
|
||||||
|
"type": "Build Module",
|
||||||
|
"hull": 260,
|
||||||
|
"workforceNeeded": 28,
|
||||||
|
"construction": {
|
||||||
|
"productionTime": 22,
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "refined-metals",
|
||||||
|
"amount": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "hull-sections",
|
||||||
|
"amount": 24
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ship-equipment",
|
||||||
|
"amount": 14
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "power-core",
|
||||||
|
"name": "Power Core",
|
||||||
|
"description": "Station backbone for power routing and core services.",
|
||||||
|
"type": "Connection",
|
||||||
|
"hull": 220,
|
||||||
|
"workforceNeeded": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "habitat-ring",
|
||||||
|
"name": "Habitat Ring",
|
||||||
|
"description": "Crew habitation and life-support section.",
|
||||||
|
"type": "Habitation",
|
||||||
|
"hull": 180,
|
||||||
|
"workforceNeeded": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "turret-grid",
|
||||||
|
"name": "Turret Grid",
|
||||||
|
"description": "Defensive hardpoints and fire-control grid.",
|
||||||
|
"type": "Defense",
|
||||||
|
"hull": 180,
|
||||||
|
"workforceNeeded": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "command-bridge",
|
||||||
|
"name": "Command Bridge",
|
||||||
|
"description": "Command-and-control section for stations and capital structures.",
|
||||||
|
"type": "Connection",
|
||||||
|
"hull": 150,
|
||||||
|
"workforceNeeded": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactor-core",
|
||||||
|
"name": "Reactor Core",
|
||||||
|
"description": "Primary reactor and power conversion system.",
|
||||||
|
"type": "Connection",
|
||||||
|
"hull": 150,
|
||||||
|
"workforceNeeded": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "capacitor-bank",
|
||||||
|
"name": "Capacitor Bank",
|
||||||
|
"description": "Energy buffering and discharge system.",
|
||||||
|
"type": "Connection",
|
||||||
|
"hull": 120,
|
||||||
|
"workforceNeeded": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ion-drive",
|
||||||
|
"name": "Ion Drive",
|
||||||
|
"description": "Primary sublight propulsion module.",
|
||||||
|
"type": "Connection",
|
||||||
|
"hull": 120,
|
||||||
|
"workforceNeeded": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ftl-core",
|
||||||
|
"name": "FTL Core",
|
||||||
|
"description": "Inter-system transit drive core.",
|
||||||
|
"type": "Connection",
|
||||||
|
"hull": 140,
|
||||||
|
"workforceNeeded": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gun-turret",
|
||||||
|
"name": "Gun Turret",
|
||||||
|
"description": "General purpose shipboard turret.",
|
||||||
|
"type": "Defense",
|
||||||
|
"hull": 100,
|
||||||
|
"workforceNeeded": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "carrier-bay",
|
||||||
|
"name": "Carrier Bay",
|
||||||
|
"description": "Launch and recovery bay for carried craft.",
|
||||||
|
"type": "Pier",
|
||||||
|
"hull": 160,
|
||||||
|
"workforceNeeded": 8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mining-turret",
|
||||||
|
"name": "Mining Turret",
|
||||||
|
"description": "Hard-rock extraction head for mining hulls.",
|
||||||
|
"type": "Production",
|
||||||
|
"hull": 90,
|
||||||
|
"workforceNeeded": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,13 @@
|
|||||||
{
|
{
|
||||||
"initialStations": [
|
"initialStations": [
|
||||||
{
|
{
|
||||||
"constructibleId": "station-core",
|
"label": "Orbital Station",
|
||||||
|
"startingModules": [
|
||||||
|
"dock-bay-small",
|
||||||
|
"power-core",
|
||||||
|
"bulk-bay",
|
||||||
|
"liquid-tank"
|
||||||
|
],
|
||||||
"systemId": "helios",
|
"systemId": "helios",
|
||||||
"planetIndex": 2,
|
"planetIndex": 2,
|
||||||
"lagrangeSide": -1
|
"lagrangeSide": -1
|
||||||
@@ -29,7 +35,7 @@
|
|||||||
"systemId": "helios"
|
"systemId": "helios"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"shipId": "gas-miner",
|
"shipId": "hauler",
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"center": [
|
"center": [
|
||||||
60,
|
60,
|
||||||
@@ -37,16 +43,6 @@
|
|||||||
28
|
28
|
||||||
],
|
],
|
||||||
"systemId": "helios"
|
"systemId": "helios"
|
||||||
},
|
|
||||||
{
|
|
||||||
"shipId": "gas-miner",
|
|
||||||
"count": 1,
|
|
||||||
"center": [
|
|
||||||
60,
|
|
||||||
0,
|
|
||||||
32
|
|
||||||
],
|
|
||||||
"systemId": "helios"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"patrolRoutes": [],
|
"patrolRoutes": [],
|
||||||
|
|||||||
@@ -13,7 +13,58 @@
|
|||||||
"hullColor": "#1f4f78",
|
"hullColor": "#1f4f78",
|
||||||
"size": 4,
|
"size": 4,
|
||||||
"maxHealth": 100,
|
"maxHealth": 100,
|
||||||
"modules": ["command-bridge", "reactor-core", "capacitor-bank", "ion-drive", "ftl-core", "gun-turret"]
|
"modules": [
|
||||||
|
"command-bridge",
|
||||||
|
"reactor-core",
|
||||||
|
"capacitor-bank",
|
||||||
|
"ion-drive",
|
||||||
|
"ftl-core",
|
||||||
|
"gun-turret"
|
||||||
|
],
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "frigate-construction",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": [
|
||||||
|
"ship-factory",
|
||||||
|
"dock-bay-small",
|
||||||
|
"container-bay",
|
||||||
|
"power-core"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "hull-sections",
|
||||||
|
"amount": 26
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "command-bridge-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "reactor-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "capacitor-bank-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ion-drive-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ftl-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "gun-turret-module",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cycleTime": 24,
|
||||||
|
"productsPerHour": 150,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 90
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "destroyer",
|
"id": "destroyer",
|
||||||
@@ -29,7 +80,59 @@
|
|||||||
"hullColor": "#6a2e26",
|
"hullColor": "#6a2e26",
|
||||||
"size": 7,
|
"size": 7,
|
||||||
"maxHealth": 240,
|
"maxHealth": 240,
|
||||||
"modules": ["command-bridge", "reactor-core", "capacitor-bank", "ion-drive", "ftl-core", "gun-turret", "gun-turret"]
|
"modules": [
|
||||||
|
"command-bridge",
|
||||||
|
"reactor-core",
|
||||||
|
"capacitor-bank",
|
||||||
|
"ion-drive",
|
||||||
|
"ftl-core",
|
||||||
|
"gun-turret",
|
||||||
|
"gun-turret"
|
||||||
|
],
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "destroyer-construction",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": [
|
||||||
|
"ship-factory",
|
||||||
|
"dock-bay-small",
|
||||||
|
"container-bay",
|
||||||
|
"power-core"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "hull-sections",
|
||||||
|
"amount": 44
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "command-bridge-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "reactor-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "capacitor-bank-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ion-drive-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ftl-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "gun-turret-module",
|
||||||
|
"amount": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cycleTime": 34,
|
||||||
|
"productsPerHour": 105.9,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 70
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cruiser",
|
"id": "cruiser",
|
||||||
@@ -45,7 +148,59 @@
|
|||||||
"hullColor": "#314562",
|
"hullColor": "#314562",
|
||||||
"size": 10,
|
"size": 10,
|
||||||
"maxHealth": 340,
|
"maxHealth": 340,
|
||||||
"modules": ["command-bridge", "reactor-core", "capacitor-bank", "ion-drive", "ftl-core", "gun-turret", "gun-turret"]
|
"modules": [
|
||||||
|
"command-bridge",
|
||||||
|
"reactor-core",
|
||||||
|
"capacitor-bank",
|
||||||
|
"ion-drive",
|
||||||
|
"ftl-core",
|
||||||
|
"gun-turret",
|
||||||
|
"gun-turret"
|
||||||
|
],
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "cruiser-construction",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": [
|
||||||
|
"ship-factory",
|
||||||
|
"dock-bay-small",
|
||||||
|
"container-bay",
|
||||||
|
"power-core"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "hull-sections",
|
||||||
|
"amount": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "command-bridge-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "reactor-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "capacitor-bank-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ion-drive-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ftl-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "gun-turret-module",
|
||||||
|
"amount": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cycleTime": 42,
|
||||||
|
"productsPerHour": 85.7,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 54
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "carrier",
|
"id": "carrier",
|
||||||
@@ -61,9 +216,75 @@
|
|||||||
"hullColor": "#35586d",
|
"hullColor": "#35586d",
|
||||||
"size": 16,
|
"size": 16,
|
||||||
"maxHealth": 900,
|
"maxHealth": 900,
|
||||||
"modules": ["command-bridge", "reactor-core", "capacitor-bank", "ion-drive", "ftl-core", "carrier-bay", "carrier-bay", "gun-turret", "habitat-ring"],
|
"modules": [
|
||||||
|
"command-bridge",
|
||||||
|
"reactor-core",
|
||||||
|
"capacitor-bank",
|
||||||
|
"ion-drive",
|
||||||
|
"ftl-core",
|
||||||
|
"carrier-bay",
|
||||||
|
"carrier-bay",
|
||||||
|
"gun-turret",
|
||||||
|
"habitat-ring"
|
||||||
|
],
|
||||||
"dockingCapacity": 6,
|
"dockingCapacity": 6,
|
||||||
"dockingClasses": ["frigate", "destroyer", "cruiser"]
|
"dockingClasses": [
|
||||||
|
"frigate",
|
||||||
|
"destroyer",
|
||||||
|
"cruiser"
|
||||||
|
],
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "carrier-construction",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": [
|
||||||
|
"ship-factory",
|
||||||
|
"dock-bay-small",
|
||||||
|
"container-bay",
|
||||||
|
"power-core"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "hull-sections",
|
||||||
|
"amount": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "command-bridge-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "reactor-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "capacitor-bank-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ion-drive-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ftl-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "carrier-bay-module",
|
||||||
|
"amount": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "gun-turret-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "habitat-ring-module",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cycleTime": 60,
|
||||||
|
"productsPerHour": 60,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 28
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "hauler",
|
"id": "hauler",
|
||||||
@@ -75,13 +296,63 @@
|
|||||||
"ftlSpeed": 0.55,
|
"ftlSpeed": 0.55,
|
||||||
"spoolTime": 3.3,
|
"spoolTime": 3.3,
|
||||||
"cargoCapacity": 180,
|
"cargoCapacity": 180,
|
||||||
"cargoKind": "bulk-liquid",
|
"cargoKind": "container",
|
||||||
"cargoItemId": "energy-cell",
|
|
||||||
"color": "#b0ff8d",
|
"color": "#b0ff8d",
|
||||||
"hullColor": "#365f2a",
|
"hullColor": "#365f2a",
|
||||||
"size": 8,
|
"size": 8,
|
||||||
"maxHealth": 180,
|
"maxHealth": 180,
|
||||||
"modules": ["command-bridge", "reactor-core", "capacitor-bank", "ion-drive", "ftl-core", "liquid-tank"]
|
"modules": [
|
||||||
|
"command-bridge",
|
||||||
|
"reactor-core",
|
||||||
|
"capacitor-bank",
|
||||||
|
"ion-drive",
|
||||||
|
"ftl-core",
|
||||||
|
"container-bay"
|
||||||
|
],
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "hauler-construction",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": [
|
||||||
|
"ship-factory",
|
||||||
|
"dock-bay-small",
|
||||||
|
"container-bay",
|
||||||
|
"power-core"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "hull-sections",
|
||||||
|
"amount": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "command-bridge-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "reactor-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "capacitor-bank-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ion-drive-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ftl-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "container-bay-module",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cycleTime": 26,
|
||||||
|
"productsPerHour": 138.5,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 8
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "constructor",
|
"id": "constructor",
|
||||||
@@ -99,7 +370,63 @@
|
|||||||
"hullColor": "#2d5d47",
|
"hullColor": "#2d5d47",
|
||||||
"size": 9,
|
"size": 9,
|
||||||
"maxHealth": 220,
|
"maxHealth": 220,
|
||||||
"modules": ["command-bridge", "reactor-core", "capacitor-bank", "ion-drive", "ftl-core", "fabricator-array", "container-bay"]
|
"modules": [
|
||||||
|
"command-bridge",
|
||||||
|
"reactor-core",
|
||||||
|
"capacitor-bank",
|
||||||
|
"ion-drive",
|
||||||
|
"ftl-core",
|
||||||
|
"fabricator-array",
|
||||||
|
"container-bay"
|
||||||
|
],
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "constructor-construction",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": [
|
||||||
|
"ship-factory",
|
||||||
|
"dock-bay-small",
|
||||||
|
"container-bay",
|
||||||
|
"power-core"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "hull-sections",
|
||||||
|
"amount": 42
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "command-bridge-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "reactor-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "capacitor-bank-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ion-drive-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "ftl-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "fabricator-array-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "container-bay-module",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cycleTime": 30,
|
||||||
|
"productsPerHour": 120,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 8
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "miner",
|
"id": "miner",
|
||||||
@@ -117,24 +444,62 @@
|
|||||||
"hullColor": "#68552b",
|
"hullColor": "#68552b",
|
||||||
"size": 6,
|
"size": 6,
|
||||||
"maxHealth": 150,
|
"maxHealth": 150,
|
||||||
"modules": ["command-bridge", "reactor-core", "capacitor-bank", "ion-drive", "ftl-core", "mining-turret", "bulk-bay"]
|
"modules": [
|
||||||
|
"command-bridge",
|
||||||
|
"reactor-core",
|
||||||
|
"capacitor-bank",
|
||||||
|
"ion-drive",
|
||||||
|
"ftl-core",
|
||||||
|
"mining-turret",
|
||||||
|
"bulk-bay"
|
||||||
|
],
|
||||||
|
"construction": {
|
||||||
|
"recipeId": "miner-construction",
|
||||||
|
"facilityCategory": "station",
|
||||||
|
"requiredModules": [
|
||||||
|
"ship-factory",
|
||||||
|
"dock-bay-small",
|
||||||
|
"container-bay",
|
||||||
|
"power-core"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"itemId": "hull-sections",
|
||||||
|
"amount": 34
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "gas-miner",
|
"itemId": "command-bridge-module",
|
||||||
"label": "Nimbus Gas Harvester",
|
"amount": 1
|
||||||
"role": "mining",
|
},
|
||||||
"shipClass": "industrial",
|
{
|
||||||
"speed": 72000,
|
"itemId": "reactor-core-module",
|
||||||
"warpSpeed": 0.145,
|
"amount": 1
|
||||||
"ftlSpeed": 0.49,
|
},
|
||||||
"spoolTime": 3.2,
|
{
|
||||||
"cargoCapacity": 120,
|
"itemId": "capacitor-bank-module",
|
||||||
"cargoKind": "bulk-gas",
|
"amount": 1
|
||||||
"cargoItemId": "gas",
|
},
|
||||||
"color": "#8ce5ff",
|
{
|
||||||
"hullColor": "#2a5668",
|
"itemId": "ion-drive-module",
|
||||||
"size": 6,
|
"amount": 1
|
||||||
"maxHealth": 150,
|
},
|
||||||
"modules": ["command-bridge", "reactor-core", "capacitor-bank", "ion-drive", "ftl-core", "gas-extractor", "gas-tank"]
|
{
|
||||||
|
"itemId": "ftl-core-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "mining-turret-module",
|
||||||
|
"amount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"itemId": "bulk-bay-module",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cycleTime": 28,
|
||||||
|
"productsPerHour": 128.6,
|
||||||
|
"maxEfficiency": 1,
|
||||||
|
"priority": 8
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user