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

@@ -115,6 +115,14 @@ public enum ModuleType
Storage,
}
public enum StorageKind
{
Condensate,
Container,
Liquid,
Solid,
}
public static class CommanderKind
{
public const string Faction = "faction";
@@ -209,20 +217,54 @@ public static class SimulationEnumMappings
_ => throw new ArgumentOutOfRangeException(nameof(moduleType), moduleType, null),
};
public static ModuleType ToModuleType(this string value) => value.Trim() switch
public static ModuleType ToModuleType(this string value)
{
"buildmodule" => ModuleType.BuildModule,
"connectionmodule" => ModuleType.ConnectionModule,
"defencemodule" => ModuleType.DefenceModule,
"dockarea" => ModuleType.DockArea,
"habitation" => ModuleType.Habitation,
"pier" => ModuleType.Pier,
"processingmodule" => ModuleType.ProcessingModule,
"production" => ModuleType.Production,
"storage" => ModuleType.Storage,
_ => throw new ArgumentOutOfRangeException(nameof(value), value, "Unsupported module type."),
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentOutOfRangeException(nameof(value), value, "Module type is required.");
}
return value.Trim().ToLowerInvariant() switch
{
"buildmodule" => ModuleType.BuildModule,
"connectionmodule" => ModuleType.ConnectionModule,
"defencemodule" => ModuleType.DefenceModule,
"dockarea" => ModuleType.DockArea,
"habitation" => ModuleType.Habitation,
"pier" => ModuleType.Pier,
"processingmodule" => ModuleType.ProcessingModule,
"production" => ModuleType.Production,
"storage" => ModuleType.Storage,
_ => throw new ArgumentOutOfRangeException(nameof(value), value, "Unsupported module type."),
};
}
public static string ToDataValue(this StorageKind storageKind) => storageKind switch
{
StorageKind.Condensate => "condensate",
StorageKind.Container => "container",
StorageKind.Liquid => "liquid",
StorageKind.Solid => "solid",
_ => throw new ArgumentOutOfRangeException(nameof(storageKind), storageKind, null),
};
public static StorageKind ToStorageKind(this string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentOutOfRangeException(nameof(value), value, "Storage kind is required.");
}
return value.Trim().ToLowerInvariant() switch
{
"condensate" => StorageKind.Condensate,
"container" => StorageKind.Container,
"liquid" => StorageKind.Liquid,
"solid" => StorageKind.Solid,
_ => throw new ArgumentOutOfRangeException(nameof(value), value, "Unsupported storage kind."),
};
}
public static string ToContractValue(this SpatialNodeKind kind) => kind switch
{
SpatialNodeKind.Star => "star",

View File

@@ -6,13 +6,55 @@ internal static class SimulationRuntimeSupport
internal static bool HasShipCapabilities(ShipDefinition definition, params string[] capabilities) =>
capabilities.All(cap => definition.Capabilities.Contains(cap, StringComparer.Ordinal));
internal static int CountStationModules(StationRuntime station, string moduleId) =>
station.Modules.Count(module => string.Equals(module.ModuleId, moduleId, StringComparison.Ordinal));
internal static int CountStationModules(StationRuntime station, ModuleType moduleType) =>
station.Modules.Count(module => module.ModuleType == moduleType);
internal static int CountStationModules(SimulationWorld world, StationRuntime station, ModuleType moduleType) =>
station.Modules.Count(module =>
world.ModuleDefinitions.TryGetValue(module.ModuleId, out var definition)
&& definition.ModuleType == moduleType);
internal static float GetStationStorageCapacity(SimulationWorld world, StationRuntime station, StorageKind storageKind)
{
SyncStorageModuleLevels(world, station, storageKind);
return GetStorageModules(world, station, storageKind)
.Sum(entry => entry.Definition.StorageCapacity);
}
internal static bool HasStorageCapacity(SimulationWorld world, StationRuntime station, StorageKind storageKind)
{
SyncStorageModuleLevels(world, station, storageKind);
return GetStorageModules(world, station, storageKind).Any();
}
private static IEnumerable<(StorageStationModuleRuntime Module, StorageModuleDefinition Definition)> GetStorageModules(
SimulationWorld world,
StationRuntime station,
StorageKind storageKind) =>
station.Modules
.OfType<StorageStationModuleRuntime>()
.Where(module => module.StorageKind == storageKind)
.Select(module => world.ModuleDefinitions.TryGetValue(module.ModuleId, out var definition) && definition is StorageModuleDefinition storageDefinition
? (Module: module, Definition: storageDefinition)
: ((StorageStationModuleRuntime Module, StorageModuleDefinition Definition)?)null)
.Where(entry => entry is not null && entry.Value.Definition.StorageKind == storageKind)
.Select(entry => entry!.Value);
private static void SyncStorageModuleLevels(SimulationWorld world, StationRuntime station, StorageKind storageKind)
{
var storageModules = GetStorageModules(world, station, storageKind)
.OrderBy(entry => entry.Module.Id, StringComparer.Ordinal)
.ToList();
if (storageModules.Count == 0)
{
return;
}
var remaining = station.Inventory
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoStorageKind == storageKind)
.Sum(entry => entry.Value);
foreach (var (module, definition) in storageModules)
{
module.CurrentLevel = MathF.Min(remaining, definition.StorageCapacity);
remaining = MathF.Max(0f, remaining - definition.StorageCapacity);
}
}
internal static void AddStationModule(SimulationWorld world, StationRuntime station, string moduleId)
{
@@ -21,13 +63,7 @@ internal static class SimulationRuntimeSupport
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(world, station);
}
@@ -39,41 +75,9 @@ internal static class SimulationRuntimeSupport
return MathF.Max(24f, MathF.Sqrt(MathF.Max(totalArea, 1f)));
}
internal static float GetStationStorageCapacity(StationRuntime station, string storageClass)
{
var baseCapacity = storageClass switch
{
"manufactured" => 400f,
_ => 0f,
};
var bulkBays = CountStationModules(station, "module_arg_stor_solid_m_01");
var liquidTanks = CountStationModules(station, "module_arg_stor_liquid_m_01");
var containerBays = CountStationModules(station, "module_arg_stor_container_m_01");
var moduleCapacity = storageClass switch
{
"solid" => bulkBays * 1000f,
"liquid" => liquidTanks * 500f,
"container" => containerBays * 800f,
"manufactured" => containerBays * 200f,
_ => 0f,
};
return baseCapacity + moduleCapacity;
}
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 GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
inventory.TryGetValue(itemId, out var amount) ? amount : 0f;
@@ -110,7 +114,8 @@ internal static class SimulationRuntimeSupport
internal static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node, SimulationWorld world) =>
HasShipCapabilities(ship.Definition, "mining")
&& world.ItemDefinitions.TryGetValue(node.ItemId, out var item)
&& string.Equals(item.CargoKind, ship.Definition.CargoKind, StringComparison.Ordinal);
&& item.CargoStorageKind is not null
&& item.CargoStorageKind == ship.Definition.CargoStorageKind;
internal static bool CanBuildClaimBeacon(ShipRuntime ship) =>
string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal);
@@ -126,13 +131,43 @@ internal static class SimulationRuntimeSupport
return 0.1f + (0.9f * staffedRatio);
}
internal static string? GetStorageRequirement(string storageClass) =>
storageClass switch
internal static string? GetStorageRequirement(
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
StorageKind? storageKind)
{
if (storageKind is not { } requiredStorageKind)
{
"solid" => "module_arg_stor_solid_m_01",
"liquid" => "module_arg_stor_liquid_m_01",
_ => null,
};
return null;
}
return moduleDefinitions.Values
.OfType<StorageModuleDefinition>()
.Where(definition => definition.StorageKind == requiredStorageKind)
.OrderBy(definition => GetPreferredStorageModuleRank(definition.Id))
.ThenBy(definition => definition.Id, StringComparer.Ordinal)
.Select(definition => definition.Id)
.FirstOrDefault();
}
private static int GetPreferredStorageModuleRank(string moduleId)
{
if (moduleId.Contains("_m_", StringComparison.Ordinal))
{
return 0;
}
if (moduleId.Contains("_s_", StringComparison.Ordinal))
{
return 1;
}
if (moduleId.Contains("_l_", StringComparison.Ordinal))
{
return 2;
}
return 3;
}
internal static float TryAddStationInventory(SimulationWorld world, StationRuntime station, string itemId, float amount)
{
@@ -141,21 +176,25 @@ internal static class SimulationRuntimeSupport
return 0f;
}
var storageClass = itemDefinition.CargoKind;
var requiredModule = GetStorageRequirement(storageClass);
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
var storageKind = itemDefinition.CargoStorageKind;
if (storageKind is null)
{
return 0f;
}
var capacity = GetStationStorageCapacity(station, storageClass);
if (!HasStorageCapacity(world, station, storageKind.Value))
{
return 0f;
}
var capacity = GetStationStorageCapacity(world, station, storageKind.Value);
if (capacity <= 0.01f)
{
return 0f;
}
var used = station.Inventory
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == storageClass)
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoStorageKind == storageKind)
.Sum(entry => entry.Value);
var accepted = MathF.Min(amount, MathF.Max(0f, capacity - used));
if (accepted <= 0.01f)