Refactor station modules into typed runtime models

This commit is contained in:
2026-03-27 14:59:15 -04:00
parent f961ac62b6
commit e8fb033a01
13 changed files with 408 additions and 169 deletions

View File

@@ -16,7 +16,7 @@ internal sealed class DataCatalogLoader(string dataRoot)
var authoredSystems = Read<List<SolarSystemDefinition>>("systems.json");
var scenario = Read<ScenarioDefinition>("scenario.json");
var modules = NormalizeModules(Read<List<ModuleDefinition>>("modules.json"));
var ships = Read<List<ShipDefinition>>("ships.json");
var ships = NormalizeShips(Read<List<ShipDefinition>>("ships.json"));
var items = NormalizeItems(Read<List<ItemDefinition>>("items.json"));
var balance = Read<BalanceDefinition>("balance.json");
var recipes = BuildRecipes(items, ships, modules);
@@ -263,15 +263,57 @@ internal sealed class DataCatalogLoader(string dataRoot)
{
item.Type = string.IsNullOrWhiteSpace(item.Group) ? "material" : item.Group;
}
if (string.IsNullOrWhiteSpace(item.CargoKind))
{
item.CargoStorageKind = null;
}
else
{
try
{
item.CargoStorageKind = item.CargoKind.ToStorageKind();
item.CargoKind = item.CargoStorageKind.Value.ToDataValue();
}
catch (ArgumentOutOfRangeException exception)
{
throw new InvalidOperationException($"Item '{item.Id}' has unsupported cargo kind '{item.CargoKind}'.", exception);
}
}
}
return items;
}
private static List<ShipDefinition> NormalizeShips(List<ShipDefinition> ships)
{
foreach (var ship in ships)
{
if (string.IsNullOrWhiteSpace(ship.CargoKind))
{
ship.CargoStorageKind = null;
continue;
}
try
{
ship.CargoStorageKind = ship.CargoKind.ToStorageKind();
ship.CargoKind = ship.CargoStorageKind.Value.ToDataValue();
}
catch (ArgumentOutOfRangeException exception)
{
throw new InvalidOperationException($"Ship '{ship.Id}' has unsupported cargo kind '{ship.CargoKind}'.", exception);
}
}
return ships;
}
private static List<ModuleDefinition> NormalizeModules(List<ModuleDefinition> modules)
{
foreach (var module in modules)
for (var index = 0; index < modules.Count; index += 1)
{
var module = modules[index];
try
{
module.ModuleType = module.Type.ToModuleType();
@@ -299,10 +341,39 @@ internal sealed class DataCatalogLoader(string dataRoot)
{
module.WorkforceNeeded = module.WorkForce?.Max ?? 0f;
}
modules[index] = CreateSpecializedModuleDefinition(module);
}
return modules;
}
private static ModuleDefinition CreateSpecializedModuleDefinition(ModuleDefinition module)
{
if (module.ModuleType == ModuleType.Storage)
{
if (module.Cargo is null)
{
throw new InvalidOperationException($"Storage module '{module.Id}' is missing cargo metadata.");
}
try
{
return new StorageModuleDefinition(module, module.Cargo.Type.ToStorageKind(), module.Cargo.Max);
}
catch (ArgumentOutOfRangeException exception)
{
throw new InvalidOperationException($"Storage module '{module.Id}' has unsupported cargo type '{module.Cargo.Type}'.", exception);
}
}
if (module.Products.Count > 0)
{
return new ProductionModuleDefinition(module);
}
return module;
}
}
internal sealed record ScenarioCatalog(

View File

@@ -101,13 +101,7 @@ internal static class LoaderSupport
return;
}
station.Modules.Add(new StationModuleRuntime
{
Id = $"{station.Id}-module-{station.Modules.Count + 1}",
ModuleId = moduleId,
Health = definition.Hull,
MaxHealth = definition.Hull,
});
station.Modules.Add(StationModuleRuntime.Create($"{station.Id}-module-{station.Modules.Count + 1}", definition));
station.Radius = GetStationRadius(moduleDefinitions, station);
}
@@ -126,14 +120,6 @@ internal static class LoaderSupport
internal static int CountModules(IEnumerable<string> modules, string moduleId) =>
modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal));
internal static int CountModules(
IEnumerable<string> modules,
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
ModuleType moduleType) =>
modules.Count(moduleId =>
moduleDefinitions.TryGetValue(moduleId, out var definition)
&& definition.ModuleType == moduleType);
internal static float ComputeWorkforceRatio(float population, float workforceRequired)
{
if (workforceRequired <= 0.01f)

View File

@@ -38,7 +38,7 @@ internal sealed class WorldBuilder(
catalog.ModuleDefinitions,
catalog.ItemDefinitions);
seedingService.InitializeStationStockpiles(stations, catalog.ModuleDefinitions);
seedingService.InitializeStationStockpiles(stations);
var refinery = seedingService.SelectRefineryStation(stations, scenario);
var patrolRoutes = BuildPatrolRoutes(scenario, systemsById);
var ships = CreateShips(scenario, systemsById, spatialLayout.Celestials, catalog.Balance, catalog.ShipDefinitions, patrolRoutes, stations, refinery);
@@ -218,14 +218,10 @@ internal sealed class WorldBuilder(
continue;
}
var storageModuleId = itemDefinition.CargoKind switch
if (SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport.GetStorageRequirement(moduleDefinitions, itemDefinition.CargoStorageKind) is { } storageModuleId)
{
"solid" => "module_arg_stor_solid_m_01",
"liquid" => "module_arg_stor_liquid_m_01",
_ => "module_arg_stor_container_m_01",
};
yield return storageModuleId;
yield return storageModuleId;
}
}
}

View File

@@ -1,4 +1,3 @@
using SpaceGame.Api.Shared.Runtime;
using static SpaceGame.Api.Universe.Scenario.LoaderSupport;
namespace SpaceGame.Api.Universe.Scenario;
@@ -61,13 +60,11 @@ internal sealed class WorldSeedingService
}
}
internal void InitializeStationStockpiles(
IReadOnlyCollection<StationRuntime> stations,
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
internal void InitializeStationStockpiles(IReadOnlyCollection<StationRuntime> stations)
{
foreach (var station in stations)
{
InitializeStationPopulation(station, moduleDefinitions);
InitializeStationPopulation(station);
}
}
@@ -244,12 +241,10 @@ internal sealed class WorldSeedingService
continue;
}
yield return itemDefinition.CargoKind switch
if (SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport.GetStorageRequirement(world.ModuleDefinitions, itemDefinition.CargoStorageKind) is { } storageModuleId)
{
"solid" => "module_arg_stor_solid_m_01",
"liquid" => "module_arg_stor_liquid_m_01",
_ => "module_arg_stor_container_m_01",
};
yield return storageModuleId;
}
}
}
@@ -554,11 +549,9 @@ internal sealed class WorldSeedingService
};
}
private static void InitializeStationPopulation(
StationRuntime station,
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
private static void InitializeStationPopulation(StationRuntime station)
{
var habitatModules = CountModules(station.InstalledModules, moduleDefinitions, ModuleType.Habitation);
var habitatModules = station.Modules.Count(module => module.ModuleType == ModuleType.Habitation);
station.PopulationCapacity = 40f + (habitatModules * 220f);
station.WorkforceRequired = MathF.Max(12f, station.Modules.Count * 14f);
station.Population = habitatModules > 0