Refactor station modules into typed runtime models
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using SpaceGame.Api.Shared.Runtime;
|
using SpaceGame.Api.Shared.Runtime;
|
||||||
|
|
||||||
@@ -115,6 +116,8 @@ public sealed class ItemDefinition
|
|||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
public string Type { get; set; } = "material";
|
public string Type { get; set; } = "material";
|
||||||
public string CargoKind { get; set; } = string.Empty;
|
public string CargoKind { get; set; } = string.Empty;
|
||||||
|
[JsonIgnore]
|
||||||
|
public StorageKind? CargoStorageKind { get; set; }
|
||||||
public float Volume { get; set; } = 1f;
|
public float Volume { get; set; } = 1f;
|
||||||
public int Version { get; set; }
|
public int Version { get; set; }
|
||||||
public string FactoryName { get; set; } = string.Empty;
|
public string FactoryName { get; set; } = string.Empty;
|
||||||
@@ -190,8 +193,41 @@ public sealed class ModuleProductionDefinition
|
|||||||
public List<RecipeInputDefinition> Wares { get; set; } = [];
|
public List<RecipeInputDefinition> Wares { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleDefinition
|
public class ModuleDefinition
|
||||||
{
|
{
|
||||||
|
public ModuleDefinition()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetsRequiredMembers]
|
||||||
|
protected ModuleDefinition(ModuleDefinition source)
|
||||||
|
{
|
||||||
|
Id = source.Id;
|
||||||
|
Name = source.Name;
|
||||||
|
Description = source.Description;
|
||||||
|
Type = source.Type;
|
||||||
|
ModuleType = source.ModuleType;
|
||||||
|
Product = source.Product;
|
||||||
|
Products = [.. source.Products];
|
||||||
|
ProductionMode = source.ProductionMode;
|
||||||
|
Radius = source.Radius;
|
||||||
|
Hull = source.Hull;
|
||||||
|
WorkforceNeeded = source.WorkforceNeeded;
|
||||||
|
Version = source.Version;
|
||||||
|
Macro = source.Macro;
|
||||||
|
MakerRace = source.MakerRace;
|
||||||
|
ExplosionDamage = source.ExplosionDamage;
|
||||||
|
Price = source.Price;
|
||||||
|
Owners = [.. source.Owners];
|
||||||
|
Cargo = source.Cargo;
|
||||||
|
WorkForce = source.WorkForce;
|
||||||
|
Docks = [.. source.Docks];
|
||||||
|
Shields = [.. source.Shields];
|
||||||
|
Turrets = [.. source.Turrets];
|
||||||
|
Production = [.. source.Production];
|
||||||
|
Construction = source.Construction;
|
||||||
|
}
|
||||||
|
|
||||||
public required string Id { get; set; }
|
public required string Id { get; set; }
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
@@ -226,6 +262,32 @@ public sealed class ModuleDefinition
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class ProductionModuleDefinition : ModuleDefinition
|
||||||
|
{
|
||||||
|
[SetsRequiredMembers]
|
||||||
|
internal ProductionModuleDefinition(ModuleDefinition source)
|
||||||
|
: base(source)
|
||||||
|
{
|
||||||
|
ProductItemIds = [.. source.Products];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<string> ProductItemIds { get; init; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class StorageModuleDefinition : ModuleDefinition
|
||||||
|
{
|
||||||
|
[SetsRequiredMembers]
|
||||||
|
internal StorageModuleDefinition(ModuleDefinition source, StorageKind storageKind, float storageCapacity)
|
||||||
|
: base(source)
|
||||||
|
{
|
||||||
|
StorageKind = storageKind;
|
||||||
|
StorageCapacity = storageCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StorageKind StorageKind { get; init; }
|
||||||
|
public float StorageCapacity { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class ModuleRecipeDefinition
|
public sealed class ModuleRecipeDefinition
|
||||||
{
|
{
|
||||||
public required string ModuleId { get; set; }
|
public required string ModuleId { get; set; }
|
||||||
@@ -277,6 +339,8 @@ public sealed class ShipDefinition
|
|||||||
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; }
|
||||||
|
[JsonIgnore]
|
||||||
|
public StorageKind? CargoStorageKind { 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; }
|
||||||
|
|||||||
@@ -115,6 +115,14 @@ public enum ModuleType
|
|||||||
Storage,
|
Storage,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum StorageKind
|
||||||
|
{
|
||||||
|
Condensate,
|
||||||
|
Container,
|
||||||
|
Liquid,
|
||||||
|
Solid,
|
||||||
|
}
|
||||||
|
|
||||||
public static class CommanderKind
|
public static class CommanderKind
|
||||||
{
|
{
|
||||||
public const string Faction = "faction";
|
public const string Faction = "faction";
|
||||||
@@ -209,7 +217,14 @@ public static class SimulationEnumMappings
|
|||||||
_ => throw new ArgumentOutOfRangeException(nameof(moduleType), moduleType, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(moduleType), moduleType, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static ModuleType ToModuleType(this string value) => value.Trim() switch
|
public static ModuleType ToModuleType(this string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), value, "Module type is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.Trim().ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
"buildmodule" => ModuleType.BuildModule,
|
"buildmodule" => ModuleType.BuildModule,
|
||||||
"connectionmodule" => ModuleType.ConnectionModule,
|
"connectionmodule" => ModuleType.ConnectionModule,
|
||||||
@@ -222,6 +237,33 @@ public static class SimulationEnumMappings
|
|||||||
"storage" => ModuleType.Storage,
|
"storage" => ModuleType.Storage,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(value), value, "Unsupported module type."),
|
_ => 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
|
public static string ToContractValue(this SpatialNodeKind kind) => kind switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,13 +6,55 @@ internal static class SimulationRuntimeSupport
|
|||||||
internal static bool HasShipCapabilities(ShipDefinition definition, params string[] capabilities) =>
|
internal static bool HasShipCapabilities(ShipDefinition definition, params string[] capabilities) =>
|
||||||
capabilities.All(cap => definition.Capabilities.Contains(cap, StringComparer.Ordinal));
|
capabilities.All(cap => definition.Capabilities.Contains(cap, StringComparer.Ordinal));
|
||||||
|
|
||||||
internal static int CountStationModules(StationRuntime station, string moduleId) =>
|
internal static int CountStationModules(StationRuntime station, ModuleType moduleType) =>
|
||||||
station.Modules.Count(module => string.Equals(module.ModuleId, moduleId, StringComparison.Ordinal));
|
station.Modules.Count(module => module.ModuleType == moduleType);
|
||||||
|
|
||||||
internal static int CountStationModules(SimulationWorld world, StationRuntime station, ModuleType moduleType) =>
|
internal static float GetStationStorageCapacity(SimulationWorld world, StationRuntime station, StorageKind storageKind)
|
||||||
station.Modules.Count(module =>
|
{
|
||||||
world.ModuleDefinitions.TryGetValue(module.ModuleId, out var definition)
|
SyncStorageModuleLevels(world, station, storageKind);
|
||||||
&& definition.ModuleType == moduleType);
|
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)
|
internal static void AddStationModule(SimulationWorld world, StationRuntime station, string moduleId)
|
||||||
{
|
{
|
||||||
@@ -21,13 +63,7 @@ internal static class SimulationRuntimeSupport
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
station.Modules.Add(new StationModuleRuntime
|
station.Modules.Add(StationModuleRuntime.Create($"{station.Id}-module-{station.Modules.Count + 1}", definition));
|
||||||
{
|
|
||||||
Id = $"{station.Id}-module-{station.Modules.Count + 1}",
|
|
||||||
ModuleId = moduleId,
|
|
||||||
Health = definition.Hull,
|
|
||||||
MaxHealth = definition.Hull,
|
|
||||||
});
|
|
||||||
station.Radius = GetStationRadius(world, station);
|
station.Radius = GetStationRadius(world, station);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,41 +75,9 @@ internal static class SimulationRuntimeSupport
|
|||||||
return MathF.Max(24f, MathF.Sqrt(MathF.Max(totalArea, 1f)));
|
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) =>
|
internal static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
||||||
modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal));
|
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) =>
|
internal static float GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
|
||||||
inventory.TryGetValue(itemId, out var amount) ? amount : 0f;
|
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) =>
|
internal static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node, SimulationWorld world) =>
|
||||||
HasShipCapabilities(ship.Definition, "mining")
|
HasShipCapabilities(ship.Definition, "mining")
|
||||||
&& world.ItemDefinitions.TryGetValue(node.ItemId, out var item)
|
&& 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) =>
|
internal static bool CanBuildClaimBeacon(ShipRuntime ship) =>
|
||||||
string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal);
|
string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal);
|
||||||
@@ -126,13 +131,43 @@ internal static class SimulationRuntimeSupport
|
|||||||
return 0.1f + (0.9f * staffedRatio);
|
return 0.1f + (0.9f * staffedRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string? GetStorageRequirement(string storageClass) =>
|
internal static string? GetStorageRequirement(
|
||||||
storageClass switch
|
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
|
||||||
|
StorageKind? storageKind)
|
||||||
{
|
{
|
||||||
"solid" => "module_arg_stor_solid_m_01",
|
if (storageKind is not { } requiredStorageKind)
|
||||||
"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)
|
internal static float TryAddStationInventory(SimulationWorld world, StationRuntime station, string itemId, float amount)
|
||||||
{
|
{
|
||||||
@@ -141,21 +176,25 @@ internal static class SimulationRuntimeSupport
|
|||||||
return 0f;
|
return 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
var storageClass = itemDefinition.CargoKind;
|
var storageKind = itemDefinition.CargoStorageKind;
|
||||||
var requiredModule = GetStorageRequirement(storageClass);
|
if (storageKind is null)
|
||||||
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
|
||||||
{
|
{
|
||||||
return 0f;
|
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)
|
if (capacity <= 0.01f)
|
||||||
{
|
{
|
||||||
return 0f;
|
return 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
var used = station.Inventory
|
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);
|
.Sum(entry => entry.Value);
|
||||||
var accepted = MathF.Min(amount, MathF.Max(0f, capacity - used));
|
var accepted = MathF.Min(amount, MathF.Max(0f, capacity - used));
|
||||||
if (accepted <= 0.01f)
|
if (accepted <= 0.01f)
|
||||||
|
|||||||
@@ -2619,15 +2619,11 @@ internal sealed class ShipAiService
|
|||||||
{
|
{
|
||||||
if (world.ItemDefinitions.TryGetValue(itemId, out var itemDefinition))
|
if (world.ItemDefinitions.TryGetValue(itemId, out var itemDefinition))
|
||||||
{
|
{
|
||||||
var storageModule = GetStorageRequirement(itemDefinition.CargoKind);
|
var storageModule = GetStorageRequirement(world.ModuleDefinitions, itemDefinition.CargoStorageKind);
|
||||||
if (storageModule is not null && !modules.Contains(storageModule, StringComparer.Ordinal))
|
if (storageModule is not null && !modules.Contains(storageModule, StringComparer.Ordinal))
|
||||||
{
|
{
|
||||||
modules.Add(storageModule);
|
modules.Add(storageModule);
|
||||||
}
|
}
|
||||||
else if (storageModule is null && !modules.Contains("module_arg_stor_container_m_01", StringComparer.Ordinal))
|
|
||||||
{
|
|
||||||
modules.Add("module_arg_stor_container_m_01");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -796,14 +796,14 @@ internal sealed class SimulationProjectionService
|
|||||||
|
|
||||||
private static IReadOnlyList<StationStorageUsageSnapshot> ToStationStorageUsageSnapshots(SimulationWorld world, StationRuntime station)
|
private static IReadOnlyList<StationStorageUsageSnapshot> ToStationStorageUsageSnapshots(SimulationWorld world, StationRuntime station)
|
||||||
{
|
{
|
||||||
string[] storageClasses = ["solid", "liquid", "container", "manufactured"];
|
StorageKind[] storageKinds = [StorageKind.Solid, StorageKind.Liquid, StorageKind.Container];
|
||||||
return storageClasses
|
return storageKinds
|
||||||
.Select(storageClass => new StationStorageUsageSnapshot(
|
.Select(storageKind => new StationStorageUsageSnapshot(
|
||||||
storageClass,
|
storageKind.ToDataValue(),
|
||||||
station.Inventory
|
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),
|
.Sum(entry => entry.Value),
|
||||||
GetStationStorageCapacity(station, storageClass)))
|
GetStationStorageCapacity(world, station, storageKind)))
|
||||||
.Where(snapshot => snapshot.Capacity > 0.01f)
|
.Where(snapshot => snapshot.Capacity > 0.01f)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
using SpaceGame.Api.Definitions;
|
||||||
|
using SpaceGame.Api.Shared.Runtime;
|
||||||
|
|
||||||
namespace SpaceGame.Api.Stations.Runtime;
|
namespace SpaceGame.Api.Stations.Runtime;
|
||||||
|
|
||||||
public sealed class StationRuntime
|
public sealed class StationRuntime
|
||||||
@@ -33,12 +36,55 @@ public sealed class StationRuntime
|
|||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class StationModuleRuntime
|
public class StationModuleRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string ModuleId { get; init; }
|
public required string ModuleId { get; init; }
|
||||||
|
public required ModuleType ModuleType { get; init; }
|
||||||
public float Health { get; set; }
|
public float Health { get; set; }
|
||||||
public float MaxHealth { get; set; }
|
public float MaxHealth { get; set; }
|
||||||
|
|
||||||
|
public static StationModuleRuntime Create(string id, ModuleDefinition definition) =>
|
||||||
|
definition switch
|
||||||
|
{
|
||||||
|
StorageModuleDefinition storage => new StorageStationModuleRuntime
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
ModuleId = storage.Id,
|
||||||
|
ModuleType = storage.ModuleType,
|
||||||
|
StorageKind = storage.StorageKind,
|
||||||
|
Health = storage.Hull,
|
||||||
|
MaxHealth = storage.Hull,
|
||||||
|
},
|
||||||
|
ProductionModuleDefinition production => new ProductionStationModuleRuntime
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
ModuleId = production.Id,
|
||||||
|
ModuleType = production.ModuleType,
|
||||||
|
ProductItemIds = [.. production.ProductItemIds],
|
||||||
|
Health = production.Hull,
|
||||||
|
MaxHealth = production.Hull,
|
||||||
|
},
|
||||||
|
_ => new StationModuleRuntime
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
ModuleId = definition.Id,
|
||||||
|
ModuleType = definition.ModuleType,
|
||||||
|
Health = definition.Hull,
|
||||||
|
MaxHealth = definition.Hull,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ProductionStationModuleRuntime : StationModuleRuntime
|
||||||
|
{
|
||||||
|
public IReadOnlyList<string> ProductItemIds { get; init; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class StorageStationModuleRuntime : StationModuleRuntime
|
||||||
|
{
|
||||||
|
public StorageKind StorageKind { get; init; }
|
||||||
|
public float CurrentLevel { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleConstructionRuntime
|
public sealed class ModuleConstructionRuntime
|
||||||
|
|||||||
@@ -162,21 +162,21 @@ internal sealed class InfrastructureSimulationService
|
|||||||
|
|
||||||
private static IEnumerable<string> GetStoragePressureCandidates(SimulationWorld world, StationRuntime station)
|
private static IEnumerable<string> GetStoragePressureCandidates(SimulationWorld world, StationRuntime station)
|
||||||
{
|
{
|
||||||
foreach (var (storageClass, moduleId) in new[]
|
foreach (var (storageKind, moduleId) in new[]
|
||||||
{
|
{
|
||||||
("solid", "module_arg_stor_solid_m_01"),
|
(StorageKind.Solid, "module_arg_stor_solid_m_01"),
|
||||||
("liquid", "module_arg_stor_liquid_m_01"),
|
(StorageKind.Liquid, "module_arg_stor_liquid_m_01"),
|
||||||
("container", "module_arg_stor_container_m_01"),
|
(StorageKind.Container, "module_arg_stor_container_m_01"),
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
var capacity = GetStationStorageCapacity(station, storageClass);
|
var capacity = GetStationStorageCapacity(world, station, storageKind);
|
||||||
if (capacity <= 0.01f)
|
if (capacity <= 0.01f)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var used = station.Inventory
|
var used = station.Inventory
|
||||||
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var def) && def.CargoKind == storageClass)
|
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var def) && def.CargoStorageKind == storageKind)
|
||||||
.Sum(entry => entry.Value);
|
.Sum(entry => entry.Value);
|
||||||
if (used / capacity >= 0.65f)
|
if (used / capacity >= 0.65f)
|
||||||
{
|
{
|
||||||
@@ -195,14 +195,10 @@ internal sealed class InfrastructureSimulationService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetStorageRequirement(itemDefinition.CargoKind) is { } storageModuleId)
|
if (GetStorageRequirement(world.ModuleDefinitions, itemDefinition.CargoStorageKind) is { } storageModuleId)
|
||||||
{
|
{
|
||||||
yield return storageModuleId;
|
yield return storageModuleId;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return "module_arg_stor_container_m_01";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (world.ModuleDefinitions.TryGetValue(recipe.ModuleId, out var moduleDefinition))
|
if (world.ModuleDefinitions.TryGetValue(recipe.ModuleId, out var moduleDefinition))
|
||||||
@@ -214,14 +210,10 @@ internal sealed class InfrastructureSimulationService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetStorageRequirement(itemDefinition.CargoKind) is { } storageModuleId)
|
if (GetStorageRequirement(world.ModuleDefinitions, itemDefinition.CargoStorageKind) is { } storageModuleId)
|
||||||
{
|
{
|
||||||
yield return storageModuleId;
|
yield return storageModuleId;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return "module_arg_stor_container_m_01";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,16 +316,16 @@ internal sealed class InfrastructureSimulationService
|
|||||||
string? objectiveCommodityId,
|
string? objectiveCommodityId,
|
||||||
bool requiredByObjective)
|
bool requiredByObjective)
|
||||||
{
|
{
|
||||||
var storageClass = storageModuleId switch
|
var storageKind = storageModuleId switch
|
||||||
{
|
{
|
||||||
"module_arg_stor_solid_m_01" => "solid",
|
"module_arg_stor_solid_m_01" => StorageKind.Solid,
|
||||||
"module_arg_stor_liquid_m_01" => "liquid",
|
"module_arg_stor_liquid_m_01" => StorageKind.Liquid,
|
||||||
_ => "container",
|
_ => StorageKind.Container,
|
||||||
};
|
};
|
||||||
|
|
||||||
var capacity = GetStationStorageCapacity(station, storageClass);
|
var capacity = GetStationStorageCapacity(world, station, storageKind);
|
||||||
var used = station.Inventory
|
var used = station.Inventory
|
||||||
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var def) && def.CargoKind == storageClass)
|
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var def) && def.CargoStorageKind == storageKind)
|
||||||
.Sum(entry => entry.Value);
|
.Sum(entry => entry.Value);
|
||||||
var utilization = capacity <= 0.01f ? 0f : used / capacity;
|
var utilization = capacity <= 0.01f ? 0f : used / capacity;
|
||||||
|
|
||||||
@@ -342,8 +334,8 @@ internal sealed class InfrastructureSimulationService
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(objectiveModuleId) && !string.IsNullOrWhiteSpace(objectiveCommodityId))
|
if (!string.IsNullOrWhiteSpace(objectiveModuleId) && !string.IsNullOrWhiteSpace(objectiveCommodityId))
|
||||||
{
|
{
|
||||||
var objectiveUsesStorage = ModuleNeedsStorageClass(world, objectiveModuleId, storageClass)
|
var objectiveUsesStorage = ModuleNeedsStorageClass(world, objectiveModuleId, storageKind)
|
||||||
|| CommodityUsesStorageClass(world, objectiveCommodityId, storageClass);
|
|| CommodityUsesStorageClass(world, objectiveCommodityId, storageKind);
|
||||||
if (objectiveUsesStorage)
|
if (objectiveUsesStorage)
|
||||||
{
|
{
|
||||||
score += 35f;
|
score += 35f;
|
||||||
@@ -579,15 +571,15 @@ internal sealed class InfrastructureSimulationService
|
|||||||
case "module_arg_stor_container_m_01":
|
case "module_arg_stor_container_m_01":
|
||||||
case "module_arg_stor_solid_m_01":
|
case "module_arg_stor_solid_m_01":
|
||||||
case "module_arg_stor_liquid_m_01":
|
case "module_arg_stor_liquid_m_01":
|
||||||
var storageClass = supportModuleId switch
|
var storageKind = supportModuleId switch
|
||||||
{
|
{
|
||||||
"module_arg_stor_solid_m_01" => "solid",
|
"module_arg_stor_solid_m_01" => StorageKind.Solid,
|
||||||
"module_arg_stor_liquid_m_01" => "liquid",
|
"module_arg_stor_liquid_m_01" => StorageKind.Liquid,
|
||||||
_ => "container",
|
_ => StorageKind.Container,
|
||||||
};
|
};
|
||||||
if (analysis.HasMissingOutputStorage
|
if (analysis.HasMissingOutputStorage
|
||||||
&& (ModuleNeedsStorageClass(world, objectiveModuleId, storageClass)
|
&& (ModuleNeedsStorageClass(world, objectiveModuleId, storageKind)
|
||||||
|| CommodityUsesStorageClass(world, objectiveCommodityId, storageClass)))
|
|| CommodityUsesStorageClass(world, objectiveCommodityId, storageKind)))
|
||||||
{
|
{
|
||||||
unlockScore += 70f;
|
unlockScore += 70f;
|
||||||
}
|
}
|
||||||
@@ -688,7 +680,7 @@ internal sealed class InfrastructureSimulationService
|
|||||||
return demand;
|
return demand;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ModuleNeedsStorageClass(SimulationWorld world, string moduleId, string storageClass)
|
private static bool ModuleNeedsStorageClass(SimulationWorld world, string moduleId, StorageKind storageKind)
|
||||||
{
|
{
|
||||||
if (!world.ModuleRecipes.TryGetValue(moduleId, out var recipe))
|
if (!world.ModuleRecipes.TryGetValue(moduleId, out var recipe))
|
||||||
{
|
{
|
||||||
@@ -697,12 +689,12 @@ internal sealed class InfrastructureSimulationService
|
|||||||
|
|
||||||
return recipe.Inputs.Any(input =>
|
return recipe.Inputs.Any(input =>
|
||||||
world.ItemDefinitions.TryGetValue(input.ItemId, out var itemDefinition)
|
world.ItemDefinitions.TryGetValue(input.ItemId, out var itemDefinition)
|
||||||
&& string.Equals(itemDefinition.CargoKind, storageClass, StringComparison.Ordinal));
|
&& itemDefinition.CargoStorageKind == storageKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CommodityUsesStorageClass(SimulationWorld world, string commodityId, string storageClass) =>
|
private static bool CommodityUsesStorageClass(SimulationWorld world, string commodityId, StorageKind storageKind) =>
|
||||||
world.ItemDefinitions.TryGetValue(commodityId, out var itemDefinition)
|
world.ItemDefinitions.TryGetValue(commodityId, out var itemDefinition)
|
||||||
&& string.Equals(itemDefinition.CargoKind, storageClass, StringComparison.Ordinal);
|
&& itemDefinition.CargoStorageKind == storageKind;
|
||||||
|
|
||||||
private static bool CanStationAcceptStationOutputSoon(SimulationWorld world, StationRuntime station, string itemId, float amount)
|
private static bool CanStationAcceptStationOutputSoon(SimulationWorld world, StationRuntime station, string itemId, float amount)
|
||||||
{
|
{
|
||||||
@@ -711,14 +703,19 @@ internal sealed class InfrastructureSimulationService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var capacity = GetStationStorageCapacity(station, itemDefinition.CargoKind);
|
if (itemDefinition.CargoStorageKind is not { } storageKind)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var capacity = GetStationStorageCapacity(world, station, storageKind);
|
||||||
if (capacity <= 0.01f)
|
if (capacity <= 0.01f)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var used = station.Inventory
|
var used = station.Inventory
|
||||||
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && string.Equals(definition.CargoKind, itemDefinition.CargoKind, StringComparison.Ordinal))
|
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoStorageKind == storageKind)
|
||||||
.Sum(entry => entry.Value);
|
.Sum(entry => entry.Value);
|
||||||
return used + amount <= capacity * 0.95f;
|
return used + amount <= capacity * 0.95f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ internal sealed class StationLifecycleService
|
|||||||
var requiredWater = station.Population * WaterConsumptionPerWorkerPerSecond * deltaSeconds;
|
var requiredWater = station.Population * WaterConsumptionPerWorkerPerSecond * deltaSeconds;
|
||||||
var consumedWater = RemoveInventory(station.Inventory, "water", requiredWater);
|
var consumedWater = RemoveInventory(station.Inventory, "water", requiredWater);
|
||||||
var waterSatisfied = requiredWater <= 0.01f || consumedWater + 0.001f >= requiredWater;
|
var waterSatisfied = requiredWater <= 0.01f || consumedWater + 0.001f >= requiredWater;
|
||||||
var habitatModules = CountModules(station.InstalledModules, world.ModuleDefinitions, ModuleType.Habitation);
|
var habitatModules = CountStationModules(station, ModuleType.Habitation);
|
||||||
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
||||||
|
|
||||||
if (waterSatisfied)
|
if (waterSatisfied)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using static SpaceGame.Api.Factions.AI.CommanderPlanningService;
|
using static SpaceGame.Api.Factions.AI.CommanderPlanningService;
|
||||||
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
|
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
|
||||||
|
using SpaceGame.Api.Shared.Runtime;
|
||||||
|
|
||||||
namespace SpaceGame.Api.Stations.Simulation;
|
namespace SpaceGame.Api.Stations.Simulation;
|
||||||
|
|
||||||
@@ -54,14 +55,14 @@ internal sealed class StationSimulationService
|
|||||||
_ => 0f,
|
_ => 0f,
|
||||||
};
|
};
|
||||||
var oreReserve = role == "refinery" ? 260f : 0f;
|
var oreReserve = role == "refinery" ? 260f : 0f;
|
||||||
var hullpartsReserve = MathF.Max(constructionHullpartsReserve, HasStationModules(station, "module_gen_build_l_01") ? 120f : 0f);
|
var hullpartsReserve = MathF.Max(constructionHullpartsReserve, HasShipyardCapability(station) ? 120f : 0f);
|
||||||
var claytronicsReserve = MathF.Max(constructionClayReserve, HasStationModules(station, "module_gen_build_l_01") ? 120f : 0f);
|
var claytronicsReserve = MathF.Max(constructionClayReserve, HasShipyardCapability(station) ? 120f : 0f);
|
||||||
var grapheneReserve = role == "graphene" ? 120f : 0f;
|
var grapheneReserve = role == "graphene" ? 120f : 0f;
|
||||||
var siliconWafersReserve = role == "siliconwafers" ? 120f : 0f;
|
var siliconWafersReserve = role == "siliconwafers" ? 120f : 0f;
|
||||||
var antimatterCellsReserve = role == "antimattercells" ? 120f : 0f;
|
var antimatterCellsReserve = role == "antimattercells" ? 120f : 0f;
|
||||||
var superfluidCoolantReserve = role == "superfluidcoolant" ? 120f : 0f;
|
var superfluidCoolantReserve = role == "superfluidcoolant" ? 120f : 0f;
|
||||||
var quantumTubesReserve = role == "quantumtubes" ? 120f : 0f;
|
var quantumTubesReserve = role == "quantumtubes" ? 120f : 0f;
|
||||||
var shipPartsReserve = HasStationModules(station, "module_gen_build_l_01")
|
var shipPartsReserve = HasShipyardCapability(station)
|
||||||
&& GetShipProductionPressure(world, station.FactionId, "military") > 0.2f
|
&& GetShipProductionPressure(world, station.FactionId, "military") > 0.2f
|
||||||
? 90f
|
? 90f
|
||||||
: 0f;
|
: 0f;
|
||||||
@@ -116,7 +117,7 @@ internal sealed class StationSimulationService
|
|||||||
var constructionHullpartsReserve = GetConstructionDemandForItem(world, site, "hullparts");
|
var constructionHullpartsReserve = GetConstructionDemandForItem(world, site, "hullparts");
|
||||||
var constructionClayReserve = GetConstructionDemandForItem(world, site, "claytronics");
|
var constructionClayReserve = GetConstructionDemandForItem(world, site, "claytronics");
|
||||||
var constructionRefinedReserve = GetConstructionDemandForItem(world, site, "refinedmetals");
|
var constructionRefinedReserve = GetConstructionDemandForItem(world, site, "refinedmetals");
|
||||||
var shipPartsReserve = HasStationModules(station, "module_gen_build_l_01")
|
var shipPartsReserve = HasShipyardCapability(station)
|
||||||
&& GetShipProductionPressure(world, station.FactionId, "military") > 0.2f
|
&& GetShipProductionPressure(world, station.FactionId, "military") > 0.2f
|
||||||
? 90f
|
? 90f
|
||||||
: 0f;
|
: 0f;
|
||||||
@@ -151,8 +152,8 @@ internal sealed class StationSimulationService
|
|||||||
"refinery" => 80f,
|
"refinery" => 80f,
|
||||||
_ => 0f,
|
_ => 0f,
|
||||||
}, constructionRefinedReserve),
|
}, constructionRefinedReserve),
|
||||||
"hullparts" => MathF.Max(constructionHullpartsReserve, HasStationModules(station, "module_gen_build_l_01") ? 120f : 0f) + shipPartsReserve,
|
"hullparts" => MathF.Max(constructionHullpartsReserve, HasShipyardCapability(station) ? 120f : 0f) + shipPartsReserve,
|
||||||
"claytronics" => MathF.Max(constructionClayReserve, HasStationModules(station, "module_gen_build_l_01") ? 120f : 0f),
|
"claytronics" => MathF.Max(constructionClayReserve, HasShipyardCapability(station) ? 120f : 0f),
|
||||||
"graphene" => MathF.Max(role == "graphene" ? 120f : 0f, role == "quantumtubes" ? 160f : 0f),
|
"graphene" => MathF.Max(role == "graphene" ? 120f : 0f, role == "quantumtubes" ? 160f : 0f),
|
||||||
"siliconwafers" => role == "siliconwafers" ? 120f : 0f,
|
"siliconwafers" => role == "siliconwafers" ? 120f : 0f,
|
||||||
"antimattercells" => MathF.Max(role == "antimattercells" ? 120f : 0f, role == "claytronics" ? 120f : 0f),
|
"antimattercells" => MathF.Max(role == "antimattercells" ? 120f : 0f, role == "claytronics" ? 120f : 0f),
|
||||||
@@ -292,7 +293,7 @@ internal sealed class StationSimulationService
|
|||||||
|
|
||||||
if (outputItemIds.Contains("hullparts"))
|
if (outputItemIds.Contains("hullparts"))
|
||||||
{
|
{
|
||||||
return HasStationModules(station, "module_gen_prod_advancedelectronics_01", "module_gen_build_l_01")
|
return HasShipyardCapability(station) && HasStationModules(station, "module_gen_prod_advancedelectronics_01")
|
||||||
? -140f * MathF.Max(expansionPressure, fleetPressure)
|
? -140f * MathF.Max(expansionPressure, fleetPressure)
|
||||||
: 280f * MathF.Max(expansionPressure, fleetPressure);
|
: 280f * MathF.Max(expansionPressure, fleetPressure);
|
||||||
}
|
}
|
||||||
@@ -407,20 +408,25 @@ internal sealed class StationSimulationService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var requiredModule = GetStorageRequirement(itemDefinition.CargoKind);
|
var storageKind = itemDefinition.CargoStorageKind;
|
||||||
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
if (storageKind is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var capacity = GetStationStorageCapacity(station, itemDefinition.CargoKind);
|
if (!HasStorageCapacity(world, station, storageKind.Value))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var capacity = GetStationStorageCapacity(world, station, storageKind.Value);
|
||||||
if (capacity <= 0.01f)
|
if (capacity <= 0.01f)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var used = station.Inventory
|
var used = station.Inventory
|
||||||
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == itemDefinition.CargoKind)
|
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoStorageKind == storageKind)
|
||||||
.Sum(entry => entry.Value);
|
.Sum(entry => entry.Value);
|
||||||
return used + amount <= capacity + 0.001f;
|
return used + amount <= capacity + 0.001f;
|
||||||
}
|
}
|
||||||
@@ -455,7 +461,7 @@ internal sealed class StationSimulationService
|
|||||||
return objective;
|
return objective;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasStationModules(station, "module_gen_build_l_01"))
|
if (HasShipyardCapability(station))
|
||||||
{
|
{
|
||||||
return "shipyard";
|
return "shipyard";
|
||||||
}
|
}
|
||||||
@@ -513,6 +519,9 @@ internal sealed class StationSimulationService
|
|||||||
return "general";
|
return "general";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool HasShipyardCapability(StationRuntime station) =>
|
||||||
|
CountStationModules(station, ModuleType.BuildModule) > 0;
|
||||||
|
|
||||||
private static float GetConstructionDemandForItem(SimulationWorld world, ConstructionSiteRuntime? site, string itemId)
|
private static float GetConstructionDemandForItem(SimulationWorld world, ConstructionSiteRuntime? site, string itemId)
|
||||||
{
|
{
|
||||||
if (site is null || !site.RequiredItems.TryGetValue(itemId, out var required))
|
if (site is null || !site.RequiredItems.TryGetValue(itemId, out var required))
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ internal sealed class DataCatalogLoader(string dataRoot)
|
|||||||
var authoredSystems = Read<List<SolarSystemDefinition>>("systems.json");
|
var authoredSystems = Read<List<SolarSystemDefinition>>("systems.json");
|
||||||
var scenario = Read<ScenarioDefinition>("scenario.json");
|
var scenario = Read<ScenarioDefinition>("scenario.json");
|
||||||
var modules = NormalizeModules(Read<List<ModuleDefinition>>("modules.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 items = NormalizeItems(Read<List<ItemDefinition>>("items.json"));
|
||||||
var balance = Read<BalanceDefinition>("balance.json");
|
var balance = Read<BalanceDefinition>("balance.json");
|
||||||
var recipes = BuildRecipes(items, ships, modules);
|
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;
|
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;
|
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)
|
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
|
try
|
||||||
{
|
{
|
||||||
module.ModuleType = module.Type.ToModuleType();
|
module.ModuleType = module.Type.ToModuleType();
|
||||||
@@ -299,10 +341,39 @@ internal sealed class DataCatalogLoader(string dataRoot)
|
|||||||
{
|
{
|
||||||
module.WorkforceNeeded = module.WorkForce?.Max ?? 0f;
|
module.WorkforceNeeded = module.WorkForce?.Max ?? 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modules[index] = CreateSpecializedModuleDefinition(module);
|
||||||
}
|
}
|
||||||
|
|
||||||
return modules;
|
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(
|
internal sealed record ScenarioCatalog(
|
||||||
|
|||||||
@@ -101,13 +101,7 @@ internal static class LoaderSupport
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
station.Modules.Add(new StationModuleRuntime
|
station.Modules.Add(StationModuleRuntime.Create($"{station.Id}-module-{station.Modules.Count + 1}", definition));
|
||||||
{
|
|
||||||
Id = $"{station.Id}-module-{station.Modules.Count + 1}",
|
|
||||||
ModuleId = moduleId,
|
|
||||||
Health = definition.Hull,
|
|
||||||
MaxHealth = definition.Hull,
|
|
||||||
});
|
|
||||||
station.Radius = GetStationRadius(moduleDefinitions, station);
|
station.Radius = GetStationRadius(moduleDefinitions, station);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,14 +120,6 @@ internal static class LoaderSupport
|
|||||||
internal static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
internal static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
||||||
modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal));
|
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)
|
internal static float ComputeWorkforceRatio(float population, float workforceRequired)
|
||||||
{
|
{
|
||||||
if (workforceRequired <= 0.01f)
|
if (workforceRequired <= 0.01f)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ internal sealed class WorldBuilder(
|
|||||||
catalog.ModuleDefinitions,
|
catalog.ModuleDefinitions,
|
||||||
catalog.ItemDefinitions);
|
catalog.ItemDefinitions);
|
||||||
|
|
||||||
seedingService.InitializeStationStockpiles(stations, catalog.ModuleDefinitions);
|
seedingService.InitializeStationStockpiles(stations);
|
||||||
var refinery = seedingService.SelectRefineryStation(stations, scenario);
|
var refinery = seedingService.SelectRefineryStation(stations, scenario);
|
||||||
var patrolRoutes = BuildPatrolRoutes(scenario, systemsById);
|
var patrolRoutes = BuildPatrolRoutes(scenario, systemsById);
|
||||||
var ships = CreateShips(scenario, systemsById, spatialLayout.Celestials, catalog.Balance, catalog.ShipDefinitions, patrolRoutes, stations, refinery);
|
var ships = CreateShips(scenario, systemsById, spatialLayout.Celestials, catalog.Balance, catalog.ShipDefinitions, patrolRoutes, stations, refinery);
|
||||||
@@ -218,16 +218,12 @@ internal sealed class WorldBuilder(
|
|||||||
continue;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void EnsureStartingModule(List<string> modules, string moduleId)
|
private static void EnsureStartingModule(List<string> modules, string moduleId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using SpaceGame.Api.Shared.Runtime;
|
|
||||||
using static SpaceGame.Api.Universe.Scenario.LoaderSupport;
|
using static SpaceGame.Api.Universe.Scenario.LoaderSupport;
|
||||||
|
|
||||||
namespace SpaceGame.Api.Universe.Scenario;
|
namespace SpaceGame.Api.Universe.Scenario;
|
||||||
@@ -61,13 +60,11 @@ internal sealed class WorldSeedingService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void InitializeStationStockpiles(
|
internal void InitializeStationStockpiles(IReadOnlyCollection<StationRuntime> stations)
|
||||||
IReadOnlyCollection<StationRuntime> stations,
|
|
||||||
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
|
|
||||||
{
|
{
|
||||||
foreach (var station in stations)
|
foreach (var station in stations)
|
||||||
{
|
{
|
||||||
InitializeStationPopulation(station, moduleDefinitions);
|
InitializeStationPopulation(station);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,12 +241,10 @@ internal sealed class WorldSeedingService
|
|||||||
continue;
|
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",
|
yield return storageModuleId;
|
||||||
"liquid" => "module_arg_stor_liquid_m_01",
|
}
|
||||||
_ => "module_arg_stor_container_m_01",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,11 +549,9 @@ internal sealed class WorldSeedingService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InitializeStationPopulation(
|
private static void InitializeStationPopulation(StationRuntime station)
|
||||||
StationRuntime station,
|
|
||||||
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
|
|
||||||
{
|
{
|
||||||
var habitatModules = CountModules(station.InstalledModules, moduleDefinitions, ModuleType.Habitation);
|
var habitatModules = station.Modules.Count(module => module.ModuleType == ModuleType.Habitation);
|
||||||
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
||||||
station.WorkforceRequired = MathF.Max(12f, station.Modules.Count * 14f);
|
station.WorkforceRequired = MathF.Max(12f, station.Modules.Count * 14f);
|
||||||
station.Population = habitatModules > 0
|
station.Population = habitatModules > 0
|
||||||
|
|||||||
Reference in New Issue
Block a user