feat: simplifying the simulation

This commit is contained in:
2026-03-17 16:08:02 -04:00
parent 3234b628ea
commit d5d0a39244
20 changed files with 374 additions and 496 deletions

View File

@@ -3,8 +3,8 @@ namespace SpaceGame.Simulation.Api.Contracts;
public sealed record ShipSnapshot( public sealed record ShipSnapshot(
string Id, string Id,
string Label, string Label,
string Role, string Kind,
string ShipClass, string Class,
string SystemId, string SystemId,
Vector3Dto LocalPosition, Vector3Dto LocalPosition,
Vector3Dto LocalVelocity, Vector3Dto LocalVelocity,
@@ -19,8 +19,7 @@ public sealed record ShipSnapshot(
string? CommanderId, string? CommanderId,
string? PolicySetId, string? PolicySetId,
float CargoCapacity, float CargoCapacity,
string? CargoItemId,
float WorkerPopulation,
float TravelSpeed, float TravelSpeed,
string TravelSpeedUnit, string TravelSpeedUnit,
IReadOnlyList<InventoryEntry> Inventory, IReadOnlyList<InventoryEntry> Inventory,
@@ -33,8 +32,8 @@ public sealed record ShipSnapshot(
public sealed record ShipDelta( public sealed record ShipDelta(
string Id, string Id,
string Label, string Label,
string Role, string Kind,
string ShipClass, string Class,
string SystemId, string SystemId,
Vector3Dto LocalPosition, Vector3Dto LocalPosition,
Vector3Dto LocalVelocity, Vector3Dto LocalVelocity,
@@ -49,8 +48,7 @@ public sealed record ShipDelta(
string? CommanderId, string? CommanderId,
string? PolicySetId, string? PolicySetId,
float CargoCapacity, float CargoCapacity,
string? CargoItemId,
float WorkerPopulation,
float TravelSpeed, float TravelSpeed,
string TravelSpeedUnit, string TravelSpeedUnit,
IReadOnlyList<InventoryEntry> Inventory, IReadOnlyList<InventoryEntry> Inventory,

View File

@@ -147,20 +147,19 @@ public sealed class ShipDefinition
{ {
public required string Id { get; set; } public required string Id { get; set; }
public required string Label { get; set; } public required string Label { get; set; }
public required string Role { get; set; } public required string Kind { get; set; }
public required string ShipClass { get; set; } public required string Class { get; set; }
public float Speed { get; set; } public float Speed { get; set; }
public float WarpSpeed { get; set; } public float WarpSpeed { get; set; }
public float FtlSpeed { get; set; } public float FtlSpeed { get; set; }
public float SpoolTime { get; set; } public float SpoolTime { get; set; }
public float CargoCapacity { get; set; } public float CargoCapacity { get; set; }
public string? CargoKind { get; set; } public string? CargoKind { get; set; }
public string? CargoItemId { get; set; }
public required string Color { get; set; } public required string Color { get; set; }
public required string HullColor { get; set; } public required string HullColor { get; set; }
public float Size { get; set; } public float Size { get; set; }
public float MaxHealth { get; set; } public float MaxHealth { get; set; }
public List<string> Modules { get; set; } = []; public List<string> Capabilities { get; set; } = [];
public ConstructionDefinition? Construction { get; set; } public ConstructionDefinition? Construction { get; set; }
} }

View File

@@ -18,7 +18,7 @@ internal sealed class ShipBehaviorStateMachine
{ {
idleState, idleState,
new PatrolShipBehaviorState(), new PatrolShipBehaviorState(),
new ResourceHarvestShipBehaviorState("auto-mine", "ore", "mining-turret"), new ResourceHarvestShipBehaviorState("auto-mine", "ore", "mining"),
new ConstructStationShipBehaviorState(), new ConstructStationShipBehaviorState(),
}; };

View File

@@ -18,7 +18,7 @@ public sealed class ShipRuntime
public required ControllerTaskRuntime ControllerTask { get; set; } public required ControllerTaskRuntime ControllerTask { get; set; }
public float ActionTimer { get; set; } public float ActionTimer { get; set; }
public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal); public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
public float WorkerPopulation { get; set; }
public string DockedStationId { get; set; } public string DockedStationId { get; set; }
public int? AssignedDockingPadIndex { get; set; } public int? AssignedDockingPadIndex { get; set; }
public string? CommanderId { get; set; } public string? CommanderId { get; set; }
@@ -62,4 +62,5 @@ public sealed class ControllerTaskRuntime
public string? TargetNodeId { get; set; } public string? TargetNodeId { get; set; }
public Vector3? TargetPosition { get; set; } public Vector3? TargetPosition { get; set; }
public float Threshold { get; set; } public float Threshold { get; set; }
public string? ItemId { get; set; }
} }

View File

@@ -62,8 +62,7 @@ public enum ControllerTaskKind
Unload, Unload,
DeliverConstruction, DeliverConstruction,
BuildConstructionSite, BuildConstructionSite,
LoadWorkers,
UnloadWorkers,
ConstructModule, ConstructModule,
Undock, Undock,
} }
@@ -105,8 +104,7 @@ public static class ShipTaskKinds
public const string Undock = "undock"; public const string Undock = "undock";
public const string LoadCargo = "load-cargo"; public const string LoadCargo = "load-cargo";
public const string UnloadCargo = "unload-cargo"; public const string UnloadCargo = "unload-cargo";
public const string LoadWorkers = "load-workers";
public const string UnloadWorkers = "unload-workers";
public const string MineNode = "mine-node"; public const string MineNode = "mine-node";
public const string HarvestGas = "harvest-gas"; public const string HarvestGas = "harvest-gas";
public const string DeliverToStation = "deliver-to-station"; public const string DeliverToStation = "deliver-to-station";
@@ -229,8 +227,7 @@ public static class SimulationEnumMappings
ControllerTaskKind.Unload => "unload", ControllerTaskKind.Unload => "unload",
ControllerTaskKind.DeliverConstruction => "deliver-construction", ControllerTaskKind.DeliverConstruction => "deliver-construction",
ControllerTaskKind.BuildConstructionSite => "build-construction-site", ControllerTaskKind.BuildConstructionSite => "build-construction-site",
ControllerTaskKind.LoadWorkers => "load-workers",
ControllerTaskKind.UnloadWorkers => "unload-workers",
ControllerTaskKind.ConstructModule => "construct-module", ControllerTaskKind.ConstructModule => "construct-module",
ControllerTaskKind.Undock => "undock", ControllerTaskKind.Undock => "undock",
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null), _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),

View File

@@ -379,7 +379,7 @@ public sealed partial class ScenarioLoader
IReadOnlyDictionary<string, List<Vector3>> patrolRoutes, IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
StationRuntime? refinery) StationRuntime? refinery)
{ {
if (string.Equals(definition.Role, "construction", StringComparison.Ordinal) && refinery is not null) if (string.Equals(definition.Kind, "construction", StringComparison.Ordinal) && refinery is not null)
{ {
return new DefaultBehaviorRuntime return new DefaultBehaviorRuntime
{ {
@@ -389,12 +389,12 @@ public sealed partial class ScenarioLoader
}; };
} }
if (HasModules(definition, "reactor-core", "capacitor-bank", "mining-turret") && refinery is not null) if (HasCapabilities(definition, "mining") && refinery is not null)
{ {
return CreateResourceHarvestBehavior("auto-mine", scenario.MiningDefaults.NodeSystemId, refinery.Id); return CreateResourceHarvestBehavior("auto-mine", scenario.MiningDefaults.NodeSystemId, refinery.Id);
} }
if (HasModules(definition, "reactor-core", "capacitor-bank", "gun-turret") && patrolRoutes.TryGetValue(systemId, out var route)) if (string.Equals(definition.Kind, "military", StringComparison.Ordinal) && patrolRoutes.TryGetValue(systemId, out var route))
{ {
return new DefaultBehaviorRuntime return new DefaultBehaviorRuntime
{ {

View File

@@ -400,8 +400,8 @@ public sealed partial class ScenarioLoader
private static bool HasInstalledModules(StationRuntime station, params string[] modules) => private static bool HasInstalledModules(StationRuntime station, params string[] modules) =>
modules.All((moduleId) => station.Modules.Any((candidate) => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal))); modules.All((moduleId) => station.Modules.Any((candidate) => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal)));
private static bool HasModules(ShipDefinition definition, params string[] modules) => private static bool HasCapabilities(ShipDefinition definition, params string[] capabilities) =>
modules.All((moduleId) => definition.Modules.Contains(moduleId, StringComparer.Ordinal)); capabilities.All((cap) => definition.Capabilities.Contains(cap, StringComparer.Ordinal));
private static void AddStationModule(StationRuntime station, IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions, string moduleId) private static void AddStationModule(StationRuntime station, IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions, string moduleId)
{ {

View File

@@ -27,8 +27,7 @@ public sealed partial class SimulationEngine
ControllerTaskKind.Unload => UpdateUnload(ship, world, deltaSeconds), ControllerTaskKind.Unload => UpdateUnload(ship, world, deltaSeconds),
ControllerTaskKind.DeliverConstruction => UpdateDeliverConstruction(ship, world, deltaSeconds), ControllerTaskKind.DeliverConstruction => UpdateDeliverConstruction(ship, world, deltaSeconds),
ControllerTaskKind.BuildConstructionSite => UpdateBuildConstructionSite(ship, world, deltaSeconds), ControllerTaskKind.BuildConstructionSite => UpdateBuildConstructionSite(ship, world, deltaSeconds),
ControllerTaskKind.LoadWorkers => UpdateLoadWorkers(ship, world, deltaSeconds),
ControllerTaskKind.UnloadWorkers => UpdateUnloadWorkers(ship, world, deltaSeconds),
ControllerTaskKind.ConstructModule => UpdateConstructModule(ship, world, deltaSeconds), ControllerTaskKind.ConstructModule => UpdateConstructModule(ship, world, deltaSeconds),
ControllerTaskKind.Undock => UpdateUndock(ship, world, deltaSeconds), ControllerTaskKind.Undock => UpdateUndock(ship, world, deltaSeconds),
_ => UpdateIdle(ship, world, deltaSeconds), _ => UpdateIdle(ship, world, deltaSeconds),
@@ -58,6 +57,12 @@ public sealed partial class SimulationEngine
if (ship.SystemId != task.TargetSystemId) if (ship.SystemId != task.TargetSystemId)
{ {
if (!HasShipCapabilities(ship.Definition, "ftl"))
{
ship.State = ShipState.Idle;
return "none";
}
var destinationEntryNode = ResolveSystemEntryNode(world, task.TargetSystemId); var destinationEntryNode = ResolveSystemEntryNode(world, task.TargetSystemId);
var destinationEntryPosition = destinationEntryNode?.Position ?? Vector3.Zero; var destinationEntryPosition = destinationEntryNode?.Position ?? Vector3.Zero;
return UpdateFtlTransit(ship, world, deltaSeconds, task.TargetSystemId, destinationEntryPosition, destinationEntryNode); return UpdateFtlTransit(ship, world, deltaSeconds, task.TargetSystemId, destinationEntryPosition, destinationEntryNode);
@@ -66,6 +71,11 @@ public sealed partial class SimulationEngine
var currentNode = ResolveCurrentNode(world, ship); var currentNode = ResolveCurrentNode(world, ship);
if (targetNode is not null && currentNode is not null && !string.Equals(currentNode.Id, targetNode.Id, StringComparison.Ordinal)) if (targetNode is not null && currentNode is not null && !string.Equals(currentNode.Id, targetNode.Id, StringComparison.Ordinal))
{ {
if (!HasShipCapabilities(ship.Definition, "warp"))
{
return UpdateLocalTravel(ship, world, deltaSeconds, task.TargetSystemId, targetPosition, targetNode, task.Threshold);
}
return UpdateWarpTransit(ship, world, deltaSeconds, targetPosition, targetNode); return UpdateWarpTransit(ship, world, deltaSeconds, targetPosition, targetNode);
} }

View File

@@ -5,14 +5,8 @@ namespace SpaceGame.Simulation.Api.Simulation;
public sealed partial class SimulationEngine public sealed partial class SimulationEngine
{ {
private static bool HasShipModules(ShipDefinition definition, params string[] modules) => private static bool HasShipCapabilities(ShipDefinition definition, params string[] capabilities) =>
modules.All(moduleId => definition.Modules.Contains(moduleId, StringComparer.Ordinal)); capabilities.All(cap => definition.Capabilities.Contains(cap, StringComparer.Ordinal));
private static bool CanTransportWorkers(ShipRuntime ship) =>
CountModules(ship.Definition.Modules, "habitat-ring") > 0;
private static float GetWorkerTransportCapacity(ShipRuntime ship) =>
CountModules(ship.Definition.Modules, "habitat-ring") * 120f;
private static int CountStationModules(StationRuntime station, string moduleId) => private static int CountStationModules(StationRuntime station, string moduleId) =>
station.Modules.Count(module => string.Equals(module.ModuleId, moduleId, StringComparison.Ordinal)); station.Modules.Count(module => string.Equals(module.ModuleId, moduleId, StringComparison.Ordinal));
@@ -56,8 +50,8 @@ public sealed partial class SimulationEngine
var moduleCapacity = storageClass switch var moduleCapacity = storageClass switch
{ {
"bulk-solid" => bulkBays * 1000f, "solid" => bulkBays * 1000f,
"bulk-liquid" => liquidTanks * 500f, "liquid" => liquidTanks * 500f,
"container" => containerBays * 800f, "container" => containerBays * 800f,
"manufactured" => containerBays * 200f, "manufactured" => containerBays * 200f,
_ => 0f, _ => 0f,
@@ -102,15 +96,13 @@ public sealed partial class SimulationEngine
private static bool HasStationModules(StationRuntime station, params string[] modules) => private static bool HasStationModules(StationRuntime station, params string[] modules) =>
modules.All(moduleId => station.Modules.Any(candidate => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal))); modules.All(moduleId => station.Modules.Any(candidate => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal)));
private static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node) => private static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node, SimulationWorld world) =>
node.ItemId switch HasShipCapabilities(ship.Definition, "mining")
{ && world.ItemDefinitions.TryGetValue(node.ItemId, out var item)
"ore" => HasShipModules(ship.Definition, "mining-turret"), && string.Equals(item.CargoKind, ship.Definition.CargoKind, StringComparison.Ordinal);
_ => false,
};
private static bool CanBuildClaimBeacon(ShipRuntime ship) => private static bool CanBuildClaimBeacon(ShipRuntime ship) =>
string.Equals(ship.Definition.Role, "military", StringComparison.Ordinal); string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal);
private static float ComputeWorkforceRatio(float population, float workforceRequired) private static float ComputeWorkforceRatio(float population, float workforceRequired)
{ {
@@ -126,8 +118,8 @@ public sealed partial class SimulationEngine
private static string? GetStorageRequirement(string storageClass) => private static string? GetStorageRequirement(string storageClass) =>
storageClass switch storageClass switch
{ {
"bulk-solid" => "bulk-bay", "solid" => "bulk-bay",
"bulk-liquid" => "liquid-tank", "liquid" => "liquid-tank",
_ => null, _ => null,
}; };

View File

@@ -143,8 +143,8 @@ public sealed partial class SimulationEngine
world.Ships.Select(ship => ToShipDelta(world, ship)).Select(ship => new ShipSnapshot( world.Ships.Select(ship => ToShipDelta(world, ship)).Select(ship => new ShipSnapshot(
ship.Id, ship.Id,
ship.Label, ship.Label,
ship.Role, ship.Kind,
ship.ShipClass, ship.Class,
ship.SystemId, ship.SystemId,
ship.LocalPosition, ship.LocalPosition,
ship.LocalVelocity, ship.LocalVelocity,
@@ -159,8 +159,6 @@ public sealed partial class SimulationEngine
ship.CommanderId, ship.CommanderId,
ship.PolicySetId, ship.PolicySetId,
ship.CargoCapacity, ship.CargoCapacity,
ship.CargoItemId,
ship.WorkerPopulation,
ship.TravelSpeed, ship.TravelSpeed,
ship.TravelSpeedUnit, ship.TravelSpeedUnit,
ship.Inventory, ship.Inventory,
@@ -482,7 +480,6 @@ public sealed partial class SimulationEngine
ship.DockedStationId ?? "none", ship.DockedStationId ?? "none",
ship.CommanderId ?? "none", ship.CommanderId ?? "none",
ship.PolicySetId ?? "none", ship.PolicySetId ?? "none",
ship.WorkerPopulation.ToString("0.###"),
ship.SpatialState.SpaceLayer, ship.SpatialState.SpaceLayer,
ship.SpatialState.CurrentNodeId ?? "none", ship.SpatialState.CurrentNodeId ?? "none",
ship.SpatialState.CurrentBubbleId ?? "none", ship.SpatialState.CurrentBubbleId ?? "none",
@@ -586,7 +583,7 @@ public sealed partial class SimulationEngine
private static IReadOnlyList<StationStorageUsageSnapshot> ToStationStorageUsageSnapshots(SimulationWorld world, StationRuntime station) private static IReadOnlyList<StationStorageUsageSnapshot> ToStationStorageUsageSnapshots(SimulationWorld world, StationRuntime station)
{ {
string[] storageClasses = ["bulk-solid", "bulk-liquid", "container", "manufactured"]; string[] storageClasses = ["solid", "liquid", "container", "manufactured"];
return storageClasses return storageClasses
.Select(storageClass => new StationStorageUsageSnapshot( .Select(storageClass => new StationStorageUsageSnapshot(
storageClass, storageClass,
@@ -666,8 +663,8 @@ public sealed partial class SimulationEngine
private ShipDelta ToShipDelta(SimulationWorld world, ShipRuntime ship) => new( private ShipDelta ToShipDelta(SimulationWorld world, ShipRuntime ship) => new(
ship.Id, ship.Id,
ship.Definition.Label, ship.Definition.Label,
ship.Definition.Role, ship.Definition.Kind,
ship.Definition.ShipClass, ship.Definition.Class,
ship.SystemId, ship.SystemId,
ToDto(ship.Position), ToDto(ship.Position),
ToDto(ship.Velocity), ToDto(ship.Velocity),
@@ -682,8 +679,7 @@ public sealed partial class SimulationEngine
ship.CommanderId, ship.CommanderId,
ship.PolicySetId, ship.PolicySetId,
ship.Definition.CargoCapacity, ship.Definition.CargoCapacity,
ship.Definition.CargoItemId,
ship.WorkerPopulation,
ToShipTravelSpeed(ship).Speed, ToShipTravelSpeed(ship).Speed,
ToShipTravelSpeed(ship).Unit, ToShipTravelSpeed(ship).Unit,
ToInventoryEntries(ship.Inventory), ToInventoryEntries(ship.Inventory),
@@ -705,14 +701,7 @@ public sealed partial class SimulationEngine
ShipState.Docking => CreateShipActionProgress("Docking", ship.ActionTimer, MathF.Max(world.Balance.DockingDuration, 0.1f)), ShipState.Docking => CreateShipActionProgress("Docking", ship.ActionTimer, MathF.Max(world.Balance.DockingDuration, 0.1f)),
ShipState.Undocking => CreateShipActionProgress("Undocking", ship.ActionTimer, MathF.Max(world.Balance.UndockingDuration, 0.1f)), ShipState.Undocking => CreateShipActionProgress("Undocking", ship.ActionTimer, MathF.Max(world.Balance.UndockingDuration, 0.1f)),
ShipState.Transferring => CreateShipRemainingActionProgress("Transfer", ship.TrackedActionTotal, GetShipCargoAmount(ship)), ShipState.Transferring => CreateShipRemainingActionProgress("Transfer", ship.TrackedActionTotal, GetShipCargoAmount(ship)),
ShipState.Loading => CreateShipRemainingActionProgress( ShipState.Loading or ShipState.Unloading => null,
"Load workers",
ship.TrackedActionTotal,
MathF.Max(0f, ship.TrackedActionTotal - ship.WorkerPopulation)),
ShipState.Unloading => CreateShipRemainingActionProgress(
"Unload workers",
ship.TrackedActionTotal,
ship.WorkerPopulation),
ShipState.DeliveringConstruction => ship.ControllerTask.TargetEntityId is null ShipState.DeliveringConstruction => ship.ControllerTask.TargetEntityId is null
? null ? null
: world.ConstructionSites.FirstOrDefault(site => site.Id == ship.ControllerTask.TargetEntityId) is not { } site : world.ConstructionSites.FirstOrDefault(site => site.Id == ship.ControllerTask.TargetEntityId) is not { } site

View File

@@ -106,10 +106,38 @@ public sealed partial class SimulationEngine
private static string? GetNextStationModuleToBuild(StationRuntime station, SimulationWorld world) private static string? GetNextStationModuleToBuild(StationRuntime station, SimulationWorld world)
{ {
// Expand storage before it becomes a bottleneck
const float StorageExpansionThreshold = 0.85f;
var storageExpansionCandidates = new[]
{
("solid", "bulk-bay"),
("liquid", "liquid-tank"),
("container", "container-bay"),
};
foreach (var (storageClass, moduleId) in storageExpansionCandidates)
{
var capacity = GetStationStorageCapacity(station, storageClass);
if (capacity <= 0.01f)
{
continue;
}
var used = station.Inventory
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var def) && def.CargoKind == storageClass)
.Sum(entry => entry.Value);
if (used / capacity >= StorageExpansionThreshold && world.ModuleRecipes.ContainsKey(moduleId))
{
return moduleId;
}
}
var priorities = GetFactionExpansionPressure(world, station.FactionId) > 0f var priorities = GetFactionExpansionPressure(world, station.FactionId) > 0f
? new (string ModuleId, int TargetCount)[] ? new (string ModuleId, int TargetCount)[]
{ {
("refinery-stack", 1), ("refinery-stack", 1),
("bulk-bay", 1),
("container-bay", 1), ("container-bay", 1),
("fabricator-array", 2), ("fabricator-array", 2),
("component-factory", 1), ("component-factory", 1),
@@ -120,6 +148,7 @@ public sealed partial class SimulationEngine
: new (string ModuleId, int TargetCount)[] : new (string ModuleId, int TargetCount)[]
{ {
("refinery-stack", 1), ("refinery-stack", 1),
("bulk-bay", 1),
("container-bay", 1), ("container-bay", 1),
("fabricator-array", 2), ("fabricator-array", 2),
("component-factory", 1), ("component-factory", 1),

View File

@@ -25,17 +25,14 @@ public sealed partial class SimulationEngine
ship.TrackedActionTotal = MathF.Max(total, 0.01f); ship.TrackedActionTotal = MathF.Max(total, 0.01f);
} }
internal static float GetShipCargoAmount(ShipRuntime ship) internal static float GetShipCargoAmount(ShipRuntime ship) =>
{ ship.Inventory.Values.Sum();
var cargoItemId = ship.Definition.CargoItemId;
return cargoItemId is null ? 0f : GetInventoryAmount(ship.Inventory, cargoItemId);
}
private string UpdateExtract(ShipRuntime ship, SimulationWorld world, float deltaSeconds) private string UpdateExtract(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
{ {
var task = ship.ControllerTask; var task = ship.ControllerTask;
var node = world.Nodes.FirstOrDefault(candidate => candidate.Id == task.TargetEntityId); var node = world.Nodes.FirstOrDefault(candidate => candidate.Id == task.TargetEntityId);
if (node is null || task.TargetPosition is null || !CanExtractNode(ship, node)) if (node is null || task.TargetPosition is null || !CanExtractNode(ship, node, world))
{ {
ship.State = ShipState.Idle; ship.State = ShipState.Idle;
ship.TargetPosition = ship.Position; ship.TargetPosition = ship.Position;
@@ -79,10 +76,7 @@ public sealed partial class SimulationEngine
return node.OreRemaining <= 0.01f ? "node-depleted" : "cargo-full"; return node.OreRemaining <= 0.01f ? "node-depleted" : "cargo-full";
} }
if (ship.Definition.CargoItemId is not null) AddInventory(ship.Inventory, node.ItemId, mined);
{
AddInventory(ship.Inventory, ship.Definition.CargoItemId, mined);
}
node.OreRemaining -= mined; node.OreRemaining -= mined;
node.OreRemaining = MathF.Max(0f, node.OreRemaining); node.OreRemaining = MathF.Max(0f, node.OreRemaining);
@@ -167,23 +161,21 @@ public sealed partial class SimulationEngine
ship.ActionTimer = 0f; ship.ActionTimer = 0f;
ship.State = ShipState.Transferring; ship.State = ShipState.Transferring;
BeginTrackedAction(ship, "transferring", GetShipCargoAmount(ship)); BeginTrackedAction(ship, "transferring", GetShipCargoAmount(ship));
var cargoItemId = ship.Definition.CargoItemId;
var moved = cargoItemId is null ? 0f : MathF.Min(GetInventoryAmount(ship.Inventory, cargoItemId), world.Balance.TransferRate * deltaSeconds);
if (cargoItemId is not null)
{
var accepted = TryAddStationInventory(world, station, cargoItemId, moved);
RemoveInventory(ship.Inventory, cargoItemId, accepted);
moved = accepted;
}
var faction = world.Factions.FirstOrDefault(candidate => candidate.Id == ship.FactionId); var faction = world.Factions.FirstOrDefault(candidate => candidate.Id == ship.FactionId);
if (faction is not null && cargoItemId == "ore") foreach (var (itemId, amount) in ship.Inventory.ToList())
{ {
faction.OreMined += moved; var moved = MathF.Min(amount, world.Balance.TransferRate * deltaSeconds);
faction.Credits += moved * 0.4f; var accepted = TryAddStationInventory(world, station, itemId, moved);
RemoveInventory(ship.Inventory, itemId, accepted);
if (faction is not null && string.Equals(itemId, "ore", StringComparison.Ordinal))
{
faction.OreMined += accepted;
faction.Credits += accepted * 0.4f;
}
} }
return cargoItemId is null || GetInventoryAmount(ship.Inventory, cargoItemId) <= 0.01f ? "unloaded" : "none"; return GetShipCargoAmount(ship) <= 0.01f ? "unloaded" : "none";
} }
private string UpdateLoadCargo(ShipRuntime ship, SimulationWorld world, float deltaSeconds) private string UpdateLoadCargo(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
@@ -209,19 +201,19 @@ public sealed partial class SimulationEngine
ship.Position = ship.TargetPosition; ship.Position = ship.TargetPosition;
ship.ActionTimer = 0f; ship.ActionTimer = 0f;
ship.State = ShipState.Loading; ship.State = ShipState.Loading;
var itemId = ship.ControllerTask.ItemId;
BeginTrackedAction(ship, "loading", MathF.Max(0f, ship.Definition.CargoCapacity - GetShipCargoAmount(ship))); BeginTrackedAction(ship, "loading", MathF.Max(0f, ship.Definition.CargoCapacity - GetShipCargoAmount(ship)));
var cargoItemId = ship.Definition.CargoItemId;
var transfer = MathF.Min(world.Balance.TransferRate * deltaSeconds, ship.Definition.CargoCapacity - GetShipCargoAmount(ship)); var transfer = MathF.Min(world.Balance.TransferRate * deltaSeconds, ship.Definition.CargoCapacity - GetShipCargoAmount(ship));
var moved = cargoItemId is null ? 0f : MathF.Min(transfer, GetInventoryAmount(station.Inventory, cargoItemId)); var moved = itemId is null ? 0f : MathF.Min(transfer, GetInventoryAmount(station.Inventory, itemId));
if (cargoItemId is not null && moved > 0.01f) if (itemId is not null && moved > 0.01f)
{ {
RemoveInventory(station.Inventory, cargoItemId, moved); RemoveInventory(station.Inventory, itemId, moved);
AddInventory(ship.Inventory, cargoItemId, moved); AddInventory(ship.Inventory, itemId, moved);
} }
return cargoItemId is null return itemId is null
|| GetShipCargoAmount(ship) >= ship.Definition.CargoCapacity - 0.01f || GetShipCargoAmount(ship) >= ship.Definition.CargoCapacity - 0.01f
|| GetInventoryAmount(station.Inventory, cargoItemId) <= 0.01f || GetInventoryAmount(station.Inventory, itemId) <= 0.01f
? "loaded" ? "loaded"
: "none"; : "none";
} }
@@ -411,65 +403,6 @@ public sealed partial class SimulationEngine
private static bool IsShipWithinSupportRange(ShipRuntime ship, Vector3 supportPosition, float threshold) => private static bool IsShipWithinSupportRange(ShipRuntime ship, Vector3 supportPosition, float threshold) =>
ship.Position.DistanceTo(supportPosition) <= MathF.Max(threshold, 6f); ship.Position.DistanceTo(supportPosition) <= MathF.Max(threshold, 6f);
private string UpdateLoadWorkers(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
{
if (ship.DockedStationId is null || !CanTransportWorkers(ship))
{
ship.State = ShipState.Blocked;
return "failed";
}
var station = world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DockedStationId);
if (station is null || station.Population <= 0.01f)
{
ship.State = ShipState.Idle;
return "none";
}
var transfer = MathF.Min(station.Population, GetWorkerTransportCapacity(ship) - ship.WorkerPopulation);
var totalTransfer = MathF.Min(station.Population, GetWorkerTransportCapacity(ship) - ship.WorkerPopulation);
transfer = MathF.Min(transfer, 4f * deltaSeconds);
if (transfer <= 0.01f)
{
return "none";
}
station.Population = MathF.Max(0f, station.Population - transfer);
ship.WorkerPopulation += transfer;
ship.State = ShipState.Loading;
BeginTrackedAction(ship, "loading", totalTransfer);
return ship.WorkerPopulation >= GetWorkerTransportCapacity(ship) - 0.01f ? "workers-loaded" : "none";
}
private string UpdateUnloadWorkers(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
{
if (ship.DockedStationId is null || !CanTransportWorkers(ship))
{
ship.State = ShipState.Blocked;
return "failed";
}
var station = world.Stations.FirstOrDefault(candidate => candidate.Id == ship.DockedStationId);
if (station is null || ship.WorkerPopulation <= 0.01f)
{
ship.State = ShipState.Idle;
return "none";
}
var transfer = MathF.Min(ship.WorkerPopulation, MathF.Max(0f, station.PopulationCapacity - station.Population));
var totalTransfer = transfer;
transfer = MathF.Min(transfer, 4f * deltaSeconds);
if (transfer <= 0.01f)
{
return "none";
}
ship.WorkerPopulation = MathF.Max(0f, ship.WorkerPopulation - transfer);
station.Population = MathF.Min(station.PopulationCapacity, station.Population + transfer);
ship.State = ShipState.Unloading;
BeginTrackedAction(ship, "unloading", totalTransfer);
return ship.WorkerPopulation <= 0.01f ? "workers-unloaded" : "none";
}
private string UpdateUndock(ShipRuntime ship, SimulationWorld world, float deltaSeconds) private string UpdateUndock(ShipRuntime ship, SimulationWorld world, float deltaSeconds)
{ {

View File

@@ -152,7 +152,7 @@ public sealed partial class SimulationEngine
.FirstOrDefault() .FirstOrDefault()
: world.Nodes.FirstOrDefault(candidate => candidate.Id == behavior.NodeId && candidate.OreRemaining > 0.01f); : 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)) if (refinery is null || node is null || !HasShipCapabilities(ship.Definition, requiredModule))
{ {
behavior.Kind = "idle"; behavior.Kind = "idle";
ship.ControllerTask = CreateIdleTask(world.Balance.ArrivalThreshold); ship.ControllerTask = CreateIdleTask(world.Balance.ArrivalThreshold);
@@ -505,8 +505,7 @@ public sealed partial class SimulationEngine
"unload" => ControllerTaskKind.Unload, "unload" => ControllerTaskKind.Unload,
"deliver-construction" => ControllerTaskKind.DeliverConstruction, "deliver-construction" => ControllerTaskKind.DeliverConstruction,
"build-construction-site" => ControllerTaskKind.BuildConstructionSite, "build-construction-site" => ControllerTaskKind.BuildConstructionSite,
"load-workers" => ControllerTaskKind.LoadWorkers,
"unload-workers" => ControllerTaskKind.UnloadWorkers,
"construct-module" => ControllerTaskKind.ConstructModule, "construct-module" => ControllerTaskKind.ConstructModule,
"undock" => ControllerTaskKind.Undock, "undock" => ControllerTaskKind.Undock,
_ => ControllerTaskKind.Idle, _ => ControllerTaskKind.Idle,

View File

@@ -235,7 +235,7 @@ public sealed partial class SimulationEngine
return false; return false;
} }
if (!string.Equals(shipDefinition.Role, "military", StringComparison.Ordinal) if (!string.Equals(shipDefinition.Kind, "military", StringComparison.Ordinal)
|| !FactionNeedsMoreWarships(world, station.FactionId)) || !FactionNeedsMoreWarships(world, station.FactionId))
{ {
return false; return false;
@@ -394,7 +394,7 @@ public sealed partial class SimulationEngine
{ {
var militaryShipCount = world.Ships.Count(ship => var militaryShipCount = world.Ships.Count(ship =>
ship.FactionId == factionId ship.FactionId == factionId
&& string.Equals(ship.Definition.Role, "military", StringComparison.Ordinal)); && string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal));
var targetSystems = Math.Max(1, Math.Min(StrategicControlTargetSystems, world.Systems.Count)); var targetSystems = Math.Max(1, Math.Min(StrategicControlTargetSystems, world.Systems.Count));
var controlledSystems = GetFactionControlledSystemsCount(world, factionId); var controlledSystems = GetFactionControlledSystemsCount(world, factionId);
var expansionDeficit = Math.Max(0, targetSystems - controlledSystems); var expansionDeficit = Math.Max(0, targetSystems - controlledSystems);
@@ -448,7 +448,7 @@ public sealed partial class SimulationEngine
private static DefaultBehaviorRuntime CreateSpawnedShipBehavior(ShipDefinition definition, StationRuntime station) private static DefaultBehaviorRuntime CreateSpawnedShipBehavior(ShipDefinition definition, StationRuntime station)
{ {
if (!string.Equals(definition.Role, "military", StringComparison.Ordinal)) if (!string.Equals(definition.Kind, "military", StringComparison.Ordinal))
{ {
return new DefaultBehaviorRuntime return new DefaultBehaviorRuntime
{ {

View File

@@ -3,8 +3,8 @@ import type { InventoryEntry, Vector3Dto } from "./contractsCommon";
export interface ShipSnapshot { export interface ShipSnapshot {
id: string; id: string;
label: string; label: string;
role: string; kind: string;
shipClass: string; class: string;
systemId: string; systemId: string;
localPosition: Vector3Dto; localPosition: Vector3Dto;
localVelocity: Vector3Dto; localVelocity: Vector3Dto;
@@ -19,8 +19,7 @@ export interface ShipSnapshot {
commanderId?: string | null; commanderId?: string | null;
policySetId?: string | null; policySetId?: string | null;
cargoCapacity: number; cargoCapacity: number;
cargoItemId?: string | null;
workerPopulation: number;
travelSpeed: number; travelSpeed: number;
travelSpeedUnit: string; travelSpeedUnit: string;
inventory: InventoryEntry[]; inventory: InventoryEntry[];

View File

@@ -26,9 +26,7 @@ export function renderFactionStrip(
return ships return ships
.map((ship) => { .map((ship) => {
const cargo = ship.cargoItemId const cargo = ship.inventory.reduce((sum, e) => sum + e.amount, 0);
? inventoryAmount(ship.inventory, ship.cargoItemId)
: 0;
const shipLocation = describeShipLocation(world, ship); const shipLocation = describeShipLocation(world, ship);
const shipState = describeShipState(world, ship); const shipState = describeShipState(world, ship);
const shipAction = describeShipCurrentAction(ship); const shipAction = describeShipCurrentAction(ship);
@@ -42,7 +40,7 @@ export function renderFactionStrip(
<div class="ship-card-header"> <div class="ship-card-header">
<h3>${ship.label}</h3> <h3>${ship.label}</h3>
<div class="ship-card-meta"> <div class="ship-card-meta">
<span class="ship-card-badge">${ship.shipClass}</span> <span class="ship-card-badge">${ship.class}</span>
<button <button
type="button" type="button"
class="ship-card-history-button" class="ship-card-history-button"

View File

@@ -196,10 +196,7 @@ export function updateDetailPanel(
return; return;
} }
const parent = describeSelectionParent(selected); const parent = describeSelectionParent(selected);
const cargoUsed = ship.cargoItemId const cargoUsed = ship.inventory.reduce((sum, e) => sum + e.amount, 0);
? inventoryAmount(ship.inventory, ship.cargoItemId)
: 0;
const cargoLabel = ship.cargoItemId ?? "none";
const shipState = describeShipState(world, ship); const shipState = describeShipState(world, ship);
const shipAction = describeShipCurrentAction(ship); const shipAction = describeShipCurrentAction(ship);
detailTitleEl.textContent = ship.label; detailTitleEl.textContent = ship.label;
@@ -217,7 +214,7 @@ export function updateDetailPanel(
</div> </div>
</div> </div>
` : ""} ` : ""}
<p>Cargo ${cargoLabel} ${cargoUsed.toFixed(0)} / ${ship.cargoCapacity.toFixed(0)}</p> <p>Cargo ${cargoUsed.toFixed(0)} / ${ship.cargoCapacity.toFixed(0)}</p>
<p>Inventory ${formatInventory(ship.inventory)}</p> <p>Inventory ${formatInventory(ship.inventory)}</p>
<p>Speed ${formatShipSpeed(ship)}</p> <p>Speed ${formatShipSpeed(ship)}</p>
<p>Camera ${cameraMode === "follow" && cameraTargetShipId === ship.id ? "camera-follow" : "tactical"}<br>Press C to toggle follow</p> <p>Camera ${cameraMode === "follow" && cameraTargetShipId === ship.id ? "camera-follow" : "tactical"}<br>Press C to toggle follow</p>

View File

@@ -2,7 +2,7 @@ import * as THREE from "three";
import type { ShipSnapshot } from "./contracts"; import type { ShipSnapshot } from "./contracts";
export function shipSize(ship: ShipSnapshot) { export function shipSize(ship: ShipSnapshot) {
switch (ship.shipClass) { switch (ship.class) {
case "capital": case "capital":
return 18; return 18;
case "cruiser": case "cruiser":
@@ -20,11 +20,11 @@ export function shipLength(ship: ShipSnapshot) {
return shipSize(ship) * 2.6; return shipSize(ship) * 2.6;
} }
export function shipColor(role: ShipSnapshot["role"]) { export function shipColor(kind: ShipSnapshot["kind"]) {
if (role === "mining") { if (kind === "mining") {
return "#ffcf6e"; return "#ffcf6e";
} }
if (role === "transport") { if (kind === "transport") {
return "#9ff0aa"; return "#9ff0aa";
} }
return "#8bc0ff"; return "#8bc0ff";
@@ -40,7 +40,7 @@ export function shipPresentationColor(ship: ShipSnapshot) {
if (ship.spatialState.movementRegime === "ftl-transit") { if (ship.spatialState.movementRegime === "ftl-transit") {
return "#ff6ad5"; return "#ff6ad5";
} }
return shipColor(ship.role); return shipColor(ship.kind);
} }
export function spatialNodeColor(kind: string) { export function spatialNodeColor(kind: string) {

View File

@@ -4,7 +4,7 @@
"name": "Raw Ore", "name": "Raw Ore",
"description": "Unprocessed asteroid ore used as the main industrial feedstock.", "description": "Unprocessed asteroid ore used as the main industrial feedstock.",
"type": "resource", "type": "resource",
"cargoKind": "bulk-solid", "cargoKind": "solid",
"volume": 1.2 "volume": 1.2
}, },
{ {
@@ -12,12 +12,15 @@
"name": "Water", "name": "Water",
"description": "Life-support and agricultural input.", "description": "Life-support and agricultural input.",
"type": "commodity", "type": "commodity",
"cargoKind": "bulk-liquid", "cargoKind": "liquid",
"volume": 1.0, "volume": 1.0,
"construction": { "construction": {
"recipeId": "water-reclamation", "recipeId": "water-reclamation",
"facilityCategory": "farm", "facilityCategory": "farm",
"requiredModules": ["liquid-tank", "solar-array"], "requiredModules": [
"liquid-tank",
"solar-array"
],
"requirements": [], "requirements": [],
"cycleTime": 6, "cycleTime": 6,
"batchSize": 12, "batchSize": 12,
@@ -36,9 +39,14 @@
"construction": { "construction": {
"recipeId": "ore-refining", "recipeId": "ore-refining",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["refinery-stack"], "requiredModules": [
"refinery-stack"
],
"requirements": [ "requirements": [
{ "itemId": "ore", "amount": 60 } {
"itemId": "ore",
"amount": 60
}
], ],
"cycleTime": 8, "cycleTime": 8,
"batchSize": 60, "batchSize": 60,
@@ -57,9 +65,14 @@
"construction": { "construction": {
"recipeId": "hull-fabrication", "recipeId": "hull-fabrication",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["fabricator-array"], "requiredModules": [
"fabricator-array"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 70 } {
"itemId": "refined-metals",
"amount": 70
}
], ],
"cycleTime": 10, "cycleTime": 10,
"batchSize": 35, "batchSize": 35,
@@ -78,9 +91,14 @@
"construction": { "construction": {
"recipeId": "ammo-fabrication", "recipeId": "ammo-fabrication",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["fabricator-array"], "requiredModules": [
"fabricator-array"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 24 } {
"itemId": "refined-metals",
"amount": 24
}
], ],
"cycleTime": 6, "cycleTime": 6,
"batchSize": 30, "batchSize": 30,
@@ -89,27 +107,6 @@
"priority": 34 "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", "id": "ship-equipment",
"name": "Ship Equipment", "name": "Ship Equipment",
@@ -120,10 +117,18 @@
"construction": { "construction": {
"recipeId": "equipment-assembly", "recipeId": "equipment-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["fabricator-array"], "requiredModules": [
"fabricator-array"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 28 }, {
{ "itemId": "water", "amount": 8 } "itemId": "refined-metals",
"amount": 28
},
{
"itemId": "water",
"amount": 8
}
], ],
"cycleTime": 11, "cycleTime": 11,
"batchSize": 18, "batchSize": 18,
@@ -142,11 +147,18 @@
"construction": { "construction": {
"recipeId": "ship-parts-integration", "recipeId": "ship-parts-integration",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["fabricator-array"], "requiredModules": [
"fabricator-array"
],
"requirements": [ "requirements": [
{ "itemId": "hull-sections", "amount": 24 }, {
{ "itemId": "naval-guns", "amount": 6 }, "itemId": "hull-sections",
{ "itemId": "ship-equipment", "amount": 10 } "amount": 24
},
{
"itemId": "ship-equipment",
"amount": 10
}
], ],
"cycleTime": 14, "cycleTime": 14,
"batchSize": 20, "batchSize": 20,
@@ -165,10 +177,18 @@
"construction": { "construction": {
"recipeId": "drone-parts-assembly", "recipeId": "drone-parts-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["fabricator-array"], "requiredModules": [
"fabricator-array"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 12 }, {
{ "itemId": "ship-equipment", "amount": 6 } "itemId": "refined-metals",
"amount": 12
},
{
"itemId": "ship-equipment",
"amount": 6
}
], ],
"cycleTime": 7, "cycleTime": 7,
"batchSize": 16, "batchSize": 16,
@@ -187,10 +207,19 @@
"construction": { "construction": {
"recipeId": "command-bridge-module-assembly", "recipeId": "command-bridge-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 20 }, {
{ "itemId": "ship-equipment", "amount": 10 } "itemId": "refined-metals",
"amount": 20
},
{
"itemId": "ship-equipment",
"amount": 10
}
], ],
"cycleTime": 9, "cycleTime": 9,
"batchSize": 1, "batchSize": 1,
@@ -209,10 +238,19 @@
"construction": { "construction": {
"recipeId": "reactor-core-module-assembly", "recipeId": "reactor-core-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 30 }, {
{ "itemId": "ship-equipment", "amount": 8 } "itemId": "refined-metals",
"amount": 30
},
{
"itemId": "ship-equipment",
"amount": 8
}
], ],
"cycleTime": 10, "cycleTime": 10,
"batchSize": 1, "batchSize": 1,
@@ -231,10 +269,19 @@
"construction": { "construction": {
"recipeId": "capacitor-bank-module-assembly", "recipeId": "capacitor-bank-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 18 }, {
{ "itemId": "ship-equipment", "amount": 4 } "itemId": "refined-metals",
"amount": 18
},
{
"itemId": "ship-equipment",
"amount": 4
}
], ],
"cycleTime": 9, "cycleTime": 9,
"batchSize": 1, "batchSize": 1,
@@ -253,10 +300,19 @@
"construction": { "construction": {
"recipeId": "ion-drive-module-assembly", "recipeId": "ion-drive-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 22 }, {
{ "itemId": "ship-equipment", "amount": 8 } "itemId": "refined-metals",
"amount": 22
},
{
"itemId": "ship-equipment",
"amount": 8
}
], ],
"cycleTime": 10, "cycleTime": 10,
"batchSize": 1, "batchSize": 1,
@@ -275,10 +331,19 @@
"construction": { "construction": {
"recipeId": "ftl-core-module-assembly", "recipeId": "ftl-core-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 34 }, {
{ "itemId": "ship-equipment", "amount": 14 } "itemId": "refined-metals",
"amount": 34
},
{
"itemId": "ship-equipment",
"amount": 14
}
], ],
"cycleTime": 12, "cycleTime": 12,
"batchSize": 1, "batchSize": 1,
@@ -297,10 +362,15 @@
"construction": { "construction": {
"recipeId": "gun-turret-module-assembly", "recipeId": "gun-turret-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "naval-guns", "amount": 8 }, {
{ "itemId": "refined-metals", "amount": 12 } "itemId": "refined-metals",
"amount": 12
}
], ],
"cycleTime": 8, "cycleTime": 8,
"batchSize": 1, "batchSize": 1,
@@ -319,11 +389,23 @@
"construction": { "construction": {
"recipeId": "carrier-bay-module-assembly", "recipeId": "carrier-bay-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "hull-sections", "amount": 18 }, {
{ "itemId": "refined-metals", "amount": 18 }, "itemId": "hull-sections",
{ "itemId": "ship-equipment", "amount": 10 } "amount": 18
},
{
"itemId": "refined-metals",
"amount": 18
},
{
"itemId": "ship-equipment",
"amount": 10
}
], ],
"cycleTime": 14, "cycleTime": 14,
"batchSize": 1, "batchSize": 1,
@@ -342,11 +424,23 @@
"construction": { "construction": {
"recipeId": "habitat-ring-module-assembly", "recipeId": "habitat-ring-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "hull-sections", "amount": 14 }, {
{ "itemId": "ship-equipment", "amount": 8 }, "itemId": "hull-sections",
{ "itemId": "water", "amount": 10 } "amount": 14
},
{
"itemId": "ship-equipment",
"amount": 8
},
{
"itemId": "water",
"amount": 10
}
], ],
"cycleTime": 12, "cycleTime": 12,
"batchSize": 1, "batchSize": 1,
@@ -365,10 +459,19 @@
"construction": { "construction": {
"recipeId": "bulk-bay-module-assembly", "recipeId": "bulk-bay-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 16 }, {
{ "itemId": "hull-sections", "amount": 10 } "itemId": "refined-metals",
"amount": 16
},
{
"itemId": "hull-sections",
"amount": 10
}
], ],
"cycleTime": 8, "cycleTime": 8,
"batchSize": 1, "batchSize": 1,
@@ -387,10 +490,19 @@
"construction": { "construction": {
"recipeId": "container-bay-module-assembly", "recipeId": "container-bay-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 12 }, {
{ "itemId": "ship-equipment", "amount": 4 } "itemId": "refined-metals",
"amount": 12
},
{
"itemId": "ship-equipment",
"amount": 4
}
], ],
"cycleTime": 8, "cycleTime": 8,
"batchSize": 1, "batchSize": 1,
@@ -409,10 +521,19 @@
"construction": { "construction": {
"recipeId": "liquid-tank-module-assembly", "recipeId": "liquid-tank-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 14 }, {
{ "itemId": "ship-equipment", "amount": 4 } "itemId": "refined-metals",
"amount": 14
},
{
"itemId": "ship-equipment",
"amount": 4
}
], ],
"cycleTime": 8, "cycleTime": 8,
"batchSize": 1, "batchSize": 1,
@@ -431,10 +552,19 @@
"construction": { "construction": {
"recipeId": "mining-turret-module-assembly", "recipeId": "mining-turret-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 18 }, {
{ "itemId": "ship-equipment", "amount": 6 } "itemId": "refined-metals",
"amount": 18
},
{
"itemId": "ship-equipment",
"amount": 6
}
], ],
"cycleTime": 9, "cycleTime": 9,
"batchSize": 1, "batchSize": 1,
@@ -453,10 +583,19 @@
"construction": { "construction": {
"recipeId": "fabricator-array-module-assembly", "recipeId": "fabricator-array-module-assembly",
"facilityCategory": "station", "facilityCategory": "station",
"requiredModules": ["component-factory", "container-bay"], "requiredModules": [
"component-factory",
"container-bay"
],
"requirements": [ "requirements": [
{ "itemId": "refined-metals", "amount": 24 }, {
{ "itemId": "ship-equipment", "amount": 10 } "itemId": "refined-metals",
"amount": 24
},
{
"itemId": "ship-equipment",
"amount": 10
}
], ],
"cycleTime": 11, "cycleTime": 11,
"batchSize": 1, "batchSize": 1,
@@ -464,167 +603,5 @@
"maxEfficiency": 1, "maxEfficiency": 1,
"priority": 20 "priority": 20
} }
},
{
"id": "trade-hub-kit",
"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",
"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",
"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",
"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",
"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",
"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",
"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
}
} }
] ]

View File

@@ -2,8 +2,8 @@
{ {
"id": "frigate", "id": "frigate",
"label": "Vanguard Frigate", "label": "Vanguard Frigate",
"role": "military", "kind": "military",
"shipClass": "frigate", "class": "frigate",
"speed": 120000, "speed": 120000,
"warpSpeed": 0.22, "warpSpeed": 0.22,
"ftlSpeed": 0.75, "ftlSpeed": 0.75,
@@ -13,13 +13,9 @@
"hullColor": "#1f4f78", "hullColor": "#1f4f78",
"size": 4, "size": 4,
"maxHealth": 100, "maxHealth": 100,
"modules": [ "capabilities": [
"command-bridge", "warp",
"reactor-core", "ftl"
"capacitor-bank",
"ion-drive",
"ftl-core",
"gun-turret"
], ],
"construction": { "construction": {
"recipeId": "frigate-construction", "recipeId": "frigate-construction",
@@ -69,8 +65,8 @@
{ {
"id": "destroyer", "id": "destroyer",
"label": "Bulwark Destroyer", "label": "Bulwark Destroyer",
"role": "military", "kind": "military",
"shipClass": "destroyer", "class": "destroyer",
"speed": 95000, "speed": 95000,
"warpSpeed": 0.18, "warpSpeed": 0.18,
"ftlSpeed": 0.68, "ftlSpeed": 0.68,
@@ -80,14 +76,9 @@
"hullColor": "#6a2e26", "hullColor": "#6a2e26",
"size": 7, "size": 7,
"maxHealth": 240, "maxHealth": 240,
"modules": [ "capabilities": [
"command-bridge", "warp",
"reactor-core", "ftl"
"capacitor-bank",
"ion-drive",
"ftl-core",
"gun-turret",
"gun-turret"
], ],
"construction": { "construction": {
"recipeId": "destroyer-construction", "recipeId": "destroyer-construction",
@@ -137,8 +128,8 @@
{ {
"id": "cruiser", "id": "cruiser",
"label": "Aegis Cruiser", "label": "Aegis Cruiser",
"role": "military", "kind": "military",
"shipClass": "cruiser", "class": "cruiser",
"speed": 85000, "speed": 85000,
"warpSpeed": 0.16, "warpSpeed": 0.16,
"ftlSpeed": 0.62, "ftlSpeed": 0.62,
@@ -148,14 +139,9 @@
"hullColor": "#314562", "hullColor": "#314562",
"size": 10, "size": 10,
"maxHealth": 340, "maxHealth": 340,
"modules": [ "capabilities": [
"command-bridge", "warp",
"reactor-core", "ftl"
"capacitor-bank",
"ion-drive",
"ftl-core",
"gun-turret",
"gun-turret"
], ],
"construction": { "construction": {
"recipeId": "cruiser-construction", "recipeId": "cruiser-construction",
@@ -205,8 +191,8 @@
{ {
"id": "carrier", "id": "carrier",
"label": "Citadel Carrier", "label": "Citadel Carrier",
"role": "military", "kind": "military",
"shipClass": "capital", "class": "capital",
"speed": 60000, "speed": 60000,
"warpSpeed": 0.12, "warpSpeed": 0.12,
"ftlSpeed": 0.5, "ftlSpeed": 0.5,
@@ -216,16 +202,9 @@
"hullColor": "#35586d", "hullColor": "#35586d",
"size": 16, "size": 16,
"maxHealth": 900, "maxHealth": 900,
"modules": [ "capabilities": [
"command-bridge", "warp",
"reactor-core", "ftl"
"capacitor-bank",
"ion-drive",
"ftl-core",
"carrier-bay",
"carrier-bay",
"gun-turret",
"habitat-ring"
], ],
"dockingCapacity": 6, "dockingCapacity": 6,
"dockingClasses": [ "dockingClasses": [
@@ -274,10 +253,6 @@
{ {
"itemId": "gun-turret-module", "itemId": "gun-turret-module",
"amount": 1 "amount": 1
},
{
"itemId": "habitat-ring-module",
"amount": 1
} }
], ],
"cycleTime": 60, "cycleTime": 60,
@@ -289,8 +264,8 @@
{ {
"id": "hauler", "id": "hauler",
"label": "Atlas Hauler", "label": "Atlas Hauler",
"role": "transport", "kind": "transport",
"shipClass": "industrial", "class": "industrial",
"speed": 70000, "speed": 70000,
"warpSpeed": 0.14, "warpSpeed": 0.14,
"ftlSpeed": 0.55, "ftlSpeed": 0.55,
@@ -301,13 +276,9 @@
"hullColor": "#365f2a", "hullColor": "#365f2a",
"size": 8, "size": 8,
"maxHealth": 180, "maxHealth": 180,
"modules": [ "capabilities": [
"command-bridge", "warp",
"reactor-core", "ftl"
"capacitor-bank",
"ion-drive",
"ftl-core",
"container-bay"
], ],
"construction": { "construction": {
"recipeId": "hauler-construction", "recipeId": "hauler-construction",
@@ -357,27 +328,21 @@
{ {
"id": "constructor", "id": "constructor",
"label": "Pioneer Constructor", "label": "Pioneer Constructor",
"role": "construction", "kind": "construction",
"shipClass": "industrial", "class": "industrial",
"speed": 65000, "speed": 65000,
"warpSpeed": 0.13, "warpSpeed": 0.13,
"ftlSpeed": 0.48, "ftlSpeed": 0.48,
"spoolTime": 3.5, "spoolTime": 3.5,
"cargoCapacity": 160, "cargoCapacity": 160,
"cargoKind": "manufactured", "cargoKind": "manufactured",
"cargoItemId": "drone-parts",
"color": "#9af0c1", "color": "#9af0c1",
"hullColor": "#2d5d47", "hullColor": "#2d5d47",
"size": 9, "size": 9,
"maxHealth": 220, "maxHealth": 220,
"modules": [ "capabilities": [
"command-bridge", "warp",
"reactor-core", "ftl"
"capacitor-bank",
"ion-drive",
"ftl-core",
"fabricator-array",
"container-bay"
], ],
"construction": { "construction": {
"recipeId": "constructor-construction", "recipeId": "constructor-construction",
@@ -431,27 +396,22 @@
{ {
"id": "miner", "id": "miner",
"label": "Prospector Miner", "label": "Prospector Miner",
"role": "mining", "kind": "mining",
"shipClass": "industrial", "class": "industrial",
"speed": 75000, "speed": 75000,
"warpSpeed": 0.15, "warpSpeed": 0.15,
"ftlSpeed": 0.5, "ftlSpeed": 0.5,
"spoolTime": 3.1, "spoolTime": 3.1,
"cargoCapacity": 120, "cargoCapacity": 120,
"cargoKind": "bulk-solid", "cargoKind": "solid",
"cargoItemId": "ore",
"color": "#ffdd75", "color": "#ffdd75",
"hullColor": "#68552b", "hullColor": "#68552b",
"size": 6, "size": 6,
"maxHealth": 150, "maxHealth": 150,
"modules": [ "capabilities": [
"command-bridge", "warp",
"reactor-core", "ftl",
"capacitor-bank", "mining"
"ion-drive",
"ftl-core",
"mining-turret",
"bulk-bay"
], ],
"construction": { "construction": {
"recipeId": "miner-construction", "recipeId": "miner-construction",