Compare commits
2 Commits
00a1e58184
...
e8fb033a01
| Author | SHA1 | Date | |
|---|---|---|---|
| e8fb033a01 | |||
| f961ac62b6 |
@@ -1,4 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using SpaceGame.Api.Shared.Runtime;
|
||||
|
||||
namespace SpaceGame.Api.Definitions;
|
||||
|
||||
@@ -114,6 +116,8 @@ public sealed class ItemDefinition
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = "material";
|
||||
public string CargoKind { get; set; } = string.Empty;
|
||||
[JsonIgnore]
|
||||
public StorageKind? CargoStorageKind { get; set; }
|
||||
public float Volume { get; set; } = 1f;
|
||||
public int Version { get; set; }
|
||||
public string FactoryName { get; set; } = string.Empty;
|
||||
@@ -189,13 +193,48 @@ public sealed class ModuleProductionDefinition
|
||||
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 Name { get; set; }
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public required string Type { get; set; }
|
||||
[JsonIgnore]
|
||||
public ModuleType ModuleType { get; set; }
|
||||
[JsonIgnore]
|
||||
public string? Product { get; set; }
|
||||
public List<string> Products { get; set; } = [];
|
||||
public string ProductionMode { get; set; } = "passive";
|
||||
@@ -223,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 required string ModuleId { get; set; }
|
||||
@@ -274,6 +339,8 @@ public sealed class ShipDefinition
|
||||
public float SpoolTime { get; set; }
|
||||
public float CargoCapacity { get; set; }
|
||||
public string? CargoKind { get; set; }
|
||||
[JsonIgnore]
|
||||
public StorageKind? CargoStorageKind { get; set; }
|
||||
public required string Color { get; set; }
|
||||
public required string HullColor { get; set; }
|
||||
public float Size { get; set; }
|
||||
|
||||
@@ -94,12 +94,33 @@ public enum SpaceLayerKind
|
||||
LocalSpace,
|
||||
}
|
||||
|
||||
public static class MovementRegimeKinds
|
||||
public enum MovementRegimeKind
|
||||
{
|
||||
public const string LocalFlight = "local-flight";
|
||||
public const string Warp = "warp";
|
||||
public const string StargateTransit = "stargate-transit";
|
||||
public const string FtlTransit = "ftl-transit";
|
||||
LocalFlight,
|
||||
Warp,
|
||||
StargateTransit,
|
||||
FtlTransit,
|
||||
}
|
||||
|
||||
public enum ModuleType
|
||||
{
|
||||
BuildModule,
|
||||
ConnectionModule,
|
||||
DefenceModule,
|
||||
DockArea,
|
||||
Habitation,
|
||||
Pier,
|
||||
ProcessingModule,
|
||||
Production,
|
||||
Storage,
|
||||
}
|
||||
|
||||
public enum StorageKind
|
||||
{
|
||||
Condensate,
|
||||
Container,
|
||||
Liquid,
|
||||
Solid,
|
||||
}
|
||||
|
||||
public static class CommanderKind
|
||||
@@ -182,6 +203,68 @@ public static class MarketOrderStateKinds
|
||||
|
||||
public static class SimulationEnumMappings
|
||||
{
|
||||
public static string ToDataValue(this ModuleType moduleType) => moduleType switch
|
||||
{
|
||||
ModuleType.BuildModule => "buildmodule",
|
||||
ModuleType.ConnectionModule => "connectionmodule",
|
||||
ModuleType.DefenceModule => "defencemodule",
|
||||
ModuleType.DockArea => "dockarea",
|
||||
ModuleType.Habitation => "habitation",
|
||||
ModuleType.Pier => "pier",
|
||||
ModuleType.ProcessingModule => "processingmodule",
|
||||
ModuleType.Production => "production",
|
||||
ModuleType.Storage => "storage",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(moduleType), moduleType, null),
|
||||
};
|
||||
|
||||
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,
|
||||
"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",
|
||||
@@ -283,4 +366,13 @@ public static class SimulationEnumMappings
|
||||
SpaceLayerKind.LocalSpace => "local-space",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
|
||||
};
|
||||
|
||||
public static string ToContractValue(this MovementRegimeKind kind) => kind switch
|
||||
{
|
||||
MovementRegimeKind.LocalFlight => "local-flight",
|
||||
MovementRegimeKind.Warp => "warp",
|
||||
MovementRegimeKind.StargateTransit => "stargate-transit",
|
||||
MovementRegimeKind.FtlTransit => "ftl-transit",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,8 +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 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)
|
||||
{
|
||||
@@ -16,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);
|
||||
}
|
||||
|
||||
@@ -34,30 +75,6 @@ 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));
|
||||
|
||||
@@ -97,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);
|
||||
@@ -113,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)
|
||||
{
|
||||
@@ -128,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)
|
||||
|
||||
@@ -1647,7 +1647,7 @@ internal sealed class ShipAiService
|
||||
{
|
||||
var distance = ship.Position.DistanceTo(targetPosition);
|
||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
||||
ship.SpatialState.Transit = null;
|
||||
ship.SpatialState.DestinationNodeId = targetCelestial?.Id;
|
||||
subTask.Progress = Math.Clamp(1f - (distance / MathF.Max(distance + GetLocalTravelSpeed(ship), 1f)), 0f, 1f);
|
||||
@@ -1678,11 +1678,11 @@ internal sealed class ShipAiService
|
||||
bool completeOnArrival)
|
||||
{
|
||||
var transit = ship.SpatialState.Transit;
|
||||
if (transit is null || transit.Regime != MovementRegimeKinds.Warp || transit.DestinationNodeId != targetCelestial.Id)
|
||||
if (transit is null || transit.Regime != MovementRegimeKind.Warp || transit.DestinationNodeId != targetCelestial.Id)
|
||||
{
|
||||
transit = new ShipTransitRuntime
|
||||
{
|
||||
Regime = MovementRegimeKinds.Warp,
|
||||
Regime = MovementRegimeKind.Warp,
|
||||
OriginNodeId = ship.SpatialState.CurrentCelestialId,
|
||||
DestinationNodeId = targetCelestial.Id,
|
||||
StartedAtUtc = world.GeneratedAtUtc,
|
||||
@@ -1692,7 +1692,7 @@ internal sealed class ShipAiService
|
||||
}
|
||||
|
||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.SystemSpace;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKinds.Warp;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKind.Warp;
|
||||
ship.SpatialState.CurrentCelestialId = null;
|
||||
ship.SpatialState.DestinationNodeId = targetCelestial.Id;
|
||||
|
||||
@@ -1735,11 +1735,11 @@ internal sealed class ShipAiService
|
||||
{
|
||||
var destinationNodeId = targetCelestial?.Id;
|
||||
var transit = ship.SpatialState.Transit;
|
||||
if (transit is null || transit.Regime != MovementRegimeKinds.FtlTransit || transit.DestinationNodeId != destinationNodeId)
|
||||
if (transit is null || transit.Regime != MovementRegimeKind.FtlTransit || transit.DestinationNodeId != destinationNodeId)
|
||||
{
|
||||
transit = new ShipTransitRuntime
|
||||
{
|
||||
Regime = MovementRegimeKinds.FtlTransit,
|
||||
Regime = MovementRegimeKind.FtlTransit,
|
||||
OriginNodeId = ship.SpatialState.CurrentCelestialId,
|
||||
DestinationNodeId = destinationNodeId,
|
||||
StartedAtUtc = world.GeneratedAtUtc,
|
||||
@@ -1749,7 +1749,7 @@ internal sealed class ShipAiService
|
||||
}
|
||||
|
||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.GalaxySpace;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKinds.FtlTransit;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKind.FtlTransit;
|
||||
ship.SpatialState.CurrentCelestialId = null;
|
||||
ship.SpatialState.DestinationNodeId = destinationNodeId;
|
||||
|
||||
@@ -1780,7 +1780,7 @@ internal sealed class ShipAiService
|
||||
ship.SpatialState.CurrentSystemId = targetSystemId;
|
||||
ship.SpatialState.Transit = null;
|
||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
||||
ship.SpatialState.CurrentCelestialId = targetCelestial?.Id;
|
||||
ship.SpatialState.DestinationNodeId = targetCelestial?.Id;
|
||||
ship.State = ShipState.Arriving;
|
||||
@@ -1795,7 +1795,7 @@ internal sealed class ShipAiService
|
||||
ship.SpatialState.CurrentSystemId = targetSystemId;
|
||||
ship.SpatialState.Transit = null;
|
||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
||||
ship.SpatialState.CurrentCelestialId = targetCelestial?.Id;
|
||||
ship.SpatialState.DestinationNodeId = targetCelestial?.Id;
|
||||
ship.State = ShipState.Arriving;
|
||||
@@ -2619,15 +2619,11 @@ internal sealed class ShipAiService
|
||||
{
|
||||
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))
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -580,9 +580,9 @@ internal sealed class SimulationProjectionService
|
||||
ship.PolicySetId ?? "none",
|
||||
ship.SpatialState.SpaceLayer.ToContractValue(),
|
||||
ship.SpatialState.CurrentCelestialId ?? "none",
|
||||
ship.SpatialState.MovementRegime,
|
||||
ship.SpatialState.MovementRegime.ToContractValue(),
|
||||
ship.SpatialState.DestinationNodeId ?? "none",
|
||||
ship.SpatialState.Transit?.Regime ?? "none",
|
||||
ship.SpatialState.Transit?.Regime.ToContractValue() ?? "none",
|
||||
ship.SpatialState.Transit?.OriginNodeId ?? "none",
|
||||
ship.SpatialState.Transit?.DestinationNodeId ?? "none",
|
||||
ship.SpatialState.Transit?.Progress.ToString("0.###") ?? "0",
|
||||
@@ -796,14 +796,14 @@ internal sealed class SimulationProjectionService
|
||||
|
||||
private static IReadOnlyList<StationStorageUsageSnapshot> ToStationStorageUsageSnapshots(SimulationWorld world, StationRuntime station)
|
||||
{
|
||||
string[] storageClasses = ["solid", "liquid", "container", "manufactured"];
|
||||
return storageClasses
|
||||
.Select(storageClass => new StationStorageUsageSnapshot(
|
||||
storageClass,
|
||||
StorageKind[] storageKinds = [StorageKind.Solid, StorageKind.Liquid, StorageKind.Container];
|
||||
return storageKinds
|
||||
.Select(storageKind => new StationStorageUsageSnapshot(
|
||||
storageKind.ToDataValue(),
|
||||
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),
|
||||
GetStationStorageCapacity(station, storageClass)))
|
||||
GetStationStorageCapacity(world, station, storageKind)))
|
||||
.Where(snapshot => snapshot.Capacity > 0.01f)
|
||||
.ToList();
|
||||
}
|
||||
@@ -921,8 +921,8 @@ internal sealed class SimulationProjectionService
|
||||
{
|
||||
return ship.SpatialState.MovementRegime switch
|
||||
{
|
||||
MovementRegimeKinds.FtlTransit => (ship.State == ShipState.Ftl ? ship.Definition.FtlSpeed : 0f, "ly/s"),
|
||||
MovementRegimeKinds.Warp => (ship.State == ShipState.Warping ? ship.Definition.WarpSpeed : 0f, "AU/s"),
|
||||
MovementRegimeKind.FtlTransit => (ship.State == ShipState.Ftl ? ship.Definition.FtlSpeed : 0f, "ly/s"),
|
||||
MovementRegimeKind.Warp => (ship.State == ShipState.Warping ? ship.Definition.WarpSpeed : 0f, "AU/s"),
|
||||
_ => (MathF.Sqrt(MathF.Max(0f, ship.Velocity.LengthSquared())) * SimulationUnits.MetersPerKilometer, "m/s"),
|
||||
};
|
||||
}
|
||||
@@ -1877,10 +1877,10 @@ internal sealed class SimulationProjectionService
|
||||
state.CurrentCelestialId,
|
||||
state.LocalPosition is null ? null : ToDto(state.LocalPosition.Value),
|
||||
state.SystemPosition is null ? null : ToDto(state.SystemPosition.Value),
|
||||
state.MovementRegime,
|
||||
state.MovementRegime.ToContractValue(),
|
||||
state.DestinationNodeId,
|
||||
state.Transit is null ? null : new ShipTransitSnapshot(
|
||||
state.Transit.Regime,
|
||||
state.Transit.Regime.ToContractValue(),
|
||||
state.Transit.OriginNodeId,
|
||||
state.Transit.DestinationNodeId,
|
||||
state.Transit.StartedAtUtc,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using SpaceGame.Api.Definitions;
|
||||
using SpaceGame.Api.Shared.Runtime;
|
||||
|
||||
namespace SpaceGame.Api.Stations.Runtime;
|
||||
|
||||
public sealed class StationRuntime
|
||||
@@ -33,12 +36,55 @@ public sealed class StationRuntime
|
||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class StationModuleRuntime
|
||||
public class StationModuleRuntime
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string ModuleId { get; init; }
|
||||
public required ModuleType ModuleType { get; init; }
|
||||
public float Health { 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
|
||||
|
||||
@@ -162,21 +162,21 @@ internal sealed class InfrastructureSimulationService
|
||||
|
||||
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"),
|
||||
("liquid", "module_arg_stor_liquid_m_01"),
|
||||
("container", "module_arg_stor_container_m_01"),
|
||||
(StorageKind.Solid, "module_arg_stor_solid_m_01"),
|
||||
(StorageKind.Liquid, "module_arg_stor_liquid_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)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
if (used / capacity >= 0.65f)
|
||||
{
|
||||
@@ -195,14 +195,10 @@ internal sealed class InfrastructureSimulationService
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetStorageRequirement(itemDefinition.CargoKind) is { } storageModuleId)
|
||||
if (GetStorageRequirement(world.ModuleDefinitions, itemDefinition.CargoStorageKind) is { } storageModuleId)
|
||||
{
|
||||
yield return storageModuleId;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return "module_arg_stor_container_m_01";
|
||||
}
|
||||
}
|
||||
|
||||
if (world.ModuleDefinitions.TryGetValue(recipe.ModuleId, out var moduleDefinition))
|
||||
@@ -214,14 +210,10 @@ internal sealed class InfrastructureSimulationService
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetStorageRequirement(itemDefinition.CargoKind) is { } storageModuleId)
|
||||
if (GetStorageRequirement(world.ModuleDefinitions, itemDefinition.CargoStorageKind) is { } storageModuleId)
|
||||
{
|
||||
yield return storageModuleId;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return "module_arg_stor_container_m_01";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,16 +316,16 @@ internal sealed class InfrastructureSimulationService
|
||||
string? objectiveCommodityId,
|
||||
bool requiredByObjective)
|
||||
{
|
||||
var storageClass = storageModuleId switch
|
||||
var storageKind = storageModuleId switch
|
||||
{
|
||||
"module_arg_stor_solid_m_01" => "solid",
|
||||
"module_arg_stor_liquid_m_01" => "liquid",
|
||||
_ => "container",
|
||||
"module_arg_stor_solid_m_01" => StorageKind.Solid,
|
||||
"module_arg_stor_liquid_m_01" => StorageKind.Liquid,
|
||||
_ => StorageKind.Container,
|
||||
};
|
||||
|
||||
var capacity = GetStationStorageCapacity(station, storageClass);
|
||||
var capacity = GetStationStorageCapacity(world, station, storageKind);
|
||||
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);
|
||||
var utilization = capacity <= 0.01f ? 0f : used / capacity;
|
||||
|
||||
@@ -342,8 +334,8 @@ internal sealed class InfrastructureSimulationService
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(objectiveModuleId) && !string.IsNullOrWhiteSpace(objectiveCommodityId))
|
||||
{
|
||||
var objectiveUsesStorage = ModuleNeedsStorageClass(world, objectiveModuleId, storageClass)
|
||||
|| CommodityUsesStorageClass(world, objectiveCommodityId, storageClass);
|
||||
var objectiveUsesStorage = ModuleNeedsStorageClass(world, objectiveModuleId, storageKind)
|
||||
|| CommodityUsesStorageClass(world, objectiveCommodityId, storageKind);
|
||||
if (objectiveUsesStorage)
|
||||
{
|
||||
score += 35f;
|
||||
@@ -579,15 +571,15 @@ internal sealed class InfrastructureSimulationService
|
||||
case "module_arg_stor_container_m_01":
|
||||
case "module_arg_stor_solid_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_liquid_m_01" => "liquid",
|
||||
_ => "container",
|
||||
"module_arg_stor_solid_m_01" => StorageKind.Solid,
|
||||
"module_arg_stor_liquid_m_01" => StorageKind.Liquid,
|
||||
_ => StorageKind.Container,
|
||||
};
|
||||
if (analysis.HasMissingOutputStorage
|
||||
&& (ModuleNeedsStorageClass(world, objectiveModuleId, storageClass)
|
||||
|| CommodityUsesStorageClass(world, objectiveCommodityId, storageClass)))
|
||||
&& (ModuleNeedsStorageClass(world, objectiveModuleId, storageKind)
|
||||
|| CommodityUsesStorageClass(world, objectiveCommodityId, storageKind)))
|
||||
{
|
||||
unlockScore += 70f;
|
||||
}
|
||||
@@ -688,7 +680,7 @@ internal sealed class InfrastructureSimulationService
|
||||
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))
|
||||
{
|
||||
@@ -697,12 +689,12 @@ internal sealed class InfrastructureSimulationService
|
||||
|
||||
return recipe.Inputs.Any(input =>
|
||||
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)
|
||||
&& string.Equals(itemDefinition.CargoKind, storageClass, StringComparison.Ordinal);
|
||||
&& itemDefinition.CargoStorageKind == storageKind;
|
||||
|
||||
private static bool CanStationAcceptStationOutputSoon(SimulationWorld world, StationRuntime station, string itemId, float amount)
|
||||
{
|
||||
@@ -711,14 +703,19 @@ internal sealed class InfrastructureSimulationService
|
||||
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)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
return used + amount <= capacity * 0.95f;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using SpaceGame.Api.Shared.Runtime;
|
||||
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
|
||||
|
||||
namespace SpaceGame.Api.Stations.Simulation;
|
||||
@@ -19,7 +20,7 @@ internal sealed class StationLifecycleService
|
||||
var factionPopulation = new Dictionary<string, float>(StringComparer.Ordinal);
|
||||
foreach (var station in world.Stations)
|
||||
{
|
||||
UpdateStationPopulation(station, deltaSeconds, events);
|
||||
UpdateStationPopulation(world, station, deltaSeconds, events);
|
||||
_stationSimulation.ReviewStationMarketOrders(world, station);
|
||||
_stationSimulation.RunStationProduction(world, station, deltaSeconds, events);
|
||||
factionPopulation[station.FactionId] = GetInventoryAmount(factionPopulation, station.FactionId) + station.Population;
|
||||
@@ -31,14 +32,14 @@ internal sealed class StationLifecycleService
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStationPopulation(StationRuntime station, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
private void UpdateStationPopulation(SimulationWorld world, StationRuntime station, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||
{
|
||||
station.WorkforceRequired = MathF.Max(12f, station.Modules.Count * 14f);
|
||||
|
||||
var requiredWater = station.Population * WaterConsumptionPerWorkerPerSecond * deltaSeconds;
|
||||
var consumedWater = RemoveInventory(station.Inventory, "water", requiredWater);
|
||||
var waterSatisfied = requiredWater <= 0.01f || consumedWater + 0.001f >= requiredWater;
|
||||
var habitatModules = CountModules(station.InstalledModules, "module_arg_hab_m_01");
|
||||
var habitatModules = CountStationModules(station, ModuleType.Habitation);
|
||||
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
||||
|
||||
if (waterSatisfied)
|
||||
@@ -101,7 +102,7 @@ internal sealed class StationLifecycleService
|
||||
CurrentCelestialId = station.CelestialId,
|
||||
LocalPosition = position,
|
||||
SystemPosition = position,
|
||||
MovementRegime = MovementRegimeKinds.LocalFlight,
|
||||
MovementRegime = MovementRegimeKind.LocalFlight,
|
||||
};
|
||||
|
||||
private static DefaultBehaviorRuntime CreateSpawnedShipBehavior(ShipDefinition definition, StationRuntime station)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using static SpaceGame.Api.Factions.AI.CommanderPlanningService;
|
||||
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
|
||||
using SpaceGame.Api.Shared.Runtime;
|
||||
|
||||
namespace SpaceGame.Api.Stations.Simulation;
|
||||
|
||||
@@ -54,14 +55,14 @@ internal sealed class StationSimulationService
|
||||
_ => 0f,
|
||||
};
|
||||
var oreReserve = role == "refinery" ? 260f : 0f;
|
||||
var hullpartsReserve = MathF.Max(constructionHullpartsReserve, HasStationModules(station, "module_gen_build_l_01") ? 120f : 0f);
|
||||
var claytronicsReserve = MathF.Max(constructionClayReserve, HasStationModules(station, "module_gen_build_l_01") ? 120f : 0f);
|
||||
var hullpartsReserve = MathF.Max(constructionHullpartsReserve, HasShipyardCapability(station) ? 120f : 0f);
|
||||
var claytronicsReserve = MathF.Max(constructionClayReserve, HasShipyardCapability(station) ? 120f : 0f);
|
||||
var grapheneReserve = role == "graphene" ? 120f : 0f;
|
||||
var siliconWafersReserve = role == "siliconwafers" ? 120f : 0f;
|
||||
var antimatterCellsReserve = role == "antimattercells" ? 120f : 0f;
|
||||
var superfluidCoolantReserve = role == "superfluidcoolant" ? 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
|
||||
? 90f
|
||||
: 0f;
|
||||
@@ -116,7 +117,7 @@ internal sealed class StationSimulationService
|
||||
var constructionHullpartsReserve = GetConstructionDemandForItem(world, site, "hullparts");
|
||||
var constructionClayReserve = GetConstructionDemandForItem(world, site, "claytronics");
|
||||
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
|
||||
? 90f
|
||||
: 0f;
|
||||
@@ -151,8 +152,8 @@ internal sealed class StationSimulationService
|
||||
"refinery" => 80f,
|
||||
_ => 0f,
|
||||
}, constructionRefinedReserve),
|
||||
"hullparts" => MathF.Max(constructionHullpartsReserve, HasStationModules(station, "module_gen_build_l_01") ? 120f : 0f) + shipPartsReserve,
|
||||
"claytronics" => MathF.Max(constructionClayReserve, HasStationModules(station, "module_gen_build_l_01") ? 120f : 0f),
|
||||
"hullparts" => MathF.Max(constructionHullpartsReserve, HasShipyardCapability(station) ? 120f : 0f) + shipPartsReserve,
|
||||
"claytronics" => MathF.Max(constructionClayReserve, HasShipyardCapability(station) ? 120f : 0f),
|
||||
"graphene" => MathF.Max(role == "graphene" ? 120f : 0f, role == "quantumtubes" ? 160f : 0f),
|
||||
"siliconwafers" => role == "siliconwafers" ? 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"))
|
||||
{
|
||||
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)
|
||||
: 280f * MathF.Max(expansionPressure, fleetPressure);
|
||||
}
|
||||
@@ -407,20 +408,25 @@ internal sealed class StationSimulationService
|
||||
return false;
|
||||
}
|
||||
|
||||
var requiredModule = GetStorageRequirement(itemDefinition.CargoKind);
|
||||
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
||||
var storageKind = itemDefinition.CargoStorageKind;
|
||||
if (storageKind is null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
return used + amount <= capacity + 0.001f;
|
||||
}
|
||||
@@ -455,7 +461,7 @@ internal sealed class StationSimulationService
|
||||
return objective;
|
||||
}
|
||||
|
||||
if (HasStationModules(station, "module_gen_build_l_01"))
|
||||
if (HasShipyardCapability(station))
|
||||
{
|
||||
return "shipyard";
|
||||
}
|
||||
@@ -513,6 +519,9 @@ internal sealed class StationSimulationService
|
||||
return "general";
|
||||
}
|
||||
|
||||
private static bool HasShipyardCapability(StationRuntime station) =>
|
||||
CountStationModules(station, ModuleType.BuildModule) > 0;
|
||||
|
||||
private static float GetConstructionDemandForItem(SimulationWorld world, ConstructionSiteRuntime? site, string itemId)
|
||||
{
|
||||
if (site is null || !site.RequiredItems.TryGetValue(itemId, out var required))
|
||||
|
||||
@@ -55,14 +55,14 @@ public sealed class ShipSpatialStateRuntime
|
||||
public string? CurrentCelestialId { get; set; }
|
||||
public Vector3? LocalPosition { get; set; }
|
||||
public Vector3? SystemPosition { get; set; }
|
||||
public string MovementRegime { get; set; } = MovementRegimeKinds.LocalFlight;
|
||||
public MovementRegimeKind MovementRegime { get; set; } = MovementRegimeKind.LocalFlight;
|
||||
public string? DestinationNodeId { get; set; }
|
||||
public ShipTransitRuntime? Transit { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ShipTransitRuntime
|
||||
{
|
||||
public required string Regime { get; init; }
|
||||
public required MovementRegimeKind Regime { get; init; }
|
||||
public string? OriginNodeId { get; init; }
|
||||
public string? DestinationNodeId { get; init; }
|
||||
public DateTimeOffset? StartedAtUtc { get; set; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using SpaceGame.Api.Shared.Runtime;
|
||||
using static SpaceGame.Api.Universe.Scenario.LoaderSupport;
|
||||
|
||||
namespace SpaceGame.Api.Universe.Scenario;
|
||||
@@ -15,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);
|
||||
@@ -262,15 +263,68 @@ 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();
|
||||
}
|
||||
catch (ArgumentOutOfRangeException exception)
|
||||
{
|
||||
throw new InvalidOperationException($"Module '{module.Id}' has unsupported type '{module.Type}'.", exception);
|
||||
}
|
||||
|
||||
module.Type = module.ModuleType.ToDataValue();
|
||||
|
||||
if (module.Products.Count == 0 && !string.IsNullOrWhiteSpace(module.Product))
|
||||
{
|
||||
module.Products = [module.Product];
|
||||
@@ -278,7 +332,7 @@ internal sealed class DataCatalogLoader(string dataRoot)
|
||||
|
||||
if (string.IsNullOrWhiteSpace(module.ProductionMode))
|
||||
{
|
||||
module.ProductionMode = string.Equals(module.Type, "buildmodule", StringComparison.Ordinal)
|
||||
module.ProductionMode = module.ModuleType == ModuleType.BuildModule
|
||||
? "commanded"
|
||||
: "passive";
|
||||
}
|
||||
@@ -287,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(
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
|
||||
using SpaceGame.Api.Shared.Runtime;
|
||||
|
||||
namespace SpaceGame.Api.Universe.Scenario;
|
||||
|
||||
internal static class LoaderSupport
|
||||
@@ -99,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -300,7 +300,7 @@ internal sealed class SpatialBuilder
|
||||
CurrentCelestialId = nearestCelestial?.Id,
|
||||
LocalPosition = position,
|
||||
SystemPosition = position,
|
||||
MovementRegime = MovementRegimeKinds.LocalFlight,
|
||||
MovementRegime = MovementRegimeKind.LocalFlight,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -241,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,7 +551,7 @@ internal sealed class WorldSeedingService
|
||||
|
||||
private static void InitializeStationPopulation(StationRuntime station)
|
||||
{
|
||||
var habitatModules = CountModules(station.InstalledModules, "module_arg_hab_m_01");
|
||||
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
|
||||
|
||||
@@ -269,7 +269,7 @@ internal sealed class OrbitalStateUpdater
|
||||
}
|
||||
|
||||
ship.SpatialState.SpaceLayer = SpaceLayerKind.LocalSpace;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKinds.LocalFlight;
|
||||
ship.SpatialState.MovementRegime = MovementRegimeKind.LocalFlight;
|
||||
var nearestCelestial = world.Celestials
|
||||
.Where(candidate => candidate.SystemId == ship.SystemId)
|
||||
.OrderBy(candidate => candidate.Position.DistanceTo(ship.Position))
|
||||
|
||||
Reference in New Issue
Block a user