feat: rework modules, items and fuel
This commit is contained in:
@@ -17,10 +17,6 @@ public sealed record StationSnapshot(
|
||||
int DockedShips,
|
||||
IReadOnlyList<string> DockedShipIds,
|
||||
int DockingPads,
|
||||
float FuelStored,
|
||||
float FuelCapacity,
|
||||
float EnergyStored,
|
||||
float EnergyCapacity,
|
||||
IReadOnlyList<StationActionProgressSnapshot> CurrentProcesses,
|
||||
IReadOnlyList<InventoryEntry> Inventory,
|
||||
string FactionId,
|
||||
@@ -30,6 +26,7 @@ public sealed record StationSnapshot(
|
||||
float PopulationCapacity,
|
||||
float WorkforceRequired,
|
||||
float WorkforceEffectiveRatio,
|
||||
IReadOnlyList<StationStorageUsageSnapshot> StorageUsage,
|
||||
IReadOnlyList<string> InstalledModules,
|
||||
IReadOnlyList<string> MarketOrderIds);
|
||||
|
||||
@@ -46,10 +43,6 @@ public sealed record StationDelta(
|
||||
int DockedShips,
|
||||
IReadOnlyList<string> DockedShipIds,
|
||||
int DockingPads,
|
||||
float FuelStored,
|
||||
float FuelCapacity,
|
||||
float EnergyStored,
|
||||
float EnergyCapacity,
|
||||
IReadOnlyList<StationActionProgressSnapshot> CurrentProcesses,
|
||||
IReadOnlyList<InventoryEntry> Inventory,
|
||||
string FactionId,
|
||||
@@ -59,6 +52,7 @@ public sealed record StationDelta(
|
||||
float PopulationCapacity,
|
||||
float WorkforceRequired,
|
||||
float WorkforceEffectiveRatio,
|
||||
IReadOnlyList<StationStorageUsageSnapshot> StorageUsage,
|
||||
IReadOnlyList<string> InstalledModules,
|
||||
IReadOnlyList<string> MarketOrderIds);
|
||||
|
||||
@@ -67,6 +61,11 @@ public sealed record StationActionProgressSnapshot(
|
||||
string Label,
|
||||
float Progress);
|
||||
|
||||
public sealed record StationStorageUsageSnapshot(
|
||||
string StorageClass,
|
||||
float Used,
|
||||
float Capacity);
|
||||
|
||||
public sealed record ClaimSnapshot(
|
||||
string Id,
|
||||
string FactionId,
|
||||
|
||||
@@ -21,7 +21,6 @@ public sealed record ShipSnapshot(
|
||||
float CargoCapacity,
|
||||
string? CargoItemId,
|
||||
float WorkerPopulation,
|
||||
float EnergyStored,
|
||||
float TravelSpeed,
|
||||
string TravelSpeedUnit,
|
||||
IReadOnlyList<InventoryEntry> Inventory,
|
||||
@@ -52,7 +51,6 @@ public sealed record ShipDelta(
|
||||
float CargoCapacity,
|
||||
string? CargoItemId,
|
||||
float WorkerPopulation,
|
||||
float EnergyStored,
|
||||
float TravelSpeed,
|
||||
string TravelSpeedUnit,
|
||||
IReadOnlyList<InventoryEntry> Inventory,
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
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 float YPlane { get; set; }
|
||||
@@ -10,16 +23,6 @@ public sealed class BalanceDefinition
|
||||
public float DockingDuration { get; set; }
|
||||
public float UndockingDuration { 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
|
||||
@@ -62,9 +65,18 @@ public sealed class ResourceNodeDefinition
|
||||
public sealed class ItemDefinition
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
public required string Label { get; set; }
|
||||
public required string Storage { get; set; }
|
||||
public string Summary { get; set; } = string.Empty;
|
||||
public required string Name { get; set; }
|
||||
public string Description { 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
|
||||
@@ -73,6 +85,25 @@ public sealed class RecipeInputDefinition
|
||||
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 required string ModuleId { get; set; }
|
||||
@@ -80,12 +111,6 @@ public sealed class ModuleRecipeDefinition
|
||||
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 required string Id { get; set; }
|
||||
@@ -136,18 +161,7 @@ public sealed class ShipDefinition
|
||||
public float Size { get; set; }
|
||||
public float MaxHealth { get; set; }
|
||||
public List<string> Modules { 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 ConstructionDefinition? Construction { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ScenarioDefinition
|
||||
@@ -160,8 +174,10 @@ public sealed class ScenarioDefinition
|
||||
|
||||
public sealed class InitialStationDefinition
|
||||
{
|
||||
public required string ConstructibleId { 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 int? PlanetIndex { get; set; }
|
||||
public int? LagrangeSide { get; set; }
|
||||
|
||||
@@ -19,8 +19,6 @@ internal sealed class ShipBehaviorStateMachine
|
||||
idleState,
|
||||
new PatrolShipBehaviorState(),
|
||||
new ResourceHarvestShipBehaviorState("auto-mine", "ore", "mining-turret"),
|
||||
new ResourceHarvestShipBehaviorState("auto-harvest-gas", "gas", "gas-extractor"),
|
||||
new EnergySupplyShipBehaviorState(),
|
||||
new ConstructStationShipBehaviorState(),
|
||||
};
|
||||
|
||||
|
||||
@@ -90,13 +90,7 @@ internal sealed class ResourceHarvestShipBehaviorState : IShipBehaviorState
|
||||
ship.DefaultBehavior.Phase = "dock";
|
||||
break;
|
||||
case ("dock", "docked"):
|
||||
ship.DefaultBehavior.Phase = SimulationEngine.GetShipCargoAmount(ship) > 0.01f ? "unload" : "refuel";
|
||||
break;
|
||||
case ("unload", "unloaded"):
|
||||
ship.DefaultBehavior.Phase = "refuel";
|
||||
break;
|
||||
case ("refuel", "refueled"):
|
||||
ship.DefaultBehavior.Phase = "undock";
|
||||
ship.DefaultBehavior.Phase = "unload";
|
||||
break;
|
||||
case ("undock", "undocked"):
|
||||
ship.DefaultBehavior.Phase = "travel-to-node";
|
||||
@@ -118,9 +112,6 @@ internal sealed class ConstructStationShipBehaviorState : IShipBehaviorState
|
||||
switch (ship.DefaultBehavior.Phase, controllerEvent)
|
||||
{
|
||||
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";
|
||||
break;
|
||||
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 Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
|
||||
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 string? CommanderId { get; set; }
|
||||
public string? PolicySetId { get; set; }
|
||||
|
||||
@@ -28,7 +28,6 @@ public enum ShipState
|
||||
{
|
||||
Idle,
|
||||
Arriving,
|
||||
CapacitorStarved,
|
||||
LocalFlight,
|
||||
SpoolingWarp,
|
||||
Warping,
|
||||
@@ -45,7 +44,6 @@ public enum ShipState
|
||||
Transferring,
|
||||
Loading,
|
||||
Unloading,
|
||||
Refueling,
|
||||
WaitingMaterials,
|
||||
ConstructionBlocked,
|
||||
Constructing,
|
||||
@@ -62,7 +60,6 @@ public enum ControllerTaskKind
|
||||
Dock,
|
||||
Load,
|
||||
Unload,
|
||||
Refuel,
|
||||
DeliverConstruction,
|
||||
BuildConstructionSite,
|
||||
LoadWorkers,
|
||||
@@ -197,7 +194,6 @@ public static class SimulationEnumMappings
|
||||
{
|
||||
ShipState.Idle => "idle",
|
||||
ShipState.Arriving => "arriving",
|
||||
ShipState.CapacitorStarved => "capacitor-starved",
|
||||
ShipState.LocalFlight => "local-flight",
|
||||
ShipState.SpoolingWarp => "spooling-warp",
|
||||
ShipState.Warping => "warping",
|
||||
@@ -214,7 +210,6 @@ public static class SimulationEnumMappings
|
||||
ShipState.Transferring => "transferring",
|
||||
ShipState.Loading => "loading",
|
||||
ShipState.Unloading => "unloading",
|
||||
ShipState.Refueling => "refueling",
|
||||
ShipState.WaitingMaterials => "waiting-materials",
|
||||
ShipState.ConstructionBlocked => "construction-blocked",
|
||||
ShipState.Constructing => "constructing",
|
||||
@@ -232,7 +227,6 @@ public static class SimulationEnumMappings
|
||||
ControllerTaskKind.Dock => "dock",
|
||||
ControllerTaskKind.Load => "load",
|
||||
ControllerTaskKind.Unload => "unload",
|
||||
ControllerTaskKind.Refuel => "refuel",
|
||||
ControllerTaskKind.DeliverConstruction => "deliver-construction",
|
||||
ControllerTaskKind.BuildConstructionSite => "build-construction-site",
|
||||
ControllerTaskKind.LoadWorkers => "load-workers",
|
||||
|
||||
@@ -21,6 +21,7 @@ public sealed class SimulationWorld
|
||||
public required List<PolicySetRuntime> Policies { get; init; }
|
||||
public required Dictionary<string, ShipDefinition> ShipDefinitions { 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, RecipeDefinition> Recipes { get; init; }
|
||||
public int TickIntervalMs { get; init; } = 200;
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
using SpaceGame.Simulation.Api.Data;
|
||||
|
||||
namespace SpaceGame.Simulation.Api.Simulation;
|
||||
|
||||
public sealed class StationRuntime
|
||||
{
|
||||
public required string Id { 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 float Radius { get; set; } = 24f;
|
||||
public required string FactionId { get; init; }
|
||||
public string? NodeId { get; set; }
|
||||
public string? BubbleId { get; set; }
|
||||
public string? AnchorNodeId { get; set; }
|
||||
public string? CommanderId { 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> ProductionLaneTimers { get; } = new(StringComparer.Ordinal);
|
||||
public Dictionary<int, string> DockingPadAssignments { get; } = new();
|
||||
public HashSet<string> MarketOrderIds { get; } = new(StringComparer.Ordinal);
|
||||
public float EnergyStored { get; set; }
|
||||
public float Population { get; set; }
|
||||
public float PopulationCapacity { get; set; }
|
||||
public float WorkforceRequired { get; set; }
|
||||
@@ -31,6 +32,14 @@ public sealed class StationRuntime
|
||||
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 required string ModuleId { get; init; }
|
||||
|
||||
@@ -254,7 +254,6 @@ public sealed partial class ScenarioLoader
|
||||
}
|
||||
|
||||
nodes.AddRange(BuildAsteroidBeltNodes(generatedIndex, planets));
|
||||
nodes.AddRange(BuildGasCloudNodes(generatedIndex, planets));
|
||||
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)
|
||||
{
|
||||
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 = 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 = "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 =
|
||||
[
|
||||
|
||||
@@ -70,7 +70,7 @@ public sealed partial class ScenarioLoader
|
||||
.ToList();
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -171,7 +171,7 @@ public sealed partial class ScenarioLoader
|
||||
NodeId = anchorNode.Id,
|
||||
BubbleId = anchorNode.BubbleId,
|
||||
TargetKind = "station-module",
|
||||
TargetDefinitionId = station.Definition.Id,
|
||||
TargetDefinitionId = "station",
|
||||
BlueprintId = moduleId,
|
||||
ClaimId = claim.Id,
|
||||
StationId = station.Id,
|
||||
@@ -213,8 +213,6 @@ public sealed partial class ScenarioLoader
|
||||
{
|
||||
foreach (var (moduleId, targetCount) in new (string ModuleId, int TargetCount)[]
|
||||
{
|
||||
("gas-tank", 1),
|
||||
("fuel-processor", 1),
|
||||
("refinery-stack", 1),
|
||||
("container-bay", 1),
|
||||
("fabricator-array", 2),
|
||||
@@ -238,7 +236,7 @@ public sealed partial class ScenarioLoader
|
||||
{
|
||||
var habitatModules = CountModules(station.InstalledModules, "habitat-ring");
|
||||
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
|
||||
? MathF.Min(station.PopulationCapacity * 0.65f, station.WorkforceRequired * 1.05f)
|
||||
: 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)
|
||||
{
|
||||
return CreateResourceHarvestBehavior("auto-mine", scenario.MiningDefaults.NodeSystemId, refinery.Id);
|
||||
|
||||
@@ -97,15 +97,15 @@ public sealed partial class ScenarioLoader
|
||||
var scenario = NormalizeScenarioToAvailableSystems(
|
||||
Read<ScenarioDefinition>("scenario.json"),
|
||||
systems.Select((system) => system.Id).ToList());
|
||||
var modules = Read<List<ModuleDefinition>>("modules.json");
|
||||
var ships = Read<List<ShipDefinition>>("ships.json");
|
||||
var constructibles = Read<List<ConstructibleDefinition>>("constructibles.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 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 constructibleDefinitions = constructibles.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 moduleRecipeDefinitions = moduleRecipes.ToDictionary((definition) => definition.ModuleId, StringComparer.Ordinal);
|
||||
@@ -178,7 +178,7 @@ public sealed partial class ScenarioLoader
|
||||
var stationIdCounter = 0;
|
||||
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;
|
||||
}
|
||||
@@ -188,7 +188,8 @@ public sealed partial class ScenarioLoader
|
||||
{
|
||||
Id = $"station-{++stationIdCounter}",
|
||||
SystemId = system.Definition.Id,
|
||||
Definition = definition,
|
||||
Label = plan.Label,
|
||||
Color = plan.Color,
|
||||
Position = placement.Position,
|
||||
FactionId = plan.FactionId ?? DefaultFactionId,
|
||||
};
|
||||
@@ -214,21 +215,23 @@ public sealed partial class ScenarioLoader
|
||||
Id = stationBubbleId,
|
||||
NodeId = stationNodeId,
|
||||
SystemId = station.SystemId,
|
||||
Radius = MathF.Max(160f, definition.Radius + 60f),
|
||||
Radius = MathF.Max(160f, GetStationRadius(moduleDefinitions, station) + 60f),
|
||||
});
|
||||
localBubbles[^1].OccupantStationIds.Add(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)
|
||||
{
|
||||
InitializeStationPopulation(station);
|
||||
station.Inventory["fuel"] = 240f;
|
||||
station.Inventory["refined-metals"] = 120f;
|
||||
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 },
|
||||
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,
|
||||
ShipDefinitions = shipDefinitions,
|
||||
ItemDefinitions = itemDefinitions,
|
||||
ModuleDefinitions = moduleDefinitions,
|
||||
ModuleRecipes = moduleRecipeDefinitions,
|
||||
Recipes = recipeDefinitions,
|
||||
OrbitalTimeSeconds = WorldSeed * 97d,
|
||||
@@ -356,8 +347,10 @@ public sealed partial class ScenarioLoader
|
||||
InitialStations = scenario.InitialStations
|
||||
.Select((station) => new InitialStationDefinition
|
||||
{
|
||||
ConstructibleId = station.ConstructibleId,
|
||||
SystemId = ResolveSystemId(station.SystemId),
|
||||
Label = station.Label,
|
||||
Color = station.Color,
|
||||
StartingModules = station.StartingModules.ToList(),
|
||||
FactionId = station.FactionId,
|
||||
PlanetIndex = station.PlanetIndex,
|
||||
LagrangeSide = station.LagrangeSide,
|
||||
@@ -404,15 +397,37 @@ public sealed partial class ScenarioLoader
|
||||
: 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) =>
|
||||
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) =>
|
||||
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 int CountModules(IEnumerable<string> modules, string moduleId) =>
|
||||
@@ -429,6 +444,89 @@ public sealed partial class ScenarioLoader
|
||||
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 float DegreesToRadians(float degrees) => degrees * (MathF.PI / 180f);
|
||||
|
||||
@@ -25,7 +25,6 @@ public sealed partial class SimulationEngine
|
||||
ControllerTaskKind.Dock => UpdateDock(ship, world, deltaSeconds),
|
||||
ControllerTaskKind.Load => UpdateLoadCargo(ship, world, deltaSeconds),
|
||||
ControllerTaskKind.Unload => UpdateUnload(ship, world, deltaSeconds),
|
||||
ControllerTaskKind.Refuel => UpdateRefuel(ship, world, deltaSeconds),
|
||||
ControllerTaskKind.DeliverConstruction => UpdateDeliverConstruction(ship, world, deltaSeconds),
|
||||
ControllerTaskKind.BuildConstructionSite => UpdateBuildConstructionSite(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)
|
||||
{
|
||||
TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds);
|
||||
ship.State = ShipState.Idle;
|
||||
ship.TargetPosition = ship.Position;
|
||||
return "none";
|
||||
@@ -133,7 +131,6 @@ public sealed partial class SimulationEngine
|
||||
if (distance <= threshold)
|
||||
{
|
||||
ship.ActionTimer = 0f;
|
||||
TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds);
|
||||
ship.Position = targetPosition;
|
||||
ship.TargetPosition = ship.Position;
|
||||
ship.SystemId = targetSystemId;
|
||||
@@ -143,13 +140,6 @@ public sealed partial class SimulationEngine
|
||||
return "arrived";
|
||||
}
|
||||
|
||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.MoveDrain * deltaSeconds))
|
||||
{
|
||||
ship.State = ShipState.CapacitorStarved;
|
||||
ship.TargetPosition = ship.Position;
|
||||
return "none";
|
||||
}
|
||||
|
||||
ship.ActionTimer = 0f;
|
||||
ship.State = ShipState.LocalFlight;
|
||||
ship.Position = ship.Position.MoveToward(targetPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
|
||||
@@ -185,13 +175,6 @@ public sealed partial class SimulationEngine
|
||||
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;
|
||||
if (!AdvanceTimedAction(ship, deltaSeconds, spoolDuration))
|
||||
{
|
||||
@@ -201,13 +184,6 @@ public sealed partial class SimulationEngine
|
||||
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
|
||||
? 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;
|
||||
}
|
||||
|
||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.IdleDrain * deltaSeconds))
|
||||
{
|
||||
ship.State = ShipState.CapacitorStarved;
|
||||
ship.TargetPosition = ship.Position;
|
||||
return "none";
|
||||
}
|
||||
|
||||
ship.State = ShipState.SpoolingFtl;
|
||||
if (!AdvanceTimedAction(ship, deltaSeconds, ship.Definition.SpoolTime))
|
||||
{
|
||||
@@ -263,13 +232,6 @@ public sealed partial class SimulationEngine
|
||||
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 destinationSystemPosition = ResolveSystemGalaxyPosition(world, targetSystemId);
|
||||
var totalDistance = MathF.Max(0.001f, originSystemPosition.DistanceTo(destinationSystemPosition));
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed partial class SimulationEngine
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,6 @@ namespace SpaceGame.Simulation.Api.Simulation;
|
||||
|
||||
public sealed partial class SimulationEngine
|
||||
{
|
||||
private const float StationEnergyCellToEnergyRatio = 1f;
|
||||
|
||||
private static bool HasShipModules(ShipDefinition definition, params string[] modules) =>
|
||||
modules.All(moduleId => definition.Modules.Contains(moduleId, StringComparer.Ordinal));
|
||||
|
||||
@@ -16,169 +14,56 @@ public sealed partial class SimulationEngine
|
||||
private static float GetWorkerTransportCapacity(ShipRuntime ship) =>
|
||||
CountModules(ship.Definition.Modules, "habitat-ring") * 120f;
|
||||
|
||||
private static void UpdateStationPower(SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
foreach (var station in world.Stations)
|
||||
{
|
||||
var previousEnergy = station.EnergyStored;
|
||||
GenerateStationEnergy(station, world, deltaSeconds);
|
||||
private static int CountStationModules(StationRuntime station, string moduleId) =>
|
||||
station.Modules.Count(module => string.Equals(module.ModuleId, moduleId, StringComparison.Ordinal));
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateShipPower(ShipRuntime ship, SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
if (!world.ModuleDefinitions.TryGetValue(moduleId, out var definition))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
var energyCapacity = powerCores * StationEnergyPerPowerCore;
|
||||
var fuelStored = GetInventoryAmount(station.Inventory, "fuel");
|
||||
var desiredEnergy = MathF.Max(0f, energyCapacity - station.EnergyStored);
|
||||
if (desiredEnergy <= 0.01f)
|
||||
station.Modules.Add(new StationModuleRuntime
|
||||
{
|
||||
station.EnergyStored = MathF.Min(station.EnergyStored, energyCapacity);
|
||||
station.Inventory["fuel"] = MathF.Min(fuelStored, tanks * StationFuelPerTank);
|
||||
return;
|
||||
Id = $"{station.Id}-module-{station.Modules.Count + 1}",
|
||||
ModuleId = moduleId,
|
||||
Health = definition.Hull,
|
||||
MaxHealth = definition.Hull,
|
||||
});
|
||||
station.Radius = GetStationRadius(world, station);
|
||||
}
|
||||
|
||||
var solarGenerated = MathF.Min(desiredEnergy, GetStationSolarGeneration(station, world) * deltaSeconds);
|
||||
if (solarGenerated > 0.01f)
|
||||
private static float GetStationRadius(SimulationWorld world, StationRuntime station)
|
||||
{
|
||||
station.EnergyStored = MathF.Min(energyCapacity, station.EnergyStored + solarGenerated);
|
||||
desiredEnergy = MathF.Max(0f, energyCapacity - station.EnergyStored);
|
||||
var totalArea = station.Modules
|
||||
.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)
|
||||
{
|
||||
var baseCapacity = station.Definition.Storage.TryGetValue(storageClass, out var capacity)
|
||||
? 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
|
||||
var baseCapacity = storageClass switch
|
||||
{
|
||||
"bulk-solid" => extraBulkBays * 1000f,
|
||||
"bulk-liquid" => extraLiquidTanks * 500f,
|
||||
"bulk-gas" => extraGasTanks * 500f,
|
||||
"container" => extraContainerBays * 800f,
|
||||
"manufactured" => 400f,
|
||||
_ => 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");
|
||||
var capacitors = CountModules(ship.Definition.Modules, "capacitor-bank");
|
||||
if (reactors <= 0 || capacitors <= 0)
|
||||
{
|
||||
ship.EnergyStored = 0f;
|
||||
ship.Inventory.Remove("fuel");
|
||||
return;
|
||||
}
|
||||
"bulk-solid" => bulkBays * 1000f,
|
||||
"bulk-liquid" => liquidTanks * 500f,
|
||||
"container" => containerBays * 800f,
|
||||
"manufactured" => containerBays * 200f,
|
||||
_ => 0f,
|
||||
};
|
||||
|
||||
var energyCapacity = capacitors * CapacitorEnergyPerModule;
|
||||
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;
|
||||
return baseCapacity + moduleCapacity;
|
||||
}
|
||||
|
||||
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) =>
|
||||
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) =>
|
||||
node.ItemId switch
|
||||
{
|
||||
"ore" => HasShipModules(ship.Definition, "reactor-core", "capacitor-bank", "mining-turret"),
|
||||
"gas" => HasShipModules(ship.Definition, "reactor-core", "capacitor-bank", "gas-extractor"),
|
||||
"ore" => HasShipModules(ship.Definition, "mining-turret"),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private static bool CanBuildClaimBeacon(ShipRuntime ship) =>
|
||||
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)
|
||||
{
|
||||
if (workforceRequired <= 0.01f)
|
||||
@@ -503,7 +128,6 @@ public sealed partial class SimulationEngine
|
||||
{
|
||||
"bulk-solid" => "bulk-bay",
|
||||
"bulk-liquid" => "liquid-tank",
|
||||
"bulk-gas" => "gas-tank",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
@@ -514,7 +138,7 @@ public sealed partial class SimulationEngine
|
||||
return 0f;
|
||||
}
|
||||
|
||||
var storageClass = itemDefinition.Storage;
|
||||
var storageClass = itemDefinition.CargoKind;
|
||||
var requiredModule = GetStorageRequirement(storageClass);
|
||||
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
||||
{
|
||||
@@ -528,7 +152,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
|
||||
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);
|
||||
var accepted = MathF.Min(amount, MathF.Max(0f, capacity - used));
|
||||
if (accepted <= 0.01f)
|
||||
|
||||
@@ -79,10 +79,6 @@ public sealed partial class SimulationEngine
|
||||
station.DockedShips,
|
||||
station.DockedShipIds,
|
||||
station.DockingPads,
|
||||
station.FuelStored,
|
||||
station.FuelCapacity,
|
||||
station.EnergyStored,
|
||||
station.EnergyCapacity,
|
||||
station.CurrentProcesses,
|
||||
station.Inventory,
|
||||
station.FactionId,
|
||||
@@ -92,6 +88,7 @@ public sealed partial class SimulationEngine
|
||||
station.PopulationCapacity,
|
||||
station.WorkforceRequired,
|
||||
station.WorkforceEffectiveRatio,
|
||||
station.StorageUsage,
|
||||
station.InstalledModules,
|
||||
station.MarketOrderIds)).ToList(),
|
||||
world.Claims.Select(ToClaimDelta).Select(claim => new ClaimSnapshot(
|
||||
@@ -104,7 +101,7 @@ public sealed partial class SimulationEngine
|
||||
claim.Health,
|
||||
claim.PlacedAtUtc,
|
||||
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.FactionId,
|
||||
site.SystemId,
|
||||
@@ -164,7 +161,6 @@ public sealed partial class SimulationEngine
|
||||
ship.CargoCapacity,
|
||||
ship.CargoItemId,
|
||||
ship.WorkerPopulation,
|
||||
ship.EnergyStored,
|
||||
ship.TravelSpeed,
|
||||
ship.TravelSpeedUnit,
|
||||
ship.Inventory,
|
||||
@@ -341,7 +337,7 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
|
||||
site.LastDeltaSignature = signature;
|
||||
deltas.Add(ToConstructionSiteDelta(site));
|
||||
deltas.Add(ToConstructionSiteDelta(world, site));
|
||||
}
|
||||
|
||||
return deltas;
|
||||
@@ -439,10 +435,6 @@ public sealed partial class SimulationEngine
|
||||
station.CommanderId ?? "none",
|
||||
station.PolicySetId ?? "none",
|
||||
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(",", station.DockedShipIds.OrderBy(id => id, StringComparer.Ordinal)),
|
||||
station.DockingPadAssignments.Count.ToString(),
|
||||
@@ -501,13 +493,11 @@ public sealed partial class SimulationEngine
|
||||
ship.SpatialState.Transit?.DestinationNodeId ?? "none",
|
||||
ship.SpatialState.Transit?.Progress.ToString("0.###") ?? "0",
|
||||
GetShipCargoAmount(ship).ToString("0.###"),
|
||||
GetInventoryAmount(ship.Inventory, "fuel").ToString("0.###"),
|
||||
ship.TrackedActionKey ?? "none",
|
||||
ship.TrackedActionTotal.ToString("0.###"),
|
||||
ship.ControllerTask.TargetEntityId is not null && world.ConstructionSites.FirstOrDefault(site => site.Id == ship.ControllerTask.TargetEntityId) is { } site
|
||||
? GetRemainingConstructionDelivery(world, site).ToString("0.###")
|
||||
: "0",
|
||||
ship.EnergyStored.ToString("0.###"),
|
||||
ship.Health.ToString("0.###"),
|
||||
ship.ActionTimer.ToString("0.###"));
|
||||
|
||||
@@ -553,21 +543,17 @@ public sealed partial class SimulationEngine
|
||||
|
||||
private static StationDelta ToStationDelta(SimulationWorld world, StationRuntime station) => new(
|
||||
station.Id,
|
||||
station.Definition.Label,
|
||||
station.Definition.Category,
|
||||
station.Label,
|
||||
station.Category,
|
||||
station.SystemId,
|
||||
ToDto(station.Position),
|
||||
station.NodeId,
|
||||
station.BubbleId,
|
||||
station.AnchorNodeId,
|
||||
station.Definition.Color,
|
||||
station.Color,
|
||||
station.DockedShipIds.Count,
|
||||
station.DockedShipIds.OrderBy(id => id, StringComparer.Ordinal).ToList(),
|
||||
GetDockingPadCount(station),
|
||||
GetInventoryAmount(station.Inventory, "fuel"),
|
||||
GetStationFuelCapacity(station),
|
||||
station.EnergyStored,
|
||||
GetStationEnergyCapacity(station),
|
||||
ToStationActionProgressSnapshots(world, station),
|
||||
ToInventoryEntries(station.Inventory),
|
||||
station.FactionId,
|
||||
@@ -577,6 +563,7 @@ public sealed partial class SimulationEngine
|
||||
station.PopulationCapacity,
|
||||
station.WorkforceRequired,
|
||||
station.WorkforceEffectiveRatio,
|
||||
ToStationStorageUsageSnapshots(world, station),
|
||||
station.InstalledModules.OrderBy(moduleId => moduleId, 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 timer = GetStationProductionTimer(station, laneKey);
|
||||
return recipe is null || station.EnergyStored <= 0.01f || timer <= 0.01f
|
||||
return recipe is null || timer <= 0.01f
|
||||
? null
|
||||
: new StationActionProgressSnapshot(
|
||||
laneKey,
|
||||
@@ -597,6 +584,20 @@ public sealed partial class SimulationEngine
|
||||
.Cast<StationActionProgressSnapshot>()
|
||||
.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(
|
||||
claim.Id,
|
||||
claim.FactionId,
|
||||
@@ -608,7 +609,7 @@ public sealed partial class SimulationEngine
|
||||
claim.PlacedAtUtc,
|
||||
claim.ActivatesAtUtc);
|
||||
|
||||
private static ConstructionSiteDelta ToConstructionSiteDelta(ConstructionSiteRuntime site) => new(
|
||||
private static ConstructionSiteDelta ToConstructionSiteDelta(SimulationWorld world, ConstructionSiteRuntime site) => new(
|
||||
site.Id,
|
||||
site.FactionId,
|
||||
site.SystemId,
|
||||
@@ -620,13 +621,25 @@ public sealed partial class SimulationEngine
|
||||
site.ClaimId,
|
||||
site.StationId,
|
||||
site.State,
|
||||
site.Progress,
|
||||
GetConstructionSiteProgress(world, site),
|
||||
ToInventoryEntries(site.Inventory),
|
||||
ToInventoryEntries(site.RequiredItems),
|
||||
ToInventoryEntries(site.DeliveredItems),
|
||||
site.AssignedConstructorShipIds.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(
|
||||
order.Id,
|
||||
order.FactionId,
|
||||
@@ -671,7 +684,6 @@ public sealed partial class SimulationEngine
|
||||
ship.Definition.CargoCapacity,
|
||||
ship.Definition.CargoItemId,
|
||||
ship.WorkerPopulation,
|
||||
ship.EnergyStored,
|
||||
ToShipTravelSpeed(ship).Speed,
|
||||
ToShipTravelSpeed(ship).Unit,
|
||||
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.Undocking => CreateShipActionProgress("Undocking", ship.ActionTimer, MathF.Max(world.Balance.UndockingDuration, 0.1f)),
|
||||
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(
|
||||
"Load workers",
|
||||
ship.TrackedActionTotal,
|
||||
|
||||
@@ -109,8 +109,6 @@ public sealed partial class SimulationEngine
|
||||
var priorities = GetFactionExpansionPressure(world, station.FactionId) > 0f
|
||||
? new (string ModuleId, int TargetCount)[]
|
||||
{
|
||||
("gas-tank", 1),
|
||||
("fuel-processor", 1),
|
||||
("refinery-stack", 1),
|
||||
("container-bay", 1),
|
||||
("fabricator-array", 2),
|
||||
@@ -121,8 +119,6 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
: new (string ModuleId, int TargetCount)[]
|
||||
{
|
||||
("gas-tank", 1),
|
||||
("fuel-processor", 1),
|
||||
("refinery-stack", 1),
|
||||
("container-bay", 1),
|
||||
("fabricator-array", 2),
|
||||
@@ -238,7 +234,7 @@ public sealed partial class SimulationEngine
|
||||
{
|
||||
var padCount = Math.Max(1, GetDockingPadCount(station));
|
||||
var angle = ((MathF.PI * 2f) / padCount) * padIndex;
|
||||
var radius = station.Definition.Radius + 18f;
|
||||
var radius = station.Radius + 18f;
|
||||
return new Vector3(
|
||||
station.Position.X + (MathF.Cos(angle) * radius),
|
||||
station.Position.Y,
|
||||
@@ -249,7 +245,7 @@ public sealed partial class SimulationEngine
|
||||
{
|
||||
var hash = Math.Abs(shipId.GetHashCode(StringComparison.Ordinal));
|
||||
var angle = (hash % 360) * (MathF.PI / 180f);
|
||||
var radius = station.Definition.Radius + 24f;
|
||||
var radius = station.Radius + 24f;
|
||||
return new Vector3(
|
||||
station.Position.X + (MathF.Cos(angle) * radius),
|
||||
station.Position.Y,
|
||||
@@ -288,7 +284,7 @@ public sealed partial class SimulationEngine
|
||||
{
|
||||
var hash = Math.Abs(shipId.GetHashCode(StringComparison.Ordinal));
|
||||
var angle = (hash % 360) * (MathF.PI / 180f);
|
||||
var radius = station.Definition.Radius + 78f;
|
||||
var radius = station.Radius + 78f;
|
||||
return new Vector3(
|
||||
station.Position.X + (MathF.Cos(angle) * radius),
|
||||
station.Position.Y,
|
||||
|
||||
@@ -56,25 +56,12 @@ public sealed partial class SimulationEngine
|
||||
if (distance > task.Threshold)
|
||||
{
|
||||
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.Position = ship.Position.MoveToward(task.TargetPosition.Value, GetLocalTravelSpeed(ship) * deltaSeconds);
|
||||
return "none";
|
||||
}
|
||||
|
||||
if (!TryConsumeShipEnergy(ship, world.Balance.Energy.MoveDrain * deltaSeconds))
|
||||
{
|
||||
ship.State = ShipState.CapacitorStarved;
|
||||
ship.TargetPosition = ship.Position;
|
||||
return "none";
|
||||
}
|
||||
|
||||
ship.State = ShipState.Mining;
|
||||
if (!AdvanceTimedAction(ship, deltaSeconds, world.Balance.MiningCycleSeconds))
|
||||
{
|
||||
@@ -121,7 +108,7 @@ public sealed partial class SimulationEngine
|
||||
ship.State = ShipState.AwaitingDock;
|
||||
ship.TargetPosition = GetDockingHoldPosition(station, ship.Id);
|
||||
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);
|
||||
}
|
||||
@@ -136,32 +123,12 @@ public sealed partial class SimulationEngine
|
||||
if (distance > 4f)
|
||||
{
|
||||
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.Position = ship.Position.MoveToward(padPosition, GetLocalTravelSpeed(ship) * deltaSeconds);
|
||||
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;
|
||||
if (!AdvanceTimedAction(ship, deltaSeconds, world.Balance.DockingDuration))
|
||||
{
|
||||
@@ -195,13 +162,6 @@ public sealed partial class SimulationEngine
|
||||
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.Position = ship.TargetPosition;
|
||||
ship.ActionTimer = 0f;
|
||||
@@ -245,13 +205,6 @@ public sealed partial class SimulationEngine
|
||||
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.Position = ship.TargetPosition;
|
||||
ship.ActionTimer = 0f;
|
||||
@@ -273,50 +226,6 @@ public sealed partial class SimulationEngine
|
||||
: "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)
|
||||
{
|
||||
var station = ResolveShipSupportStation(ship, world);
|
||||
@@ -344,14 +253,6 @@ public sealed partial class SimulationEngine
|
||||
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))
|
||||
{
|
||||
ship.ActionTimer = 0f;
|
||||
@@ -377,7 +278,7 @@ public sealed partial class SimulationEngine
|
||||
return "none";
|
||||
}
|
||||
|
||||
station.InstalledModules.Add(station.ActiveConstruction.ModuleId);
|
||||
AddStationModule(world, station, station.ActiveConstruction.ModuleId);
|
||||
station.ActiveConstruction = null;
|
||||
return "module-constructed";
|
||||
}
|
||||
@@ -409,14 +310,6 @@ public sealed partial class SimulationEngine
|
||||
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;
|
||||
@@ -487,14 +380,6 @@ public sealed partial class SimulationEngine
|
||||
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.Position = ship.TargetPosition;
|
||||
ship.ActionTimer = 0f;
|
||||
@@ -506,7 +391,7 @@ public sealed partial class SimulationEngine
|
||||
return "none";
|
||||
}
|
||||
|
||||
station.InstalledModules.Add(site.BlueprintId);
|
||||
AddStationModule(world, station, site.BlueprintId);
|
||||
PrepareNextConstructionSiteStep(world, station, site);
|
||||
return "site-constructed";
|
||||
}
|
||||
@@ -601,19 +486,6 @@ public sealed partial class SimulationEngine
|
||||
? task.TargetPosition.Value
|
||||
: GetUndockTargetPosition(station, ship.AssignedDockingPadIndex, world.Balance.UndockDistance);
|
||||
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;
|
||||
if (!AdvanceTimedAction(ship, deltaSeconds, world.Balance.UndockingDuration))
|
||||
|
||||
@@ -160,10 +160,6 @@ public sealed partial class SimulationEngine
|
||||
}
|
||||
|
||||
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
|
||||
&& behavior.Phase is "travel-to-node" or "extract")
|
||||
@@ -177,21 +173,12 @@ public sealed partial class SimulationEngine
|
||||
{
|
||||
behavior.Phase = "unload";
|
||||
}
|
||||
else if (behavior.Phase == "undock")
|
||||
{
|
||||
// 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")
|
||||
else if (behavior.Phase is "dock" or "unload")
|
||||
{
|
||||
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";
|
||||
}
|
||||
@@ -216,7 +203,7 @@ public sealed partial class SimulationEngine
|
||||
TargetEntityId = refinery.Id,
|
||||
TargetSystemId = refinery.SystemId,
|
||||
TargetPosition = refinery.Position,
|
||||
Threshold = refinery.Definition.Radius + 8f,
|
||||
Threshold = refinery.Radius + 8f,
|
||||
};
|
||||
break;
|
||||
case "dock":
|
||||
@@ -226,7 +213,7 @@ public sealed partial class SimulationEngine
|
||||
TargetEntityId = refinery.Id,
|
||||
TargetSystemId = refinery.SystemId,
|
||||
TargetPosition = refinery.Position,
|
||||
Threshold = refinery.Definition.Radius + 4f,
|
||||
Threshold = refinery.Radius + 4f,
|
||||
};
|
||||
break;
|
||||
case "unload":
|
||||
@@ -239,16 +226,6 @@ public sealed partial class SimulationEngine
|
||||
Threshold = 0f,
|
||||
};
|
||||
break;
|
||||
case "refuel":
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Refuel,
|
||||
TargetEntityId = refinery.Id,
|
||||
TargetSystemId = refinery.SystemId,
|
||||
TargetPosition = refinery.Position,
|
||||
Threshold = 0f,
|
||||
};
|
||||
break;
|
||||
case "undock":
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
@@ -298,107 +275,6 @@ public sealed partial class SimulationEngine
|
||||
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) =>
|
||||
phase switch
|
||||
{
|
||||
@@ -426,14 +302,6 @@ public sealed partial class SimulationEngine
|
||||
TargetPosition = station.Position,
|
||||
Threshold = 8f,
|
||||
},
|
||||
"refuel" => new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Refuel,
|
||||
TargetEntityId = station.Id,
|
||||
TargetSystemId = station.SystemId,
|
||||
TargetPosition = station.Position,
|
||||
Threshold = 12f,
|
||||
},
|
||||
"undock" => new ControllerTaskRuntime
|
||||
{
|
||||
Kind = ControllerTaskKind.Undock,
|
||||
@@ -486,11 +354,7 @@ public sealed partial class SimulationEngine
|
||||
|
||||
if (isAtConstructionHold)
|
||||
{
|
||||
if (NeedsRefuel(ship, world))
|
||||
{
|
||||
behavior.Phase = "refuel";
|
||||
}
|
||||
else if (site is not null && site.State == ConstructionSiteStateKinds.Active && !IsConstructionSiteReady(world, site))
|
||||
if (site is not null && site.State == ConstructionSiteStateKinds.Active && !IsConstructionSiteReady(world, site))
|
||||
{
|
||||
behavior.Phase = "deliver-to-site";
|
||||
}
|
||||
@@ -518,16 +382,6 @@ public sealed partial class SimulationEngine
|
||||
|
||||
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":
|
||||
ship.ControllerTask = new ControllerTaskRuntime
|
||||
{
|
||||
@@ -649,7 +503,6 @@ public sealed partial class SimulationEngine
|
||||
"dock" => ControllerTaskKind.Dock,
|
||||
"load" => ControllerTaskKind.Load,
|
||||
"unload" => ControllerTaskKind.Unload,
|
||||
"refuel" => ControllerTaskKind.Refuel,
|
||||
"deliver-construction" => ControllerTaskKind.DeliverConstruction,
|
||||
"build-construction-site" => ControllerTaskKind.BuildConstructionSite,
|
||||
"load-workers" => ControllerTaskKind.LoadWorkers,
|
||||
|
||||
@@ -26,16 +26,15 @@ public sealed partial class SimulationEngine
|
||||
|
||||
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 consumedWater = RemoveInventory(station.Inventory, "water", requiredWater);
|
||||
var waterSatisfied = requiredWater <= 0.01f || consumedWater + 0.001f >= requiredWater;
|
||||
var hasPower = station.EnergyStored > 0.01f;
|
||||
var habitatModules = CountModules(station.InstalledModules, "habitat-ring");
|
||||
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
||||
|
||||
if (waterSatisfied && hasPower)
|
||||
if (waterSatisfied)
|
||||
{
|
||||
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));
|
||||
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 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 refinedReserve = HasStationModules(station, "fabricator-array") ? 140f : 40f;
|
||||
var oreReserve = HasRefineryCapability(station) ? 180f : 0f;
|
||||
var gasReserve = CanProcessFuel(station) ? 120f : 0f;
|
||||
var shipPartsReserve = HasStationModules(station, "fabricator-array")
|
||||
&& !HasStationModules(station, "component-factory", "ship-factory")
|
||||
&& FactionNeedsMoreWarships(world, station.FactionId)
|
||||
? 90f
|
||||
: 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, "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, "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, "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);
|
||||
|
||||
ReconcileStationMarketOrders(world, station, desiredOrders);
|
||||
@@ -99,18 +89,13 @@ public sealed partial class SimulationEngine
|
||||
foreach (var laneKey in GetStationProductionLanes(station))
|
||||
{
|
||||
var recipe = SelectProductionRecipe(world, station, laneKey);
|
||||
if (recipe is null || station.EnergyStored <= 0.01f)
|
||||
if (recipe is null)
|
||||
{
|
||||
station.ProductionLaneTimers[laneKey] = 0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
var throughput = GetStationProductionThroughput(station, recipe);
|
||||
if (!TryConsumeStationEnergy(station, world.Balance.Energy.MoveDrain * deltaSeconds * throughput))
|
||||
{
|
||||
station.ProductionLaneTimers[laneKey] = 0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
var produced = 0f;
|
||||
station.ProductionLaneTimers[laneKey] = GetStationProductionTimer(station, laneKey) + (deltaSeconds * station.WorkforceEffectiveRatio * throughput);
|
||||
@@ -139,7 +124,7 @@ public sealed partial class SimulationEngine
|
||||
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)
|
||||
{
|
||||
faction.GoodsProduced += produced;
|
||||
@@ -154,11 +139,6 @@ public sealed partial class SimulationEngine
|
||||
yield return "refinery";
|
||||
}
|
||||
|
||||
if (CountModules(station.InstalledModules, "fuel-processor") > 0)
|
||||
{
|
||||
yield return "fuel";
|
||||
}
|
||||
|
||||
if (CountModules(station.InstalledModules, "fabricator-array") > 0)
|
||||
{
|
||||
yield return "fabrication";
|
||||
@@ -186,11 +166,6 @@ public sealed partial class SimulationEngine
|
||||
|
||||
private static string? GetStationProductionLaneKey(RecipeDefinition recipe)
|
||||
{
|
||||
if (recipe.RequiredModules.Contains("fuel-processor", StringComparer.Ordinal))
|
||||
{
|
||||
return "fuel";
|
||||
}
|
||||
|
||||
if (recipe.RequiredModules.Contains("refinery-stack", StringComparer.Ordinal))
|
||||
{
|
||||
return "refinery";
|
||||
@@ -217,13 +192,6 @@ public sealed partial class SimulationEngine
|
||||
private static float GetStationRecipePriority(SimulationWorld world, StationRuntime station, RecipeDefinition recipe)
|
||||
{
|
||||
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 fleetPressure = FactionNeedsMoreWarships(world, station.FactionId) ? 1f : 0f;
|
||||
@@ -251,9 +219,9 @@ public sealed partial class SimulationEngine
|
||||
|
||||
private static bool RecipeAppliesToStation(StationRuntime station, RecipeDefinition recipe)
|
||||
{
|
||||
var categoryMatch = string.Equals(station.Definition.Category, recipe.FacilityCategory, StringComparison.Ordinal)
|
||||
|| (string.Equals(recipe.FacilityCategory, "station", StringComparison.Ordinal)
|
||||
&& station.Definition.Category is "station" or "shipyard" or "defense" or "gate");
|
||||
var categoryMatch = string.Equals(recipe.FacilityCategory, "station", StringComparison.Ordinal)
|
||||
|| string.Equals(recipe.FacilityCategory, "farm", StringComparison.Ordinal)
|
||||
|| string.Equals(recipe.FacilityCategory, station.Category, StringComparison.Ordinal);
|
||||
return categoryMatch && recipe.RequiredModules.All(moduleId => station.InstalledModules.Contains(moduleId, StringComparer.Ordinal));
|
||||
}
|
||||
|
||||
@@ -289,20 +257,20 @@ public sealed partial class SimulationEngine
|
||||
return false;
|
||||
}
|
||||
|
||||
var requiredModule = GetStorageRequirement(itemDefinition.Storage);
|
||||
var requiredModule = GetStorageRequirement(itemDefinition.CargoKind);
|
||||
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var capacity = GetStationStorageCapacity(station, itemDefinition.Storage);
|
||||
var capacity = GetStationStorageCapacity(station, itemDefinition.CargoKind);
|
||||
if (capacity <= 0.01f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
return used + amount <= capacity + 0.001f;
|
||||
}
|
||||
@@ -387,9 +355,6 @@ public sealed partial class SimulationEngine
|
||||
private static bool HasRefineryCapability(StationRuntime station) =>
|
||||
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)
|
||||
{
|
||||
if (recipe.ShipOutputId is null || !world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var definition))
|
||||
@@ -397,7 +362,7 @@ public sealed partial class SimulationEngine
|
||||
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
|
||||
{
|
||||
Id = $"ship-{world.Ships.Count + 1}",
|
||||
@@ -412,14 +377,13 @@ public sealed partial class SimulationEngine
|
||||
Health = definition.MaxHealth,
|
||||
};
|
||||
|
||||
ship.Inventory["fuel"] = 120f;
|
||||
world.Ships.Add(ship);
|
||||
if (world.Factions.FirstOrDefault(candidate => candidate.Id == station.FactionId) is { } faction)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -492,7 +456,7 @@ public sealed partial class SimulationEngine
|
||||
};
|
||||
}
|
||||
|
||||
var patrolRadius = station.Definition.Radius + 90f;
|
||||
var patrolRadius = station.Radius + 90f;
|
||||
return new DefaultBehaviorRuntime
|
||||
{
|
||||
Kind = "patrol",
|
||||
@@ -513,11 +477,6 @@ public sealed partial class SimulationEngine
|
||||
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))
|
||||
{
|
||||
return Math.Max(1, CountModules(station.InstalledModules, "fabricator-array"));
|
||||
|
||||
@@ -5,12 +5,6 @@ namespace SpaceGame.Simulation.Api.Simulation;
|
||||
public sealed partial class SimulationEngine
|
||||
{
|
||||
private readonly OrbitalSimulationOptions _orbitalSimulation;
|
||||
private const float ShipFuelToEnergyRatio = 12f;
|
||||
private const float StationFuelToEnergyRatio = 18f;
|
||||
private const float CapacitorEnergyPerModule = 120f;
|
||||
private const float StationEnergyPerPowerCore = 480f;
|
||||
private const float ShipFuelPerReactor = 100f;
|
||||
private const float StationFuelPerTank = 500f;
|
||||
private const float WaterConsumptionPerWorkerPerSecond = 0.004f;
|
||||
private const float PopulationGrowthPerSecond = 0.012f;
|
||||
private const float PopulationAttritionPerSecond = 0.018f;
|
||||
@@ -20,12 +14,10 @@ public sealed partial class SimulationEngine
|
||||
new((engine, world, deltaSeconds, nowUtc, events) => engine.UpdateOrbitalState(world)),
|
||||
new((engine, world, deltaSeconds, nowUtc, events) => UpdateClaims(world, events)),
|
||||
new((engine, world, deltaSeconds, nowUtc, events) => UpdateConstructionSites(world, events)),
|
||||
new((engine, world, deltaSeconds, nowUtc, events) => UpdateStationPower(world, deltaSeconds, events)),
|
||||
new((engine, world, deltaSeconds, nowUtc, events) => engine.UpdateStations(world, deltaSeconds, events)),
|
||||
];
|
||||
private static readonly IReadOnlyList<ShipUpdateStep> _shipUpdatePipeline =
|
||||
[
|
||||
new((engine, ship, world, deltaSeconds, events) => UpdateShipPower(ship, world, deltaSeconds, events)),
|
||||
new((engine, ship, world, deltaSeconds, events) => engine.RefreshControlLayers(ship, world)),
|
||||
new((engine, ship, world, deltaSeconds, events) => engine.PlanControllerTask(ship, world)),
|
||||
];
|
||||
|
||||
@@ -6,6 +6,12 @@ export interface StationActionProgressSnapshot {
|
||||
progress: number;
|
||||
}
|
||||
|
||||
export interface StationStorageUsageSnapshot {
|
||||
storageClass: string;
|
||||
used: number;
|
||||
capacity: number;
|
||||
}
|
||||
|
||||
export interface StationSnapshot {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -19,10 +25,6 @@ export interface StationSnapshot {
|
||||
dockedShips: number;
|
||||
dockedShipIds: string[];
|
||||
dockingPads: number;
|
||||
fuelStored: number;
|
||||
fuelCapacity: number;
|
||||
energyStored: number;
|
||||
energyCapacity: number;
|
||||
currentProcesses: StationActionProgressSnapshot[];
|
||||
inventory: InventoryEntry[];
|
||||
factionId: string;
|
||||
@@ -32,6 +34,7 @@ export interface StationSnapshot {
|
||||
populationCapacity: number;
|
||||
workforceRequired: number;
|
||||
workforceEffectiveRatio: number;
|
||||
storageUsage: StationStorageUsageSnapshot[];
|
||||
installedModules: string[];
|
||||
marketOrderIds: string[];
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ export interface ShipSnapshot {
|
||||
cargoCapacity: number;
|
||||
cargoItemId?: string | null;
|
||||
workerPopulation: number;
|
||||
energyStored: number;
|
||||
travelSpeed: number;
|
||||
travelSpeedUnit: string;
|
||||
inventory: InventoryEntry[];
|
||||
|
||||
@@ -26,7 +26,6 @@ export function renderFactionStrip(
|
||||
|
||||
return ships
|
||||
.map((ship) => {
|
||||
const fuel = inventoryAmount(ship.inventory, "fuel");
|
||||
const cargo = ship.cargoItemId
|
||||
? inventoryAmount(ship.inventory, ship.cargoItemId)
|
||||
: 0;
|
||||
@@ -54,7 +53,7 @@ export function renderFactionStrip(
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
${shipAction ? `
|
||||
<div class="ship-action-progress">
|
||||
|
||||
@@ -37,6 +37,94 @@ interface SystemPanelParams {
|
||||
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 {
|
||||
const claims = [...world.claims.values()].filter((claim) =>
|
||||
claim.systemId === systemId && claim.state !== "destroyed");
|
||||
@@ -108,7 +196,6 @@ export function updateDetailPanel(
|
||||
return;
|
||||
}
|
||||
const parent = describeSelectionParent(selected);
|
||||
const fuelStored = inventoryAmount(ship.inventory, "fuel");
|
||||
const cargoUsed = ship.cargoItemId
|
||||
? inventoryAmount(ship.inventory, ship.cargoItemId)
|
||||
: 0;
|
||||
@@ -130,7 +217,6 @@ export function updateDetailPanel(
|
||||
</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>Inventory ${formatInventory(ship.inventory)}</p>
|
||||
<p>Speed ${formatShipSpeed(ship)}</p>
|
||||
@@ -145,17 +231,12 @@ export function updateDetailPanel(
|
||||
return;
|
||||
}
|
||||
const parent = describeSelectionParent(selected);
|
||||
const installedModules = station.installedModules.length > 0
|
||||
? 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 moduleList = formatModuleListWithConstruction(world, station.id, station.installedModules, station.currentProcesses);
|
||||
const dockedShipLabels = station.dockedShipIds.length > 0
|
||||
? station.dockedShipIds.map((shipId) => world.ships.get(shipId)?.label ?? shipId).join("<br>")
|
||||
: "none";
|
||||
const stationInventory = station.inventory.filter((entry) => entry.itemId !== "fuel");
|
||||
const stationInventory = station.inventory;
|
||||
const stationStorageUsage = formatStorageUsage(station.storageUsage);
|
||||
const stationProcesses = station.currentProcesses;
|
||||
const stationProcessingHtml = stationProcesses.length > 0
|
||||
? stationProcesses.map((process) => `
|
||||
@@ -175,14 +256,12 @@ export function updateDetailPanel(
|
||||
<p>${station.category} · ${station.systemId}</p>
|
||||
<p>Parent ${parent}</p>
|
||||
${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}
|
||||
<br>
|
||||
${dockedShipLabels}</p>
|
||||
<p>Modules ${installedModules}</p>
|
||||
<p>Constructing ${activeConstruction}</p>
|
||||
<p>Modules ${moduleList}</p>
|
||||
<p>Storage ${stationStorageUsage}</p>
|
||||
<p>Inventory ${formatInventory(stationInventory)}</p>
|
||||
<p>History available in the separate history window.</p>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -332,8 +332,6 @@ function describeControllerTask(taskKind: string): string {
|
||||
return "docking";
|
||||
case "unload":
|
||||
return "transfer";
|
||||
case "refuel":
|
||||
return "refuel";
|
||||
case "deliver-construction":
|
||||
return "material delivery";
|
||||
case "build-construction-site":
|
||||
|
||||
@@ -164,7 +164,7 @@ Typical outputs:
|
||||
- current destination node
|
||||
- local tactical task
|
||||
- retreat decision
|
||||
- docking/refuel intent
|
||||
- docking intent
|
||||
- trade or delivery acceptance
|
||||
|
||||
## Commander Ownership
|
||||
|
||||
@@ -390,19 +390,6 @@ Suggested station-side workforce fields:
|
||||
|
||||
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 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
|
||||
- life-support goods
|
||||
- construction goods
|
||||
- fuel-chain goods
|
||||
- population-related units
|
||||
|
||||
- [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:
|
||||
|
||||
- production input demand
|
||||
- fuel shortages
|
||||
- construction material shortages
|
||||
- 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.
|
||||
|
||||
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:
|
||||
|
||||
- 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
|
||||
2. inspect production queues or goals
|
||||
3. inspect incoming and outgoing reservations
|
||||
4. inspect fuel, defense, and construction reserves
|
||||
4. inspect defense, and construction reserves
|
||||
5. update buy orders
|
||||
6. update sell orders
|
||||
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
|
||||
2. move them to useful stations
|
||||
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
|
||||
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:
|
||||
|
||||
- a hauler sees a profitable sell-to-buy opportunity
|
||||
- a station commander requests urgent fuel delivery
|
||||
- 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.
|
||||
@@ -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:
|
||||
|
||||
- travel time
|
||||
- fuel cost
|
||||
- risk
|
||||
- behavioral restrictions
|
||||
- territorial or regional limits
|
||||
@@ -236,7 +231,6 @@ The economy will work better if stations can reserve expected inventory changes.
|
||||
|
||||
Examples:
|
||||
|
||||
- incoming fuel is reserved for station power
|
||||
- outbound metals are reserved for a construction project
|
||||
- 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:
|
||||
|
||||
- `Claim at Helios IV L4 destroyed by pirates`
|
||||
- `Station buy order for fuel opened`
|
||||
- `Miner completed warp to refinery node`
|
||||
|
||||
This helps reuse the same event model for:
|
||||
|
||||
@@ -33,10 +33,9 @@ The intended categories are:
|
||||
2. processed industrial goods
|
||||
3. life-support goods
|
||||
4. civilian goods
|
||||
5. fuel and power-chain goods
|
||||
6. construction goods
|
||||
7. population-related units
|
||||
8. special logistics goods later
|
||||
5. construction goods
|
||||
6. population-related units
|
||||
7. special logistics goods later
|
||||
|
||||
## Raw Resources
|
||||
|
||||
@@ -86,20 +85,6 @@ Current important example:
|
||||
|
||||
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
|
||||
|
||||
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 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:
|
||||
|
||||
@@ -144,12 +129,6 @@ The current design implies at least these roles:
|
||||
- `ore`
|
||||
- raw industrial input
|
||||
|
||||
- `gas`
|
||||
- raw fuel-chain input
|
||||
|
||||
- `fuel`
|
||||
- operational energy good
|
||||
|
||||
- `food`
|
||||
- workforce life-support
|
||||
|
||||
@@ -173,12 +152,11 @@ Not every item should necessarily fit in every hold type forever.
|
||||
|
||||
Useful distinctions later may include:
|
||||
|
||||
- bulk industrial cargo
|
||||
- liquid cargo
|
||||
- gas cargo
|
||||
- containerized finished goods
|
||||
- human transport capacity
|
||||
- livestock capacity
|
||||
- solid storage
|
||||
- liquid storage
|
||||
- container storage
|
||||
- passengers
|
||||
- livestock
|
||||
|
||||
For now, the important rule is simply:
|
||||
|
||||
@@ -191,7 +169,6 @@ Items should participate in the market according to their role.
|
||||
Examples:
|
||||
|
||||
- life-support goods generate recurring demand
|
||||
- fuel goods generate operational demand
|
||||
- construction goods generate burst demand during expansion
|
||||
- industrial goods feed production chains
|
||||
- worker transport supports station staffing
|
||||
@@ -220,7 +197,7 @@ The following rules should remain true unless deliberately revised:
|
||||
|
||||
- workforce depends on real support 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
|
||||
- commanders are not ordinary trade cargo
|
||||
- livestock is distinct from workers
|
||||
|
||||
@@ -48,7 +48,6 @@ Examples:
|
||||
- no docking module means no docking service
|
||||
- no habitat module means no population growth or human transport
|
||||
- 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 shipyard-related module means no ship production
|
||||
|
||||
@@ -92,7 +91,6 @@ Likely station-side categories include:
|
||||
- storage
|
||||
- habitat
|
||||
- refinery
|
||||
- fuel processing
|
||||
- manufacturing
|
||||
- shipyard or construction support
|
||||
- defense
|
||||
@@ -141,7 +139,6 @@ Examples:
|
||||
- reactor
|
||||
- capacitor
|
||||
- station power core
|
||||
- fuel systems
|
||||
|
||||
### Production Modules
|
||||
|
||||
@@ -150,7 +147,6 @@ These convert goods into other goods or into built output.
|
||||
Examples:
|
||||
|
||||
- refinery
|
||||
- fuel processor
|
||||
- factory
|
||||
- shipyard support
|
||||
|
||||
@@ -198,7 +194,6 @@ They may require:
|
||||
- build time
|
||||
- power
|
||||
- workforce
|
||||
- fuel or energy inputs
|
||||
- docking or logistics support
|
||||
|
||||
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:
|
||||
|
||||
- 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 storage module determines what volume or class of goods can be held
|
||||
- a livestock module participates in the food chain
|
||||
|
||||
@@ -49,7 +49,6 @@ A recipe should conceptually define:
|
||||
- cycle time
|
||||
- valid producing module types
|
||||
- optional workforce requirement
|
||||
- optional power or fuel requirement
|
||||
|
||||
Recipes should be first-class design objects, not hidden assumptions inside modules.
|
||||
|
||||
@@ -60,7 +59,6 @@ Recipes are executed by production-capable modules.
|
||||
Examples:
|
||||
|
||||
- refinery module
|
||||
- fuel processing module
|
||||
- factory module
|
||||
- food-chain module later
|
||||
- shipyard support module
|
||||
@@ -112,16 +110,6 @@ For now:
|
||||
|
||||
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
|
||||
|
||||
If inputs are missing:
|
||||
@@ -153,7 +141,6 @@ The exact recipes can evolve, but the intended shape includes chains like:
|
||||
|
||||
2. refining or processing
|
||||
- ore -> refined goods
|
||||
- gas -> fuel
|
||||
- food-loop conversions later
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
Depending on modules and category, a station may provide:
|
||||
@@ -175,11 +158,9 @@ Depending on modules and category, a station may provide:
|
||||
- docking
|
||||
- storage
|
||||
- refining
|
||||
- fuel processing
|
||||
- manufacturing
|
||||
- repair later
|
||||
- fitting later
|
||||
- rearm and resupply later
|
||||
- repair
|
||||
- fitting, rearm and resupply later
|
||||
- habitats
|
||||
|
||||
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:
|
||||
|
||||
- expand into this system
|
||||
- keep this station fueled
|
||||
- defend this claim
|
||||
- protect trade in this region
|
||||
- supply this station with workers
|
||||
@@ -60,7 +59,6 @@ Examples:
|
||||
- dock at station
|
||||
- claim Lagrange point
|
||||
- build station here
|
||||
- deliver fuel
|
||||
- escort this ship
|
||||
- defend this bubble
|
||||
|
||||
@@ -223,7 +221,6 @@ Examples:
|
||||
- deny dock request
|
||||
- transfer goods
|
||||
- request defense
|
||||
- request emergency fuel support
|
||||
|
||||
These may be implemented as station jobs, station operations, or station-side tasks.
|
||||
|
||||
@@ -237,7 +234,7 @@ Examples:
|
||||
- flee to nearest allowed station
|
||||
- hold position if no valid route 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.
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ Workers consume, per worker:
|
||||
|
||||
- food
|
||||
- water
|
||||
- energy
|
||||
- consumer goods
|
||||
|
||||
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:
|
||||
|
||||
- fuel
|
||||
- workers
|
||||
- support goods
|
||||
- eventually a station commander
|
||||
@@ -159,7 +157,6 @@ Relevant shortages include:
|
||||
|
||||
- food shortage
|
||||
- water shortage
|
||||
- energy shortage
|
||||
- consumer goods shortage
|
||||
|
||||
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
|
||||
- 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
|
||||
- stations retain a small baseline efficiency at zero workforce
|
||||
- population can be transported between stations
|
||||
|
||||
@@ -6,12 +6,5 @@
|
||||
"transferRate": 56,
|
||||
"dockingDuration": 1.2,
|
||||
"undockingDuration": 1.2,
|
||||
"undockDistance": 42,
|
||||
"energy": {
|
||||
"idleDrain": 0.7,
|
||||
"moveDrain": 1.8,
|
||||
"warpDrain": 7,
|
||||
"shipRechargeRate": 10,
|
||||
"stationSolarCharge": 5
|
||||
}
|
||||
"undockDistance": 42
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
"label": "Raw Ore",
|
||||
"storage": "bulk-solid",
|
||||
"summary": "Unprocessed asteroid ore used as the main industrial feedstock."
|
||||
},
|
||||
{
|
||||
"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."
|
||||
"name": "Raw Ore",
|
||||
"description": "Unprocessed asteroid ore used as the main industrial feedstock.",
|
||||
"type": "resource",
|
||||
"cargoKind": "bulk-solid",
|
||||
"volume": 1.2
|
||||
},
|
||||
{
|
||||
"id": "water",
|
||||
"label": "Water",
|
||||
"storage": "bulk-liquid",
|
||||
"summary": "Life-support and agricultural input."
|
||||
"name": "Water",
|
||||
"description": "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",
|
||||
"label": "Drone Parts",
|
||||
"storage": "container",
|
||||
"summary": "Containerized industrial freight."
|
||||
"name": "Drone Parts",
|
||||
"description": "Containerized industrial freight for construction support.",
|
||||
"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",
|
||||
"label": "Trade Hub Kit",
|
||||
"storage": "manufactured",
|
||||
"summary": "Deployable prefab package for a trade hub station."
|
||||
"name": "Trade Hub Kit",
|
||||
"description": "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",
|
||||
"label": "Refinery Kit",
|
||||
"storage": "manufactured",
|
||||
"summary": "Deployable prefab package for a refining station."
|
||||
"name": "Refinery Kit",
|
||||
"description": "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",
|
||||
"label": "Farm Ring Kit",
|
||||
"storage": "manufactured",
|
||||
"summary": "Deployable prefab package for a farm station."
|
||||
"name": "Farm Ring Kit",
|
||||
"description": "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",
|
||||
"label": "Manufactory Kit",
|
||||
"storage": "manufactured",
|
||||
"summary": "Deployable prefab package for an orbital manufactory."
|
||||
"name": "Manufactory Kit",
|
||||
"description": "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",
|
||||
"label": "Shipyard Kit",
|
||||
"storage": "manufactured",
|
||||
"summary": "Deployable prefab package for an orbital shipyard."
|
||||
"name": "Shipyard Kit",
|
||||
"description": "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",
|
||||
"label": "Defense Grid Kit",
|
||||
"storage": "manufactured",
|
||||
"summary": "Deployable prefab package for a defense platform."
|
||||
"name": "Defense Grid Kit",
|
||||
"description": "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",
|
||||
"label": "Stargate Kit",
|
||||
"storage": "manufactured",
|
||||
"summary": "Deployable prefab package for a stargate structure."
|
||||
"name": "Stargate Kit",
|
||||
"description": "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": [
|
||||
{
|
||||
"constructibleId": "station-core",
|
||||
"label": "Orbital Station",
|
||||
"startingModules": [
|
||||
"dock-bay-small",
|
||||
"power-core",
|
||||
"bulk-bay",
|
||||
"liquid-tank"
|
||||
],
|
||||
"systemId": "helios",
|
||||
"planetIndex": 2,
|
||||
"lagrangeSide": -1
|
||||
@@ -29,7 +35,7 @@
|
||||
"systemId": "helios"
|
||||
},
|
||||
{
|
||||
"shipId": "gas-miner",
|
||||
"shipId": "hauler",
|
||||
"count": 1,
|
||||
"center": [
|
||||
60,
|
||||
@@ -37,16 +43,6 @@
|
||||
28
|
||||
],
|
||||
"systemId": "helios"
|
||||
},
|
||||
{
|
||||
"shipId": "gas-miner",
|
||||
"count": 1,
|
||||
"center": [
|
||||
60,
|
||||
0,
|
||||
32
|
||||
],
|
||||
"systemId": "helios"
|
||||
}
|
||||
],
|
||||
"patrolRoutes": [],
|
||||
|
||||
@@ -13,7 +13,58 @@
|
||||
"hullColor": "#1f4f78",
|
||||
"size": 4,
|
||||
"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",
|
||||
@@ -29,7 +80,59 @@
|
||||
"hullColor": "#6a2e26",
|
||||
"size": 7,
|
||||
"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",
|
||||
@@ -45,7 +148,59 @@
|
||||
"hullColor": "#314562",
|
||||
"size": 10,
|
||||
"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",
|
||||
@@ -61,9 +216,75 @@
|
||||
"hullColor": "#35586d",
|
||||
"size": 16,
|
||||
"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,
|
||||
"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",
|
||||
@@ -75,13 +296,63 @@
|
||||
"ftlSpeed": 0.55,
|
||||
"spoolTime": 3.3,
|
||||
"cargoCapacity": 180,
|
||||
"cargoKind": "bulk-liquid",
|
||||
"cargoItemId": "energy-cell",
|
||||
"cargoKind": "container",
|
||||
"color": "#b0ff8d",
|
||||
"hullColor": "#365f2a",
|
||||
"size": 8,
|
||||
"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",
|
||||
@@ -99,7 +370,63 @@
|
||||
"hullColor": "#2d5d47",
|
||||
"size": 9,
|
||||
"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",
|
||||
@@ -117,24 +444,62 @@
|
||||
"hullColor": "#68552b",
|
||||
"size": 6,
|
||||
"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",
|
||||
"label": "Nimbus Gas Harvester",
|
||||
"role": "mining",
|
||||
"shipClass": "industrial",
|
||||
"speed": 72000,
|
||||
"warpSpeed": 0.145,
|
||||
"ftlSpeed": 0.49,
|
||||
"spoolTime": 3.2,
|
||||
"cargoCapacity": 120,
|
||||
"cargoKind": "bulk-gas",
|
||||
"cargoItemId": "gas",
|
||||
"color": "#8ce5ff",
|
||||
"hullColor": "#2a5668",
|
||||
"size": 6,
|
||||
"maxHealth": 150,
|
||||
"modules": ["command-bridge", "reactor-core", "capacitor-bank", "ion-drive", "ftl-core", "gas-extractor", "gas-tank"]
|
||||
"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": "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