Compare commits
2 Commits
768d705fb7
...
766fef1c8f
| Author | SHA1 | Date | |
|---|---|---|---|
| 766fef1c8f | |||
| cfee1306de |
15
.idea/.idea.SpaceGame/.idea/.gitignore
generated
vendored
Normal file
15
.idea/.idea.SpaceGame/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/contentModel.xml
|
||||||
|
/modules.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/.idea.SpaceGame.iml
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
1
.idea/.idea.SpaceGame/.idea/.name
generated
Normal file
1
.idea/.idea.SpaceGame/.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
SpaceGame
|
||||||
4
.idea/.idea.SpaceGame/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.SpaceGame/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
8
.idea/.idea.SpaceGame/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.SpaceGame/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/.idea.SpaceGame/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.SpaceGame/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
54
apps/backend/.editorconfig
Normal file
54
apps/backend/.editorconfig
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*.{cs,csx}]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# .NET/C# conventions
|
||||||
|
dotnet_sort_system_directives_first = true
|
||||||
|
dotnet_separate_import_directive_groups = false
|
||||||
|
dotnet_style_qualification_for_field = false:suggestion
|
||||||
|
dotnet_style_qualification_for_property = false:suggestion
|
||||||
|
dotnet_style_qualification_for_method = false:suggestion
|
||||||
|
dotnet_style_qualification_for_event = false:suggestion
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||||
|
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||||
|
dotnet_style_object_initializer = true:suggestion
|
||||||
|
dotnet_style_collection_initializer = true:suggestion
|
||||||
|
dotnet_style_coalesce_expression = true:suggestion
|
||||||
|
dotnet_style_null_propagation = true:suggestion
|
||||||
|
dotnet_style_explicit_tuple_names = true:suggestion
|
||||||
|
dotnet_style_prefer_auto_properties = true:suggestion
|
||||||
|
dotnet_style_prefer_collection_expression = true:suggestion
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
|
||||||
|
|
||||||
|
csharp_prefer_braces = true:suggestion
|
||||||
|
csharp_style_var_for_built_in_types = false:suggestion
|
||||||
|
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||||
|
csharp_style_var_elsewhere = false:suggestion
|
||||||
|
csharp_prefer_simple_using_statement = true:suggestion
|
||||||
|
csharp_style_prefer_switch_expression = true:suggestion
|
||||||
|
csharp_style_prefer_primary_constructors = false:silent
|
||||||
|
csharp_new_line_before_open_brace = all
|
||||||
|
|
||||||
|
[*.{csproj,props,targets,sln,slnx}]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.{json,jsonc}]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
@@ -4,323 +4,323 @@ namespace SpaceGame.Api.Definitions;
|
|||||||
|
|
||||||
public sealed class ConstructionDefinition
|
public sealed class ConstructionDefinition
|
||||||
{
|
{
|
||||||
public string? RecipeId { get; set; }
|
public string? RecipeId { get; set; }
|
||||||
public string FacilityCategory { get; set; } = "station";
|
public string FacilityCategory { get; set; } = "station";
|
||||||
public List<string> RequiredModules { get; set; } = [];
|
public List<string> RequiredModules { get; set; } = [];
|
||||||
public List<RecipeInputDefinition> Requirements { get; set; } = [];
|
public List<RecipeInputDefinition> Requirements { get; set; } = [];
|
||||||
public float CycleTime { get; set; }
|
public float CycleTime { get; set; }
|
||||||
public float BatchSize { get; set; } = 1f;
|
public float BatchSize { get; set; } = 1f;
|
||||||
public float ProductsPerHour { get; set; }
|
public float ProductsPerHour { get; set; }
|
||||||
public float MaxEfficiency { get; set; } = 1f;
|
public float MaxEfficiency { get; set; } = 1f;
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ItemPriceDefinition
|
public sealed class ItemPriceDefinition
|
||||||
{
|
{
|
||||||
public float Min { get; set; }
|
public float Min { get; set; }
|
||||||
public float Max { get; set; }
|
public float Max { get; set; }
|
||||||
public float Avg { get; set; }
|
public float Avg { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ItemEffectDefinition
|
public sealed class ItemEffectDefinition
|
||||||
{
|
{
|
||||||
public required string Type { get; set; }
|
public required string Type { get; set; }
|
||||||
public float Product { get; set; }
|
public float Product { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ItemProductionDefinition
|
public sealed class ItemProductionDefinition
|
||||||
{
|
{
|
||||||
public float Time { get; set; }
|
public float Time { get; set; }
|
||||||
public float Amount { get; set; }
|
public float Amount { get; set; }
|
||||||
public string Method { get; set; } = "default";
|
public string Method { get; set; } = "default";
|
||||||
public string Name { get; set; } = "Universal";
|
public string Name { get; set; } = "Universal";
|
||||||
public List<RecipeInputDefinition> Wares { get; set; } = [];
|
public List<RecipeInputDefinition> Wares { get; set; } = [];
|
||||||
public List<ItemEffectDefinition> Effects { get; set; } = [];
|
public List<ItemEffectDefinition> Effects { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class BalanceDefinition
|
public sealed class BalanceDefinition
|
||||||
{
|
{
|
||||||
public float SimulationSpeedMultiplier { get; set; } = 1f;
|
public float SimulationSpeedMultiplier { get; set; } = 1f;
|
||||||
public float YPlane { get; set; }
|
public float YPlane { get; set; }
|
||||||
public float ArrivalThreshold { get; set; }
|
public float ArrivalThreshold { get; set; }
|
||||||
public float MiningRate { get; set; }
|
public float MiningRate { get; set; }
|
||||||
public float MiningCycleSeconds { get; set; }
|
public float MiningCycleSeconds { get; set; }
|
||||||
public float TransferRate { get; set; }
|
public float TransferRate { get; set; }
|
||||||
public float DockingDuration { get; set; }
|
public float DockingDuration { get; set; }
|
||||||
public float UndockingDuration { get; set; }
|
public float UndockingDuration { get; set; }
|
||||||
public float UndockDistance { get; set; }
|
public float UndockDistance { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class StarDefinition
|
public sealed class StarDefinition
|
||||||
{
|
{
|
||||||
public string Kind { get; set; } = "main-sequence";
|
public string Kind { get; set; } = "main-sequence";
|
||||||
public required string Color { get; set; }
|
public required string Color { get; set; }
|
||||||
public required string Glow { get; set; }
|
public required string Glow { get; set; }
|
||||||
public float Size { get; set; }
|
public float Size { get; set; }
|
||||||
public float OrbitRadius { get; set; }
|
public float OrbitRadius { get; set; }
|
||||||
public float OrbitSpeed { get; set; }
|
public float OrbitSpeed { get; set; }
|
||||||
public float OrbitPhaseAtEpoch { get; set; }
|
public float OrbitPhaseAtEpoch { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MoonDefinition
|
public sealed class MoonDefinition
|
||||||
{
|
{
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public float Size { get; set; }
|
public float Size { get; set; }
|
||||||
public required string Color { get; set; }
|
public required string Color { get; set; }
|
||||||
public float OrbitRadius { get; set; }
|
public float OrbitRadius { get; set; }
|
||||||
public float OrbitSpeed { get; set; }
|
public float OrbitSpeed { get; set; }
|
||||||
public float OrbitPhaseAtEpoch { get; set; }
|
public float OrbitPhaseAtEpoch { get; set; }
|
||||||
public float OrbitInclination { get; set; }
|
public float OrbitInclination { get; set; }
|
||||||
public float OrbitLongitudeOfAscendingNode { get; set; }
|
public float OrbitLongitudeOfAscendingNode { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class SolarSystemDefinition
|
public sealed class SolarSystemDefinition
|
||||||
{
|
{
|
||||||
public required string Id { get; set; }
|
public required string Id { get; set; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public required float[] Position { get; set; }
|
public required float[] Position { get; set; }
|
||||||
public required List<StarDefinition> Stars { get; set; }
|
public required List<StarDefinition> Stars { get; set; }
|
||||||
public required AsteroidFieldDefinition AsteroidField { get; set; }
|
public required AsteroidFieldDefinition AsteroidField { get; set; }
|
||||||
public required List<ResourceNodeDefinition> ResourceNodes { get; set; }
|
public required List<ResourceNodeDefinition> ResourceNodes { get; set; }
|
||||||
public required List<PlanetDefinition> Planets { get; set; }
|
public required List<PlanetDefinition> Planets { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class AsteroidFieldDefinition
|
public sealed class AsteroidFieldDefinition
|
||||||
{
|
{
|
||||||
public int DecorationCount { get; set; }
|
public int DecorationCount { get; set; }
|
||||||
public float RadiusOffset { get; set; }
|
public float RadiusOffset { get; set; }
|
||||||
public float RadiusVariance { get; set; }
|
public float RadiusVariance { get; set; }
|
||||||
public float HeightVariance { get; set; }
|
public float HeightVariance { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ResourceNodeDefinition
|
public sealed class ResourceNodeDefinition
|
||||||
{
|
{
|
||||||
public string SourceKind { get; set; } = "local-space";
|
public string SourceKind { get; set; } = "local-space";
|
||||||
public string? AnchorReference { get; set; }
|
public string? AnchorReference { get; set; }
|
||||||
public float Angle { get; set; }
|
public float Angle { get; set; }
|
||||||
public float RadiusOffset { get; set; }
|
public float RadiusOffset { get; set; }
|
||||||
public float InclinationDegrees { get; set; }
|
public float InclinationDegrees { get; set; }
|
||||||
public int? AnchorPlanetIndex { get; set; }
|
public int? AnchorPlanetIndex { get; set; }
|
||||||
public int? AnchorMoonIndex { get; set; }
|
public int? AnchorMoonIndex { get; set; }
|
||||||
public float OreAmount { get; set; }
|
public float OreAmount { get; set; }
|
||||||
public required string ItemId { get; set; }
|
public required string ItemId { get; set; }
|
||||||
public int ShardCount { get; set; }
|
public int ShardCount { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ItemDefinition
|
public sealed class ItemDefinition
|
||||||
{
|
{
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
public string Icon { get; set; } = string.Empty;
|
public string Icon { get; set; } = string.Empty;
|
||||||
public string Group { get; set; } = string.Empty;
|
public string Group { get; set; } = string.Empty;
|
||||||
public ItemPriceDefinition? Price { get; set; }
|
public ItemPriceDefinition? Price { get; set; }
|
||||||
public List<string> Illegal { get; set; } = [];
|
public List<string> Illegal { get; set; } = [];
|
||||||
public List<ItemProductionDefinition> Production { get; set; } = [];
|
public List<ItemProductionDefinition> Production { get; set; } = [];
|
||||||
public ConstructionDefinition? Construction { get; set; }
|
public ConstructionDefinition? Construction { get; set; }
|
||||||
[JsonPropertyName("transport")]
|
[JsonPropertyName("transport")]
|
||||||
public string Transport
|
public string Transport
|
||||||
{
|
{
|
||||||
set => CargoKind = value;
|
set => CargoKind = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RecipeOutputDefinition
|
public sealed class RecipeOutputDefinition
|
||||||
{
|
{
|
||||||
public required string ItemId { get; set; }
|
public required string ItemId { get; set; }
|
||||||
public float Amount { get; set; }
|
public float Amount { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RecipeInputDefinition
|
public sealed class RecipeInputDefinition
|
||||||
{
|
{
|
||||||
public string ItemId { get; set; } = string.Empty;
|
public string ItemId { get; set; } = string.Empty;
|
||||||
public float Amount { get; set; }
|
public float Amount { get; set; }
|
||||||
[JsonPropertyName("ware")]
|
[JsonPropertyName("ware")]
|
||||||
public string Ware
|
public string Ware
|
||||||
{
|
{
|
||||||
set => ItemId = value;
|
set => ItemId = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleConstructionDefinition
|
public sealed class ModuleConstructionDefinition
|
||||||
{
|
{
|
||||||
public required List<RecipeInputDefinition> Requirements { get; set; }
|
public required List<RecipeInputDefinition> Requirements { get; set; }
|
||||||
public float ProductionTime { get; set; }
|
public float ProductionTime { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleDockDefinition
|
public sealed class ModuleDockDefinition
|
||||||
{
|
{
|
||||||
public int Capacity { get; set; }
|
public int Capacity { get; set; }
|
||||||
public required string Size { get; set; }
|
public required string Size { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleCargoDefinition
|
public sealed class ModuleCargoDefinition
|
||||||
{
|
{
|
||||||
public float Max { get; set; }
|
public float Max { get; set; }
|
||||||
public required string Type { get; set; }
|
public required string Type { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleWorkForceDefinition
|
public sealed class ModuleWorkForceDefinition
|
||||||
{
|
{
|
||||||
public float Capacity { get; set; }
|
public float Capacity { get; set; }
|
||||||
public float Max { get; set; }
|
public float Max { get; set; }
|
||||||
public string Race { get; set; } = string.Empty;
|
public string Race { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleMountDefinition
|
public sealed class ModuleMountDefinition
|
||||||
{
|
{
|
||||||
public required string Group { get; set; }
|
public required string Group { get; set; }
|
||||||
public required string Size { get; set; }
|
public required string Size { get; set; }
|
||||||
public bool Hittable { get; set; }
|
public bool Hittable { get; set; }
|
||||||
public List<string> Types { get; set; } = [];
|
public List<string> Types { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleProductionDefinition
|
public sealed class ModuleProductionDefinition
|
||||||
{
|
{
|
||||||
public float Time { get; set; }
|
public float Time { get; set; }
|
||||||
public float Amount { get; set; }
|
public float Amount { get; set; }
|
||||||
public string Method { get; set; } = "default";
|
public string Method { get; set; } = "default";
|
||||||
public string Name { get; set; } = "Universal";
|
public string Name { get; set; } = "Universal";
|
||||||
public List<RecipeInputDefinition> Wares { get; set; } = [];
|
public List<RecipeInputDefinition> Wares { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleDefinition
|
public sealed class ModuleDefinition
|
||||||
{
|
{
|
||||||
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;
|
||||||
public required string Type { get; set; }
|
public required string Type { get; set; }
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string? Product { get; set; }
|
public string? Product { get; set; }
|
||||||
public List<string> Products { get; set; } = [];
|
public List<string> Products { get; set; } = [];
|
||||||
public string ProductionMode { get; set; } = "passive";
|
public string ProductionMode { get; set; } = "passive";
|
||||||
public float Radius { get; set; } = 12f;
|
public float Radius { get; set; } = 12f;
|
||||||
public float Hull { get; set; } = 100f;
|
public float Hull { get; set; } = 100f;
|
||||||
public float WorkforceNeeded { get; set; }
|
public float WorkforceNeeded { get; set; }
|
||||||
public int Version { get; set; }
|
public int Version { get; set; }
|
||||||
public string Macro { get; set; } = string.Empty;
|
public string Macro { get; set; } = string.Empty;
|
||||||
public string MakerRace { get; set; } = string.Empty;
|
public string MakerRace { get; set; } = string.Empty;
|
||||||
public int ExplosionDamage { get; set; }
|
public int ExplosionDamage { get; set; }
|
||||||
public ItemPriceDefinition? Price { get; set; }
|
public ItemPriceDefinition? Price { get; set; }
|
||||||
public List<string> Owners { get; set; } = [];
|
public List<string> Owners { get; set; } = [];
|
||||||
public ModuleCargoDefinition? Cargo { get; set; }
|
public ModuleCargoDefinition? Cargo { get; set; }
|
||||||
public ModuleWorkForceDefinition? WorkForce { get; set; }
|
public ModuleWorkForceDefinition? WorkForce { get; set; }
|
||||||
public List<ModuleDockDefinition> Docks { get; set; } = [];
|
public List<ModuleDockDefinition> Docks { get; set; } = [];
|
||||||
public List<ModuleMountDefinition> Shields { get; set; } = [];
|
public List<ModuleMountDefinition> Shields { get; set; } = [];
|
||||||
public List<ModuleMountDefinition> Turrets { get; set; } = [];
|
public List<ModuleMountDefinition> Turrets { get; set; } = [];
|
||||||
public List<ModuleProductionDefinition> Production { get; set; } = [];
|
public List<ModuleProductionDefinition> Production { get; set; } = [];
|
||||||
public ModuleConstructionDefinition? Construction { get; set; }
|
public ModuleConstructionDefinition? Construction { get; set; }
|
||||||
[JsonPropertyName("product")]
|
[JsonPropertyName("product")]
|
||||||
public List<string> ProductIds
|
public List<string> ProductIds
|
||||||
{
|
{
|
||||||
get => Products;
|
get => Products;
|
||||||
set => Products = value ?? [];
|
set => Products = value ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleRecipeDefinition
|
public sealed class ModuleRecipeDefinition
|
||||||
{
|
{
|
||||||
public required string ModuleId { get; set; }
|
public required string ModuleId { get; set; }
|
||||||
public float Duration { get; set; }
|
public float Duration { get; set; }
|
||||||
public required List<RecipeInputDefinition> Inputs { get; set; }
|
public required List<RecipeInputDefinition> Inputs { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RecipeDefinition
|
public sealed class RecipeDefinition
|
||||||
{
|
{
|
||||||
public required string Id { get; set; }
|
public required string Id { get; set; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public required string FacilityCategory { get; set; }
|
public required string FacilityCategory { get; set; }
|
||||||
public float Duration { get; set; }
|
public float Duration { get; set; }
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
public List<string> RequiredModules { get; set; } = [];
|
public List<string> RequiredModules { get; set; } = [];
|
||||||
public List<RecipeInputDefinition> Inputs { get; set; } = [];
|
public List<RecipeInputDefinition> Inputs { get; set; } = [];
|
||||||
public List<RecipeOutputDefinition> Outputs { get; set; } = [];
|
public List<RecipeOutputDefinition> Outputs { get; set; } = [];
|
||||||
public string? ShipOutputId { get; set; }
|
public string? ShipOutputId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlanetDefinition
|
public sealed class PlanetDefinition
|
||||||
{
|
{
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string PlanetType { get; set; } = "terrestrial";
|
public string PlanetType { get; set; } = "terrestrial";
|
||||||
public string Shape { get; set; } = "sphere";
|
public string Shape { get; set; } = "sphere";
|
||||||
public List<MoonDefinition> Moons { get; set; } = [];
|
public List<MoonDefinition> Moons { get; set; } = [];
|
||||||
public float OrbitRadius { get; set; }
|
public float OrbitRadius { get; set; }
|
||||||
public float OrbitSpeed { get; set; }
|
public float OrbitSpeed { get; set; }
|
||||||
public float OrbitEccentricity { get; set; }
|
public float OrbitEccentricity { get; set; }
|
||||||
public float OrbitInclination { get; set; }
|
public float OrbitInclination { get; set; }
|
||||||
public float OrbitLongitudeOfAscendingNode { get; set; }
|
public float OrbitLongitudeOfAscendingNode { get; set; }
|
||||||
public float OrbitArgumentOfPeriapsis { get; set; }
|
public float OrbitArgumentOfPeriapsis { get; set; }
|
||||||
public float OrbitPhaseAtEpoch { get; set; }
|
public float OrbitPhaseAtEpoch { get; set; }
|
||||||
public float Size { get; set; }
|
public float Size { get; set; }
|
||||||
public required string Color { get; set; }
|
public required string Color { get; set; }
|
||||||
public float Tilt { get; set; }
|
public float Tilt { get; set; }
|
||||||
public bool HasRing { get; set; }
|
public bool HasRing { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ShipDefinition
|
public sealed class ShipDefinition
|
||||||
{
|
{
|
||||||
public required string Id { get; set; }
|
public required string Id { get; set; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public required string Class { get; set; }
|
public required string Class { get; set; }
|
||||||
public float Speed { get; set; }
|
public float Speed { get; set; }
|
||||||
public float WarpSpeed { get; set; }
|
public float WarpSpeed { get; set; }
|
||||||
public float FtlSpeed { get; set; }
|
public float FtlSpeed { get; set; }
|
||||||
public float SpoolTime { get; set; }
|
public float SpoolTime { get; set; }
|
||||||
public float CargoCapacity { get; set; }
|
public float CargoCapacity { get; set; }
|
||||||
public string? CargoKind { get; set; }
|
public string? CargoKind { get; set; }
|
||||||
public required string Color { get; set; }
|
public required string Color { get; set; }
|
||||||
public required string HullColor { get; set; }
|
public required string HullColor { get; set; }
|
||||||
public float Size { get; set; }
|
public float Size { get; set; }
|
||||||
public float MaxHealth { get; set; }
|
public float MaxHealth { get; set; }
|
||||||
public List<string> Capabilities { get; set; } = [];
|
public List<string> Capabilities { get; set; } = [];
|
||||||
public ConstructionDefinition? Construction { get; set; }
|
public ConstructionDefinition? Construction { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ScenarioDefinition
|
public sealed class ScenarioDefinition
|
||||||
{
|
{
|
||||||
public required List<InitialStationDefinition> InitialStations { get; set; }
|
public required List<InitialStationDefinition> InitialStations { get; set; }
|
||||||
public required List<ShipFormationDefinition> ShipFormations { get; set; }
|
public required List<ShipFormationDefinition> ShipFormations { get; set; }
|
||||||
public required List<PatrolRouteDefinition> PatrolRoutes { get; set; }
|
public required List<PatrolRouteDefinition> PatrolRoutes { get; set; }
|
||||||
public required MiningDefaultsDefinition MiningDefaults { get; set; }
|
public required MiningDefaultsDefinition MiningDefaults { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class InitialStationDefinition
|
public sealed class InitialStationDefinition
|
||||||
{
|
{
|
||||||
public required string SystemId { get; set; }
|
public required string SystemId { get; set; }
|
||||||
public string Label { get; set; } = "Orbital Station";
|
public string Label { get; set; } = "Orbital Station";
|
||||||
public string Color { get; set; } = "#8df0d2";
|
public string Color { get; set; } = "#8df0d2";
|
||||||
public string Objective { get; set; } = "general";
|
public string Objective { get; set; } = "general";
|
||||||
public List<string> StartingModules { get; set; } = [];
|
public List<string> StartingModules { get; set; } = [];
|
||||||
public string? FactionId { get; set; }
|
public string? FactionId { get; set; }
|
||||||
public int? PlanetIndex { get; set; }
|
public int? PlanetIndex { get; set; }
|
||||||
public int? LagrangeSide { get; set; }
|
public int? LagrangeSide { get; set; }
|
||||||
public float[]? Position { get; set; }
|
public float[]? Position { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ShipFormationDefinition
|
public sealed class ShipFormationDefinition
|
||||||
{
|
{
|
||||||
public required string ShipId { get; set; }
|
public required string ShipId { get; set; }
|
||||||
public int Count { get; set; }
|
public int Count { get; set; }
|
||||||
public required float[] Center { get; set; }
|
public required float[] Center { get; set; }
|
||||||
public required string SystemId { get; set; }
|
public required string SystemId { get; set; }
|
||||||
public string? FactionId { get; set; }
|
public string? FactionId { get; set; }
|
||||||
public Dictionary<string, float> StartingInventory { get; set; } = new(StringComparer.Ordinal);
|
public Dictionary<string, float> StartingInventory { get; set; } = new(StringComparer.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PatrolRouteDefinition
|
public sealed class PatrolRouteDefinition
|
||||||
{
|
{
|
||||||
public required string SystemId { get; set; }
|
public required string SystemId { get; set; }
|
||||||
public required List<float[]> Points { get; set; }
|
public required List<float[]> Points { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class MiningDefaultsDefinition
|
public sealed class MiningDefaultsDefinition
|
||||||
{
|
{
|
||||||
public required string NodeSystemId { get; set; }
|
public required string NodeSystemId { get; set; }
|
||||||
public required string RefinerySystemId { get; set; }
|
public required string RefinerySystemId { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,33 +2,33 @@ namespace SpaceGame.Api.Economy.Runtime;
|
|||||||
|
|
||||||
public sealed class MarketOrderRuntime
|
public sealed class MarketOrderRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string FactionId { get; init; }
|
public required string FactionId { get; init; }
|
||||||
public string? StationId { get; init; }
|
public string? StationId { get; init; }
|
||||||
public string? ConstructionSiteId { get; init; }
|
public string? ConstructionSiteId { get; init; }
|
||||||
public required string Kind { get; init; }
|
public required string Kind { get; init; }
|
||||||
public required string ItemId { get; init; }
|
public required string ItemId { get; init; }
|
||||||
public float Amount { get; init; }
|
public float Amount { get; init; }
|
||||||
public float RemainingAmount { get; set; }
|
public float RemainingAmount { get; set; }
|
||||||
public float Valuation { get; set; }
|
public float Valuation { get; set; }
|
||||||
public float? ReserveThreshold { get; set; }
|
public float? ReserveThreshold { get; set; }
|
||||||
public string? PolicySetId { get; set; }
|
public string? PolicySetId { get; set; }
|
||||||
public string State { get; set; } = MarketOrderStateKinds.Open;
|
public string State { get; set; } = MarketOrderStateKinds.Open;
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PolicySetRuntime
|
public sealed class PolicySetRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string OwnerKind { get; init; }
|
public required string OwnerKind { get; init; }
|
||||||
public required string OwnerId { get; init; }
|
public required string OwnerId { get; init; }
|
||||||
public string TradeAccessPolicy { get; set; } = "owner-and-allies";
|
public string TradeAccessPolicy { get; set; } = "owner-and-allies";
|
||||||
public string DockingAccessPolicy { get; set; } = "owner-and-allies";
|
public string DockingAccessPolicy { get; set; } = "owner-and-allies";
|
||||||
public string ConstructionAccessPolicy { get; set; } = "owner-only";
|
public string ConstructionAccessPolicy { get; set; } = "owner-only";
|
||||||
public string OperationalRangePolicy { get; set; } = "unrestricted";
|
public string OperationalRangePolicy { get; set; } = "unrestricted";
|
||||||
public string CombatEngagementPolicy { get; set; } = "defensive";
|
public string CombatEngagementPolicy { get; set; } = "defensive";
|
||||||
public bool AvoidHostileSystems { get; set; } = true;
|
public bool AvoidHostileSystems { get; set; } = true;
|
||||||
public float FleeHullRatio { get; set; } = 0.35f;
|
public float FleeHullRatio { get; set; } = 0.35f;
|
||||||
public HashSet<string> BlacklistedSystemIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> BlacklistedSystemIds { get; } = new(StringComparer.Ordinal);
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,347 +2,347 @@ namespace SpaceGame.Api.Factions.Runtime;
|
|||||||
|
|
||||||
public sealed class FactionRuntime
|
public sealed class FactionRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; init; }
|
public required string Label { get; init; }
|
||||||
public required string Color { get; init; }
|
public required string Color { get; init; }
|
||||||
public float Credits { get; set; }
|
public float Credits { get; set; }
|
||||||
public float PopulationTotal { get; set; }
|
public float PopulationTotal { get; set; }
|
||||||
public float OreMined { get; set; }
|
public float OreMined { get; set; }
|
||||||
public float GoodsProduced { get; set; }
|
public float GoodsProduced { get; set; }
|
||||||
public int ShipsBuilt { get; set; }
|
public int ShipsBuilt { get; set; }
|
||||||
public int ShipsLost { get; set; }
|
public int ShipsLost { get; set; }
|
||||||
public HashSet<string> CommanderIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> CommanderIds { get; } = new(StringComparer.Ordinal);
|
||||||
public string? DefaultPolicySetId { get; set; }
|
public string? DefaultPolicySetId { get; set; }
|
||||||
public FactionDoctrineRuntime Doctrine { get; set; } = new();
|
public FactionDoctrineRuntime Doctrine { get; set; } = new();
|
||||||
public FactionMemoryRuntime Memory { get; set; } = new();
|
public FactionMemoryRuntime Memory { get; set; } = new();
|
||||||
public FactionStrategicStateRuntime StrategicState { get; set; } = new();
|
public FactionStrategicStateRuntime StrategicState { get; set; } = new();
|
||||||
public List<FactionDecisionLogEntryRuntime> DecisionLog { get; } = [];
|
public List<FactionDecisionLogEntryRuntime> DecisionLog { get; } = [];
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CommanderRuntime
|
public sealed class CommanderRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public required string FactionId { get; init; }
|
public required string FactionId { get; init; }
|
||||||
public string? ParentCommanderId { get; set; }
|
public string? ParentCommanderId { get; set; }
|
||||||
public string? ControlledEntityId { get; set; }
|
public string? ControlledEntityId { get; set; }
|
||||||
public string? PolicySetId { get; set; }
|
public string? PolicySetId { get; set; }
|
||||||
public string? Doctrine { get; set; }
|
public string? Doctrine { get; set; }
|
||||||
public float ReplanTimer { get; set; }
|
public float ReplanTimer { get; set; }
|
||||||
public bool NeedsReplan { get; set; } = true;
|
public bool NeedsReplan { get; set; } = true;
|
||||||
public CommanderAssignmentRuntime? Assignment { get; set; }
|
public CommanderAssignmentRuntime? Assignment { get; set; }
|
||||||
public CommanderSkillProfileRuntime Skills { get; set; } = new();
|
public CommanderSkillProfileRuntime Skills { get; set; } = new();
|
||||||
public HashSet<string> SubordinateCommanderIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> SubordinateCommanderIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> ActiveObjectiveIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> ActiveObjectiveIds { get; } = new(StringComparer.Ordinal);
|
||||||
public bool IsAlive { get; set; } = true;
|
public bool IsAlive { get; set; } = true;
|
||||||
public int PlanningCycle { get; set; }
|
public int PlanningCycle { get; set; }
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CommanderAssignmentRuntime
|
public sealed class CommanderAssignmentRuntime
|
||||||
{
|
{
|
||||||
public required string ObjectiveId { get; set; }
|
public required string ObjectiveId { get; set; }
|
||||||
public string? CampaignId { get; set; }
|
public string? CampaignId { get; set; }
|
||||||
public string? TheaterId { get; set; }
|
public string? TheaterId { get; set; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public required string BehaviorKind { get; set; }
|
public required string BehaviorKind { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public float Priority { get; set; }
|
public float Priority { get; set; }
|
||||||
public string? HomeSystemId { get; set; }
|
public string? HomeSystemId { get; set; }
|
||||||
public string? HomeStationId { get; set; }
|
public string? HomeStationId { get; set; }
|
||||||
public string? TargetSystemId { get; set; }
|
public string? TargetSystemId { get; set; }
|
||||||
public string? TargetEntityId { get; set; }
|
public string? TargetEntityId { get; set; }
|
||||||
public Vector3? TargetPosition { get; set; }
|
public Vector3? TargetPosition { get; set; }
|
||||||
public string? ItemId { get; set; }
|
public string? ItemId { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class CommanderSkillProfileRuntime
|
public sealed class CommanderSkillProfileRuntime
|
||||||
{
|
{
|
||||||
public int Leadership { get; set; } = 3;
|
public int Leadership { get; set; } = 3;
|
||||||
public int Coordination { get; set; } = 3;
|
public int Coordination { get; set; } = 3;
|
||||||
public int Strategy { get; set; } = 3;
|
public int Strategy { get; set; } = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionDoctrineRuntime
|
public sealed class FactionDoctrineRuntime
|
||||||
{
|
{
|
||||||
public string StrategicPosture { get; set; } = "balanced";
|
public string StrategicPosture { get; set; } = "balanced";
|
||||||
public string ExpansionPosture { get; set; } = "measured";
|
public string ExpansionPosture { get; set; } = "measured";
|
||||||
public string MilitaryPosture { get; set; } = "defensive";
|
public string MilitaryPosture { get; set; } = "defensive";
|
||||||
public string EconomicPosture { get; set; } = "self-sufficient";
|
public string EconomicPosture { get; set; } = "self-sufficient";
|
||||||
public int DesiredControlledSystems { get; set; } = 3;
|
public int DesiredControlledSystems { get; set; } = 3;
|
||||||
public int DesiredMilitaryPerFront { get; set; } = 2;
|
public int DesiredMilitaryPerFront { get; set; } = 2;
|
||||||
public int DesiredMinersPerSystem { get; set; } = 1;
|
public int DesiredMinersPerSystem { get; set; } = 1;
|
||||||
public int DesiredTransportsPerSystem { get; set; } = 1;
|
public int DesiredTransportsPerSystem { get; set; } = 1;
|
||||||
public int DesiredConstructors { get; set; } = 1;
|
public int DesiredConstructors { get; set; } = 1;
|
||||||
public float ReserveCreditsRatio { get; set; } = 0.2f;
|
public float ReserveCreditsRatio { get; set; } = 0.2f;
|
||||||
public float ExpansionBudgetRatio { get; set; } = 0.25f;
|
public float ExpansionBudgetRatio { get; set; } = 0.25f;
|
||||||
public float WarBudgetRatio { get; set; } = 0.35f;
|
public float WarBudgetRatio { get; set; } = 0.35f;
|
||||||
public float ReserveMilitaryRatio { get; set; } = 0.2f;
|
public float ReserveMilitaryRatio { get; set; } = 0.2f;
|
||||||
public float OffensiveReadinessThreshold { get; set; } = 0.62f;
|
public float OffensiveReadinessThreshold { get; set; } = 0.62f;
|
||||||
public float SupplySecurityBias { get; set; } = 0.55f;
|
public float SupplySecurityBias { get; set; } = 0.55f;
|
||||||
public float FailureAversion { get; set; } = 0.45f;
|
public float FailureAversion { get; set; } = 0.45f;
|
||||||
public int ReinforcementLeadPerFront { get; set; } = 1;
|
public int ReinforcementLeadPerFront { get; set; } = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionMemoryRuntime
|
public sealed class FactionMemoryRuntime
|
||||||
{
|
{
|
||||||
public int LastPlanCycle { get; set; }
|
public int LastPlanCycle { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; }
|
public DateTimeOffset UpdatedAtUtc { get; set; }
|
||||||
public int LastObservedShipsBuilt { get; set; }
|
public int LastObservedShipsBuilt { get; set; }
|
||||||
public int LastObservedShipsLost { get; set; }
|
public int LastObservedShipsLost { get; set; }
|
||||||
public float LastObservedCredits { get; set; }
|
public float LastObservedCredits { get; set; }
|
||||||
public HashSet<string> KnownSystemIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> KnownSystemIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> KnownEnemyFactionIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> KnownEnemyFactionIds { get; } = new(StringComparer.Ordinal);
|
||||||
public List<FactionSystemMemoryRuntime> SystemMemories { get; } = [];
|
public List<FactionSystemMemoryRuntime> SystemMemories { get; } = [];
|
||||||
public List<FactionCommodityMemoryRuntime> CommodityMemories { get; } = [];
|
public List<FactionCommodityMemoryRuntime> CommodityMemories { get; } = [];
|
||||||
public List<FactionOutcomeRecordRuntime> RecentOutcomes { get; } = [];
|
public List<FactionOutcomeRecordRuntime> RecentOutcomes { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionSystemMemoryRuntime
|
public sealed class FactionSystemMemoryRuntime
|
||||||
{
|
{
|
||||||
public required string SystemId { get; init; }
|
public required string SystemId { get; init; }
|
||||||
public DateTimeOffset LastSeenAtUtc { get; set; }
|
public DateTimeOffset LastSeenAtUtc { get; set; }
|
||||||
public int LastEnemyShipCount { get; set; }
|
public int LastEnemyShipCount { get; set; }
|
||||||
public int LastEnemyStationCount { get; set; }
|
public int LastEnemyStationCount { get; set; }
|
||||||
public bool ControlledByFaction { get; set; }
|
public bool ControlledByFaction { get; set; }
|
||||||
public string? LastRole { get; set; }
|
public string? LastRole { get; set; }
|
||||||
public float FrontierPressure { get; set; }
|
public float FrontierPressure { get; set; }
|
||||||
public float RouteRisk { get; set; }
|
public float RouteRisk { get; set; }
|
||||||
public float HistoricalShortagePressure { get; set; }
|
public float HistoricalShortagePressure { get; set; }
|
||||||
public int OffensiveFailures { get; set; }
|
public int OffensiveFailures { get; set; }
|
||||||
public int DefensiveFailures { get; set; }
|
public int DefensiveFailures { get; set; }
|
||||||
public int OffensiveSuccesses { get; set; }
|
public int OffensiveSuccesses { get; set; }
|
||||||
public int DefensiveSuccesses { get; set; }
|
public int DefensiveSuccesses { get; set; }
|
||||||
public DateTimeOffset? LastContestedAtUtc { get; set; }
|
public DateTimeOffset? LastContestedAtUtc { get; set; }
|
||||||
public DateTimeOffset? LastShortageAtUtc { get; set; }
|
public DateTimeOffset? LastShortageAtUtc { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionCommodityMemoryRuntime
|
public sealed class FactionCommodityMemoryRuntime
|
||||||
{
|
{
|
||||||
public required string ItemId { get; init; }
|
public required string ItemId { get; init; }
|
||||||
public float HistoricalShortageScore { get; set; }
|
public float HistoricalShortageScore { get; set; }
|
||||||
public float HistoricalSurplusScore { get; set; }
|
public float HistoricalSurplusScore { get; set; }
|
||||||
public float LastObservedBacklog { get; set; }
|
public float LastObservedBacklog { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; }
|
public DateTimeOffset UpdatedAtUtc { get; set; }
|
||||||
public DateTimeOffset? LastCriticalAtUtc { get; set; }
|
public DateTimeOffset? LastCriticalAtUtc { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionOutcomeRecordRuntime
|
public sealed class FactionOutcomeRecordRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public required string Summary { get; set; }
|
public required string Summary { get; set; }
|
||||||
public string? RelatedCampaignId { get; set; }
|
public string? RelatedCampaignId { get; set; }
|
||||||
public string? RelatedObjectiveId { get; set; }
|
public string? RelatedObjectiveId { get; set; }
|
||||||
public DateTimeOffset OccurredAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset OccurredAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionStrategicStateRuntime
|
public sealed class FactionStrategicStateRuntime
|
||||||
{
|
{
|
||||||
public int PlanCycle { get; set; }
|
public int PlanCycle { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; }
|
public DateTimeOffset UpdatedAtUtc { get; set; }
|
||||||
public string Status { get; set; } = "stable";
|
public string Status { get; set; } = "stable";
|
||||||
public FactionBudgetRuntime Budget { get; set; } = new();
|
public FactionBudgetRuntime Budget { get; set; } = new();
|
||||||
public FactionEconomicAssessmentRuntime EconomicAssessment { get; set; } = new();
|
public FactionEconomicAssessmentRuntime EconomicAssessment { get; set; } = new();
|
||||||
public FactionThreatAssessmentRuntime ThreatAssessment { get; set; } = new();
|
public FactionThreatAssessmentRuntime ThreatAssessment { get; set; } = new();
|
||||||
public List<FactionTheaterRuntime> Theaters { get; } = [];
|
public List<FactionTheaterRuntime> Theaters { get; } = [];
|
||||||
public List<FactionCampaignRuntime> Campaigns { get; } = [];
|
public List<FactionCampaignRuntime> Campaigns { get; } = [];
|
||||||
public List<FactionOperationalObjectiveRuntime> Objectives { get; } = [];
|
public List<FactionOperationalObjectiveRuntime> Objectives { get; } = [];
|
||||||
public List<FactionAssetReservationRuntime> Reservations { get; } = [];
|
public List<FactionAssetReservationRuntime> Reservations { get; } = [];
|
||||||
public List<FactionProductionProgramRuntime> ProductionPrograms { get; } = [];
|
public List<FactionProductionProgramRuntime> ProductionPrograms { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionBudgetRuntime
|
public sealed class FactionBudgetRuntime
|
||||||
{
|
{
|
||||||
public float ReservedCredits { get; set; }
|
public float ReservedCredits { get; set; }
|
||||||
public float ExpansionCredits { get; set; }
|
public float ExpansionCredits { get; set; }
|
||||||
public float WarCredits { get; set; }
|
public float WarCredits { get; set; }
|
||||||
public int ReservedMilitaryAssets { get; set; }
|
public int ReservedMilitaryAssets { get; set; }
|
||||||
public int ReservedLogisticsAssets { get; set; }
|
public int ReservedLogisticsAssets { get; set; }
|
||||||
public int ReservedConstructionAssets { get; set; }
|
public int ReservedConstructionAssets { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionEconomicAssessmentRuntime
|
public sealed class FactionEconomicAssessmentRuntime
|
||||||
{
|
{
|
||||||
public int PlanCycle { get; set; }
|
public int PlanCycle { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; }
|
public DateTimeOffset UpdatedAtUtc { get; set; }
|
||||||
public int MilitaryShipCount { get; set; }
|
public int MilitaryShipCount { get; set; }
|
||||||
public int MinerShipCount { get; set; }
|
public int MinerShipCount { get; set; }
|
||||||
public int TransportShipCount { get; set; }
|
public int TransportShipCount { get; set; }
|
||||||
public int ConstructorShipCount { get; set; }
|
public int ConstructorShipCount { get; set; }
|
||||||
public int ControlledSystemCount { get; set; }
|
public int ControlledSystemCount { get; set; }
|
||||||
public int TargetMilitaryShipCount { get; set; }
|
public int TargetMilitaryShipCount { get; set; }
|
||||||
public int TargetMinerShipCount { get; set; }
|
public int TargetMinerShipCount { get; set; }
|
||||||
public int TargetTransportShipCount { get; set; }
|
public int TargetTransportShipCount { get; set; }
|
||||||
public int TargetConstructorShipCount { get; set; }
|
public int TargetConstructorShipCount { get; set; }
|
||||||
public bool HasShipyard { get; set; }
|
public bool HasShipyard { get; set; }
|
||||||
public bool HasWarIndustrySupplyChain { get; set; }
|
public bool HasWarIndustrySupplyChain { get; set; }
|
||||||
public string? PrimaryExpansionSiteId { get; set; }
|
public string? PrimaryExpansionSiteId { get; set; }
|
||||||
public string? PrimaryExpansionSystemId { get; set; }
|
public string? PrimaryExpansionSystemId { get; set; }
|
||||||
public float ReplacementPressure { get; set; }
|
public float ReplacementPressure { get; set; }
|
||||||
public float SustainmentScore { get; set; }
|
public float SustainmentScore { get; set; }
|
||||||
public float LogisticsSecurityScore { get; set; }
|
public float LogisticsSecurityScore { get; set; }
|
||||||
public int CriticalShortageCount { get; set; }
|
public int CriticalShortageCount { get; set; }
|
||||||
public string? IndustrialBottleneckItemId { get; set; }
|
public string? IndustrialBottleneckItemId { get; set; }
|
||||||
public List<FactionCommoditySignalRuntime> CommoditySignals { get; } = [];
|
public List<FactionCommoditySignalRuntime> CommoditySignals { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionThreatAssessmentRuntime
|
public sealed class FactionThreatAssessmentRuntime
|
||||||
{
|
{
|
||||||
public int PlanCycle { get; set; }
|
public int PlanCycle { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; }
|
public DateTimeOffset UpdatedAtUtc { get; set; }
|
||||||
public int EnemyFactionCount { get; set; }
|
public int EnemyFactionCount { get; set; }
|
||||||
public int EnemyShipCount { get; set; }
|
public int EnemyShipCount { get; set; }
|
||||||
public int EnemyStationCount { get; set; }
|
public int EnemyStationCount { get; set; }
|
||||||
public string? PrimaryThreatFactionId { get; set; }
|
public string? PrimaryThreatFactionId { get; set; }
|
||||||
public string? PrimaryThreatSystemId { get; set; }
|
public string? PrimaryThreatSystemId { get; set; }
|
||||||
public List<FactionThreatSignalRuntime> ThreatSignals { get; } = [];
|
public List<FactionThreatSignalRuntime> ThreatSignals { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionTheaterRuntime
|
public sealed class FactionTheaterRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public required string SystemId { get; set; }
|
public required string SystemId { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public float Priority { get; set; }
|
public float Priority { get; set; }
|
||||||
public float SupplyRisk { get; set; }
|
public float SupplyRisk { get; set; }
|
||||||
public float FriendlyAssetValue { get; set; }
|
public float FriendlyAssetValue { get; set; }
|
||||||
public string? TargetFactionId { get; set; }
|
public string? TargetFactionId { get; set; }
|
||||||
public string? AnchorEntityId { get; set; }
|
public string? AnchorEntityId { get; set; }
|
||||||
public Vector3? AnchorPosition { get; set; }
|
public Vector3? AnchorPosition { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<string> CampaignIds { get; } = [];
|
public List<string> CampaignIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionCampaignRuntime
|
public sealed class FactionCampaignRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public string Status { get; set; } = "planned";
|
public string Status { get; set; } = "planned";
|
||||||
public float Priority { get; set; }
|
public float Priority { get; set; }
|
||||||
public string? TheaterId { get; set; }
|
public string? TheaterId { get; set; }
|
||||||
public string? TargetFactionId { get; set; }
|
public string? TargetFactionId { get; set; }
|
||||||
public string? TargetSystemId { get; set; }
|
public string? TargetSystemId { get; set; }
|
||||||
public string? TargetEntityId { get; set; }
|
public string? TargetEntityId { get; set; }
|
||||||
public string? CommodityId { get; set; }
|
public string? CommodityId { get; set; }
|
||||||
public string? SupportStationId { get; set; }
|
public string? SupportStationId { get; set; }
|
||||||
public int CurrentStepIndex { get; set; }
|
public int CurrentStepIndex { get; set; }
|
||||||
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public string? Summary { get; set; }
|
public string? Summary { get; set; }
|
||||||
public string? PauseReason { get; set; }
|
public string? PauseReason { get; set; }
|
||||||
public float ContinuationScore { get; set; }
|
public float ContinuationScore { get; set; }
|
||||||
public float SupplyAdequacy { get; set; }
|
public float SupplyAdequacy { get; set; }
|
||||||
public float ReplacementPressure { get; set; }
|
public float ReplacementPressure { get; set; }
|
||||||
public int FailureCount { get; set; }
|
public int FailureCount { get; set; }
|
||||||
public int SuccessCount { get; set; }
|
public int SuccessCount { get; set; }
|
||||||
public string? FleetCommanderId { get; set; }
|
public string? FleetCommanderId { get; set; }
|
||||||
public bool RequiresReinforcement { get; set; }
|
public bool RequiresReinforcement { get; set; }
|
||||||
public List<FactionPlanStepRuntime> Steps { get; } = [];
|
public List<FactionPlanStepRuntime> Steps { get; } = [];
|
||||||
public List<string> ObjectiveIds { get; } = [];
|
public List<string> ObjectiveIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionOperationalObjectiveRuntime
|
public sealed class FactionOperationalObjectiveRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string CampaignId { get; set; }
|
public required string CampaignId { get; set; }
|
||||||
public string? TheaterId { get; set; }
|
public string? TheaterId { get; set; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public required string DelegationKind { get; set; }
|
public required string DelegationKind { get; set; }
|
||||||
public required string BehaviorKind { get; set; }
|
public required string BehaviorKind { get; set; }
|
||||||
public string Status { get; set; } = "planned";
|
public string Status { get; set; } = "planned";
|
||||||
public float Priority { get; set; }
|
public float Priority { get; set; }
|
||||||
public string? CommanderId { get; set; }
|
public string? CommanderId { get; set; }
|
||||||
public string? HomeSystemId { get; set; }
|
public string? HomeSystemId { get; set; }
|
||||||
public string? HomeStationId { get; set; }
|
public string? HomeStationId { get; set; }
|
||||||
public string? TargetSystemId { get; set; }
|
public string? TargetSystemId { get; set; }
|
||||||
public string? TargetEntityId { get; set; }
|
public string? TargetEntityId { get; set; }
|
||||||
public Vector3? TargetPosition { get; set; }
|
public Vector3? TargetPosition { get; set; }
|
||||||
public string? ItemId { get; set; }
|
public string? ItemId { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public int CurrentStepIndex { get; set; }
|
public int CurrentStepIndex { get; set; }
|
||||||
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public bool UseOrders { get; set; }
|
public bool UseOrders { get; set; }
|
||||||
public string? StagingOrderKind { get; set; }
|
public string? StagingOrderKind { get; set; }
|
||||||
public int ReinforcementLevel { get; set; }
|
public int ReinforcementLevel { get; set; }
|
||||||
public List<FactionPlanStepRuntime> Steps { get; } = [];
|
public List<FactionPlanStepRuntime> Steps { get; } = [];
|
||||||
public List<string> ReservedAssetIds { get; } = [];
|
public List<string> ReservedAssetIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionPlanStepRuntime
|
public sealed class FactionPlanStepRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public string Status { get; set; } = "planned";
|
public string Status { get; set; } = "planned";
|
||||||
public string? Summary { get; set; }
|
public string? Summary { get; set; }
|
||||||
public string? BlockingReason { get; set; }
|
public string? BlockingReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionAssetReservationRuntime
|
public sealed class FactionAssetReservationRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string ObjectiveId { get; set; }
|
public required string ObjectiveId { get; set; }
|
||||||
public string? CampaignId { get; set; }
|
public string? CampaignId { get; set; }
|
||||||
public required string AssetKind { get; set; }
|
public required string AssetKind { get; set; }
|
||||||
public required string AssetId { get; set; }
|
public required string AssetId { get; set; }
|
||||||
public float Priority { get; set; }
|
public float Priority { get; set; }
|
||||||
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionProductionProgramRuntime
|
public sealed class FactionProductionProgramRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public string Status { get; set; } = "planned";
|
public string Status { get; set; } = "planned";
|
||||||
public float Priority { get; set; }
|
public float Priority { get; set; }
|
||||||
public string? CampaignId { get; set; }
|
public string? CampaignId { get; set; }
|
||||||
public string? CommodityId { get; set; }
|
public string? CommodityId { get; set; }
|
||||||
public string? ModuleId { get; set; }
|
public string? ModuleId { get; set; }
|
||||||
public string? ShipKind { get; set; }
|
public string? ShipKind { get; set; }
|
||||||
public string? TargetSystemId { get; set; }
|
public string? TargetSystemId { get; set; }
|
||||||
public int TargetCount { get; set; }
|
public int TargetCount { get; set; }
|
||||||
public int CurrentCount { get; set; }
|
public int CurrentCount { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionDecisionLogEntryRuntime
|
public sealed class FactionDecisionLogEntryRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public required string Summary { get; set; }
|
public required string Summary { get; set; }
|
||||||
public string? RelatedEntityId { get; set; }
|
public string? RelatedEntityId { get; set; }
|
||||||
public int PlanCycle { get; set; }
|
public int PlanCycle { get; set; }
|
||||||
public DateTimeOffset OccurredAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset OccurredAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionCommoditySignalRuntime
|
public sealed class FactionCommoditySignalRuntime
|
||||||
{
|
{
|
||||||
public required string ItemId { get; init; }
|
public required string ItemId { get; init; }
|
||||||
public float AvailableStock { get; set; }
|
public float AvailableStock { get; set; }
|
||||||
public float OnHand { get; set; }
|
public float OnHand { get; set; }
|
||||||
public float ProductionRatePerSecond { get; set; }
|
public float ProductionRatePerSecond { get; set; }
|
||||||
public float CommittedProductionRatePerSecond { get; set; }
|
public float CommittedProductionRatePerSecond { get; set; }
|
||||||
public float UsageRatePerSecond { get; set; }
|
public float UsageRatePerSecond { get; set; }
|
||||||
public float NetRatePerSecond { get; set; }
|
public float NetRatePerSecond { get; set; }
|
||||||
public float ProjectedNetRatePerSecond { get; set; }
|
public float ProjectedNetRatePerSecond { get; set; }
|
||||||
public float LevelSeconds { get; set; }
|
public float LevelSeconds { get; set; }
|
||||||
public string Level { get; set; } = "unknown";
|
public string Level { get; set; } = "unknown";
|
||||||
public float ProjectedProductionRatePerSecond { get; set; }
|
public float ProjectedProductionRatePerSecond { get; set; }
|
||||||
public float BuyBacklog { get; set; }
|
public float BuyBacklog { get; set; }
|
||||||
public float ReservedForConstruction { get; set; }
|
public float ReservedForConstruction { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FactionThreatSignalRuntime
|
public sealed class FactionThreatSignalRuntime
|
||||||
{
|
{
|
||||||
public required string ScopeId { get; init; }
|
public required string ScopeId { get; init; }
|
||||||
public required string ScopeKind { get; init; }
|
public required string ScopeKind { get; init; }
|
||||||
public int EnemyShipCount { get; set; }
|
public int EnemyShipCount { get; set; }
|
||||||
public int EnemyStationCount { get; set; }
|
public int EnemyStationCount { get; set; }
|
||||||
public string? EnemyFactionId { get; set; }
|
public string? EnemyFactionId { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,335 +2,335 @@ namespace SpaceGame.Api.Geopolitics.Runtime;
|
|||||||
|
|
||||||
public sealed class GeopoliticalStateRuntime
|
public sealed class GeopoliticalStateRuntime
|
||||||
{
|
{
|
||||||
public int Cycle { get; set; }
|
public int Cycle { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<SystemRouteLinkRuntime> Routes { get; } = [];
|
public List<SystemRouteLinkRuntime> Routes { get; } = [];
|
||||||
public DiplomaticStateRuntime Diplomacy { get; set; } = new();
|
public DiplomaticStateRuntime Diplomacy { get; set; } = new();
|
||||||
public TerritoryStateRuntime Territory { get; set; } = new();
|
public TerritoryStateRuntime Territory { get; set; } = new();
|
||||||
public EconomyRegionStateRuntime EconomyRegions { get; set; } = new();
|
public EconomyRegionStateRuntime EconomyRegions { get; set; } = new();
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class SystemRouteLinkRuntime
|
public sealed class SystemRouteLinkRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string SourceSystemId { get; set; }
|
public required string SourceSystemId { get; set; }
|
||||||
public required string DestinationSystemId { get; set; }
|
public required string DestinationSystemId { get; set; }
|
||||||
public float Distance { get; set; }
|
public float Distance { get; set; }
|
||||||
public bool IsPrimaryLane { get; set; } = true;
|
public bool IsPrimaryLane { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DiplomaticStateRuntime
|
public sealed class DiplomaticStateRuntime
|
||||||
{
|
{
|
||||||
public List<DiplomaticRelationRuntime> Relations { get; } = [];
|
public List<DiplomaticRelationRuntime> Relations { get; } = [];
|
||||||
public List<TreatyRuntime> Treaties { get; } = [];
|
public List<TreatyRuntime> Treaties { get; } = [];
|
||||||
public List<DiplomaticIncidentRuntime> Incidents { get; } = [];
|
public List<DiplomaticIncidentRuntime> Incidents { get; } = [];
|
||||||
public List<BorderTensionRuntime> BorderTensions { get; } = [];
|
public List<BorderTensionRuntime> BorderTensions { get; } = [];
|
||||||
public List<WarStateRuntime> Wars { get; } = [];
|
public List<WarStateRuntime> Wars { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DiplomaticRelationRuntime
|
public sealed class DiplomaticRelationRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string FactionAId { get; set; }
|
public required string FactionAId { get; set; }
|
||||||
public required string FactionBId { get; set; }
|
public required string FactionBId { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string Posture { get; set; } = "neutral";
|
public string Posture { get; set; } = "neutral";
|
||||||
public float TrustScore { get; set; }
|
public float TrustScore { get; set; }
|
||||||
public float TensionScore { get; set; }
|
public float TensionScore { get; set; }
|
||||||
public float GrievanceScore { get; set; }
|
public float GrievanceScore { get; set; }
|
||||||
public string TradeAccessPolicy { get; set; } = "restricted";
|
public string TradeAccessPolicy { get; set; } = "restricted";
|
||||||
public string MilitaryAccessPolicy { get; set; } = "restricted";
|
public string MilitaryAccessPolicy { get; set; } = "restricted";
|
||||||
public string? WarStateId { get; set; }
|
public string? WarStateId { get; set; }
|
||||||
public DateTimeOffset? CeasefireUntilUtc { get; set; }
|
public DateTimeOffset? CeasefireUntilUtc { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<string> ActiveTreatyIds { get; } = [];
|
public List<string> ActiveTreatyIds { get; } = [];
|
||||||
public List<string> ActiveIncidentIds { get; } = [];
|
public List<string> ActiveIncidentIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TreatyRuntime
|
public sealed class TreatyRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string TradeAccessPolicy { get; set; } = "restricted";
|
public string TradeAccessPolicy { get; set; } = "restricted";
|
||||||
public string MilitaryAccessPolicy { get; set; } = "restricted";
|
public string MilitaryAccessPolicy { get; set; } = "restricted";
|
||||||
public string? Summary { get; set; }
|
public string? Summary { get; set; }
|
||||||
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<string> FactionIds { get; } = [];
|
public List<string> FactionIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DiplomaticIncidentRuntime
|
public sealed class DiplomaticIncidentRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public required string SourceFactionId { get; set; }
|
public required string SourceFactionId { get; set; }
|
||||||
public required string TargetFactionId { get; set; }
|
public required string TargetFactionId { get; set; }
|
||||||
public string? SystemId { get; set; }
|
public string? SystemId { get; set; }
|
||||||
public string? BorderEdgeId { get; set; }
|
public string? BorderEdgeId { get; set; }
|
||||||
public required string Summary { get; set; }
|
public required string Summary { get; set; }
|
||||||
public float Severity { get; set; }
|
public float Severity { get; set; }
|
||||||
public float EscalationScore { get; set; }
|
public float EscalationScore { get; set; }
|
||||||
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public DateTimeOffset LastObservedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset LastObservedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class BorderTensionRuntime
|
public sealed class BorderTensionRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string RelationId { get; set; }
|
public required string RelationId { get; set; }
|
||||||
public required string BorderEdgeId { get; set; }
|
public required string BorderEdgeId { get; set; }
|
||||||
public required string FactionAId { get; set; }
|
public required string FactionAId { get; set; }
|
||||||
public required string FactionBId { get; set; }
|
public required string FactionBId { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public float TensionScore { get; set; }
|
public float TensionScore { get; set; }
|
||||||
public float IncidentScore { get; set; }
|
public float IncidentScore { get; set; }
|
||||||
public float MilitaryPressure { get; set; }
|
public float MilitaryPressure { get; set; }
|
||||||
public float AccessFriction { get; set; }
|
public float AccessFriction { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<string> SystemIds { get; } = [];
|
public List<string> SystemIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class WarStateRuntime
|
public sealed class WarStateRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string RelationId { get; set; }
|
public required string RelationId { get; set; }
|
||||||
public required string FactionAId { get; set; }
|
public required string FactionAId { get; set; }
|
||||||
public required string FactionBId { get; set; }
|
public required string FactionBId { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string WarGoal { get; set; } = "territorial-pressure";
|
public string WarGoal { get; set; } = "territorial-pressure";
|
||||||
public float EscalationScore { get; set; }
|
public float EscalationScore { get; set; }
|
||||||
public DateTimeOffset StartedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset StartedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public DateTimeOffset? CeasefireUntilUtc { get; set; }
|
public DateTimeOffset? CeasefireUntilUtc { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<string> ActiveFrontLineIds { get; } = [];
|
public List<string> ActiveFrontLineIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TerritoryStateRuntime
|
public sealed class TerritoryStateRuntime
|
||||||
{
|
{
|
||||||
public List<TerritoryClaimRuntime> Claims { get; } = [];
|
public List<TerritoryClaimRuntime> Claims { get; } = [];
|
||||||
public List<TerritoryInfluenceRuntime> Influences { get; } = [];
|
public List<TerritoryInfluenceRuntime> Influences { get; } = [];
|
||||||
public List<TerritoryControlStateRuntime> ControlStates { get; } = [];
|
public List<TerritoryControlStateRuntime> ControlStates { get; } = [];
|
||||||
public List<SectorStrategicProfileRuntime> StrategicProfiles { get; } = [];
|
public List<SectorStrategicProfileRuntime> StrategicProfiles { get; } = [];
|
||||||
public List<BorderEdgeRuntime> BorderEdges { get; } = [];
|
public List<BorderEdgeRuntime> BorderEdges { get; } = [];
|
||||||
public List<FrontLineRuntime> FrontLines { get; } = [];
|
public List<FrontLineRuntime> FrontLines { get; } = [];
|
||||||
public List<TerritoryZoneRuntime> Zones { get; } = [];
|
public List<TerritoryZoneRuntime> Zones { get; } = [];
|
||||||
public List<TerritoryPressureRuntime> Pressures { get; } = [];
|
public List<TerritoryPressureRuntime> Pressures { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TerritoryClaimRuntime
|
public sealed class TerritoryClaimRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public string? SourceClaimId { get; set; }
|
public string? SourceClaimId { get; set; }
|
||||||
public required string FactionId { get; set; }
|
public required string FactionId { get; set; }
|
||||||
public required string SystemId { get; set; }
|
public required string SystemId { get; set; }
|
||||||
public required string CelestialId { get; set; }
|
public required string CelestialId { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string ClaimKind { get; set; } = "infrastructure";
|
public string ClaimKind { get; set; } = "infrastructure";
|
||||||
public float ClaimStrength { get; set; }
|
public float ClaimStrength { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TerritoryInfluenceRuntime
|
public sealed class TerritoryInfluenceRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string SystemId { get; set; }
|
public required string SystemId { get; set; }
|
||||||
public required string FactionId { get; set; }
|
public required string FactionId { get; set; }
|
||||||
public float ClaimStrength { get; set; }
|
public float ClaimStrength { get; set; }
|
||||||
public float AssetStrength { get; set; }
|
public float AssetStrength { get; set; }
|
||||||
public float LogisticsStrength { get; set; }
|
public float LogisticsStrength { get; set; }
|
||||||
public float TotalInfluence { get; set; }
|
public float TotalInfluence { get; set; }
|
||||||
public bool IsContesting { get; set; }
|
public bool IsContesting { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TerritoryControlStateRuntime
|
public sealed class TerritoryControlStateRuntime
|
||||||
{
|
{
|
||||||
public required string SystemId { get; init; }
|
public required string SystemId { get; init; }
|
||||||
public string? ControllerFactionId { get; set; }
|
public string? ControllerFactionId { get; set; }
|
||||||
public string? PrimaryClaimantFactionId { get; set; }
|
public string? PrimaryClaimantFactionId { get; set; }
|
||||||
public string ControlKind { get; set; } = "unclaimed";
|
public string ControlKind { get; set; } = "unclaimed";
|
||||||
public bool IsContested { get; set; }
|
public bool IsContested { get; set; }
|
||||||
public float ControlScore { get; set; }
|
public float ControlScore { get; set; }
|
||||||
public float StrategicValue { get; set; }
|
public float StrategicValue { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<string> ClaimantFactionIds { get; } = [];
|
public List<string> ClaimantFactionIds { get; } = [];
|
||||||
public List<string> InfluencingFactionIds { get; } = [];
|
public List<string> InfluencingFactionIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class SectorStrategicProfileRuntime
|
public sealed class SectorStrategicProfileRuntime
|
||||||
{
|
{
|
||||||
public required string SystemId { get; init; }
|
public required string SystemId { get; init; }
|
||||||
public string? ControllerFactionId { get; set; }
|
public string? ControllerFactionId { get; set; }
|
||||||
public string ZoneKind { get; set; } = "unclaimed";
|
public string ZoneKind { get; set; } = "unclaimed";
|
||||||
public bool IsContested { get; set; }
|
public bool IsContested { get; set; }
|
||||||
public float StrategicValue { get; set; }
|
public float StrategicValue { get; set; }
|
||||||
public float SecurityRating { get; set; }
|
public float SecurityRating { get; set; }
|
||||||
public float TerritorialPressure { get; set; }
|
public float TerritorialPressure { get; set; }
|
||||||
public float LogisticsValue { get; set; }
|
public float LogisticsValue { get; set; }
|
||||||
public string? EconomicRegionId { get; set; }
|
public string? EconomicRegionId { get; set; }
|
||||||
public string? FrontLineId { get; set; }
|
public string? FrontLineId { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class BorderEdgeRuntime
|
public sealed class BorderEdgeRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string SourceSystemId { get; set; }
|
public required string SourceSystemId { get; set; }
|
||||||
public required string DestinationSystemId { get; set; }
|
public required string DestinationSystemId { get; set; }
|
||||||
public string? SourceFactionId { get; set; }
|
public string? SourceFactionId { get; set; }
|
||||||
public string? DestinationFactionId { get; set; }
|
public string? DestinationFactionId { get; set; }
|
||||||
public bool IsContested { get; set; }
|
public bool IsContested { get; set; }
|
||||||
public string? RelationId { get; set; }
|
public string? RelationId { get; set; }
|
||||||
public float TensionScore { get; set; }
|
public float TensionScore { get; set; }
|
||||||
public float CorridorImportance { get; set; }
|
public float CorridorImportance { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class FrontLineRuntime
|
public sealed class FrontLineRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public string Kind { get; set; } = "border-front";
|
public string Kind { get; set; } = "border-front";
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string? AnchorSystemId { get; set; }
|
public string? AnchorSystemId { get; set; }
|
||||||
public float PressureScore { get; set; }
|
public float PressureScore { get; set; }
|
||||||
public float SupplyRisk { get; set; }
|
public float SupplyRisk { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<string> FactionIds { get; } = [];
|
public List<string> FactionIds { get; } = [];
|
||||||
public List<string> SystemIds { get; } = [];
|
public List<string> SystemIds { get; } = [];
|
||||||
public List<string> BorderEdgeIds { get; } = [];
|
public List<string> BorderEdgeIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TerritoryZoneRuntime
|
public sealed class TerritoryZoneRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string SystemId { get; set; }
|
public required string SystemId { get; set; }
|
||||||
public string? FactionId { get; set; }
|
public string? FactionId { get; set; }
|
||||||
public string Kind { get; set; } = "unclaimed";
|
public string Kind { get; set; } = "unclaimed";
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string? Reason { get; set; }
|
public string? Reason { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TerritoryPressureRuntime
|
public sealed class TerritoryPressureRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string SystemId { get; set; }
|
public required string SystemId { get; set; }
|
||||||
public string? FactionId { get; set; }
|
public string? FactionId { get; set; }
|
||||||
public string Kind { get; set; } = "border-pressure";
|
public string Kind { get; set; } = "border-pressure";
|
||||||
public float PressureScore { get; set; }
|
public float PressureScore { get; set; }
|
||||||
public float SecurityScore { get; set; }
|
public float SecurityScore { get; set; }
|
||||||
public float HostileInfluence { get; set; }
|
public float HostileInfluence { get; set; }
|
||||||
public float CorridorRisk { get; set; }
|
public float CorridorRisk { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class EconomyRegionStateRuntime
|
public sealed class EconomyRegionStateRuntime
|
||||||
{
|
{
|
||||||
public List<EconomicRegionRuntime> Regions { get; } = [];
|
public List<EconomicRegionRuntime> Regions { get; } = [];
|
||||||
public List<SupplyNetworkRuntime> SupplyNetworks { get; } = [];
|
public List<SupplyNetworkRuntime> SupplyNetworks { get; } = [];
|
||||||
public List<LogisticsCorridorRuntime> Corridors { get; } = [];
|
public List<LogisticsCorridorRuntime> Corridors { get; } = [];
|
||||||
public List<RegionalProductionProfileRuntime> ProductionProfiles { get; } = [];
|
public List<RegionalProductionProfileRuntime> ProductionProfiles { get; } = [];
|
||||||
public List<RegionalTradeBalanceRuntime> TradeBalances { get; } = [];
|
public List<RegionalTradeBalanceRuntime> TradeBalances { get; } = [];
|
||||||
public List<RegionalBottleneckRuntime> Bottlenecks { get; } = [];
|
public List<RegionalBottleneckRuntime> Bottlenecks { get; } = [];
|
||||||
public List<RegionalSecurityAssessmentRuntime> SecurityAssessments { get; } = [];
|
public List<RegionalSecurityAssessmentRuntime> SecurityAssessments { get; } = [];
|
||||||
public List<RegionalEconomicAssessmentRuntime> EconomicAssessments { get; } = [];
|
public List<RegionalEconomicAssessmentRuntime> EconomicAssessments { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class EconomicRegionRuntime
|
public sealed class EconomicRegionRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public string? FactionId { get; set; }
|
public string? FactionId { get; set; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string Kind { get; set; } = "balanced-region";
|
public string Kind { get; set; } = "balanced-region";
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public required string CoreSystemId { get; set; }
|
public required string CoreSystemId { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<string> SystemIds { get; } = [];
|
public List<string> SystemIds { get; } = [];
|
||||||
public List<string> StationIds { get; } = [];
|
public List<string> StationIds { get; } = [];
|
||||||
public List<string> FrontLineIds { get; } = [];
|
public List<string> FrontLineIds { get; } = [];
|
||||||
public List<string> CorridorIds { get; } = [];
|
public List<string> CorridorIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class SupplyNetworkRuntime
|
public sealed class SupplyNetworkRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string RegionId { get; set; }
|
public required string RegionId { get; set; }
|
||||||
public float ThroughputScore { get; set; }
|
public float ThroughputScore { get; set; }
|
||||||
public float RiskScore { get; set; }
|
public float RiskScore { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<string> StationIds { get; } = [];
|
public List<string> StationIds { get; } = [];
|
||||||
public List<string> ProducerItemIds { get; } = [];
|
public List<string> ProducerItemIds { get; } = [];
|
||||||
public List<string> ConsumerItemIds { get; } = [];
|
public List<string> ConsumerItemIds { get; } = [];
|
||||||
public List<string> ConstructionItemIds { get; } = [];
|
public List<string> ConstructionItemIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class LogisticsCorridorRuntime
|
public sealed class LogisticsCorridorRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public string? FactionId { get; set; }
|
public string? FactionId { get; set; }
|
||||||
public string Kind { get; set; } = "supply-corridor";
|
public string Kind { get; set; } = "supply-corridor";
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public float RiskScore { get; set; }
|
public float RiskScore { get; set; }
|
||||||
public float ThroughputScore { get; set; }
|
public float ThroughputScore { get; set; }
|
||||||
public string AccessState { get; set; } = "restricted";
|
public string AccessState { get; set; } = "restricted";
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<string> SystemPathIds { get; } = [];
|
public List<string> SystemPathIds { get; } = [];
|
||||||
public List<string> RegionIds { get; } = [];
|
public List<string> RegionIds { get; } = [];
|
||||||
public List<string> BorderEdgeIds { get; } = [];
|
public List<string> BorderEdgeIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RegionalProductionProfileRuntime
|
public sealed class RegionalProductionProfileRuntime
|
||||||
{
|
{
|
||||||
public required string RegionId { get; set; }
|
public required string RegionId { get; set; }
|
||||||
public string PrimaryIndustry { get; set; } = "mixed";
|
public string PrimaryIndustry { get; set; } = "mixed";
|
||||||
public int ShipyardCount { get; set; }
|
public int ShipyardCount { get; set; }
|
||||||
public int StationCount { get; set; }
|
public int StationCount { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public List<string> ProducedItemIds { get; } = [];
|
public List<string> ProducedItemIds { get; } = [];
|
||||||
public List<string> ScarceItemIds { get; } = [];
|
public List<string> ScarceItemIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RegionalTradeBalanceRuntime
|
public sealed class RegionalTradeBalanceRuntime
|
||||||
{
|
{
|
||||||
public required string RegionId { get; set; }
|
public required string RegionId { get; set; }
|
||||||
public int ImportsRequiredCount { get; set; }
|
public int ImportsRequiredCount { get; set; }
|
||||||
public int ExportsSurplusCount { get; set; }
|
public int ExportsSurplusCount { get; set; }
|
||||||
public int CriticalShortageCount { get; set; }
|
public int CriticalShortageCount { get; set; }
|
||||||
public float NetTradeScore { get; set; }
|
public float NetTradeScore { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RegionalBottleneckRuntime
|
public sealed class RegionalBottleneckRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string RegionId { get; set; }
|
public required string RegionId { get; set; }
|
||||||
public required string ItemId { get; set; }
|
public required string ItemId { get; set; }
|
||||||
public string Cause { get; set; } = "regional-shortage";
|
public string Cause { get; set; } = "regional-shortage";
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public float Severity { get; set; }
|
public float Severity { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RegionalSecurityAssessmentRuntime
|
public sealed class RegionalSecurityAssessmentRuntime
|
||||||
{
|
{
|
||||||
public required string RegionId { get; set; }
|
public required string RegionId { get; set; }
|
||||||
public float SupplyRisk { get; set; }
|
public float SupplyRisk { get; set; }
|
||||||
public float BorderPressure { get; set; }
|
public float BorderPressure { get; set; }
|
||||||
public int ActiveWarCount { get; set; }
|
public int ActiveWarCount { get; set; }
|
||||||
public int HostileRelationCount { get; set; }
|
public int HostileRelationCount { get; set; }
|
||||||
public float AccessFriction { get; set; }
|
public float AccessFriction { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RegionalEconomicAssessmentRuntime
|
public sealed class RegionalEconomicAssessmentRuntime
|
||||||
{
|
{
|
||||||
public required string RegionId { get; set; }
|
public required string RegionId { get; set; }
|
||||||
public float SustainmentScore { get; set; }
|
public float SustainmentScore { get; set; }
|
||||||
public float ProductionDepth { get; set; }
|
public float ProductionDepth { get; set; }
|
||||||
public float ConstructionPressure { get; set; }
|
public float ConstructionPressure { get; set; }
|
||||||
public float CorridorDependency { get; set; }
|
public float CorridorDependency { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,53 +2,53 @@ namespace SpaceGame.Api.Industry.Planning;
|
|||||||
|
|
||||||
internal static class CommodityOperationalSignal
|
internal static class CommodityOperationalSignal
|
||||||
{
|
{
|
||||||
internal static float ComputeNeedScore(FactionCommoditySnapshot commodity, float targetLevelSeconds)
|
internal static float ComputeNeedScore(FactionCommoditySnapshot commodity, float targetLevelSeconds)
|
||||||
{
|
|
||||||
var productionDeficit = MathF.Max(0f, commodity.ConsumptionRatePerSecond - commodity.ProjectedProductionRatePerSecond);
|
|
||||||
var levelDeficit = MathF.Max(0f, targetLevelSeconds - commodity.LevelSeconds) / MathF.Max(targetLevelSeconds, 1f);
|
|
||||||
var backlogPressure = MathF.Max(0f, commodity.BuyBacklog + commodity.ReservedForConstruction - commodity.AvailableStock);
|
|
||||||
|
|
||||||
var levelWeight = commodity.Level switch
|
|
||||||
{
|
{
|
||||||
CommodityLevelKind.Critical => 140f,
|
var productionDeficit = MathF.Max(0f, commodity.ConsumptionRatePerSecond - commodity.ProjectedProductionRatePerSecond);
|
||||||
CommodityLevelKind.Low => 80f,
|
var levelDeficit = MathF.Max(0f, targetLevelSeconds - commodity.LevelSeconds) / MathF.Max(targetLevelSeconds, 1f);
|
||||||
CommodityLevelKind.Stable => 20f,
|
var backlogPressure = MathF.Max(0f, commodity.BuyBacklog + commodity.ReservedForConstruction - commodity.AvailableStock);
|
||||||
_ => 0f,
|
|
||||||
};
|
|
||||||
|
|
||||||
return levelWeight
|
var levelWeight = commodity.Level switch
|
||||||
+ (productionDeficit * 140f)
|
{
|
||||||
+ (levelDeficit * 120f)
|
CommodityLevelKind.Critical => 140f,
|
||||||
+ backlogPressure;
|
CommodityLevelKind.Low => 80f,
|
||||||
}
|
CommodityLevelKind.Stable => 20f,
|
||||||
|
_ => 0f,
|
||||||
|
};
|
||||||
|
|
||||||
internal static bool IsOperational(FactionCommoditySnapshot commodity, float targetLevelSeconds) =>
|
return levelWeight
|
||||||
commodity.ProjectedProductionRatePerSecond > 0.01f
|
+ (productionDeficit * 140f)
|
||||||
&& commodity.ProjectedNetRatePerSecond >= -0.01f
|
+ (levelDeficit * 120f)
|
||||||
&& commodity.LevelSeconds >= targetLevelSeconds
|
+ backlogPressure;
|
||||||
&& commodity.Level is CommodityLevelKind.Stable or CommodityLevelKind.Surplus;
|
|
||||||
|
|
||||||
internal static bool IsStrained(FactionCommoditySnapshot commodity, float targetLevelSeconds) =>
|
|
||||||
!IsOperational(commodity, targetLevelSeconds)
|
|
||||||
|| commodity.Level is CommodityLevelKind.Critical or CommodityLevelKind.Low;
|
|
||||||
|
|
||||||
internal static float ComputeFeasibilityFactor(FactionCommoditySnapshot commodity, float targetLevelSeconds)
|
|
||||||
{
|
|
||||||
if (commodity.AvailableStock <= 0.01f && commodity.ProjectedProductionRatePerSecond <= 0.01f)
|
|
||||||
{
|
|
||||||
return 0.65f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (commodity.Level is CommodityLevelKind.Critical)
|
internal static bool IsOperational(FactionCommoditySnapshot commodity, float targetLevelSeconds) =>
|
||||||
{
|
commodity.ProjectedProductionRatePerSecond > 0.01f
|
||||||
return 0.72f;
|
&& commodity.ProjectedNetRatePerSecond >= -0.01f
|
||||||
}
|
&& commodity.LevelSeconds >= targetLevelSeconds
|
||||||
|
&& commodity.Level is CommodityLevelKind.Stable or CommodityLevelKind.Surplus;
|
||||||
|
|
||||||
if (commodity.Level is CommodityLevelKind.Low || commodity.LevelSeconds < targetLevelSeconds)
|
internal static bool IsStrained(FactionCommoditySnapshot commodity, float targetLevelSeconds) =>
|
||||||
{
|
!IsOperational(commodity, targetLevelSeconds)
|
||||||
return 0.84f;
|
|| commodity.Level is CommodityLevelKind.Critical or CommodityLevelKind.Low;
|
||||||
}
|
|
||||||
|
|
||||||
return 1f;
|
internal static float ComputeFeasibilityFactor(FactionCommoditySnapshot commodity, float targetLevelSeconds)
|
||||||
}
|
{
|
||||||
|
if (commodity.AvailableStock <= 0.01f && commodity.ProjectedProductionRatePerSecond <= 0.01f)
|
||||||
|
{
|
||||||
|
return 0.65f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commodity.Level is CommodityLevelKind.Critical)
|
||||||
|
{
|
||||||
|
return 0.72f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (commodity.Level is CommodityLevelKind.Low || commodity.LevelSeconds < targetLevelSeconds)
|
||||||
|
{
|
||||||
|
return 0.84f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,202 +4,202 @@ using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
|
|||||||
|
|
||||||
internal sealed class FactionEconomySnapshot
|
internal sealed class FactionEconomySnapshot
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, FactionCommoditySnapshot> commodities = new(StringComparer.Ordinal);
|
private readonly Dictionary<string, FactionCommoditySnapshot> commodities = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
internal IReadOnlyDictionary<string, FactionCommoditySnapshot> Commodities => commodities;
|
internal IReadOnlyDictionary<string, FactionCommoditySnapshot> Commodities => commodities;
|
||||||
|
|
||||||
internal FactionCommoditySnapshot GetCommodity(string itemId)
|
internal FactionCommoditySnapshot GetCommodity(string itemId)
|
||||||
{
|
|
||||||
if (!commodities.TryGetValue(itemId, out var commodity))
|
|
||||||
{
|
{
|
||||||
commodity = new FactionCommoditySnapshot(itemId);
|
if (!commodities.TryGetValue(itemId, out var commodity))
|
||||||
commodities[itemId] = commodity;
|
{
|
||||||
}
|
commodity = new FactionCommoditySnapshot(itemId);
|
||||||
|
commodities[itemId] = commodity;
|
||||||
|
}
|
||||||
|
|
||||||
return commodity;
|
return commodity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class FactionCommoditySnapshot
|
internal sealed class FactionCommoditySnapshot
|
||||||
{
|
{
|
||||||
internal FactionCommoditySnapshot(string itemId)
|
internal FactionCommoditySnapshot(string itemId)
|
||||||
{
|
{
|
||||||
ItemId = itemId;
|
ItemId = itemId;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal string ItemId { get; }
|
internal string ItemId { get; }
|
||||||
internal float OnHand { get; set; }
|
internal float OnHand { get; set; }
|
||||||
internal float ReservedForConstruction { get; set; }
|
internal float ReservedForConstruction { get; set; }
|
||||||
internal float BuyBacklog { get; set; }
|
internal float BuyBacklog { get; set; }
|
||||||
internal float SellBacklog { get; set; }
|
internal float SellBacklog { get; set; }
|
||||||
internal float Inbound { get; set; }
|
internal float Inbound { get; set; }
|
||||||
internal float ProductionRatePerSecond { get; set; }
|
internal float ProductionRatePerSecond { get; set; }
|
||||||
internal float CommittedProductionRatePerSecond { get; set; }
|
internal float CommittedProductionRatePerSecond { get; set; }
|
||||||
internal float ConsumptionRatePerSecond { get; set; }
|
internal float ConsumptionRatePerSecond { get; set; }
|
||||||
|
|
||||||
internal float AvailableStock => MathF.Max(0f, OnHand + Inbound - ReservedForConstruction);
|
internal float AvailableStock => MathF.Max(0f, OnHand + Inbound - ReservedForConstruction);
|
||||||
internal float NetRatePerSecond => ProductionRatePerSecond - ConsumptionRatePerSecond;
|
internal float NetRatePerSecond => ProductionRatePerSecond - ConsumptionRatePerSecond;
|
||||||
internal float ProjectedProductionRatePerSecond => ProductionRatePerSecond + CommittedProductionRatePerSecond;
|
internal float ProjectedProductionRatePerSecond => ProductionRatePerSecond + CommittedProductionRatePerSecond;
|
||||||
internal float ProjectedNetRatePerSecond => ProjectedProductionRatePerSecond - ConsumptionRatePerSecond;
|
internal float ProjectedNetRatePerSecond => ProjectedProductionRatePerSecond - ConsumptionRatePerSecond;
|
||||||
internal float OperationalUsageRatePerSecond => MathF.Max(ConsumptionRatePerSecond, BuyBacklog / 180f);
|
internal float OperationalUsageRatePerSecond => MathF.Max(ConsumptionRatePerSecond, BuyBacklog / 180f);
|
||||||
internal float LevelSeconds => AvailableStock <= 0.01f
|
internal float LevelSeconds => AvailableStock <= 0.01f
|
||||||
? 0f
|
? 0f
|
||||||
: AvailableStock / MathF.Max(OperationalUsageRatePerSecond, 0.01f);
|
: AvailableStock / MathF.Max(OperationalUsageRatePerSecond, 0.01f);
|
||||||
|
|
||||||
internal CommodityLevelKind Level =>
|
internal CommodityLevelKind Level =>
|
||||||
LevelSeconds switch
|
LevelSeconds switch
|
||||||
{
|
{
|
||||||
<= 60f => CommodityLevelKind.Critical,
|
<= 60f => CommodityLevelKind.Critical,
|
||||||
<= 180f => CommodityLevelKind.Low,
|
<= 180f => CommodityLevelKind.Low,
|
||||||
<= 480f => CommodityLevelKind.Stable,
|
<= 480f => CommodityLevelKind.Stable,
|
||||||
_ => CommodityLevelKind.Surplus,
|
_ => CommodityLevelKind.Surplus,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum CommodityLevelKind
|
internal enum CommodityLevelKind
|
||||||
{
|
{
|
||||||
Critical,
|
Critical,
|
||||||
Low,
|
Low,
|
||||||
Stable,
|
Stable,
|
||||||
Surplus,
|
Surplus,
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class FactionEconomyAnalyzer
|
internal static class FactionEconomyAnalyzer
|
||||||
{
|
{
|
||||||
internal static FactionEconomySnapshot Build(SimulationWorld world, string factionId)
|
internal static FactionEconomySnapshot Build(SimulationWorld world, string factionId)
|
||||||
{
|
|
||||||
var snapshot = new FactionEconomySnapshot();
|
|
||||||
|
|
||||||
foreach (var station in world.Stations.Where(station => string.Equals(station.FactionId, factionId, StringComparison.Ordinal)))
|
|
||||||
{
|
{
|
||||||
foreach (var (itemId, amount) in station.Inventory)
|
var snapshot = new FactionEconomySnapshot();
|
||||||
{
|
|
||||||
snapshot.GetCommodity(itemId).OnHand += amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var laneKey in StationSimulationService.GetStationProductionLanes(world, station))
|
foreach (var station in world.Stations.Where(station => string.Equals(station.FactionId, factionId, StringComparison.Ordinal)))
|
||||||
{
|
|
||||||
var recipe = StationSimulationService.SelectProductionRecipe(world, station, laneKey);
|
|
||||||
if (recipe is null)
|
|
||||||
{
|
{
|
||||||
continue;
|
foreach (var (itemId, amount) in station.Inventory)
|
||||||
|
{
|
||||||
|
snapshot.GetCommodity(itemId).OnHand += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var laneKey in StationSimulationService.GetStationProductionLanes(world, station))
|
||||||
|
{
|
||||||
|
var recipe = StationSimulationService.SelectProductionRecipe(world, station, laneKey);
|
||||||
|
if (recipe is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var throughput = StationSimulationService.GetStationProductionThroughput(world, station, recipe);
|
||||||
|
var cyclesPerSecond = (station.WorkforceEffectiveRatio * throughput) / MathF.Max(recipe.Duration, 0.01f);
|
||||||
|
if (cyclesPerSecond <= 0.0001f)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var input in recipe.Inputs)
|
||||||
|
{
|
||||||
|
snapshot.GetCommodity(input.ItemId).ConsumptionRatePerSecond += input.Amount * cyclesPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var output in recipe.Outputs)
|
||||||
|
{
|
||||||
|
snapshot.GetCommodity(output.ItemId).ProductionRatePerSecond += output.Amount * cyclesPerSecond;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var throughput = StationSimulationService.GetStationProductionThroughput(world, station, recipe);
|
foreach (var order in world.MarketOrders.Where(order =>
|
||||||
var cyclesPerSecond = (station.WorkforceEffectiveRatio * throughput) / MathF.Max(recipe.Duration, 0.01f);
|
string.Equals(order.FactionId, factionId, StringComparison.Ordinal)
|
||||||
if (cyclesPerSecond <= 0.0001f)
|
&& order.State != MarketOrderStateKinds.Cancelled
|
||||||
|
&& order.RemainingAmount > 0.01f))
|
||||||
{
|
{
|
||||||
continue;
|
var commodity = snapshot.GetCommodity(order.ItemId);
|
||||||
|
if (string.Equals(order.Kind, MarketOrderKinds.Buy, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
commodity.BuyBacklog += order.RemainingAmount;
|
||||||
|
}
|
||||||
|
else if (string.Equals(order.Kind, MarketOrderKinds.Sell, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
commodity.SellBacklog += order.RemainingAmount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var input in recipe.Inputs)
|
foreach (var site in world.ConstructionSites.Where(site =>
|
||||||
|
string.Equals(site.FactionId, factionId, StringComparison.Ordinal)
|
||||||
|
&& site.State is not ConstructionSiteStateKinds.Completed and not ConstructionSiteStateKinds.Destroyed))
|
||||||
{
|
{
|
||||||
snapshot.GetCommodity(input.ItemId).ConsumptionRatePerSecond += input.Amount * cyclesPerSecond;
|
ApplyCommittedProduction(world, snapshot, site);
|
||||||
|
|
||||||
|
foreach (var required in site.RequiredItems)
|
||||||
|
{
|
||||||
|
var remaining = MathF.Max(0f, required.Value - GetConstructionDeliveredAmount(world, site, required.Key));
|
||||||
|
if (remaining > 0.01f)
|
||||||
|
{
|
||||||
|
snapshot.GetCommodity(required.Key).ReservedForConstruction += remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var output in recipe.Outputs)
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyCommittedProduction(
|
||||||
|
SimulationWorld world,
|
||||||
|
FactionEconomySnapshot snapshot,
|
||||||
|
ConstructionSiteRuntime site)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(site.BlueprintId)
|
||||||
|
|| !world.ModuleRecipes.TryGetValue(site.BlueprintId, out var recipe))
|
||||||
{
|
{
|
||||||
snapshot.GetCommodity(output.ItemId).ProductionRatePerSecond += output.Amount * cyclesPerSecond;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var order in world.MarketOrders.Where(order =>
|
var recipeOutputs = world.Recipes.Values
|
||||||
string.Equals(order.FactionId, factionId, StringComparison.Ordinal)
|
.Where(candidate => string.Equals(StationSimulationService.GetStationProductionLaneKey(world, candidate), site.BlueprintId, StringComparison.Ordinal))
|
||||||
&& order.State != MarketOrderStateKinds.Cancelled
|
.SelectMany(candidate => candidate.Outputs)
|
||||||
&& order.RemainingAmount > 0.01f))
|
.GroupBy(output => output.ItemId, StringComparer.Ordinal)
|
||||||
{
|
.ToDictionary(group => group.Key, group => group.Sum(output => output.Amount), StringComparer.Ordinal);
|
||||||
var commodity = snapshot.GetCommodity(order.ItemId);
|
if (recipeOutputs.Count == 0)
|
||||||
if (string.Equals(order.Kind, MarketOrderKinds.Buy, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
commodity.BuyBacklog += order.RemainingAmount;
|
|
||||||
}
|
|
||||||
else if (string.Equals(order.Kind, MarketOrderKinds.Sell, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
commodity.SellBacklog += order.RemainingAmount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var site in world.ConstructionSites.Where(site =>
|
|
||||||
string.Equals(site.FactionId, factionId, StringComparison.Ordinal)
|
|
||||||
&& site.State is not ConstructionSiteStateKinds.Completed and not ConstructionSiteStateKinds.Destroyed))
|
|
||||||
{
|
|
||||||
ApplyCommittedProduction(world, snapshot, site);
|
|
||||||
|
|
||||||
foreach (var required in site.RequiredItems)
|
|
||||||
{
|
|
||||||
var remaining = MathF.Max(0f, required.Value - GetConstructionDeliveredAmount(world, site, required.Key));
|
|
||||||
if (remaining > 0.01f)
|
|
||||||
{
|
{
|
||||||
snapshot.GetCommodity(required.Key).ReservedForConstruction += remaining;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var materialFraction = 0f;
|
||||||
|
var materialTerms = 0;
|
||||||
|
foreach (var required in site.RequiredItems)
|
||||||
|
{
|
||||||
|
materialTerms += 1;
|
||||||
|
materialFraction += required.Value <= 0.01f
|
||||||
|
? 1f
|
||||||
|
: Math.Clamp(GetConstructionDeliveredAmount(world, site, required.Key) / required.Value, 0f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
materialFraction = materialTerms == 0 ? 1f : materialFraction / materialTerms;
|
||||||
|
|
||||||
|
var buildFraction = recipe.Duration <= 0.01f
|
||||||
|
? 0f
|
||||||
|
: Math.Clamp(site.Progress / recipe.Duration, 0f, 1f);
|
||||||
|
var readiness = site.State switch
|
||||||
|
{
|
||||||
|
ConstructionSiteStateKinds.Active => 0.3f,
|
||||||
|
ConstructionSiteStateKinds.Planned => 0.15f,
|
||||||
|
_ => 0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
readiness += materialFraction * 0.45f;
|
||||||
|
readiness += buildFraction * 0.25f;
|
||||||
|
|
||||||
|
if (site.AssignedConstructorShipIds.Count > 0)
|
||||||
|
{
|
||||||
|
readiness += 0.1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
readiness = Math.Clamp(readiness, 0f, 1f);
|
||||||
|
if (readiness <= 0.01f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cyclesPerSecond = readiness / MathF.Max(recipe.Duration, 0.01f);
|
||||||
|
foreach (var (productItemId, amount) in recipeOutputs)
|
||||||
|
{
|
||||||
|
snapshot.GetCommodity(productItemId).CommittedProductionRatePerSecond += amount * cyclesPerSecond;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ApplyCommittedProduction(
|
|
||||||
SimulationWorld world,
|
|
||||||
FactionEconomySnapshot snapshot,
|
|
||||||
ConstructionSiteRuntime site)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(site.BlueprintId)
|
|
||||||
|| !world.ModuleRecipes.TryGetValue(site.BlueprintId, out var recipe))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var recipeOutputs = world.Recipes.Values
|
|
||||||
.Where(candidate => string.Equals(StationSimulationService.GetStationProductionLaneKey(world, candidate), site.BlueprintId, StringComparison.Ordinal))
|
|
||||||
.SelectMany(candidate => candidate.Outputs)
|
|
||||||
.GroupBy(output => output.ItemId, StringComparer.Ordinal)
|
|
||||||
.ToDictionary(group => group.Key, group => group.Sum(output => output.Amount), StringComparer.Ordinal);
|
|
||||||
if (recipeOutputs.Count == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var materialFraction = 0f;
|
|
||||||
var materialTerms = 0;
|
|
||||||
foreach (var required in site.RequiredItems)
|
|
||||||
{
|
|
||||||
materialTerms += 1;
|
|
||||||
materialFraction += required.Value <= 0.01f
|
|
||||||
? 1f
|
|
||||||
: Math.Clamp(GetConstructionDeliveredAmount(world, site, required.Key) / required.Value, 0f, 1f);
|
|
||||||
}
|
|
||||||
|
|
||||||
materialFraction = materialTerms == 0 ? 1f : materialFraction / materialTerms;
|
|
||||||
|
|
||||||
var buildFraction = recipe.Duration <= 0.01f
|
|
||||||
? 0f
|
|
||||||
: Math.Clamp(site.Progress / recipe.Duration, 0f, 1f);
|
|
||||||
var readiness = site.State switch
|
|
||||||
{
|
|
||||||
ConstructionSiteStateKinds.Active => 0.3f,
|
|
||||||
ConstructionSiteStateKinds.Planned => 0.15f,
|
|
||||||
_ => 0f,
|
|
||||||
};
|
|
||||||
|
|
||||||
readiness += materialFraction * 0.45f;
|
|
||||||
readiness += buildFraction * 0.25f;
|
|
||||||
|
|
||||||
if (site.AssignedConstructorShipIds.Count > 0)
|
|
||||||
{
|
|
||||||
readiness += 0.1f;
|
|
||||||
}
|
|
||||||
|
|
||||||
readiness = Math.Clamp(readiness, 0f, 1f);
|
|
||||||
if (readiness <= 0.01f)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cyclesPerSecond = readiness / MathF.Max(recipe.Duration, 0.01f);
|
|
||||||
foreach (var (productItemId, amount) in recipeOutputs)
|
|
||||||
{
|
|
||||||
snapshot.GetCommodity(productItemId).CommittedProductionRatePerSecond += amount * cyclesPerSecond;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,52 +2,52 @@ namespace SpaceGame.Api.Industry.Planning;
|
|||||||
|
|
||||||
public sealed class ProductionGraph
|
public sealed class ProductionGraph
|
||||||
{
|
{
|
||||||
public required IReadOnlyDictionary<string, ProductionCommodityNode> Commodities { get; init; }
|
public required IReadOnlyDictionary<string, ProductionCommodityNode> Commodities { get; init; }
|
||||||
public required IReadOnlyDictionary<string, ProductionProcessNode> Processes { get; init; }
|
public required IReadOnlyDictionary<string, ProductionProcessNode> Processes { get; init; }
|
||||||
public required IReadOnlyDictionary<string, IReadOnlyList<ProductionProcessNode>> ProcessesByOutputId { get; init; }
|
public required IReadOnlyDictionary<string, IReadOnlyList<ProductionProcessNode>> ProcessesByOutputId { get; init; }
|
||||||
public required IReadOnlyDictionary<string, IReadOnlyList<ProductionProcessNode>> ProcessesByInputId { get; init; }
|
public required IReadOnlyDictionary<string, IReadOnlyList<ProductionProcessNode>> ProcessesByInputId { get; init; }
|
||||||
public required IReadOnlyDictionary<string, IReadOnlyList<string>> OutputsByModuleId { get; init; }
|
public required IReadOnlyDictionary<string, IReadOnlyList<string>> OutputsByModuleId { get; init; }
|
||||||
|
|
||||||
public IReadOnlyList<ProductionProcessNode> GetProcessesForOutput(string itemId) =>
|
public IReadOnlyList<ProductionProcessNode> GetProcessesForOutput(string itemId) =>
|
||||||
ProcessesByOutputId.TryGetValue(itemId, out var processes) ? processes : [];
|
ProcessesByOutputId.TryGetValue(itemId, out var processes) ? processes : [];
|
||||||
|
|
||||||
public IReadOnlyList<ProductionProcessNode> GetProcessesForInput(string itemId) =>
|
public IReadOnlyList<ProductionProcessNode> GetProcessesForInput(string itemId) =>
|
||||||
ProcessesByInputId.TryGetValue(itemId, out var processes) ? processes : [];
|
ProcessesByInputId.TryGetValue(itemId, out var processes) ? processes : [];
|
||||||
|
|
||||||
public string? GetPrimaryProducerModule(string itemId) =>
|
public string? GetPrimaryProducerModule(string itemId) =>
|
||||||
GetProcessesForOutput(itemId)
|
GetProcessesForOutput(itemId)
|
||||||
.SelectMany(process => process.RequiredModuleIds)
|
.SelectMany(process => process.RequiredModuleIds)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
|
|
||||||
public string? GetPrimaryOutputForModule(string moduleId) =>
|
public string? GetPrimaryOutputForModule(string moduleId) =>
|
||||||
OutputsByModuleId.TryGetValue(moduleId, out var outputs)
|
OutputsByModuleId.TryGetValue(moduleId, out var outputs)
|
||||||
? outputs.FirstOrDefault()
|
? outputs.FirstOrDefault()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
public IReadOnlyList<string> GetImmediateInputs(string itemId) =>
|
public IReadOnlyList<string> GetImmediateInputs(string itemId) =>
|
||||||
GetProcessesForOutput(itemId)
|
GetProcessesForOutput(itemId)
|
||||||
.SelectMany(process => process.Inputs.Keys)
|
.SelectMany(process => process.Inputs.Keys)
|
||||||
.Distinct(StringComparer.Ordinal)
|
.Distinct(StringComparer.Ordinal)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ProductionCommodityNode
|
public sealed class ProductionCommodityNode
|
||||||
{
|
{
|
||||||
public required string ItemId { get; init; }
|
public required string ItemId { get; init; }
|
||||||
public required string Name { get; init; }
|
public required string Name { get; init; }
|
||||||
public required string Group { get; init; }
|
public required string Group { get; init; }
|
||||||
public required string CargoKind { get; init; }
|
public required string CargoKind { get; init; }
|
||||||
public List<string> ProducerProcessIds { get; } = [];
|
public List<string> ProducerProcessIds { get; } = [];
|
||||||
public List<string> ConsumerProcessIds { get; } = [];
|
public List<string> ConsumerProcessIds { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ProductionProcessNode
|
public sealed class ProductionProcessNode
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; init; }
|
public required string Label { get; init; }
|
||||||
public required string FacilityCategory { get; init; }
|
public required string FacilityCategory { get; init; }
|
||||||
public required IReadOnlyList<string> RequiredModuleIds { get; init; }
|
public required IReadOnlyList<string> RequiredModuleIds { get; init; }
|
||||||
public required IReadOnlyDictionary<string, float> Inputs { get; init; }
|
public required IReadOnlyDictionary<string, float> Inputs { get; init; }
|
||||||
public required IReadOnlyDictionary<string, float> Outputs { get; init; }
|
public required IReadOnlyDictionary<string, float> Outputs { get; init; }
|
||||||
public required bool ProducesShip { get; init; }
|
public required bool ProducesShip { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,104 +2,104 @@ namespace SpaceGame.Api.Industry.Planning;
|
|||||||
|
|
||||||
internal static class ProductionGraphBuilder
|
internal static class ProductionGraphBuilder
|
||||||
{
|
{
|
||||||
internal static ProductionGraph Build(
|
internal static ProductionGraph Build(
|
||||||
IReadOnlyCollection<ItemDefinition> items,
|
IReadOnlyCollection<ItemDefinition> items,
|
||||||
IReadOnlyCollection<RecipeDefinition> recipes,
|
IReadOnlyCollection<RecipeDefinition> recipes,
|
||||||
IReadOnlyCollection<ModuleDefinition> modules)
|
IReadOnlyCollection<ModuleDefinition> modules)
|
||||||
{
|
|
||||||
var commodities = items.ToDictionary(
|
|
||||||
item => item.Id,
|
|
||||||
item => new ProductionCommodityNode
|
|
||||||
{
|
|
||||||
ItemId = item.Id,
|
|
||||||
Name = item.Name,
|
|
||||||
Group = item.Group,
|
|
||||||
CargoKind = item.CargoKind,
|
|
||||||
},
|
|
||||||
StringComparer.Ordinal);
|
|
||||||
|
|
||||||
var processes = new Dictionary<string, ProductionProcessNode>(StringComparer.Ordinal);
|
|
||||||
var processesByOutputId = new Dictionary<string, List<ProductionProcessNode>>(StringComparer.Ordinal);
|
|
||||||
var processesByInputId = new Dictionary<string, List<ProductionProcessNode>>(StringComparer.Ordinal);
|
|
||||||
var outputsByModuleId = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal);
|
|
||||||
|
|
||||||
foreach (var recipe in recipes)
|
|
||||||
{
|
{
|
||||||
var outputs = recipe.Outputs
|
var commodities = items.ToDictionary(
|
||||||
.GroupBy(output => output.ItemId, StringComparer.Ordinal)
|
item => item.Id,
|
||||||
.ToDictionary(group => group.Key, group => group.Sum(output => output.Amount), StringComparer.Ordinal);
|
item => new ProductionCommodityNode
|
||||||
var inputs = recipe.Inputs
|
{
|
||||||
.GroupBy(input => input.ItemId, StringComparer.Ordinal)
|
ItemId = item.Id,
|
||||||
.ToDictionary(group => group.Key, group => group.Sum(input => input.Amount), StringComparer.Ordinal);
|
Name = item.Name,
|
||||||
var process = new ProductionProcessNode
|
Group = item.Group,
|
||||||
{
|
CargoKind = item.CargoKind,
|
||||||
Id = recipe.Id,
|
},
|
||||||
Label = recipe.Label,
|
StringComparer.Ordinal);
|
||||||
FacilityCategory = recipe.FacilityCategory,
|
|
||||||
RequiredModuleIds = recipe.RequiredModules.ToList(),
|
|
||||||
Inputs = inputs,
|
|
||||||
Outputs = outputs,
|
|
||||||
ProducesShip = recipe.ShipOutputId is not null,
|
|
||||||
};
|
|
||||||
|
|
||||||
processes[process.Id] = process;
|
var processes = new Dictionary<string, ProductionProcessNode>(StringComparer.Ordinal);
|
||||||
|
var processesByOutputId = new Dictionary<string, List<ProductionProcessNode>>(StringComparer.Ordinal);
|
||||||
|
var processesByInputId = new Dictionary<string, List<ProductionProcessNode>>(StringComparer.Ordinal);
|
||||||
|
var outputsByModuleId = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal);
|
||||||
|
|
||||||
foreach (var output in outputs.Keys)
|
foreach (var recipe in recipes)
|
||||||
{
|
|
||||||
if (!commodities.ContainsKey(output))
|
|
||||||
{
|
{
|
||||||
continue;
|
var outputs = recipe.Outputs
|
||||||
|
.GroupBy(output => output.ItemId, StringComparer.Ordinal)
|
||||||
|
.ToDictionary(group => group.Key, group => group.Sum(output => output.Amount), StringComparer.Ordinal);
|
||||||
|
var inputs = recipe.Inputs
|
||||||
|
.GroupBy(input => input.ItemId, StringComparer.Ordinal)
|
||||||
|
.ToDictionary(group => group.Key, group => group.Sum(input => input.Amount), StringComparer.Ordinal);
|
||||||
|
var process = new ProductionProcessNode
|
||||||
|
{
|
||||||
|
Id = recipe.Id,
|
||||||
|
Label = recipe.Label,
|
||||||
|
FacilityCategory = recipe.FacilityCategory,
|
||||||
|
RequiredModuleIds = recipe.RequiredModules.ToList(),
|
||||||
|
Inputs = inputs,
|
||||||
|
Outputs = outputs,
|
||||||
|
ProducesShip = recipe.ShipOutputId is not null,
|
||||||
|
};
|
||||||
|
|
||||||
|
processes[process.Id] = process;
|
||||||
|
|
||||||
|
foreach (var output in outputs.Keys)
|
||||||
|
{
|
||||||
|
if (!commodities.ContainsKey(output))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
commodities[output].ProducerProcessIds.Add(process.Id);
|
||||||
|
if (!processesByOutputId.TryGetValue(output, out var outputProcesses))
|
||||||
|
{
|
||||||
|
outputProcesses = [];
|
||||||
|
processesByOutputId[output] = outputProcesses;
|
||||||
|
}
|
||||||
|
|
||||||
|
outputProcesses.Add(process);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var input in inputs.Keys)
|
||||||
|
{
|
||||||
|
if (!commodities.ContainsKey(input))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
commodities[input].ConsumerProcessIds.Add(process.Id);
|
||||||
|
if (!processesByInputId.TryGetValue(input, out var inputProcesses))
|
||||||
|
{
|
||||||
|
inputProcesses = [];
|
||||||
|
processesByInputId[input] = inputProcesses;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputProcesses.Add(process);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commodities[output].ProducerProcessIds.Add(process.Id);
|
foreach (var module in modules)
|
||||||
if (!processesByOutputId.TryGetValue(output, out var outputProcesses))
|
|
||||||
{
|
{
|
||||||
outputProcesses = [];
|
if (!outputsByModuleId.TryGetValue(module.Id, out var outputs))
|
||||||
processesByOutputId[output] = outputProcesses;
|
{
|
||||||
|
outputs = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
outputsByModuleId[module.Id] = outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var product in module.Products)
|
||||||
|
{
|
||||||
|
outputs.Add(product);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outputProcesses.Add(process);
|
return new ProductionGraph
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var input in inputs.Keys)
|
|
||||||
{
|
|
||||||
if (!commodities.ContainsKey(input))
|
|
||||||
{
|
{
|
||||||
continue;
|
Commodities = commodities,
|
||||||
}
|
Processes = processes,
|
||||||
|
ProcessesByOutputId = processesByOutputId.ToDictionary(entry => entry.Key, entry => (IReadOnlyList<ProductionProcessNode>)entry.Value, StringComparer.Ordinal),
|
||||||
commodities[input].ConsumerProcessIds.Add(process.Id);
|
ProcessesByInputId = processesByInputId.ToDictionary(entry => entry.Key, entry => (IReadOnlyList<ProductionProcessNode>)entry.Value, StringComparer.Ordinal),
|
||||||
if (!processesByInputId.TryGetValue(input, out var inputProcesses))
|
OutputsByModuleId = outputsByModuleId.ToDictionary(entry => entry.Key, entry => (IReadOnlyList<string>)entry.Value.OrderBy(value => value, StringComparer.Ordinal).ToList(), StringComparer.Ordinal),
|
||||||
{
|
};
|
||||||
inputProcesses = [];
|
|
||||||
processesByInputId[input] = inputProcesses;
|
|
||||||
}
|
|
||||||
|
|
||||||
inputProcesses.Add(process);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var module in modules)
|
|
||||||
{
|
|
||||||
if (!outputsByModuleId.TryGetValue(module.Id, out var outputs))
|
|
||||||
{
|
|
||||||
outputs = new HashSet<string>(StringComparer.Ordinal);
|
|
||||||
outputsByModuleId[module.Id] = outputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var product in module.Products)
|
|
||||||
{
|
|
||||||
outputs.Add(product);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ProductionGraph
|
|
||||||
{
|
|
||||||
Commodities = commodities,
|
|
||||||
Processes = processes,
|
|
||||||
ProcessesByOutputId = processesByOutputId.ToDictionary(entry => entry.Key, entry => (IReadOnlyList<ProductionProcessNode>)entry.Value, StringComparer.Ordinal),
|
|
||||||
ProcessesByInputId = processesByInputId.ToDictionary(entry => entry.Key, entry => (IReadOnlyList<ProductionProcessNode>)entry.Value, StringComparer.Ordinal),
|
|
||||||
OutputsByModuleId = outputsByModuleId.ToDictionary(entry => entry.Key, entry => (IReadOnlyList<string>)entry.Value.OrderBy(value => value, StringComparer.Ordinal).ToList(), StringComparer.Ordinal),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,29 +4,29 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class CreatePlayerOrganizationHandler(WorldService worldService) : Endpoint<PlayerOrganizationCommandRequest, PlayerFactionSnapshot>
|
public sealed class CreatePlayerOrganizationHandler(WorldService worldService) : Endpoint<PlayerOrganizationCommandRequest, PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Post("/api/player-faction/organizations");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(PlayerOrganizationCommandRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var snapshot = worldService.CreatePlayerOrganization(request);
|
Post("/api/player-faction/organizations");
|
||||||
if (snapshot is null)
|
AllowAnonymous();
|
||||||
{
|
}
|
||||||
await SendNotFoundAsync(cancellationToken);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
public override async Task HandleAsync(PlayerOrganizationCommandRequest request, CancellationToken cancellationToken)
|
||||||
}
|
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
{
|
{
|
||||||
AddError(ex.Message);
|
try
|
||||||
await SendErrorsAsync(cancellation: cancellationToken);
|
{
|
||||||
|
var snapshot = worldService.CreatePlayerOrganization(request);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
AddError(ex.Message);
|
||||||
|
await SendErrorsAsync(cancellation: cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,26 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class DeletePlayerDirectiveRequest
|
public sealed class DeletePlayerDirectiveRequest
|
||||||
{
|
{
|
||||||
public string DirectiveId { get; set; } = string.Empty;
|
public string DirectiveId { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DeletePlayerDirectiveHandler(WorldService worldService) : Endpoint<DeletePlayerDirectiveRequest, PlayerFactionSnapshot>
|
public sealed class DeletePlayerDirectiveHandler(WorldService worldService) : Endpoint<DeletePlayerDirectiveRequest, PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Delete("/api/player-faction/directives/{directiveId}");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(DeletePlayerDirectiveRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var snapshot = worldService.DeletePlayerDirective(request.DirectiveId);
|
|
||||||
if (snapshot is null)
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Delete("/api/player-faction/directives/{directiveId}");
|
||||||
return;
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
public override async Task HandleAsync(DeletePlayerDirectiveRequest request, CancellationToken cancellationToken)
|
||||||
}
|
{
|
||||||
|
var snapshot = worldService.DeletePlayerDirective(request.DirectiveId);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,34 +4,34 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class DeletePlayerOrganizationRequest
|
public sealed class DeletePlayerOrganizationRequest
|
||||||
{
|
{
|
||||||
public string OrganizationId { get; set; } = string.Empty;
|
public string OrganizationId { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DeletePlayerOrganizationHandler(WorldService worldService) : Endpoint<DeletePlayerOrganizationRequest, PlayerFactionSnapshot>
|
public sealed class DeletePlayerOrganizationHandler(WorldService worldService) : Endpoint<DeletePlayerOrganizationRequest, PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Delete("/api/player-faction/organizations/{organizationId}");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(DeletePlayerOrganizationRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var snapshot = worldService.DeletePlayerOrganization(request.OrganizationId);
|
Delete("/api/player-faction/organizations/{organizationId}");
|
||||||
if (snapshot is null)
|
AllowAnonymous();
|
||||||
{
|
}
|
||||||
await SendNotFoundAsync(cancellationToken);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
public override async Task HandleAsync(DeletePlayerOrganizationRequest request, CancellationToken cancellationToken)
|
||||||
}
|
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
{
|
{
|
||||||
AddError(ex.Message);
|
try
|
||||||
await SendErrorsAsync(cancellation: cancellationToken);
|
{
|
||||||
|
var snapshot = worldService.DeletePlayerOrganization(request.OrganizationId);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
AddError(ex.Message);
|
||||||
|
await SendErrorsAsync(cancellation: cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,21 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class GetPlayerFactionHandler(WorldService worldService) : EndpointWithoutRequest<PlayerFactionSnapshot>
|
public sealed class GetPlayerFactionHandler(WorldService worldService) : EndpointWithoutRequest<PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Get("/api/player-faction");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var snapshot = worldService.GetPlayerFaction();
|
|
||||||
if (snapshot is null)
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Get("/api/player-faction");
|
||||||
return;
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
public override async Task HandleAsync(CancellationToken cancellationToken)
|
||||||
}
|
{
|
||||||
|
var snapshot = worldService.GetPlayerFaction();
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,37 +4,37 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class UpdatePlayerOrganizationMembershipHandler(WorldService worldService) : Endpoint<PlayerOrganizationMembershipCommandRequest, PlayerFactionSnapshot>
|
public sealed class UpdatePlayerOrganizationMembershipHandler(WorldService worldService) : Endpoint<PlayerOrganizationMembershipCommandRequest, PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Put("/api/player-faction/organizations/{organizationId}/membership");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(PlayerOrganizationMembershipCommandRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var organizationId = Route<string>("organizationId");
|
Put("/api/player-faction/organizations/{organizationId}/membership");
|
||||||
if (string.IsNullOrWhiteSpace(organizationId))
|
AllowAnonymous();
|
||||||
{
|
|
||||||
AddError("organizationId route parameter is required.");
|
|
||||||
await SendErrorsAsync(cancellation: cancellationToken);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var snapshot = worldService.UpdatePlayerOrganizationMembership(organizationId, request);
|
|
||||||
if (snapshot is null)
|
|
||||||
{
|
|
||||||
await SendNotFoundAsync(cancellationToken);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
|
public override async Task HandleAsync(PlayerOrganizationMembershipCommandRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
AddError(ex.Message);
|
try
|
||||||
await SendErrorsAsync(cancellation: cancellationToken);
|
{
|
||||||
|
var organizationId = Route<string>("organizationId");
|
||||||
|
if (string.IsNullOrWhiteSpace(organizationId))
|
||||||
|
{
|
||||||
|
AddError("organizationId route parameter is required.");
|
||||||
|
await SendErrorsAsync(cancellation: cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var snapshot = worldService.UpdatePlayerOrganizationMembership(organizationId, request);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
AddError(ex.Message);
|
||||||
|
await SendErrorsAsync(cancellation: cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,21 +4,21 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class UpdatePlayerStrategicIntentHandler(WorldService worldService) : Endpoint<PlayerStrategicIntentCommandRequest, PlayerFactionSnapshot>
|
public sealed class UpdatePlayerStrategicIntentHandler(WorldService worldService) : Endpoint<PlayerStrategicIntentCommandRequest, PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Put("/api/player-faction/strategic-intent");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(PlayerStrategicIntentCommandRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var snapshot = worldService.UpdatePlayerStrategicIntent(request);
|
|
||||||
if (snapshot is null)
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Put("/api/player-faction/strategic-intent");
|
||||||
return;
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
public override async Task HandleAsync(PlayerStrategicIntentCommandRequest request, CancellationToken cancellationToken)
|
||||||
}
|
{
|
||||||
|
var snapshot = worldService.UpdatePlayerStrategicIntent(request);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,28 +4,28 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class UpsertPlayerAssignmentHandler(WorldService worldService) : Endpoint<PlayerAssetAssignmentCommandRequest, PlayerFactionSnapshot>
|
public sealed class UpsertPlayerAssignmentHandler(WorldService worldService) : Endpoint<PlayerAssetAssignmentCommandRequest, PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Put("/api/player-faction/assets/{assetId}/assignment");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(PlayerAssetAssignmentCommandRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var assetId = Route<string>("assetId");
|
|
||||||
if (string.IsNullOrWhiteSpace(assetId))
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Put("/api/player-faction/assets/{assetId}/assignment");
|
||||||
return;
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
var snapshot = worldService.UpsertPlayerAssignment(assetId, request);
|
public override async Task HandleAsync(PlayerAssetAssignmentCommandRequest request, CancellationToken cancellationToken)
|
||||||
if (snapshot is null)
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
var assetId = Route<string>("assetId");
|
||||||
return;
|
if (string.IsNullOrWhiteSpace(assetId))
|
||||||
}
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
var snapshot = worldService.UpsertPlayerAssignment(assetId, request);
|
||||||
}
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,23 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class UpsertPlayerAutomationPolicyHandler(WorldService worldService) : Endpoint<PlayerAutomationPolicyCommandRequest, PlayerFactionSnapshot>
|
public sealed class UpsertPlayerAutomationPolicyHandler(WorldService worldService) : Endpoint<PlayerAutomationPolicyCommandRequest, PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Post("/api/player-faction/automation-policies");
|
|
||||||
Put("/api/player-faction/automation-policies/{automationPolicyId}");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(PlayerAutomationPolicyCommandRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var automationPolicyId = Route<string?>("automationPolicyId");
|
|
||||||
var snapshot = worldService.UpsertPlayerAutomationPolicy(string.IsNullOrWhiteSpace(automationPolicyId) ? null : automationPolicyId, request);
|
|
||||||
if (snapshot is null)
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Post("/api/player-faction/automation-policies");
|
||||||
return;
|
Put("/api/player-faction/automation-policies/{automationPolicyId}");
|
||||||
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
public override async Task HandleAsync(PlayerAutomationPolicyCommandRequest request, CancellationToken cancellationToken)
|
||||||
}
|
{
|
||||||
|
var automationPolicyId = Route<string?>("automationPolicyId");
|
||||||
|
var snapshot = worldService.UpsertPlayerAutomationPolicy(string.IsNullOrWhiteSpace(automationPolicyId) ? null : automationPolicyId, request);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,23 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class UpsertPlayerDirectiveHandler(WorldService worldService) : Endpoint<PlayerDirectiveCommandRequest, PlayerFactionSnapshot>
|
public sealed class UpsertPlayerDirectiveHandler(WorldService worldService) : Endpoint<PlayerDirectiveCommandRequest, PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Post("/api/player-faction/directives");
|
|
||||||
Put("/api/player-faction/directives/{directiveId}");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(PlayerDirectiveCommandRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var directiveId = Route<string?>("directiveId");
|
|
||||||
var snapshot = worldService.UpsertPlayerDirective(string.IsNullOrWhiteSpace(directiveId) ? null : directiveId, request);
|
|
||||||
if (snapshot is null)
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Post("/api/player-faction/directives");
|
||||||
return;
|
Put("/api/player-faction/directives/{directiveId}");
|
||||||
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
public override async Task HandleAsync(PlayerDirectiveCommandRequest request, CancellationToken cancellationToken)
|
||||||
}
|
{
|
||||||
|
var directiveId = Route<string?>("directiveId");
|
||||||
|
var snapshot = worldService.UpsertPlayerDirective(string.IsNullOrWhiteSpace(directiveId) ? null : directiveId, request);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,23 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class UpsertPlayerPolicyHandler(WorldService worldService) : Endpoint<PlayerPolicyCommandRequest, PlayerFactionSnapshot>
|
public sealed class UpsertPlayerPolicyHandler(WorldService worldService) : Endpoint<PlayerPolicyCommandRequest, PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Post("/api/player-faction/policies");
|
|
||||||
Put("/api/player-faction/policies/{policyId}");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(PlayerPolicyCommandRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var policyId = Route<string?>("policyId");
|
|
||||||
var snapshot = worldService.UpsertPlayerPolicy(string.IsNullOrWhiteSpace(policyId) ? null : policyId, request);
|
|
||||||
if (snapshot is null)
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Post("/api/player-faction/policies");
|
||||||
return;
|
Put("/api/player-faction/policies/{policyId}");
|
||||||
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
public override async Task HandleAsync(PlayerPolicyCommandRequest request, CancellationToken cancellationToken)
|
||||||
}
|
{
|
||||||
|
var policyId = Route<string?>("policyId");
|
||||||
|
var snapshot = worldService.UpsertPlayerPolicy(string.IsNullOrWhiteSpace(policyId) ? null : policyId, request);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,23 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class UpsertPlayerProductionProgramHandler(WorldService worldService) : Endpoint<PlayerProductionProgramCommandRequest, PlayerFactionSnapshot>
|
public sealed class UpsertPlayerProductionProgramHandler(WorldService worldService) : Endpoint<PlayerProductionProgramCommandRequest, PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Post("/api/player-faction/production-programs");
|
|
||||||
Put("/api/player-faction/production-programs/{productionProgramId}");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(PlayerProductionProgramCommandRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var productionProgramId = Route<string?>("productionProgramId");
|
|
||||||
var snapshot = worldService.UpsertPlayerProductionProgram(string.IsNullOrWhiteSpace(productionProgramId) ? null : productionProgramId, request);
|
|
||||||
if (snapshot is null)
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Post("/api/player-faction/production-programs");
|
||||||
return;
|
Put("/api/player-faction/production-programs/{productionProgramId}");
|
||||||
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
public override async Task HandleAsync(PlayerProductionProgramCommandRequest request, CancellationToken cancellationToken)
|
||||||
}
|
{
|
||||||
|
var productionProgramId = Route<string?>("productionProgramId");
|
||||||
|
var snapshot = worldService.UpsertPlayerProductionProgram(string.IsNullOrWhiteSpace(productionProgramId) ? null : productionProgramId, request);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,23 @@ namespace SpaceGame.Api.PlayerFaction.Api;
|
|||||||
|
|
||||||
public sealed class UpsertPlayerReinforcementPolicyHandler(WorldService worldService) : Endpoint<PlayerReinforcementPolicyCommandRequest, PlayerFactionSnapshot>
|
public sealed class UpsertPlayerReinforcementPolicyHandler(WorldService worldService) : Endpoint<PlayerReinforcementPolicyCommandRequest, PlayerFactionSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Post("/api/player-faction/reinforcement-policies");
|
|
||||||
Put("/api/player-faction/reinforcement-policies/{reinforcementPolicyId}");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(PlayerReinforcementPolicyCommandRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var reinforcementPolicyId = Route<string?>("reinforcementPolicyId");
|
|
||||||
var snapshot = worldService.UpsertPlayerReinforcementPolicy(string.IsNullOrWhiteSpace(reinforcementPolicyId) ? null : reinforcementPolicyId, request);
|
|
||||||
if (snapshot is null)
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Post("/api/player-faction/reinforcement-policies");
|
||||||
return;
|
Put("/api/player-faction/reinforcement-policies/{reinforcementPolicyId}");
|
||||||
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
public override async Task HandleAsync(PlayerReinforcementPolicyCommandRequest request, CancellationToken cancellationToken)
|
||||||
}
|
{
|
||||||
|
var reinforcementPolicyId = Route<string?>("reinforcementPolicyId");
|
||||||
|
var snapshot = worldService.UpsertPlayerReinforcementPolicy(string.IsNullOrWhiteSpace(reinforcementPolicyId) ? null : reinforcementPolicyId, request);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,305 +2,305 @@ namespace SpaceGame.Api.PlayerFaction.Runtime;
|
|||||||
|
|
||||||
public sealed class PlayerFactionRuntime
|
public sealed class PlayerFactionRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public required string SovereignFactionId { get; set; }
|
public required string SovereignFactionId { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public PlayerAssetRegistryRuntime AssetRegistry { get; set; } = new();
|
public PlayerAssetRegistryRuntime AssetRegistry { get; set; } = new();
|
||||||
public PlayerStrategicIntentRuntime StrategicIntent { get; set; } = new();
|
public PlayerStrategicIntentRuntime StrategicIntent { get; set; } = new();
|
||||||
public List<PlayerFleetRuntime> Fleets { get; } = [];
|
public List<PlayerFleetRuntime> Fleets { get; } = [];
|
||||||
public List<PlayerTaskForceRuntime> TaskForces { get; } = [];
|
public List<PlayerTaskForceRuntime> TaskForces { get; } = [];
|
||||||
public List<PlayerStationGroupRuntime> StationGroups { get; } = [];
|
public List<PlayerStationGroupRuntime> StationGroups { get; } = [];
|
||||||
public List<PlayerEconomicRegionRuntime> EconomicRegions { get; } = [];
|
public List<PlayerEconomicRegionRuntime> EconomicRegions { get; } = [];
|
||||||
public List<PlayerFrontRuntime> Fronts { get; } = [];
|
public List<PlayerFrontRuntime> Fronts { get; } = [];
|
||||||
public List<PlayerReserveGroupRuntime> Reserves { get; } = [];
|
public List<PlayerReserveGroupRuntime> Reserves { get; } = [];
|
||||||
public List<PlayerFactionPolicyRuntime> Policies { get; } = [];
|
public List<PlayerFactionPolicyRuntime> Policies { get; } = [];
|
||||||
public List<PlayerAutomationPolicyRuntime> AutomationPolicies { get; } = [];
|
public List<PlayerAutomationPolicyRuntime> AutomationPolicies { get; } = [];
|
||||||
public List<PlayerReinforcementPolicyRuntime> ReinforcementPolicies { get; } = [];
|
public List<PlayerReinforcementPolicyRuntime> ReinforcementPolicies { get; } = [];
|
||||||
public List<PlayerProductionProgramRuntime> ProductionPrograms { get; } = [];
|
public List<PlayerProductionProgramRuntime> ProductionPrograms { get; } = [];
|
||||||
public List<PlayerDirectiveRuntime> Directives { get; } = [];
|
public List<PlayerDirectiveRuntime> Directives { get; } = [];
|
||||||
public List<PlayerAssignmentRuntime> Assignments { get; } = [];
|
public List<PlayerAssignmentRuntime> Assignments { get; } = [];
|
||||||
public List<PlayerDecisionLogEntryRuntime> DecisionLog { get; } = [];
|
public List<PlayerDecisionLogEntryRuntime> DecisionLog { get; } = [];
|
||||||
public List<PlayerAlertRuntime> Alerts { get; } = [];
|
public List<PlayerAlertRuntime> Alerts { get; } = [];
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerAssetRegistryRuntime
|
public sealed class PlayerAssetRegistryRuntime
|
||||||
{
|
{
|
||||||
public HashSet<string> ShipIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> ShipIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> StationIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> StationIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> CommanderIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> CommanderIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> ClaimIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> ClaimIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> ConstructionSiteIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> ConstructionSiteIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> PolicySetIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> PolicySetIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> MarketOrderIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> MarketOrderIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> FleetIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> FleetIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> TaskForceIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> TaskForceIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> StationGroupIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> StationGroupIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> EconomicRegionIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> EconomicRegionIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> FrontIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> FrontIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> ReserveIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> ReserveIds { get; } = new(StringComparer.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerStrategicIntentRuntime
|
public sealed class PlayerStrategicIntentRuntime
|
||||||
{
|
{
|
||||||
public string StrategicPosture { get; set; } = "balanced";
|
public string StrategicPosture { get; set; } = "balanced";
|
||||||
public string EconomicPosture { get; set; } = "delegated";
|
public string EconomicPosture { get; set; } = "delegated";
|
||||||
public string MilitaryPosture { get; set; } = "layered-defense";
|
public string MilitaryPosture { get; set; } = "layered-defense";
|
||||||
public string LogisticsPosture { get; set; } = "stable";
|
public string LogisticsPosture { get; set; } = "stable";
|
||||||
public float DesiredReserveRatio { get; set; } = 0.2f;
|
public float DesiredReserveRatio { get; set; } = 0.2f;
|
||||||
public bool AllowDelegatedCombatAutomation { get; set; } = true;
|
public bool AllowDelegatedCombatAutomation { get; set; } = true;
|
||||||
public bool AllowDelegatedEconomicAutomation { get; set; } = true;
|
public bool AllowDelegatedEconomicAutomation { get; set; } = true;
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerFleetRuntime
|
public sealed class PlayerFleetRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string Role { get; set; } = "general-purpose";
|
public string Role { get; set; } = "general-purpose";
|
||||||
public string? CommanderId { get; set; }
|
public string? CommanderId { get; set; }
|
||||||
public string? FrontId { get; set; }
|
public string? FrontId { get; set; }
|
||||||
public string? HomeSystemId { get; set; }
|
public string? HomeSystemId { get; set; }
|
||||||
public string? HomeStationId { get; set; }
|
public string? HomeStationId { get; set; }
|
||||||
public string? PolicyId { get; set; }
|
public string? PolicyId { get; set; }
|
||||||
public string? AutomationPolicyId { get; set; }
|
public string? AutomationPolicyId { get; set; }
|
||||||
public string? ReinforcementPolicyId { get; set; }
|
public string? ReinforcementPolicyId { get; set; }
|
||||||
public List<string> AssetIds { get; } = [];
|
public List<string> AssetIds { get; } = [];
|
||||||
public List<string> TaskForceIds { get; } = [];
|
public List<string> TaskForceIds { get; } = [];
|
||||||
public List<string> DirectiveIds { get; } = [];
|
public List<string> DirectiveIds { get; } = [];
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerTaskForceRuntime
|
public sealed class PlayerTaskForceRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string Role { get; set; } = "task-force";
|
public string Role { get; set; } = "task-force";
|
||||||
public string? FleetId { get; set; }
|
public string? FleetId { get; set; }
|
||||||
public string? CommanderId { get; set; }
|
public string? CommanderId { get; set; }
|
||||||
public string? FrontId { get; set; }
|
public string? FrontId { get; set; }
|
||||||
public string? PolicyId { get; set; }
|
public string? PolicyId { get; set; }
|
||||||
public string? AutomationPolicyId { get; set; }
|
public string? AutomationPolicyId { get; set; }
|
||||||
public List<string> AssetIds { get; } = [];
|
public List<string> AssetIds { get; } = [];
|
||||||
public List<string> DirectiveIds { get; } = [];
|
public List<string> DirectiveIds { get; } = [];
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerStationGroupRuntime
|
public sealed class PlayerStationGroupRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string Role { get; set; } = "industrial-group";
|
public string Role { get; set; } = "industrial-group";
|
||||||
public string? EconomicRegionId { get; set; }
|
public string? EconomicRegionId { get; set; }
|
||||||
public string? PolicyId { get; set; }
|
public string? PolicyId { get; set; }
|
||||||
public string? AutomationPolicyId { get; set; }
|
public string? AutomationPolicyId { get; set; }
|
||||||
public List<string> StationIds { get; } = [];
|
public List<string> StationIds { get; } = [];
|
||||||
public List<string> DirectiveIds { get; } = [];
|
public List<string> DirectiveIds { get; } = [];
|
||||||
public List<string> FocusItemIds { get; } = [];
|
public List<string> FocusItemIds { get; } = [];
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerEconomicRegionRuntime
|
public sealed class PlayerEconomicRegionRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string Role { get; set; } = "balanced-region";
|
public string Role { get; set; } = "balanced-region";
|
||||||
public string? SharedEconomicRegionId { get; set; }
|
public string? SharedEconomicRegionId { get; set; }
|
||||||
public string? PolicyId { get; set; }
|
public string? PolicyId { get; set; }
|
||||||
public string? AutomationPolicyId { get; set; }
|
public string? AutomationPolicyId { get; set; }
|
||||||
public List<string> SystemIds { get; } = [];
|
public List<string> SystemIds { get; } = [];
|
||||||
public List<string> StationGroupIds { get; } = [];
|
public List<string> StationGroupIds { get; } = [];
|
||||||
public List<string> DirectiveIds { get; } = [];
|
public List<string> DirectiveIds { get; } = [];
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerFrontRuntime
|
public sealed class PlayerFrontRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public float Priority { get; set; } = 50f;
|
public float Priority { get; set; } = 50f;
|
||||||
public string Posture { get; set; } = "hold";
|
public string Posture { get; set; } = "hold";
|
||||||
public string? SharedFrontLineId { get; set; }
|
public string? SharedFrontLineId { get; set; }
|
||||||
public string? TargetFactionId { get; set; }
|
public string? TargetFactionId { get; set; }
|
||||||
public List<string> SystemIds { get; } = [];
|
public List<string> SystemIds { get; } = [];
|
||||||
public List<string> FleetIds { get; } = [];
|
public List<string> FleetIds { get; } = [];
|
||||||
public List<string> ReserveIds { get; } = [];
|
public List<string> ReserveIds { get; } = [];
|
||||||
public List<string> DirectiveIds { get; } = [];
|
public List<string> DirectiveIds { get; } = [];
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerReserveGroupRuntime
|
public sealed class PlayerReserveGroupRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string Status { get; set; } = "ready";
|
public string Status { get; set; } = "ready";
|
||||||
public string ReserveKind { get; set; } = "military";
|
public string ReserveKind { get; set; } = "military";
|
||||||
public string? HomeSystemId { get; set; }
|
public string? HomeSystemId { get; set; }
|
||||||
public string? PolicyId { get; set; }
|
public string? PolicyId { get; set; }
|
||||||
public List<string> AssetIds { get; } = [];
|
public List<string> AssetIds { get; } = [];
|
||||||
public List<string> FrontIds { get; } = [];
|
public List<string> FrontIds { get; } = [];
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerFactionPolicyRuntime
|
public sealed class PlayerFactionPolicyRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string ScopeKind { get; set; } = "player-faction";
|
public string ScopeKind { get; set; } = "player-faction";
|
||||||
public string? ScopeId { get; set; }
|
public string? ScopeId { get; set; }
|
||||||
public string? PolicySetId { get; set; }
|
public string? PolicySetId { get; set; }
|
||||||
public bool AllowDelegatedCombat { get; set; } = true;
|
public bool AllowDelegatedCombat { get; set; } = true;
|
||||||
public bool AllowDelegatedTrade { get; set; } = true;
|
public bool AllowDelegatedTrade { get; set; } = true;
|
||||||
public float ReserveCreditsRatio { get; set; } = 0.2f;
|
public float ReserveCreditsRatio { get; set; } = 0.2f;
|
||||||
public float ReserveMilitaryRatio { get; set; } = 0.2f;
|
public float ReserveMilitaryRatio { get; set; } = 0.2f;
|
||||||
public string TradeAccessPolicy { get; set; } = "owner-and-allies";
|
public string TradeAccessPolicy { get; set; } = "owner-and-allies";
|
||||||
public string DockingAccessPolicy { get; set; } = "owner-and-allies";
|
public string DockingAccessPolicy { get; set; } = "owner-and-allies";
|
||||||
public string ConstructionAccessPolicy { get; set; } = "owner-only";
|
public string ConstructionAccessPolicy { get; set; } = "owner-only";
|
||||||
public string OperationalRangePolicy { get; set; } = "unrestricted";
|
public string OperationalRangePolicy { get; set; } = "unrestricted";
|
||||||
public string CombatEngagementPolicy { get; set; } = "defensive";
|
public string CombatEngagementPolicy { get; set; } = "defensive";
|
||||||
public bool AvoidHostileSystems { get; set; } = true;
|
public bool AvoidHostileSystems { get; set; } = true;
|
||||||
public float FleeHullRatio { get; set; } = 0.35f;
|
public float FleeHullRatio { get; set; } = 0.35f;
|
||||||
public HashSet<string> BlacklistedSystemIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> BlacklistedSystemIds { get; } = new(StringComparer.Ordinal);
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerAutomationPolicyRuntime
|
public sealed class PlayerAutomationPolicyRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string ScopeKind { get; set; } = "player-faction";
|
public string ScopeKind { get; set; } = "player-faction";
|
||||||
public string? ScopeId { get; set; }
|
public string? ScopeId { get; set; }
|
||||||
public bool Enabled { get; set; } = true;
|
public bool Enabled { get; set; } = true;
|
||||||
public string BehaviorKind { get; set; } = "idle";
|
public string BehaviorKind { get; set; } = "idle";
|
||||||
public bool UseOrders { get; set; }
|
public bool UseOrders { get; set; }
|
||||||
public string? StagingOrderKind { get; set; }
|
public string? StagingOrderKind { get; set; }
|
||||||
public int MaxSystemRange { get; set; }
|
public int MaxSystemRange { get; set; }
|
||||||
public bool KnownStationsOnly { get; set; }
|
public bool KnownStationsOnly { get; set; }
|
||||||
public float Radius { get; set; } = 24f;
|
public float Radius { get; set; } = 24f;
|
||||||
public float WaitSeconds { get; set; } = 3f;
|
public float WaitSeconds { get; set; } = 3f;
|
||||||
public string? PreferredItemId { get; set; }
|
public string? PreferredItemId { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public List<ShipOrderTemplateRuntime> RepeatOrders { get; } = [];
|
public List<ShipOrderTemplateRuntime> RepeatOrders { get; } = [];
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerReinforcementPolicyRuntime
|
public sealed class PlayerReinforcementPolicyRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string ScopeKind { get; set; } = "player-faction";
|
public string ScopeKind { get; set; } = "player-faction";
|
||||||
public string? ScopeId { get; set; }
|
public string? ScopeId { get; set; }
|
||||||
public string ShipKind { get; set; } = "military";
|
public string ShipKind { get; set; } = "military";
|
||||||
public int DesiredAssetCount { get; set; }
|
public int DesiredAssetCount { get; set; }
|
||||||
public int MinimumReserveCount { get; set; }
|
public int MinimumReserveCount { get; set; }
|
||||||
public bool AutoTransferReserves { get; set; } = true;
|
public bool AutoTransferReserves { get; set; } = true;
|
||||||
public bool AutoQueueProduction { get; set; } = true;
|
public bool AutoQueueProduction { get; set; } = true;
|
||||||
public string? SourceReserveId { get; set; }
|
public string? SourceReserveId { get; set; }
|
||||||
public string? TargetFrontId { get; set; }
|
public string? TargetFrontId { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerProductionProgramRuntime
|
public sealed class PlayerProductionProgramRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string Kind { get; set; } = "ship-production";
|
public string Kind { get; set; } = "ship-production";
|
||||||
public string? TargetShipKind { get; set; }
|
public string? TargetShipKind { get; set; }
|
||||||
public string? TargetModuleId { get; set; }
|
public string? TargetModuleId { get; set; }
|
||||||
public string? TargetItemId { get; set; }
|
public string? TargetItemId { get; set; }
|
||||||
public int TargetCount { get; set; }
|
public int TargetCount { get; set; }
|
||||||
public int CurrentCount { get; set; }
|
public int CurrentCount { get; set; }
|
||||||
public string? StationGroupId { get; set; }
|
public string? StationGroupId { get; set; }
|
||||||
public string? ReinforcementPolicyId { get; set; }
|
public string? ReinforcementPolicyId { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerDirectiveRuntime
|
public sealed class PlayerDirectiveRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public string Kind { get; set; } = "hold";
|
public string Kind { get; set; } = "hold";
|
||||||
public string ScopeKind { get; set; } = "asset";
|
public string ScopeKind { get; set; } = "asset";
|
||||||
public string ScopeId { get; set; } = string.Empty;
|
public string ScopeId { get; set; } = string.Empty;
|
||||||
public string? TargetEntityId { get; set; }
|
public string? TargetEntityId { get; set; }
|
||||||
public string? TargetSystemId { get; set; }
|
public string? TargetSystemId { get; set; }
|
||||||
public Vector3? TargetPosition { get; set; }
|
public Vector3? TargetPosition { get; set; }
|
||||||
public string? HomeSystemId { get; set; }
|
public string? HomeSystemId { get; set; }
|
||||||
public string? HomeStationId { get; set; }
|
public string? HomeStationId { get; set; }
|
||||||
public string? SourceStationId { get; set; }
|
public string? SourceStationId { get; set; }
|
||||||
public string? DestinationStationId { get; set; }
|
public string? DestinationStationId { get; set; }
|
||||||
public string BehaviorKind { get; set; } = "idle";
|
public string BehaviorKind { get; set; } = "idle";
|
||||||
public bool UseOrders { get; set; }
|
public bool UseOrders { get; set; }
|
||||||
public string? StagingOrderKind { get; set; }
|
public string? StagingOrderKind { get; set; }
|
||||||
public string? ItemId { get; set; }
|
public string? ItemId { get; set; }
|
||||||
public string? PreferredNodeId { get; set; }
|
public string? PreferredNodeId { get; set; }
|
||||||
public string? PreferredConstructionSiteId { get; set; }
|
public string? PreferredConstructionSiteId { get; set; }
|
||||||
public string? PreferredModuleId { get; set; }
|
public string? PreferredModuleId { get; set; }
|
||||||
public int Priority { get; set; } = 50;
|
public int Priority { get; set; } = 50;
|
||||||
public float Radius { get; set; } = 24f;
|
public float Radius { get; set; } = 24f;
|
||||||
public float WaitSeconds { get; set; } = 3f;
|
public float WaitSeconds { get; set; } = 3f;
|
||||||
public int MaxSystemRange { get; set; }
|
public int MaxSystemRange { get; set; }
|
||||||
public bool KnownStationsOnly { get; set; }
|
public bool KnownStationsOnly { get; set; }
|
||||||
public List<Vector3> PatrolPoints { get; } = [];
|
public List<Vector3> PatrolPoints { get; } = [];
|
||||||
public List<ShipOrderTemplateRuntime> RepeatOrders { get; } = [];
|
public List<ShipOrderTemplateRuntime> RepeatOrders { get; } = [];
|
||||||
public string? PolicyId { get; set; }
|
public string? PolicyId { get; set; }
|
||||||
public string? AutomationPolicyId { get; set; }
|
public string? AutomationPolicyId { get; set; }
|
||||||
public string? Notes { get; set; }
|
public string? Notes { get; set; }
|
||||||
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerAssignmentRuntime
|
public sealed class PlayerAssignmentRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string AssetKind { get; set; }
|
public required string AssetKind { get; set; }
|
||||||
public required string AssetId { get; set; }
|
public required string AssetId { get; set; }
|
||||||
public string? FleetId { get; set; }
|
public string? FleetId { get; set; }
|
||||||
public string? TaskForceId { get; set; }
|
public string? TaskForceId { get; set; }
|
||||||
public string? StationGroupId { get; set; }
|
public string? StationGroupId { get; set; }
|
||||||
public string? EconomicRegionId { get; set; }
|
public string? EconomicRegionId { get; set; }
|
||||||
public string? FrontId { get; set; }
|
public string? FrontId { get; set; }
|
||||||
public string? ReserveId { get; set; }
|
public string? ReserveId { get; set; }
|
||||||
public string? DirectiveId { get; set; }
|
public string? DirectiveId { get; set; }
|
||||||
public string? PolicyId { get; set; }
|
public string? PolicyId { get; set; }
|
||||||
public string? AutomationPolicyId { get; set; }
|
public string? AutomationPolicyId { get; set; }
|
||||||
public string Role { get; set; } = "line";
|
public string Role { get; set; } = "line";
|
||||||
public string Status { get; set; } = "active";
|
public string Status { get; set; } = "active";
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerDecisionLogEntryRuntime
|
public sealed class PlayerDecisionLogEntryRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public required string Summary { get; set; }
|
public required string Summary { get; set; }
|
||||||
public string? RelatedEntityKind { get; set; }
|
public string? RelatedEntityKind { get; set; }
|
||||||
public string? RelatedEntityId { get; set; }
|
public string? RelatedEntityId { get; set; }
|
||||||
public DateTimeOffset OccurredAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset OccurredAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PlayerAlertRuntime
|
public sealed class PlayerAlertRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public required string Severity { get; set; }
|
public required string Severity { get; set; }
|
||||||
public required string Summary { get; set; }
|
public required string Summary { get; set; }
|
||||||
public string? AssetKind { get; set; }
|
public string? AssetKind { get; set; }
|
||||||
public string? AssetId { get; set; }
|
public string? AssetId { get; set; }
|
||||||
public string? RelatedDirectiveId { get; set; }
|
public string? RelatedDirectiveId { get; set; }
|
||||||
public string Status { get; set; } = "open";
|
public string Status { get; set; } = "open";
|
||||||
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset CreatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,13 +6,13 @@ var builder = WebApplication.CreateBuilder(args);
|
|||||||
|
|
||||||
builder.Services.AddCors((options) =>
|
builder.Services.AddCors((options) =>
|
||||||
{
|
{
|
||||||
options.AddDefaultPolicy((policy) =>
|
options.AddDefaultPolicy((policy) =>
|
||||||
{
|
{
|
||||||
policy
|
policy
|
||||||
.AllowAnyHeader()
|
.AllowAnyHeader()
|
||||||
.AllowAnyMethod()
|
.AllowAnyMethod()
|
||||||
.AllowAnyOrigin();
|
.AllowAnyOrigin();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
builder.Services.Configure<WorldGenerationOptions>(builder.Configuration.GetSection("WorldGeneration"));
|
builder.Services.Configure<WorldGenerationOptions>(builder.Configuration.GetSection("WorldGeneration"));
|
||||||
builder.Services.Configure<OrbitalSimulationOptions>(builder.Configuration.GetSection("OrbitalSimulation"));
|
builder.Services.Configure<OrbitalSimulationOptions>(builder.Configuration.GetSection("OrbitalSimulation"));
|
||||||
|
|||||||
@@ -2,276 +2,276 @@ namespace SpaceGame.Api.Shared.Runtime;
|
|||||||
|
|
||||||
public enum SpatialNodeKind
|
public enum SpatialNodeKind
|
||||||
{
|
{
|
||||||
Star,
|
Star,
|
||||||
Planet,
|
Planet,
|
||||||
Moon,
|
Moon,
|
||||||
LagrangePoint,
|
LagrangePoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WorkStatus
|
public enum WorkStatus
|
||||||
{
|
{
|
||||||
Pending,
|
Pending,
|
||||||
Active,
|
Active,
|
||||||
Blocked,
|
Blocked,
|
||||||
Completed,
|
Completed,
|
||||||
Failed,
|
Failed,
|
||||||
Interrupted,
|
Interrupted,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum OrderStatus
|
public enum OrderStatus
|
||||||
{
|
{
|
||||||
Queued,
|
Queued,
|
||||||
Active,
|
Active,
|
||||||
Completed,
|
Completed,
|
||||||
Cancelled,
|
Cancelled,
|
||||||
Failed,
|
Failed,
|
||||||
Interrupted,
|
Interrupted,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AiPlanStatus
|
public enum AiPlanStatus
|
||||||
{
|
{
|
||||||
Planned,
|
Planned,
|
||||||
Running,
|
Running,
|
||||||
Blocked,
|
Blocked,
|
||||||
Completed,
|
Completed,
|
||||||
Failed,
|
Failed,
|
||||||
Interrupted,
|
Interrupted,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AiPlanStepStatus
|
public enum AiPlanStepStatus
|
||||||
{
|
{
|
||||||
Planned,
|
Planned,
|
||||||
Running,
|
Running,
|
||||||
Blocked,
|
Blocked,
|
||||||
Completed,
|
Completed,
|
||||||
Failed,
|
Failed,
|
||||||
Interrupted,
|
Interrupted,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AiPlanSourceKind
|
public enum AiPlanSourceKind
|
||||||
{
|
{
|
||||||
Rule,
|
Rule,
|
||||||
Order,
|
Order,
|
||||||
DefaultBehavior,
|
DefaultBehavior,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ShipState
|
public enum ShipState
|
||||||
{
|
{
|
||||||
Idle,
|
Idle,
|
||||||
Arriving,
|
Arriving,
|
||||||
LocalFlight,
|
LocalFlight,
|
||||||
SpoolingWarp,
|
SpoolingWarp,
|
||||||
Warping,
|
Warping,
|
||||||
SpoolingFtl,
|
SpoolingFtl,
|
||||||
Ftl,
|
Ftl,
|
||||||
CargoFull,
|
CargoFull,
|
||||||
MiningApproach,
|
MiningApproach,
|
||||||
Mining,
|
Mining,
|
||||||
NodeDepleted,
|
NodeDepleted,
|
||||||
AwaitingDock,
|
AwaitingDock,
|
||||||
DockingApproach,
|
DockingApproach,
|
||||||
Docking,
|
Docking,
|
||||||
Docked,
|
Docked,
|
||||||
Transferring,
|
Transferring,
|
||||||
Loading,
|
Loading,
|
||||||
Unloading,
|
Unloading,
|
||||||
WaitingMaterials,
|
WaitingMaterials,
|
||||||
ConstructionBlocked,
|
ConstructionBlocked,
|
||||||
Constructing,
|
Constructing,
|
||||||
DeliveringConstruction,
|
DeliveringConstruction,
|
||||||
Blocked,
|
Blocked,
|
||||||
Undocking,
|
Undocking,
|
||||||
EngagingTarget,
|
EngagingTarget,
|
||||||
HoldingPosition,
|
HoldingPosition,
|
||||||
Fleeing,
|
Fleeing,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SpaceLayerKinds
|
public static class SpaceLayerKinds
|
||||||
{
|
{
|
||||||
public const string UniverseSpace = "universe-space";
|
public const string UniverseSpace = "universe-space";
|
||||||
public const string GalaxySpace = "galaxy-space";
|
public const string GalaxySpace = "galaxy-space";
|
||||||
public const string SystemSpace = "system-space";
|
public const string SystemSpace = "system-space";
|
||||||
public const string LocalSpace = "local-space";
|
public const string LocalSpace = "local-space";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MovementRegimeKinds
|
public static class MovementRegimeKinds
|
||||||
{
|
{
|
||||||
public const string LocalFlight = "local-flight";
|
public const string LocalFlight = "local-flight";
|
||||||
public const string Warp = "warp";
|
public const string Warp = "warp";
|
||||||
public const string StargateTransit = "stargate-transit";
|
public const string StargateTransit = "stargate-transit";
|
||||||
public const string FtlTransit = "ftl-transit";
|
public const string FtlTransit = "ftl-transit";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CommanderKind
|
public static class CommanderKind
|
||||||
{
|
{
|
||||||
public const string Faction = "faction";
|
public const string Faction = "faction";
|
||||||
public const string Station = "station";
|
public const string Station = "station";
|
||||||
public const string Ship = "ship";
|
public const string Ship = "ship";
|
||||||
public const string Fleet = "fleet";
|
public const string Fleet = "fleet";
|
||||||
public const string Sector = "sector";
|
public const string Sector = "sector";
|
||||||
public const string TaskGroup = "task-group";
|
public const string TaskGroup = "task-group";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ShipTaskKinds
|
public static class ShipTaskKinds
|
||||||
{
|
{
|
||||||
public const string HoldPosition = "hold-position";
|
public const string HoldPosition = "hold-position";
|
||||||
public const string Travel = "travel";
|
public const string Travel = "travel";
|
||||||
public const string FollowTarget = "follow-target";
|
public const string FollowTarget = "follow-target";
|
||||||
public const string MineNode = "mine-node";
|
public const string MineNode = "mine-node";
|
||||||
public const string Dock = "dock";
|
public const string Dock = "dock";
|
||||||
public const string Undock = "undock";
|
public const string Undock = "undock";
|
||||||
public const string LoadCargo = "load-cargo";
|
public const string LoadCargo = "load-cargo";
|
||||||
public const string UnloadCargo = "unload-cargo";
|
public const string UnloadCargo = "unload-cargo";
|
||||||
public const string TransferCargoToShip = "transfer-cargo-to-ship";
|
public const string TransferCargoToShip = "transfer-cargo-to-ship";
|
||||||
public const string SalvageWreck = "salvage-wreck";
|
public const string SalvageWreck = "salvage-wreck";
|
||||||
public const string DeliverConstruction = "deliver-construction";
|
public const string DeliverConstruction = "deliver-construction";
|
||||||
public const string ConstructModule = "construct-module";
|
public const string ConstructModule = "construct-module";
|
||||||
public const string BuildConstructionSite = "build-construction-site";
|
public const string BuildConstructionSite = "build-construction-site";
|
||||||
public const string AttackTarget = "attack-target";
|
public const string AttackTarget = "attack-target";
|
||||||
public const string Flee = "flee";
|
public const string Flee = "flee";
|
||||||
public const string Wait = "wait";
|
public const string Wait = "wait";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ShipOrderKinds
|
public static class ShipOrderKinds
|
||||||
{
|
{
|
||||||
public const string Move = "move";
|
public const string Move = "move";
|
||||||
public const string DockAtStation = "dock-at-station";
|
public const string DockAtStation = "dock-at-station";
|
||||||
public const string DockAndWait = "dock-and-wait";
|
public const string DockAndWait = "dock-and-wait";
|
||||||
public const string FlyAndWait = "fly-and-wait";
|
public const string FlyAndWait = "fly-and-wait";
|
||||||
public const string FlyToObject = "fly-to-object";
|
public const string FlyToObject = "fly-to-object";
|
||||||
public const string FollowShip = "follow-ship";
|
public const string FollowShip = "follow-ship";
|
||||||
public const string TradeRoute = "trade-route";
|
public const string TradeRoute = "trade-route";
|
||||||
public const string MineAndDeliver = "mine-and-deliver";
|
public const string MineAndDeliver = "mine-and-deliver";
|
||||||
public const string BuildAtSite = "build-at-site";
|
public const string BuildAtSite = "build-at-site";
|
||||||
public const string AttackTarget = "attack-target";
|
public const string AttackTarget = "attack-target";
|
||||||
public const string HoldPosition = "hold-position";
|
public const string HoldPosition = "hold-position";
|
||||||
public const string RepeatOrders = "repeat-orders";
|
public const string RepeatOrders = "repeat-orders";
|
||||||
public const string Flee = "flee";
|
public const string Flee = "flee";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ClaimStateKinds
|
public static class ClaimStateKinds
|
||||||
{
|
{
|
||||||
public const string Placed = "placed";
|
public const string Placed = "placed";
|
||||||
public const string Activating = "activating";
|
public const string Activating = "activating";
|
||||||
public const string Active = "active";
|
public const string Active = "active";
|
||||||
public const string Destroyed = "destroyed";
|
public const string Destroyed = "destroyed";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ConstructionSiteStateKinds
|
public static class ConstructionSiteStateKinds
|
||||||
{
|
{
|
||||||
public const string Planned = "planned";
|
public const string Planned = "planned";
|
||||||
public const string Active = "active";
|
public const string Active = "active";
|
||||||
public const string Paused = "paused";
|
public const string Paused = "paused";
|
||||||
public const string Completed = "completed";
|
public const string Completed = "completed";
|
||||||
public const string Destroyed = "destroyed";
|
public const string Destroyed = "destroyed";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MarketOrderKinds
|
public static class MarketOrderKinds
|
||||||
{
|
{
|
||||||
public const string Buy = "buy";
|
public const string Buy = "buy";
|
||||||
public const string Sell = "sell";
|
public const string Sell = "sell";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MarketOrderStateKinds
|
public static class MarketOrderStateKinds
|
||||||
{
|
{
|
||||||
public const string Open = "open";
|
public const string Open = "open";
|
||||||
public const string PartiallyFilled = "partially-filled";
|
public const string PartiallyFilled = "partially-filled";
|
||||||
public const string Filled = "filled";
|
public const string Filled = "filled";
|
||||||
public const string Cancelled = "cancelled";
|
public const string Cancelled = "cancelled";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SimulationEnumMappings
|
public static class SimulationEnumMappings
|
||||||
{
|
{
|
||||||
public static string ToContractValue(this SpatialNodeKind kind) => kind switch
|
public static string ToContractValue(this SpatialNodeKind kind) => kind switch
|
||||||
{
|
{
|
||||||
SpatialNodeKind.Star => "star",
|
SpatialNodeKind.Star => "star",
|
||||||
SpatialNodeKind.Planet => "planet",
|
SpatialNodeKind.Planet => "planet",
|
||||||
SpatialNodeKind.Moon => "moon",
|
SpatialNodeKind.Moon => "moon",
|
||||||
SpatialNodeKind.LagrangePoint => "lagrange-point",
|
SpatialNodeKind.LagrangePoint => "lagrange-point",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string ToContractValue(this WorkStatus status) => status switch
|
public static string ToContractValue(this WorkStatus status) => status switch
|
||||||
{
|
{
|
||||||
WorkStatus.Pending => "pending",
|
WorkStatus.Pending => "pending",
|
||||||
WorkStatus.Active => "active",
|
WorkStatus.Active => "active",
|
||||||
WorkStatus.Blocked => "blocked",
|
WorkStatus.Blocked => "blocked",
|
||||||
WorkStatus.Completed => "completed",
|
WorkStatus.Completed => "completed",
|
||||||
WorkStatus.Failed => "failed",
|
WorkStatus.Failed => "failed",
|
||||||
WorkStatus.Interrupted => "interrupted",
|
WorkStatus.Interrupted => "interrupted",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string ToContractValue(this OrderStatus status) => status switch
|
public static string ToContractValue(this OrderStatus status) => status switch
|
||||||
{
|
{
|
||||||
OrderStatus.Queued => "queued",
|
OrderStatus.Queued => "queued",
|
||||||
OrderStatus.Active => "active",
|
OrderStatus.Active => "active",
|
||||||
OrderStatus.Completed => "completed",
|
OrderStatus.Completed => "completed",
|
||||||
OrderStatus.Cancelled => "cancelled",
|
OrderStatus.Cancelled => "cancelled",
|
||||||
OrderStatus.Failed => "failed",
|
OrderStatus.Failed => "failed",
|
||||||
OrderStatus.Interrupted => "interrupted",
|
OrderStatus.Interrupted => "interrupted",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string ToContractValue(this AiPlanStatus status) => status switch
|
public static string ToContractValue(this AiPlanStatus status) => status switch
|
||||||
{
|
{
|
||||||
AiPlanStatus.Planned => "planned",
|
AiPlanStatus.Planned => "planned",
|
||||||
AiPlanStatus.Running => "running",
|
AiPlanStatus.Running => "running",
|
||||||
AiPlanStatus.Blocked => "blocked",
|
AiPlanStatus.Blocked => "blocked",
|
||||||
AiPlanStatus.Completed => "completed",
|
AiPlanStatus.Completed => "completed",
|
||||||
AiPlanStatus.Failed => "failed",
|
AiPlanStatus.Failed => "failed",
|
||||||
AiPlanStatus.Interrupted => "interrupted",
|
AiPlanStatus.Interrupted => "interrupted",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string ToContractValue(this AiPlanStepStatus status) => status switch
|
public static string ToContractValue(this AiPlanStepStatus status) => status switch
|
||||||
{
|
{
|
||||||
AiPlanStepStatus.Planned => "planned",
|
AiPlanStepStatus.Planned => "planned",
|
||||||
AiPlanStepStatus.Running => "running",
|
AiPlanStepStatus.Running => "running",
|
||||||
AiPlanStepStatus.Blocked => "blocked",
|
AiPlanStepStatus.Blocked => "blocked",
|
||||||
AiPlanStepStatus.Completed => "completed",
|
AiPlanStepStatus.Completed => "completed",
|
||||||
AiPlanStepStatus.Failed => "failed",
|
AiPlanStepStatus.Failed => "failed",
|
||||||
AiPlanStepStatus.Interrupted => "interrupted",
|
AiPlanStepStatus.Interrupted => "interrupted",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(status), status, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string ToContractValue(this AiPlanSourceKind kind) => kind switch
|
public static string ToContractValue(this AiPlanSourceKind kind) => kind switch
|
||||||
{
|
{
|
||||||
AiPlanSourceKind.Rule => "rule",
|
AiPlanSourceKind.Rule => "rule",
|
||||||
AiPlanSourceKind.Order => "order",
|
AiPlanSourceKind.Order => "order",
|
||||||
AiPlanSourceKind.DefaultBehavior => "default-behavior",
|
AiPlanSourceKind.DefaultBehavior => "default-behavior",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string ToContractValue(this ShipState state) => state switch
|
public static string ToContractValue(this ShipState state) => state switch
|
||||||
{
|
{
|
||||||
ShipState.Idle => "idle",
|
ShipState.Idle => "idle",
|
||||||
ShipState.Arriving => "arriving",
|
ShipState.Arriving => "arriving",
|
||||||
ShipState.LocalFlight => "local-flight",
|
ShipState.LocalFlight => "local-flight",
|
||||||
ShipState.SpoolingWarp => "spooling-warp",
|
ShipState.SpoolingWarp => "spooling-warp",
|
||||||
ShipState.Warping => "warping",
|
ShipState.Warping => "warping",
|
||||||
ShipState.SpoolingFtl => "spooling-ftl",
|
ShipState.SpoolingFtl => "spooling-ftl",
|
||||||
ShipState.Ftl => "ftl",
|
ShipState.Ftl => "ftl",
|
||||||
ShipState.CargoFull => "cargo-full",
|
ShipState.CargoFull => "cargo-full",
|
||||||
ShipState.MiningApproach => "mining-approach",
|
ShipState.MiningApproach => "mining-approach",
|
||||||
ShipState.Mining => "mining",
|
ShipState.Mining => "mining",
|
||||||
ShipState.NodeDepleted => "node-depleted",
|
ShipState.NodeDepleted => "node-depleted",
|
||||||
ShipState.AwaitingDock => "awaiting-dock",
|
ShipState.AwaitingDock => "awaiting-dock",
|
||||||
ShipState.DockingApproach => "docking-approach",
|
ShipState.DockingApproach => "docking-approach",
|
||||||
ShipState.Docking => "docking",
|
ShipState.Docking => "docking",
|
||||||
ShipState.Docked => "docked",
|
ShipState.Docked => "docked",
|
||||||
ShipState.Transferring => "transferring",
|
ShipState.Transferring => "transferring",
|
||||||
ShipState.Loading => "loading",
|
ShipState.Loading => "loading",
|
||||||
ShipState.Unloading => "unloading",
|
ShipState.Unloading => "unloading",
|
||||||
ShipState.WaitingMaterials => "waiting-materials",
|
ShipState.WaitingMaterials => "waiting-materials",
|
||||||
ShipState.ConstructionBlocked => "construction-blocked",
|
ShipState.ConstructionBlocked => "construction-blocked",
|
||||||
ShipState.Constructing => "constructing",
|
ShipState.Constructing => "constructing",
|
||||||
ShipState.DeliveringConstruction => "delivering-construction",
|
ShipState.DeliveringConstruction => "delivering-construction",
|
||||||
ShipState.Blocked => "blocked",
|
ShipState.Blocked => "blocked",
|
||||||
ShipState.Undocking => "undocking",
|
ShipState.Undocking => "undocking",
|
||||||
ShipState.EngagingTarget => "engaging-target",
|
ShipState.EngagingTarget => "engaging-target",
|
||||||
ShipState.HoldingPosition => "holding-position",
|
ShipState.HoldingPosition => "holding-position",
|
||||||
ShipState.Fleeing => "fleeing",
|
ShipState.Fleeing => "fleeing",
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(state), state, null),
|
_ => throw new ArgumentOutOfRangeException(nameof(state), state, null),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,179 +3,179 @@ namespace SpaceGame.Api.Shared.Runtime;
|
|||||||
|
|
||||||
internal static class SimulationRuntimeSupport
|
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, string moduleId) =>
|
||||||
station.Modules.Count(module => string.Equals(module.ModuleId, moduleId, StringComparison.Ordinal));
|
station.Modules.Count(module => string.Equals(module.ModuleId, moduleId, StringComparison.Ordinal));
|
||||||
|
|
||||||
internal static void AddStationModule(SimulationWorld world, StationRuntime station, string moduleId)
|
internal static void AddStationModule(SimulationWorld world, StationRuntime station, string moduleId)
|
||||||
{
|
|
||||||
if (!world.ModuleDefinitions.TryGetValue(moduleId, out var definition))
|
|
||||||
{
|
{
|
||||||
return;
|
if (!world.ModuleDefinitions.TryGetValue(moduleId, out var definition))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
station.Modules.Add(new StationModuleRuntime
|
||||||
|
{
|
||||||
|
Id = $"{station.Id}-module-{station.Modules.Count + 1}",
|
||||||
|
ModuleId = moduleId,
|
||||||
|
Health = definition.Hull,
|
||||||
|
MaxHealth = definition.Hull,
|
||||||
|
});
|
||||||
|
station.Radius = GetStationRadius(world, station);
|
||||||
}
|
}
|
||||||
|
|
||||||
station.Modules.Add(new StationModuleRuntime
|
internal static float GetStationRadius(SimulationWorld world, StationRuntime station)
|
||||||
{
|
{
|
||||||
Id = $"{station.Id}-module-{station.Modules.Count + 1}",
|
var totalArea = station.Modules
|
||||||
ModuleId = moduleId,
|
.Select(module => world.ModuleDefinitions.TryGetValue(module.ModuleId, out var definition) ? definition.Radius * definition.Radius : 0f)
|
||||||
Health = definition.Hull,
|
.Sum();
|
||||||
MaxHealth = definition.Hull,
|
return MathF.Max(24f, MathF.Sqrt(MathF.Max(totalArea, 1f)));
|
||||||
});
|
|
||||||
station.Radius = GetStationRadius(world, station);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static float GetStationRadius(SimulationWorld world, StationRuntime station)
|
|
||||||
{
|
|
||||||
var totalArea = station.Modules
|
|
||||||
.Select(module => world.ModuleDefinitions.TryGetValue(module.ModuleId, out var definition) ? definition.Radius * definition.Radius : 0f)
|
|
||||||
.Sum();
|
|
||||||
return MathF.Max(24f, MathF.Sqrt(MathF.Max(totalArea, 1f)));
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static float GetStationStorageCapacity(StationRuntime station, string storageClass)
|
|
||||||
{
|
|
||||||
var baseCapacity = storageClass switch
|
|
||||||
{
|
|
||||||
"manufactured" => 400f,
|
|
||||||
_ => 0f,
|
|
||||||
};
|
|
||||||
|
|
||||||
var bulkBays = CountStationModules(station, "module_arg_stor_solid_m_01");
|
|
||||||
var liquidTanks = CountStationModules(station, "module_arg_stor_liquid_m_01");
|
|
||||||
var containerBays = CountStationModules(station, "module_arg_stor_container_m_01");
|
|
||||||
|
|
||||||
var moduleCapacity = storageClass switch
|
|
||||||
{
|
|
||||||
"solid" => bulkBays * 1000f,
|
|
||||||
"liquid" => liquidTanks * 500f,
|
|
||||||
"container" => containerBays * 800f,
|
|
||||||
"manufactured" => containerBays * 200f,
|
|
||||||
_ => 0f,
|
|
||||||
};
|
|
||||||
|
|
||||||
return baseCapacity + moduleCapacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
|
||||||
modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal));
|
|
||||||
|
|
||||||
internal static float GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
|
|
||||||
inventory.TryGetValue(itemId, out var amount) ? amount : 0f;
|
|
||||||
|
|
||||||
internal static void AddInventory(IDictionary<string, float> inventory, string itemId, float amount)
|
|
||||||
{
|
|
||||||
if (amount <= 0f)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inventory[itemId] = GetInventoryAmount((IReadOnlyDictionary<string, float>)inventory, itemId) + amount;
|
internal static float GetStationStorageCapacity(StationRuntime station, string storageClass)
|
||||||
}
|
{
|
||||||
|
var baseCapacity = storageClass switch
|
||||||
|
{
|
||||||
|
"manufactured" => 400f,
|
||||||
|
_ => 0f,
|
||||||
|
};
|
||||||
|
|
||||||
internal static float RemoveInventory(IDictionary<string, float> inventory, string itemId, float amount)
|
var bulkBays = CountStationModules(station, "module_arg_stor_solid_m_01");
|
||||||
{
|
var liquidTanks = CountStationModules(station, "module_arg_stor_liquid_m_01");
|
||||||
var current = GetInventoryAmount((IReadOnlyDictionary<string, float>)inventory, itemId);
|
var containerBays = CountStationModules(station, "module_arg_stor_container_m_01");
|
||||||
var removed = MathF.Min(current, amount);
|
|
||||||
var remaining = current - removed;
|
var moduleCapacity = storageClass switch
|
||||||
if (remaining <= 0.001f)
|
{
|
||||||
{
|
"solid" => bulkBays * 1000f,
|
||||||
inventory.Remove(itemId);
|
"liquid" => liquidTanks * 500f,
|
||||||
}
|
"container" => containerBays * 800f,
|
||||||
else
|
"manufactured" => containerBays * 200f,
|
||||||
{
|
_ => 0f,
|
||||||
inventory[itemId] = remaining;
|
};
|
||||||
|
|
||||||
|
return baseCapacity + moduleCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
return removed;
|
internal static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
||||||
}
|
modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal));
|
||||||
|
|
||||||
internal static bool HasStationModules(StationRuntime station, params string[] modules) =>
|
internal static float GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
|
||||||
modules.All(moduleId => station.Modules.Any(candidate => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal)));
|
inventory.TryGetValue(itemId, out var amount) ? amount : 0f;
|
||||||
|
|
||||||
internal static bool CanExtractNode(ShipRuntime ship, ResourceNodeRuntime node, SimulationWorld world) =>
|
internal static void AddInventory(IDictionary<string, float> inventory, string itemId, float amount)
|
||||||
HasShipCapabilities(ship.Definition, "mining")
|
|
||||||
&& world.ItemDefinitions.TryGetValue(node.ItemId, out var item)
|
|
||||||
&& string.Equals(item.CargoKind, ship.Definition.CargoKind, StringComparison.Ordinal);
|
|
||||||
|
|
||||||
internal static bool CanBuildClaimBeacon(ShipRuntime ship) =>
|
|
||||||
string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal);
|
|
||||||
|
|
||||||
internal static float ComputeWorkforceRatio(float population, float workforceRequired)
|
|
||||||
{
|
|
||||||
if (workforceRequired <= 0.01f)
|
|
||||||
{
|
{
|
||||||
return 1f;
|
if (amount <= 0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory[itemId] = GetInventoryAmount((IReadOnlyDictionary<string, float>)inventory, itemId) + amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
var staffedRatio = MathF.Min(1f, population / workforceRequired);
|
internal static float RemoveInventory(IDictionary<string, float> inventory, string itemId, float amount)
|
||||||
return 0.1f + (0.9f * staffedRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static string? GetStorageRequirement(string storageClass) =>
|
|
||||||
storageClass switch
|
|
||||||
{
|
|
||||||
"solid" => "module_arg_stor_solid_m_01",
|
|
||||||
"liquid" => "module_arg_stor_liquid_m_01",
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
internal static float TryAddStationInventory(SimulationWorld world, StationRuntime station, string itemId, float amount)
|
|
||||||
{
|
|
||||||
if (amount <= 0f || !world.ItemDefinitions.TryGetValue(itemId, out var itemDefinition))
|
|
||||||
{
|
{
|
||||||
return 0f;
|
var current = GetInventoryAmount((IReadOnlyDictionary<string, float>)inventory, itemId);
|
||||||
|
var removed = MathF.Min(current, amount);
|
||||||
|
var remaining = current - removed;
|
||||||
|
if (remaining <= 0.001f)
|
||||||
|
{
|
||||||
|
inventory.Remove(itemId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inventory[itemId] = remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
var storageClass = itemDefinition.CargoKind;
|
internal static bool HasStationModules(StationRuntime station, params string[] modules) =>
|
||||||
var requiredModule = GetStorageRequirement(storageClass);
|
modules.All(moduleId => station.Modules.Any(candidate => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal)));
|
||||||
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
internal static bool CanBuildClaimBeacon(ShipRuntime ship) =>
|
||||||
|
string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
internal static float ComputeWorkforceRatio(float population, float workforceRequired)
|
||||||
{
|
{
|
||||||
return 0f;
|
if (workforceRequired <= 0.01f)
|
||||||
|
{
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var staffedRatio = MathF.Min(1f, population / workforceRequired);
|
||||||
|
return 0.1f + (0.9f * staffedRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
var capacity = GetStationStorageCapacity(station, storageClass);
|
internal static string? GetStorageRequirement(string storageClass) =>
|
||||||
if (capacity <= 0.01f)
|
storageClass switch
|
||||||
|
{
|
||||||
|
"solid" => "module_arg_stor_solid_m_01",
|
||||||
|
"liquid" => "module_arg_stor_liquid_m_01",
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
internal static float TryAddStationInventory(SimulationWorld world, StationRuntime station, string itemId, float amount)
|
||||||
{
|
{
|
||||||
return 0f;
|
if (amount <= 0f || !world.ItemDefinitions.TryGetValue(itemId, out var itemDefinition))
|
||||||
|
{
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var storageClass = itemDefinition.CargoKind;
|
||||||
|
var requiredModule = GetStorageRequirement(storageClass);
|
||||||
|
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
||||||
|
{
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var capacity = GetStationStorageCapacity(station, storageClass);
|
||||||
|
if (capacity <= 0.01f)
|
||||||
|
{
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var used = station.Inventory
|
||||||
|
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == storageClass)
|
||||||
|
.Sum(entry => entry.Value);
|
||||||
|
var accepted = MathF.Min(amount, MathF.Max(0f, capacity - used));
|
||||||
|
if (accepted <= 0.01f)
|
||||||
|
{
|
||||||
|
return 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddInventory(station.Inventory, itemId, accepted);
|
||||||
|
return accepted;
|
||||||
}
|
}
|
||||||
|
|
||||||
var used = station.Inventory
|
internal static bool CanStartModuleConstruction(StationRuntime station, ModuleRecipeDefinition recipe) =>
|
||||||
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var definition) && definition.CargoKind == storageClass)
|
recipe.Inputs.All(input => GetInventoryAmount(station.Inventory, input.ItemId) + 0.001f >= input.Amount);
|
||||||
.Sum(entry => entry.Value);
|
|
||||||
var accepted = MathF.Min(amount, MathF.Max(0f, capacity - used));
|
internal static ConstructionSiteRuntime? GetConstructionSiteForStation(SimulationWorld world, string stationId) =>
|
||||||
if (accepted <= 0.01f)
|
world.ConstructionSites.FirstOrDefault(site =>
|
||||||
|
string.Equals(site.StationId, stationId, StringComparison.Ordinal)
|
||||||
|
&& site.State is not ConstructionSiteStateKinds.Completed and not ConstructionSiteStateKinds.Destroyed);
|
||||||
|
|
||||||
|
internal static float GetConstructionDeliveredAmount(SimulationWorld world, ConstructionSiteRuntime site, string itemId)
|
||||||
{
|
{
|
||||||
return 0f;
|
if (site.StationId is not null
|
||||||
|
&& world.Stations.FirstOrDefault(candidate => candidate.Id == site.StationId) is { } station)
|
||||||
|
{
|
||||||
|
return GetInventoryAmount(station.Inventory, itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetInventoryAmount(site.DeliveredItems, itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddInventory(station.Inventory, itemId, accepted);
|
internal static bool IsConstructionSiteReady(SimulationWorld world, ConstructionSiteRuntime site) =>
|
||||||
return accepted;
|
site.RequiredItems.All(entry => GetConstructionDeliveredAmount(world, site, entry.Key) + 0.001f >= entry.Value);
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool CanStartModuleConstruction(StationRuntime station, ModuleRecipeDefinition recipe) =>
|
internal static float GetShipCargoAmount(ShipRuntime ship) =>
|
||||||
recipe.Inputs.All(input => GetInventoryAmount(station.Inventory, input.ItemId) + 0.001f >= input.Amount);
|
ship.Inventory.Values.Sum();
|
||||||
|
|
||||||
internal static ConstructionSiteRuntime? GetConstructionSiteForStation(SimulationWorld world, string stationId) =>
|
|
||||||
world.ConstructionSites.FirstOrDefault(site =>
|
|
||||||
string.Equals(site.StationId, stationId, StringComparison.Ordinal)
|
|
||||||
&& site.State is not ConstructionSiteStateKinds.Completed and not ConstructionSiteStateKinds.Destroyed);
|
|
||||||
|
|
||||||
internal static float GetConstructionDeliveredAmount(SimulationWorld world, ConstructionSiteRuntime site, string itemId)
|
|
||||||
{
|
|
||||||
if (site.StationId is not null
|
|
||||||
&& world.Stations.FirstOrDefault(candidate => candidate.Id == site.StationId) is { } station)
|
|
||||||
{
|
|
||||||
return GetInventoryAmount(station.Inventory, itemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetInventoryAmount(site.DeliveredItems, itemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool IsConstructionSiteReady(SimulationWorld world, ConstructionSiteRuntime site) =>
|
|
||||||
site.RequiredItems.All(entry => GetConstructionDeliveredAmount(world, site, entry.Key) + 0.001f >= entry.Value);
|
|
||||||
|
|
||||||
internal static float GetShipCargoAmount(ShipRuntime ship) =>
|
|
||||||
ship.Inventory.Values.Sum();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,36 +4,36 @@ namespace SpaceGame.Api.Ships.Api;
|
|||||||
|
|
||||||
public sealed class EnqueueShipOrderHandler(WorldService worldService) : Endpoint<ShipOrderCommandRequest, ShipSnapshot>
|
public sealed class EnqueueShipOrderHandler(WorldService worldService) : Endpoint<ShipOrderCommandRequest, ShipSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Post("/api/ships/{shipId}/orders");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(ShipOrderCommandRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var shipId = Route<string>("shipId");
|
|
||||||
if (string.IsNullOrWhiteSpace(shipId))
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Post("/api/ships/{shipId}/orders");
|
||||||
return;
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
public override async Task HandleAsync(ShipOrderCommandRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var snapshot = worldService.EnqueueShipOrder(shipId, request);
|
var shipId = Route<string>("shipId");
|
||||||
if (snapshot is null)
|
if (string.IsNullOrWhiteSpace(shipId))
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
await SendNotFoundAsync(cancellationToken);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
try
|
||||||
|
{
|
||||||
|
var snapshot = worldService.EnqueueShipOrder(shipId, request);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex)
|
||||||
|
{
|
||||||
|
AddError(ex.Message);
|
||||||
|
await SendErrorsAsync(cancellation: cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
{
|
|
||||||
AddError(ex.Message);
|
|
||||||
await SendErrorsAsync(cancellation: cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,27 +4,27 @@ namespace SpaceGame.Api.Ships.Api;
|
|||||||
|
|
||||||
public sealed class RemoveShipOrderRequest
|
public sealed class RemoveShipOrderRequest
|
||||||
{
|
{
|
||||||
public string ShipId { get; set; } = string.Empty;
|
public string ShipId { get; set; } = string.Empty;
|
||||||
public string OrderId { get; set; } = string.Empty;
|
public string OrderId { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RemoveShipOrderHandler(WorldService worldService) : Endpoint<RemoveShipOrderRequest, ShipSnapshot>
|
public sealed class RemoveShipOrderHandler(WorldService worldService) : Endpoint<RemoveShipOrderRequest, ShipSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Delete("/api/ships/{shipId}/orders/{orderId}");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(RemoveShipOrderRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var snapshot = worldService.RemoveShipOrder(request.ShipId, request.OrderId);
|
|
||||||
if (snapshot is null)
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Delete("/api/ships/{shipId}/orders/{orderId}");
|
||||||
return;
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
public override async Task HandleAsync(RemoveShipOrderRequest request, CancellationToken cancellationToken)
|
||||||
}
|
{
|
||||||
|
var snapshot = worldService.RemoveShipOrder(request.ShipId, request.OrderId);
|
||||||
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,28 +4,28 @@ namespace SpaceGame.Api.Ships.Api;
|
|||||||
|
|
||||||
public sealed class UpdateShipDefaultBehaviorHandler(WorldService worldService) : Endpoint<ShipDefaultBehaviorCommandRequest, ShipSnapshot>
|
public sealed class UpdateShipDefaultBehaviorHandler(WorldService worldService) : Endpoint<ShipDefaultBehaviorCommandRequest, ShipSnapshot>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Put("/api/ships/{shipId}/default-behavior");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(ShipDefaultBehaviorCommandRequest request, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var shipId = Route<string>("shipId");
|
|
||||||
if (string.IsNullOrWhiteSpace(shipId))
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
Put("/api/ships/{shipId}/default-behavior");
|
||||||
return;
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
var snapshot = worldService.UpdateShipDefaultBehavior(shipId, request);
|
public override async Task HandleAsync(ShipDefaultBehaviorCommandRequest request, CancellationToken cancellationToken)
|
||||||
if (snapshot is null)
|
|
||||||
{
|
{
|
||||||
await SendNotFoundAsync(cancellationToken);
|
var shipId = Route<string>("shipId");
|
||||||
return;
|
if (string.IsNullOrWhiteSpace(shipId))
|
||||||
}
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await SendOkAsync(snapshot, cancellationToken);
|
var snapshot = worldService.UpdateShipDefaultBehavior(shipId, request);
|
||||||
}
|
if (snapshot is null)
|
||||||
|
{
|
||||||
|
await SendNotFoundAsync(cancellationToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendOkAsync(snapshot, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,156 +2,156 @@ namespace SpaceGame.Api.Ships.Runtime;
|
|||||||
|
|
||||||
public sealed class ShipRuntime
|
public sealed class ShipRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string SystemId { get; set; }
|
public required string SystemId { get; set; }
|
||||||
public required ShipDefinition Definition { get; init; }
|
public required ShipDefinition Definition { get; init; }
|
||||||
public required string FactionId { get; init; }
|
public required string FactionId { get; init; }
|
||||||
public required Vector3 Position { get; set; }
|
public required Vector3 Position { get; set; }
|
||||||
public required Vector3 TargetPosition { get; set; }
|
public required Vector3 TargetPosition { get; set; }
|
||||||
public required ShipSpatialStateRuntime SpatialState { get; set; }
|
public required ShipSpatialStateRuntime SpatialState { get; set; }
|
||||||
public Vector3 Velocity { get; set; } = Vector3.Zero;
|
public Vector3 Velocity { get; set; } = Vector3.Zero;
|
||||||
public ShipState State { get; set; } = ShipState.Idle;
|
public ShipState State { get; set; } = ShipState.Idle;
|
||||||
public required DefaultBehaviorRuntime DefaultBehavior { get; set; }
|
public required DefaultBehaviorRuntime DefaultBehavior { get; set; }
|
||||||
public List<ShipOrderRuntime> OrderQueue { get; } = [];
|
public List<ShipOrderRuntime> OrderQueue { get; } = [];
|
||||||
public ShipPlanRuntime? ActivePlan { get; set; }
|
public ShipPlanRuntime? ActivePlan { get; set; }
|
||||||
public required ShipSkillProfileRuntime Skills { get; set; }
|
public required ShipSkillProfileRuntime Skills { get; set; }
|
||||||
public bool NeedsReplan { get; set; } = true;
|
public bool NeedsReplan { get; set; } = true;
|
||||||
public float ReplanCooldownSeconds { get; set; }
|
public float ReplanCooldownSeconds { get; set; }
|
||||||
public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
|
public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
|
||||||
public string? DockedStationId { get; set; }
|
public string? DockedStationId { get; set; }
|
||||||
public int? AssignedDockingPadIndex { get; set; }
|
public int? AssignedDockingPadIndex { get; set; }
|
||||||
public string? CommanderId { get; set; }
|
public string? CommanderId { get; set; }
|
||||||
public string? PolicySetId { get; set; }
|
public string? PolicySetId { get; set; }
|
||||||
public string ControlSourceKind { get; set; } = "unassigned";
|
public string ControlSourceKind { get; set; } = "unassigned";
|
||||||
public string? ControlSourceId { get; set; }
|
public string? ControlSourceId { get; set; }
|
||||||
public string? ControlReason { get; set; }
|
public string? ControlReason { get; set; }
|
||||||
public string? LastReplanReason { get; set; }
|
public string? LastReplanReason { get; set; }
|
||||||
public string? LastAccessFailureReason { get; set; }
|
public string? LastAccessFailureReason { get; set; }
|
||||||
public float Health { get; set; }
|
public float Health { get; set; }
|
||||||
public HashSet<string> KnownStationIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> KnownStationIds { get; } = new(StringComparer.Ordinal);
|
||||||
public List<string> History { get; } = [];
|
public List<string> History { get; } = [];
|
||||||
public string LastSignature { get; set; } = string.Empty;
|
public string LastSignature { get; set; } = string.Empty;
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ShipSkillProfileRuntime
|
public sealed class ShipSkillProfileRuntime
|
||||||
{
|
{
|
||||||
public int Navigation { get; set; }
|
public int Navigation { get; set; }
|
||||||
public int Trade { get; set; }
|
public int Trade { get; set; }
|
||||||
public int Mining { get; set; }
|
public int Mining { get; set; }
|
||||||
public int Combat { get; set; }
|
public int Combat { get; set; }
|
||||||
public int Construction { get; set; }
|
public int Construction { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ShipOrderRuntime
|
public sealed class ShipOrderRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; init; }
|
public required string Kind { get; init; }
|
||||||
public OrderStatus Status { get; set; } = OrderStatus.Queued;
|
public OrderStatus Status { get; set; } = OrderStatus.Queued;
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
public bool InterruptCurrentPlan { get; set; } = true;
|
public bool InterruptCurrentPlan { get; set; } = true;
|
||||||
public DateTimeOffset CreatedAtUtc { get; init; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset CreatedAtUtc { get; init; } = DateTimeOffset.UtcNow;
|
||||||
public string? Label { get; set; }
|
public string? Label { get; set; }
|
||||||
public string? TargetEntityId { get; set; }
|
public string? TargetEntityId { get; set; }
|
||||||
public string? TargetSystemId { get; set; }
|
public string? TargetSystemId { get; set; }
|
||||||
public Vector3? TargetPosition { get; set; }
|
public Vector3? TargetPosition { get; set; }
|
||||||
public string? SourceStationId { get; set; }
|
public string? SourceStationId { get; set; }
|
||||||
public string? DestinationStationId { get; set; }
|
public string? DestinationStationId { get; set; }
|
||||||
public string? ItemId { get; set; }
|
public string? ItemId { get; set; }
|
||||||
public string? NodeId { get; set; }
|
public string? NodeId { get; set; }
|
||||||
public string? ConstructionSiteId { get; set; }
|
public string? ConstructionSiteId { get; set; }
|
||||||
public string? ModuleId { get; set; }
|
public string? ModuleId { get; set; }
|
||||||
public float WaitSeconds { get; set; }
|
public float WaitSeconds { get; set; }
|
||||||
public float Radius { get; set; }
|
public float Radius { get; set; }
|
||||||
public int? MaxSystemRange { get; set; }
|
public int? MaxSystemRange { get; set; }
|
||||||
public bool KnownStationsOnly { get; set; }
|
public bool KnownStationsOnly { get; set; }
|
||||||
public string? FailureReason { get; set; }
|
public string? FailureReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DefaultBehaviorRuntime
|
public sealed class DefaultBehaviorRuntime
|
||||||
{
|
{
|
||||||
public required string Kind { get; set; }
|
public required string Kind { get; set; }
|
||||||
public string? HomeSystemId { get; set; }
|
public string? HomeSystemId { get; set; }
|
||||||
public string? HomeStationId { get; set; }
|
public string? HomeStationId { get; set; }
|
||||||
public string? AreaSystemId { get; set; }
|
public string? AreaSystemId { get; set; }
|
||||||
public string? TargetEntityId { get; set; }
|
public string? TargetEntityId { get; set; }
|
||||||
public string? PreferredItemId { get; set; }
|
public string? PreferredItemId { get; set; }
|
||||||
public string? PreferredNodeId { get; set; }
|
public string? PreferredNodeId { get; set; }
|
||||||
public string? PreferredConstructionSiteId { get; set; }
|
public string? PreferredConstructionSiteId { get; set; }
|
||||||
public string? PreferredModuleId { get; set; }
|
public string? PreferredModuleId { get; set; }
|
||||||
public Vector3? TargetPosition { get; set; }
|
public Vector3? TargetPosition { get; set; }
|
||||||
public float WaitSeconds { get; set; } = 3f;
|
public float WaitSeconds { get; set; } = 3f;
|
||||||
public float Radius { get; set; } = 24f;
|
public float Radius { get; set; } = 24f;
|
||||||
public int MaxSystemRange { get; set; }
|
public int MaxSystemRange { get; set; }
|
||||||
public bool KnownStationsOnly { get; set; }
|
public bool KnownStationsOnly { get; set; }
|
||||||
public List<Vector3> PatrolPoints { get; set; } = [];
|
public List<Vector3> PatrolPoints { get; set; } = [];
|
||||||
public int PatrolIndex { get; set; }
|
public int PatrolIndex { get; set; }
|
||||||
public List<ShipOrderTemplateRuntime> RepeatOrders { get; set; } = [];
|
public List<ShipOrderTemplateRuntime> RepeatOrders { get; set; } = [];
|
||||||
public int RepeatIndex { get; set; }
|
public int RepeatIndex { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ShipOrderTemplateRuntime
|
public sealed class ShipOrderTemplateRuntime
|
||||||
{
|
{
|
||||||
public required string Kind { get; init; }
|
public required string Kind { get; init; }
|
||||||
public string? Label { get; set; }
|
public string? Label { get; set; }
|
||||||
public string? TargetEntityId { get; set; }
|
public string? TargetEntityId { get; set; }
|
||||||
public string? TargetSystemId { get; set; }
|
public string? TargetSystemId { get; set; }
|
||||||
public Vector3? TargetPosition { get; set; }
|
public Vector3? TargetPosition { get; set; }
|
||||||
public string? SourceStationId { get; set; }
|
public string? SourceStationId { get; set; }
|
||||||
public string? DestinationStationId { get; set; }
|
public string? DestinationStationId { get; set; }
|
||||||
public string? ItemId { get; set; }
|
public string? ItemId { get; set; }
|
||||||
public string? NodeId { get; set; }
|
public string? NodeId { get; set; }
|
||||||
public string? ConstructionSiteId { get; set; }
|
public string? ConstructionSiteId { get; set; }
|
||||||
public string? ModuleId { get; set; }
|
public string? ModuleId { get; set; }
|
||||||
public float WaitSeconds { get; set; }
|
public float WaitSeconds { get; set; }
|
||||||
public float Radius { get; set; }
|
public float Radius { get; set; }
|
||||||
public int? MaxSystemRange { get; set; }
|
public int? MaxSystemRange { get; set; }
|
||||||
public bool KnownStationsOnly { get; set; }
|
public bool KnownStationsOnly { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ShipPlanRuntime
|
public sealed class ShipPlanRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required AiPlanSourceKind SourceKind { get; init; }
|
public required AiPlanSourceKind SourceKind { get; init; }
|
||||||
public required string SourceId { get; init; }
|
public required string SourceId { get; init; }
|
||||||
public required string Kind { get; init; }
|
public required string Kind { get; init; }
|
||||||
public required string Summary { get; set; }
|
public required string Summary { get; set; }
|
||||||
public AiPlanStatus Status { get; set; } = AiPlanStatus.Planned;
|
public AiPlanStatus Status { get; set; } = AiPlanStatus.Planned;
|
||||||
public int CurrentStepIndex { get; set; }
|
public int CurrentStepIndex { get; set; }
|
||||||
public DateTimeOffset CreatedAtUtc { get; init; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset CreatedAtUtc { get; init; } = DateTimeOffset.UtcNow;
|
||||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||||
public string? InterruptReason { get; set; }
|
public string? InterruptReason { get; set; }
|
||||||
public string? FailureReason { get; set; }
|
public string? FailureReason { get; set; }
|
||||||
public List<ShipPlanStepRuntime> Steps { get; } = [];
|
public List<ShipPlanStepRuntime> Steps { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ShipPlanStepRuntime
|
public sealed class ShipPlanStepRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; init; }
|
public required string Kind { get; init; }
|
||||||
public required string Summary { get; set; }
|
public required string Summary { get; set; }
|
||||||
public AiPlanStepStatus Status { get; set; } = AiPlanStepStatus.Planned;
|
public AiPlanStepStatus Status { get; set; } = AiPlanStepStatus.Planned;
|
||||||
public int CurrentSubTaskIndex { get; set; }
|
public int CurrentSubTaskIndex { get; set; }
|
||||||
public string? BlockingReason { get; set; }
|
public string? BlockingReason { get; set; }
|
||||||
public List<ShipSubTaskRuntime> SubTasks { get; } = [];
|
public List<ShipSubTaskRuntime> SubTasks { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ShipSubTaskRuntime
|
public sealed class ShipSubTaskRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string Kind { get; init; }
|
public required string Kind { get; init; }
|
||||||
public required string Summary { get; set; }
|
public required string Summary { get; set; }
|
||||||
public WorkStatus Status { get; set; } = WorkStatus.Pending;
|
public WorkStatus Status { get; set; } = WorkStatus.Pending;
|
||||||
public string? TargetEntityId { get; set; }
|
public string? TargetEntityId { get; set; }
|
||||||
public string? TargetSystemId { get; set; }
|
public string? TargetSystemId { get; set; }
|
||||||
public string? TargetNodeId { get; set; }
|
public string? TargetNodeId { get; set; }
|
||||||
public Vector3? TargetPosition { get; set; }
|
public Vector3? TargetPosition { get; set; }
|
||||||
public string? ItemId { get; set; }
|
public string? ItemId { get; set; }
|
||||||
public string? ModuleId { get; set; }
|
public string? ModuleId { get; set; }
|
||||||
public float Threshold { get; set; }
|
public float Threshold { get; set; }
|
||||||
public float Amount { get; set; }
|
public float Amount { get; set; }
|
||||||
public float ElapsedSeconds { get; set; }
|
public float ElapsedSeconds { get; set; }
|
||||||
public float TotalSeconds { get; set; }
|
public float TotalSeconds { get; set; }
|
||||||
public float Progress { get; set; }
|
public float Progress { get; set; }
|
||||||
public string? BlockingReason { get; set; }
|
public string? BlockingReason { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,146 +3,146 @@ namespace SpaceGame.Api.Simulation.Core;
|
|||||||
|
|
||||||
public sealed class SimulationEngine
|
public sealed class SimulationEngine
|
||||||
{
|
{
|
||||||
private readonly OrbitalSimulationOptions _orbitalSimulation;
|
private readonly OrbitalSimulationOptions _orbitalSimulation;
|
||||||
private readonly OrbitalStateUpdater _orbitalStateUpdater;
|
private readonly OrbitalStateUpdater _orbitalStateUpdater;
|
||||||
private readonly InfrastructureSimulationService _infrastructureSimulation;
|
private readonly InfrastructureSimulationService _infrastructureSimulation;
|
||||||
private readonly GeopoliticalSimulationService _geopolitics;
|
private readonly GeopoliticalSimulationService _geopolitics;
|
||||||
private readonly CommanderPlanningService _commanderPlanning;
|
private readonly CommanderPlanningService _commanderPlanning;
|
||||||
private readonly PlayerFactionService _playerFaction;
|
private readonly PlayerFactionService _playerFaction;
|
||||||
private readonly StationSimulationService _stationSimulation;
|
private readonly StationSimulationService _stationSimulation;
|
||||||
private readonly StationLifecycleService _stationLifecycle;
|
private readonly StationLifecycleService _stationLifecycle;
|
||||||
private readonly ShipAiService _shipAi;
|
private readonly ShipAiService _shipAi;
|
||||||
private readonly SimulationProjectionService _projection;
|
private readonly SimulationProjectionService _projection;
|
||||||
|
|
||||||
public SimulationEngine(OrbitalSimulationOptions? orbitalSimulation = null)
|
public SimulationEngine(OrbitalSimulationOptions? orbitalSimulation = null)
|
||||||
{
|
|
||||||
_orbitalSimulation = orbitalSimulation ?? new OrbitalSimulationOptions();
|
|
||||||
_orbitalStateUpdater = new OrbitalStateUpdater(_orbitalSimulation);
|
|
||||||
_infrastructureSimulation = new InfrastructureSimulationService();
|
|
||||||
_geopolitics = new GeopoliticalSimulationService();
|
|
||||||
_commanderPlanning = new CommanderPlanningService();
|
|
||||||
_playerFaction = new PlayerFactionService();
|
|
||||||
_stationSimulation = new StationSimulationService();
|
|
||||||
_stationLifecycle = new StationLifecycleService(_stationSimulation);
|
|
||||||
_shipAi = new ShipAiService();
|
|
||||||
_projection = new SimulationProjectionService(_orbitalSimulation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WorldDelta Tick(SimulationWorld world, float deltaSeconds, long sequence)
|
|
||||||
{
|
|
||||||
var nowUtc = DateTimeOffset.UtcNow;
|
|
||||||
var events = new List<SimulationEventRecord>();
|
|
||||||
var simulationDeltaSeconds = deltaSeconds * MathF.Max(world.Balance.SimulationSpeedMultiplier, 0.01f);
|
|
||||||
world.GeneratedAtUtc = nowUtc;
|
|
||||||
|
|
||||||
world.OrbitalTimeSeconds += simulationDeltaSeconds * _orbitalSimulation.SimulatedSecondsPerRealSecond;
|
|
||||||
|
|
||||||
_orbitalStateUpdater.Update(world);
|
|
||||||
_infrastructureSimulation.UpdateClaims(world, events);
|
|
||||||
_infrastructureSimulation.UpdateConstructionSites(world, events);
|
|
||||||
_geopolitics.Update(world, simulationDeltaSeconds, events);
|
|
||||||
_commanderPlanning.UpdateCommanders(world, simulationDeltaSeconds, events);
|
|
||||||
_playerFaction.Update(world, simulationDeltaSeconds, events);
|
|
||||||
_stationLifecycle.UpdateStations(world, simulationDeltaSeconds, events);
|
|
||||||
|
|
||||||
foreach (var ship in world.Ships.ToList())
|
|
||||||
{
|
{
|
||||||
if (ship.Health <= 0f)
|
_orbitalSimulation = orbitalSimulation ?? new OrbitalSimulationOptions();
|
||||||
{
|
_orbitalStateUpdater = new OrbitalStateUpdater(_orbitalSimulation);
|
||||||
continue;
|
_infrastructureSimulation = new InfrastructureSimulationService();
|
||||||
}
|
_geopolitics = new GeopoliticalSimulationService();
|
||||||
|
_commanderPlanning = new CommanderPlanningService();
|
||||||
var previousPosition = ship.Position;
|
_playerFaction = new PlayerFactionService();
|
||||||
_shipAi.UpdateShip(world, ship, simulationDeltaSeconds, events);
|
_stationSimulation = new StationSimulationService();
|
||||||
ship.Velocity = ship.Position.Subtract(previousPosition).Divide(simulationDeltaSeconds);
|
_stationLifecycle = new StationLifecycleService(_stationSimulation);
|
||||||
|
_shipAi = new ShipAiService();
|
||||||
|
_projection = new SimulationProjectionService(_orbitalSimulation);
|
||||||
}
|
}
|
||||||
|
|
||||||
_orbitalStateUpdater.SyncSpatialState(world);
|
public WorldDelta Tick(SimulationWorld world, float deltaSeconds, long sequence)
|
||||||
CleanupDestroyedEntities(world, events);
|
|
||||||
return _projection.BuildDelta(world, sequence, events);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WorldSnapshot BuildSnapshot(SimulationWorld world, long sequence) =>
|
|
||||||
_projection.BuildSnapshot(world, sequence);
|
|
||||||
|
|
||||||
public void PrimeDeltaBaseline(SimulationWorld world) =>
|
|
||||||
_projection.PrimeDeltaBaseline(world);
|
|
||||||
|
|
||||||
internal static float GetShipCargoAmount(ShipRuntime ship) =>
|
|
||||||
SimulationRuntimeSupport.GetShipCargoAmount(ship);
|
|
||||||
|
|
||||||
private static void CleanupDestroyedEntities(SimulationWorld world, ICollection<SimulationEventRecord> events)
|
|
||||||
{
|
|
||||||
foreach (var ship in world.Ships.Where(candidate => candidate.Health <= 0f).ToList())
|
|
||||||
{
|
{
|
||||||
CreateWreck(world, "ship", ship.Id, ship.SystemId, ship.Position, ship.Definition.CargoCapacity + (ship.Definition.MaxHealth * 0.08f));
|
var nowUtc = DateTimeOffset.UtcNow;
|
||||||
world.Ships.Remove(ship);
|
var events = new List<SimulationEventRecord>();
|
||||||
if (ship.DockedStationId is not null && world.Stations.FirstOrDefault(station => station.Id == ship.DockedStationId) is { } dockedStation)
|
var simulationDeltaSeconds = deltaSeconds * MathF.Max(world.Balance.SimulationSpeedMultiplier, 0.01f);
|
||||||
{
|
world.GeneratedAtUtc = nowUtc;
|
||||||
dockedStation.DockedShipIds.Remove(ship.Id);
|
|
||||||
dockedStation.DockingPadAssignments.Remove(ship.AssignedDockingPadIndex ?? -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (world.Factions.FirstOrDefault(candidate => candidate.Id == ship.FactionId) is { } faction)
|
world.OrbitalTimeSeconds += simulationDeltaSeconds * _orbitalSimulation.SimulatedSecondsPerRealSecond;
|
||||||
{
|
|
||||||
faction.ShipsLost += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ship.CommanderId is not null && world.Commanders.FirstOrDefault(candidate => candidate.Id == ship.CommanderId) is { } commander)
|
_orbitalStateUpdater.Update(world);
|
||||||
{
|
_infrastructureSimulation.UpdateClaims(world, events);
|
||||||
commander.IsAlive = false;
|
_infrastructureSimulation.UpdateConstructionSites(world, events);
|
||||||
}
|
_geopolitics.Update(world, simulationDeltaSeconds, events);
|
||||||
|
_commanderPlanning.UpdateCommanders(world, simulationDeltaSeconds, events);
|
||||||
|
_playerFaction.Update(world, simulationDeltaSeconds, events);
|
||||||
|
_stationLifecycle.UpdateStations(world, simulationDeltaSeconds, events);
|
||||||
|
|
||||||
events.Add(new SimulationEventRecord("ship", ship.Id, "destroyed", $"{ship.Definition.Label} was destroyed.", DateTimeOffset.UtcNow));
|
foreach (var ship in world.Ships.ToList())
|
||||||
|
{
|
||||||
|
if (ship.Health <= 0f)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousPosition = ship.Position;
|
||||||
|
_shipAi.UpdateShip(world, ship, simulationDeltaSeconds, events);
|
||||||
|
ship.Velocity = ship.Position.Subtract(previousPosition).Divide(simulationDeltaSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
_orbitalStateUpdater.SyncSpatialState(world);
|
||||||
|
CleanupDestroyedEntities(world, events);
|
||||||
|
return _projection.BuildDelta(world, sequence, events);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var station in world.Stations.Where(candidate => candidate.Health <= 0f).ToList())
|
public WorldSnapshot BuildSnapshot(SimulationWorld world, long sequence) =>
|
||||||
|
_projection.BuildSnapshot(world, sequence);
|
||||||
|
|
||||||
|
public void PrimeDeltaBaseline(SimulationWorld world) =>
|
||||||
|
_projection.PrimeDeltaBaseline(world);
|
||||||
|
|
||||||
|
internal static float GetShipCargoAmount(ShipRuntime ship) =>
|
||||||
|
SimulationRuntimeSupport.GetShipCargoAmount(ship);
|
||||||
|
|
||||||
|
private static void CleanupDestroyedEntities(SimulationWorld world, ICollection<SimulationEventRecord> events)
|
||||||
{
|
{
|
||||||
CreateWreck(world, "station", station.Id, station.SystemId, station.Position, station.MaxHealth * 0.12f);
|
foreach (var ship in world.Ships.Where(candidate => candidate.Health <= 0f).ToList())
|
||||||
world.Stations.Remove(station);
|
{
|
||||||
|
CreateWreck(world, "ship", ship.Id, ship.SystemId, ship.Position, ship.Definition.CargoCapacity + (ship.Definition.MaxHealth * 0.08f));
|
||||||
|
world.Ships.Remove(ship);
|
||||||
|
if (ship.DockedStationId is not null && world.Stations.FirstOrDefault(station => station.Id == ship.DockedStationId) is { } dockedStation)
|
||||||
|
{
|
||||||
|
dockedStation.DockedShipIds.Remove(ship.Id);
|
||||||
|
dockedStation.DockingPadAssignments.Remove(ship.AssignedDockingPadIndex ?? -1);
|
||||||
|
}
|
||||||
|
|
||||||
if (station.CelestialId is not null && world.Celestials.FirstOrDefault(candidate => candidate.Id == station.CelestialId) is { } celestial)
|
if (world.Factions.FirstOrDefault(candidate => candidate.Id == ship.FactionId) is { } faction)
|
||||||
{
|
{
|
||||||
celestial.OccupyingStructureId = null;
|
faction.ShipsLost += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var claim in world.Claims.Where(candidate => candidate.CelestialId == station.CelestialId))
|
if (ship.CommanderId is not null && world.Commanders.FirstOrDefault(candidate => candidate.Id == ship.CommanderId) is { } commander)
|
||||||
{
|
{
|
||||||
claim.Health = 0f;
|
commander.IsAlive = false;
|
||||||
claim.State = ClaimStateKinds.Destroyed;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var site in world.ConstructionSites.Where(candidate => candidate.StationId == station.Id))
|
events.Add(new SimulationEventRecord("ship", ship.Id, "destroyed", $"{ship.Definition.Label} was destroyed.", DateTimeOffset.UtcNow));
|
||||||
{
|
}
|
||||||
site.State = ConstructionSiteStateKinds.Destroyed;
|
|
||||||
}
|
|
||||||
|
|
||||||
events.Add(new SimulationEventRecord("station", station.Id, "destroyed", $"{station.Label} was destroyed.", DateTimeOffset.UtcNow));
|
foreach (var station in world.Stations.Where(candidate => candidate.Health <= 0f).ToList())
|
||||||
}
|
{
|
||||||
}
|
CreateWreck(world, "station", station.Id, station.SystemId, station.Position, station.MaxHealth * 0.12f);
|
||||||
|
world.Stations.Remove(station);
|
||||||
|
|
||||||
private static void CreateWreck(SimulationWorld world, string sourceKind, string sourceEntityId, string systemId, Vector3 position, float amount)
|
if (station.CelestialId is not null && world.Celestials.FirstOrDefault(candidate => candidate.Id == station.CelestialId) is { } celestial)
|
||||||
{
|
{
|
||||||
var itemId = world.ItemDefinitions.ContainsKey("scrapmetal")
|
celestial.OccupyingStructureId = null;
|
||||||
? "scrapmetal"
|
}
|
||||||
: world.ItemDefinitions.ContainsKey("rawscrap")
|
|
||||||
? "rawscrap"
|
foreach (var claim in world.Claims.Where(candidate => candidate.CelestialId == station.CelestialId))
|
||||||
: world.ItemDefinitions.Keys.OrderBy(id => id, StringComparer.Ordinal).FirstOrDefault();
|
{
|
||||||
if (itemId is null || amount <= 0.01f)
|
claim.Health = 0f;
|
||||||
{
|
claim.State = ClaimStateKinds.Destroyed;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
foreach (var site in world.ConstructionSites.Where(candidate => candidate.StationId == station.Id))
|
||||||
|
{
|
||||||
|
site.State = ConstructionSiteStateKinds.Destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
events.Add(new SimulationEventRecord("station", station.Id, "destroyed", $"{station.Label} was destroyed.", DateTimeOffset.UtcNow));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
world.Wrecks.Add(new WreckRuntime
|
private static void CreateWreck(SimulationWorld world, string sourceKind, string sourceEntityId, string systemId, Vector3 position, float amount)
|
||||||
{
|
{
|
||||||
Id = $"wreck-{sourceKind}-{sourceEntityId}",
|
var itemId = world.ItemDefinitions.ContainsKey("scrapmetal")
|
||||||
SourceKind = sourceKind,
|
? "scrapmetal"
|
||||||
SourceEntityId = sourceEntityId,
|
: world.ItemDefinitions.ContainsKey("rawscrap")
|
||||||
SystemId = systemId,
|
? "rawscrap"
|
||||||
Position = position,
|
: world.ItemDefinitions.Keys.OrderBy(id => id, StringComparer.Ordinal).FirstOrDefault();
|
||||||
ItemId = itemId,
|
if (itemId is null || amount <= 0.01f)
|
||||||
RemainingAmount = amount,
|
{
|
||||||
MaxAmount = amount,
|
return;
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
world.Wrecks.Add(new WreckRuntime
|
||||||
|
{
|
||||||
|
Id = $"wreck-{sourceKind}-{sourceEntityId}",
|
||||||
|
SourceKind = sourceKind,
|
||||||
|
SourceEntityId = sourceEntityId,
|
||||||
|
SystemId = systemId,
|
||||||
|
Position = position,
|
||||||
|
ItemId = itemId,
|
||||||
|
RemainingAmount = amount,
|
||||||
|
MaxAmount = amount,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,35 +2,35 @@ namespace SpaceGame.Api.Stations.Runtime;
|
|||||||
|
|
||||||
public sealed class ClaimRuntime
|
public sealed class ClaimRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string FactionId { get; init; }
|
public required string FactionId { get; init; }
|
||||||
public required string SystemId { get; init; }
|
public required string SystemId { get; init; }
|
||||||
public required string CelestialId { get; init; }
|
public required string CelestialId { get; init; }
|
||||||
public string? CommanderId { get; set; }
|
public string? CommanderId { get; set; }
|
||||||
public DateTimeOffset PlacedAtUtc { get; init; }
|
public DateTimeOffset PlacedAtUtc { get; init; }
|
||||||
public DateTimeOffset ActivatesAtUtc { get; set; }
|
public DateTimeOffset ActivatesAtUtc { get; set; }
|
||||||
public string State { get; set; } = ClaimStateKinds.Placed;
|
public string State { get; set; } = ClaimStateKinds.Placed;
|
||||||
public float Health { get; set; }
|
public float Health { get; set; }
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ConstructionSiteRuntime
|
public sealed class ConstructionSiteRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string FactionId { get; init; }
|
public required string FactionId { get; init; }
|
||||||
public required string SystemId { get; init; }
|
public required string SystemId { get; init; }
|
||||||
public required string CelestialId { get; init; }
|
public required string CelestialId { get; init; }
|
||||||
public required string TargetKind { get; init; }
|
public required string TargetKind { get; init; }
|
||||||
public required string TargetDefinitionId { get; init; }
|
public required string TargetDefinitionId { get; init; }
|
||||||
public string? BlueprintId { get; set; }
|
public string? BlueprintId { get; set; }
|
||||||
public string? ClaimId { get; set; }
|
public string? ClaimId { get; set; }
|
||||||
public string? StationId { get; set; }
|
public string? StationId { get; set; }
|
||||||
public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
|
public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
|
||||||
public Dictionary<string, float> RequiredItems { get; } = new(StringComparer.Ordinal);
|
public Dictionary<string, float> RequiredItems { get; } = new(StringComparer.Ordinal);
|
||||||
public Dictionary<string, float> DeliveredItems { get; } = new(StringComparer.Ordinal);
|
public Dictionary<string, float> DeliveredItems { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> AssignedConstructorShipIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> AssignedConstructorShipIds { get; } = new(StringComparer.Ordinal);
|
||||||
public HashSet<string> MarketOrderIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> MarketOrderIds { get; } = new(StringComparer.Ordinal);
|
||||||
public float Progress { get; set; }
|
public float Progress { get; set; }
|
||||||
public string State { get; set; } = ConstructionSiteStateKinds.Planned;
|
public string State { get; set; } = ConstructionSiteStateKinds.Planned;
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,49 +2,49 @@ namespace SpaceGame.Api.Stations.Runtime;
|
|||||||
|
|
||||||
public sealed class StationRuntime
|
public sealed class StationRuntime
|
||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string SystemId { get; init; }
|
public required string SystemId { get; init; }
|
||||||
public required string Label { get; set; }
|
public required string Label { get; set; }
|
||||||
public string Category { get; set; } = "station";
|
public string Category { get; set; } = "station";
|
||||||
public string Objective { get; set; } = "general";
|
public string Objective { get; set; } = "general";
|
||||||
public string Color { get; set; } = "#8df0d2";
|
public string Color { get; set; } = "#8df0d2";
|
||||||
public required Vector3 Position { get; set; }
|
public required Vector3 Position { get; set; }
|
||||||
public float Radius { get; set; } = 24f;
|
public float Radius { get; set; } = 24f;
|
||||||
public required string FactionId { get; init; }
|
public required string FactionId { get; init; }
|
||||||
public string? CelestialId { get; set; }
|
public string? CelestialId { get; set; }
|
||||||
public string? CommanderId { get; set; }
|
public string? CommanderId { get; set; }
|
||||||
public string? PolicySetId { get; set; }
|
public string? PolicySetId { get; set; }
|
||||||
public List<StationModuleRuntime> Modules { get; } = [];
|
public List<StationModuleRuntime> Modules { get; } = [];
|
||||||
public float Health { get; set; } = 600f;
|
public float Health { get; set; } = 600f;
|
||||||
public float MaxHealth { get; set; } = 600f;
|
public float MaxHealth { get; set; } = 600f;
|
||||||
public IEnumerable<string> InstalledModules => Modules.Select((module) => module.ModuleId);
|
public IEnumerable<string> InstalledModules => Modules.Select((module) => module.ModuleId);
|
||||||
public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
|
public Dictionary<string, float> Inventory { get; } = new(StringComparer.Ordinal);
|
||||||
public Dictionary<string, float> ProductionLaneTimers { get; } = new(StringComparer.Ordinal);
|
public Dictionary<string, float> ProductionLaneTimers { get; } = new(StringComparer.Ordinal);
|
||||||
public Dictionary<int, string> DockingPadAssignments { get; } = new();
|
public Dictionary<int, string> DockingPadAssignments { get; } = new();
|
||||||
public HashSet<string> MarketOrderIds { get; } = new(StringComparer.Ordinal);
|
public HashSet<string> MarketOrderIds { get; } = new(StringComparer.Ordinal);
|
||||||
public float Population { get; set; }
|
public float Population { get; set; }
|
||||||
public float PopulationCapacity { get; set; }
|
public float PopulationCapacity { get; set; }
|
||||||
public float WorkforceRequired { get; set; }
|
public float WorkforceRequired { get; set; }
|
||||||
public float WorkforceEffectiveRatio { get; set; } = 0.1f;
|
public float WorkforceEffectiveRatio { get; set; } = 0.1f;
|
||||||
public float PopulationGrowthProgress { get; set; }
|
public float PopulationGrowthProgress { get; set; }
|
||||||
public float ShipProductionProgressSeconds { get; set; }
|
public float ShipProductionProgressSeconds { get; set; }
|
||||||
public HashSet<string> DockedShipIds { get; } = [];
|
public HashSet<string> DockedShipIds { get; } = [];
|
||||||
public ModuleConstructionRuntime? ActiveConstruction { get; set; }
|
public ModuleConstructionRuntime? ActiveConstruction { get; set; }
|
||||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class StationModuleRuntime
|
public sealed 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 float Health { get; set; }
|
public float Health { get; set; }
|
||||||
public float MaxHealth { get; set; }
|
public float MaxHealth { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ModuleConstructionRuntime
|
public sealed class ModuleConstructionRuntime
|
||||||
{
|
{
|
||||||
public required string ModuleId { get; init; }
|
public required string ModuleId { get; init; }
|
||||||
public float ProgressSeconds { get; set; }
|
public float ProgressSeconds { get; set; }
|
||||||
public float RequiredSeconds { get; init; }
|
public float RequiredSeconds { get; init; }
|
||||||
public string AssignedConstructorShipId { get; set; } = string.Empty;
|
public string AssignedConstructorShipId { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,208 +4,208 @@ namespace SpaceGame.Api.Stations.Simulation;
|
|||||||
|
|
||||||
internal sealed class StationLifecycleService
|
internal sealed class StationLifecycleService
|
||||||
{
|
{
|
||||||
private const float WaterConsumptionPerWorkerPerSecond = 0.004f;
|
private const float WaterConsumptionPerWorkerPerSecond = 0.004f;
|
||||||
private const float PopulationGrowthPerSecond = 0.012f;
|
private const float PopulationGrowthPerSecond = 0.012f;
|
||||||
private const float PopulationAttritionPerSecond = 0.018f;
|
private const float PopulationAttritionPerSecond = 0.018f;
|
||||||
private readonly StationSimulationService _stationSimulation;
|
private readonly StationSimulationService _stationSimulation;
|
||||||
|
|
||||||
internal StationLifecycleService(StationSimulationService stationSimulation)
|
internal StationLifecycleService(StationSimulationService stationSimulation)
|
||||||
{
|
|
||||||
_stationSimulation = stationSimulation;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void UpdateStations(SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
|
||||||
{
|
|
||||||
var factionPopulation = new Dictionary<string, float>(StringComparer.Ordinal);
|
|
||||||
foreach (var station in world.Stations)
|
|
||||||
{
|
{
|
||||||
UpdateStationPopulation(station, deltaSeconds, events);
|
_stationSimulation = stationSimulation;
|
||||||
_stationSimulation.ReviewStationMarketOrders(world, station);
|
|
||||||
_stationSimulation.RunStationProduction(world, station, deltaSeconds, events);
|
|
||||||
factionPopulation[station.FactionId] = GetInventoryAmount(factionPopulation, station.FactionId) + station.Population;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var faction in world.Factions)
|
internal void UpdateStations(SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||||
{
|
{
|
||||||
faction.PopulationTotal = GetInventoryAmount(factionPopulation, faction.Id);
|
var factionPopulation = new Dictionary<string, float>(StringComparer.Ordinal);
|
||||||
}
|
foreach (var station in world.Stations)
|
||||||
}
|
{
|
||||||
|
UpdateStationPopulation(station, deltaSeconds, events);
|
||||||
|
_stationSimulation.ReviewStationMarketOrders(world, station);
|
||||||
|
_stationSimulation.RunStationProduction(world, station, deltaSeconds, events);
|
||||||
|
factionPopulation[station.FactionId] = GetInventoryAmount(factionPopulation, station.FactionId) + station.Population;
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateStationPopulation(StationRuntime station, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
foreach (var faction in world.Factions)
|
||||||
{
|
{
|
||||||
station.WorkforceRequired = MathF.Max(12f, station.Modules.Count * 14f);
|
faction.PopulationTotal = GetInventoryAmount(factionPopulation, faction.Id);
|
||||||
|
}
|
||||||
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");
|
|
||||||
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
|
||||||
|
|
||||||
if (waterSatisfied)
|
|
||||||
{
|
|
||||||
if (habitatModules > 0 && station.Population < station.PopulationCapacity)
|
|
||||||
{
|
|
||||||
station.Population = MathF.Min(station.PopulationCapacity, station.Population + (PopulationGrowthPerSecond * deltaSeconds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (station.Population > 0f)
|
|
||||||
{
|
|
||||||
var previous = station.Population;
|
|
||||||
station.Population = MathF.Max(0f, station.Population - (PopulationAttritionPerSecond * deltaSeconds));
|
|
||||||
if (MathF.Floor(previous) > MathF.Floor(station.Population))
|
|
||||||
{
|
|
||||||
events.Add(new SimulationEventRecord("station", station.Id, "population-loss", $"{station.Label} lost population due to support shortages.", DateTimeOffset.UtcNow));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
station.WorkforceEffectiveRatio = ComputeWorkforceRatio(station.Population, station.WorkforceRequired);
|
private void UpdateStationPopulation(StationRuntime station, float deltaSeconds, ICollection<SimulationEventRecord> events)
|
||||||
}
|
|
||||||
|
|
||||||
internal static float CompleteShipRecipe(SimulationWorld world, StationRuntime station, RecipeDefinition recipe, ICollection<SimulationEventRecord> events)
|
|
||||||
{
|
|
||||||
if (recipe.ShipOutputId is null || !world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var definition))
|
|
||||||
{
|
{
|
||||||
return 0f;
|
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");
|
||||||
|
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
||||||
|
|
||||||
|
if (waterSatisfied)
|
||||||
|
{
|
||||||
|
if (habitatModules > 0 && station.Population < station.PopulationCapacity)
|
||||||
|
{
|
||||||
|
station.Population = MathF.Min(station.PopulationCapacity, station.Population + (PopulationGrowthPerSecond * deltaSeconds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (station.Population > 0f)
|
||||||
|
{
|
||||||
|
var previous = station.Population;
|
||||||
|
station.Population = MathF.Max(0f, station.Population - (PopulationAttritionPerSecond * deltaSeconds));
|
||||||
|
if (MathF.Floor(previous) > MathF.Floor(station.Population))
|
||||||
|
{
|
||||||
|
events.Add(new SimulationEventRecord("station", station.Id, "population-loss", $"{station.Label} lost population due to support shortages.", DateTimeOffset.UtcNow));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
station.WorkforceEffectiveRatio = ComputeWorkforceRatio(station.Population, station.WorkforceRequired);
|
||||||
}
|
}
|
||||||
|
|
||||||
var spawnPosition = new Vector3(station.Position.X + GetStationRadius(world, station) + 32f, station.Position.Y, station.Position.Z);
|
internal static float CompleteShipRecipe(SimulationWorld world, StationRuntime station, RecipeDefinition recipe, ICollection<SimulationEventRecord> events)
|
||||||
var ship = new ShipRuntime
|
|
||||||
{
|
{
|
||||||
Id = $"ship-{world.Ships.Count + 1}",
|
if (recipe.ShipOutputId is null || !world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var definition))
|
||||||
SystemId = station.SystemId,
|
{
|
||||||
Definition = definition,
|
return 0f;
|
||||||
FactionId = station.FactionId,
|
}
|
||||||
Position = spawnPosition,
|
|
||||||
TargetPosition = spawnPosition,
|
var spawnPosition = new Vector3(station.Position.X + GetStationRadius(world, station) + 32f, station.Position.Y, station.Position.Z);
|
||||||
SpatialState = CreateSpawnedShipSpatialState(station, spawnPosition),
|
var ship = new ShipRuntime
|
||||||
DefaultBehavior = CreateSpawnedShipBehavior(definition, station),
|
{
|
||||||
Skills = WorldSeedingService.CreateSkills(definition),
|
Id = $"ship-{world.Ships.Count + 1}",
|
||||||
Health = definition.MaxHealth,
|
SystemId = station.SystemId,
|
||||||
|
Definition = definition,
|
||||||
|
FactionId = station.FactionId,
|
||||||
|
Position = spawnPosition,
|
||||||
|
TargetPosition = spawnPosition,
|
||||||
|
SpatialState = CreateSpawnedShipSpatialState(station, spawnPosition),
|
||||||
|
DefaultBehavior = CreateSpawnedShipBehavior(definition, station),
|
||||||
|
Skills = WorldSeedingService.CreateSkills(definition),
|
||||||
|
Health = definition.MaxHealth,
|
||||||
|
};
|
||||||
|
|
||||||
|
world.Ships.Add(ship);
|
||||||
|
EnsureSpawnedShipCommander(world, station, ship);
|
||||||
|
if (world.Factions.FirstOrDefault(candidate => candidate.Id == station.FactionId) is { } faction)
|
||||||
|
{
|
||||||
|
faction.ShipsBuilt += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
events.Add(new SimulationEventRecord("station", station.Id, "ship-built", $"{station.Label} launched {definition.Label}.", DateTimeOffset.UtcNow));
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ShipSpatialStateRuntime CreateSpawnedShipSpatialState(StationRuntime station, Vector3 position) => new()
|
||||||
|
{
|
||||||
|
CurrentSystemId = station.SystemId,
|
||||||
|
SpaceLayer = SpaceLayerKinds.LocalSpace,
|
||||||
|
CurrentCelestialId = station.CelestialId,
|
||||||
|
LocalPosition = position,
|
||||||
|
SystemPosition = position,
|
||||||
|
MovementRegime = MovementRegimeKinds.LocalFlight,
|
||||||
};
|
};
|
||||||
|
|
||||||
world.Ships.Add(ship);
|
private static DefaultBehaviorRuntime CreateSpawnedShipBehavior(ShipDefinition definition, StationRuntime station)
|
||||||
EnsureSpawnedShipCommander(world, station, ship);
|
|
||||||
if (world.Factions.FirstOrDefault(candidate => candidate.Id == station.FactionId) is { } faction)
|
|
||||||
{
|
{
|
||||||
faction.ShipsBuilt += 1;
|
if (!string.Equals(definition.Kind, "military", StringComparison.Ordinal))
|
||||||
}
|
{
|
||||||
|
return new DefaultBehaviorRuntime
|
||||||
|
{
|
||||||
|
Kind = string.Equals(definition.Kind, "transport", StringComparison.Ordinal) ? "advanced-auto-trade" : "idle",
|
||||||
|
HomeSystemId = station.SystemId,
|
||||||
|
HomeStationId = station.Id,
|
||||||
|
MaxSystemRange = string.Equals(definition.Kind, "transport", StringComparison.Ordinal) ? 2 : 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
events.Add(new SimulationEventRecord("station", station.Id, "ship-built", $"{station.Label} launched {definition.Label}.", DateTimeOffset.UtcNow));
|
var patrolRadius = station.Radius + 90f;
|
||||||
return 1f;
|
return new DefaultBehaviorRuntime
|
||||||
}
|
{
|
||||||
|
Kind = "patrol",
|
||||||
private static ShipSpatialStateRuntime CreateSpawnedShipSpatialState(StationRuntime station, Vector3 position) => new()
|
HomeSystemId = station.SystemId,
|
||||||
{
|
HomeStationId = station.Id,
|
||||||
CurrentSystemId = station.SystemId,
|
AreaSystemId = station.SystemId,
|
||||||
SpaceLayer = SpaceLayerKinds.LocalSpace,
|
PatrolPoints =
|
||||||
CurrentCelestialId = station.CelestialId,
|
[
|
||||||
LocalPosition = position,
|
new Vector3(station.Position.X + patrolRadius, station.Position.Y, station.Position.Z),
|
||||||
SystemPosition = position,
|
|
||||||
MovementRegime = MovementRegimeKinds.LocalFlight,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static DefaultBehaviorRuntime CreateSpawnedShipBehavior(ShipDefinition definition, StationRuntime station)
|
|
||||||
{
|
|
||||||
if (!string.Equals(definition.Kind, "military", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return new DefaultBehaviorRuntime
|
|
||||||
{
|
|
||||||
Kind = string.Equals(definition.Kind, "transport", StringComparison.Ordinal) ? "advanced-auto-trade" : "idle",
|
|
||||||
HomeSystemId = station.SystemId,
|
|
||||||
HomeStationId = station.Id,
|
|
||||||
MaxSystemRange = string.Equals(definition.Kind, "transport", StringComparison.Ordinal) ? 2 : 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var patrolRadius = station.Radius + 90f;
|
|
||||||
return new DefaultBehaviorRuntime
|
|
||||||
{
|
|
||||||
Kind = "patrol",
|
|
||||||
HomeSystemId = station.SystemId,
|
|
||||||
HomeStationId = station.Id,
|
|
||||||
AreaSystemId = station.SystemId,
|
|
||||||
PatrolPoints =
|
|
||||||
[
|
|
||||||
new Vector3(station.Position.X + patrolRadius, station.Position.Y, station.Position.Z),
|
|
||||||
new Vector3(station.Position.X, station.Position.Y, station.Position.Z + patrolRadius),
|
new Vector3(station.Position.X, station.Position.Y, station.Position.Z + patrolRadius),
|
||||||
new Vector3(station.Position.X - patrolRadius, station.Position.Y, station.Position.Z),
|
new Vector3(station.Position.X - patrolRadius, station.Position.Y, station.Position.Z),
|
||||||
new Vector3(station.Position.X, station.Position.Y, station.Position.Z - patrolRadius),
|
new Vector3(station.Position.X, station.Position.Y, station.Position.Z - patrolRadius),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
internal static void EnsureStationCommander(SimulationWorld world, StationRuntime station)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(station.CommanderId))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var factionCommander = world.Commanders.FirstOrDefault(candidate =>
|
internal static void EnsureStationCommander(SimulationWorld world, StationRuntime station)
|
||||||
string.Equals(candidate.Kind, CommanderKind.Faction, StringComparison.Ordinal)
|
|
||||||
&& string.Equals(candidate.FactionId, station.FactionId, StringComparison.Ordinal));
|
|
||||||
var faction = world.Factions.FirstOrDefault(candidate => string.Equals(candidate.Id, station.FactionId, StringComparison.Ordinal));
|
|
||||||
if (factionCommander is null || faction is null)
|
|
||||||
{
|
{
|
||||||
return;
|
if (!string.IsNullOrWhiteSpace(station.CommanderId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var factionCommander = world.Commanders.FirstOrDefault(candidate =>
|
||||||
|
string.Equals(candidate.Kind, CommanderKind.Faction, StringComparison.Ordinal)
|
||||||
|
&& string.Equals(candidate.FactionId, station.FactionId, StringComparison.Ordinal));
|
||||||
|
var faction = world.Factions.FirstOrDefault(candidate => string.Equals(candidate.Id, station.FactionId, StringComparison.Ordinal));
|
||||||
|
if (factionCommander is null || faction is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var commander = new CommanderRuntime
|
||||||
|
{
|
||||||
|
Id = $"commander-station-{station.Id}",
|
||||||
|
Kind = CommanderKind.Station,
|
||||||
|
FactionId = station.FactionId,
|
||||||
|
ParentCommanderId = factionCommander.Id,
|
||||||
|
ControlledEntityId = station.Id,
|
||||||
|
PolicySetId = factionCommander.PolicySetId,
|
||||||
|
Doctrine = "station-control",
|
||||||
|
Skills = new CommanderSkillProfileRuntime
|
||||||
|
{
|
||||||
|
Leadership = 3,
|
||||||
|
Coordination = Math.Clamp(3 + (station.Modules.Count / 8), 3, 5),
|
||||||
|
Strategy = 3,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
station.CommanderId = commander.Id;
|
||||||
|
station.PolicySetId = factionCommander.PolicySetId;
|
||||||
|
factionCommander.SubordinateCommanderIds.Add(commander.Id);
|
||||||
|
faction.CommanderIds.Add(commander.Id);
|
||||||
|
world.Commanders.Add(commander);
|
||||||
}
|
}
|
||||||
|
|
||||||
var commander = new CommanderRuntime
|
private static void EnsureSpawnedShipCommander(SimulationWorld world, StationRuntime station, ShipRuntime ship)
|
||||||
{
|
{
|
||||||
Id = $"commander-station-{station.Id}",
|
var factionCommander = world.Commanders.FirstOrDefault(candidate =>
|
||||||
Kind = CommanderKind.Station,
|
string.Equals(candidate.Kind, CommanderKind.Faction, StringComparison.Ordinal)
|
||||||
FactionId = station.FactionId,
|
&& string.Equals(candidate.FactionId, station.FactionId, StringComparison.Ordinal));
|
||||||
ParentCommanderId = factionCommander.Id,
|
var faction = world.Factions.FirstOrDefault(candidate => string.Equals(candidate.Id, station.FactionId, StringComparison.Ordinal));
|
||||||
ControlledEntityId = station.Id,
|
if (factionCommander is null || faction is null)
|
||||||
PolicySetId = factionCommander.PolicySetId,
|
{
|
||||||
Doctrine = "station-control",
|
return;
|
||||||
Skills = new CommanderSkillProfileRuntime
|
}
|
||||||
{
|
|
||||||
Leadership = 3,
|
|
||||||
Coordination = Math.Clamp(3 + (station.Modules.Count / 8), 3, 5),
|
|
||||||
Strategy = 3,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
station.CommanderId = commander.Id;
|
var commander = new CommanderRuntime
|
||||||
station.PolicySetId = factionCommander.PolicySetId;
|
{
|
||||||
factionCommander.SubordinateCommanderIds.Add(commander.Id);
|
Id = $"commander-ship-{ship.Id}",
|
||||||
faction.CommanderIds.Add(commander.Id);
|
Kind = CommanderKind.Ship,
|
||||||
world.Commanders.Add(commander);
|
FactionId = ship.FactionId,
|
||||||
}
|
ParentCommanderId = factionCommander.Id,
|
||||||
|
ControlledEntityId = ship.Id,
|
||||||
|
PolicySetId = factionCommander.PolicySetId,
|
||||||
|
Doctrine = "ship-control",
|
||||||
|
Skills = new CommanderSkillProfileRuntime
|
||||||
|
{
|
||||||
|
Leadership = Math.Clamp((ship.Skills.Navigation + ship.Skills.Combat + 1) / 2, 2, 5),
|
||||||
|
Coordination = Math.Clamp((ship.Skills.Trade + ship.Skills.Mining + 1) / 2, 2, 5),
|
||||||
|
Strategy = Math.Clamp((ship.Skills.Combat + ship.Skills.Construction + 1) / 2, 2, 5),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
private static void EnsureSpawnedShipCommander(SimulationWorld world, StationRuntime station, ShipRuntime ship)
|
ship.CommanderId = commander.Id;
|
||||||
{
|
ship.PolicySetId = factionCommander.PolicySetId;
|
||||||
var factionCommander = world.Commanders.FirstOrDefault(candidate =>
|
factionCommander.SubordinateCommanderIds.Add(commander.Id);
|
||||||
string.Equals(candidate.Kind, CommanderKind.Faction, StringComparison.Ordinal)
|
faction.CommanderIds.Add(commander.Id);
|
||||||
&& string.Equals(candidate.FactionId, station.FactionId, StringComparison.Ordinal));
|
world.Commanders.Add(commander);
|
||||||
var faction = world.Factions.FirstOrDefault(candidate => string.Equals(candidate.Id, station.FactionId, StringComparison.Ordinal));
|
|
||||||
if (factionCommander is null || faction is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var commander = new CommanderRuntime
|
|
||||||
{
|
|
||||||
Id = $"commander-ship-{ship.Id}",
|
|
||||||
Kind = CommanderKind.Ship,
|
|
||||||
FactionId = ship.FactionId,
|
|
||||||
ParentCommanderId = factionCommander.Id,
|
|
||||||
ControlledEntityId = ship.Id,
|
|
||||||
PolicySetId = factionCommander.PolicySetId,
|
|
||||||
Doctrine = "ship-control",
|
|
||||||
Skills = new CommanderSkillProfileRuntime
|
|
||||||
{
|
|
||||||
Leadership = Math.Clamp((ship.Skills.Navigation + ship.Skills.Combat + 1) / 2, 2, 5),
|
|
||||||
Coordination = Math.Clamp((ship.Skills.Trade + ship.Skills.Mining + 1) / 2, 2, 5),
|
|
||||||
Strategy = Math.Clamp((ship.Skills.Combat + ship.Skills.Construction + 1) / 2, 2, 5),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
ship.CommanderId = commander.Id;
|
|
||||||
ship.PolicySetId = factionCommander.PolicySetId;
|
|
||||||
factionCommander.SubordinateCommanderIds.Add(commander.Id);
|
|
||||||
faction.CommanderIds.Add(commander.Id);
|
|
||||||
world.Commanders.Add(commander);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,12 +5,12 @@ namespace SpaceGame.Api.Universe.Api;
|
|||||||
|
|
||||||
public sealed class GetBalanceHandler(WorldService worldService) : EndpointWithoutRequest
|
public sealed class GetBalanceHandler(WorldService worldService) : EndpointWithoutRequest
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
{
|
||||||
Get("/api/balance");
|
Get("/api/balance");
|
||||||
AllowAnonymous();
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task HandleAsync(CancellationToken cancellationToken) =>
|
public override Task HandleAsync(CancellationToken cancellationToken) =>
|
||||||
SendOkAsync(worldService.GetBalance(), cancellationToken);
|
SendOkAsync(worldService.GetBalance(), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,44 +6,44 @@ namespace SpaceGame.Api.Universe.Api;
|
|||||||
|
|
||||||
public sealed class GetTelemetryHandler(TelemetryService telemetry, WorldService worldService) : EndpointWithoutRequest
|
public sealed class GetTelemetryHandler(TelemetryService telemetry, WorldService worldService) : EndpointWithoutRequest
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Get("/api/telemetry");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task HandleAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var status = worldService.GetStatus();
|
|
||||||
var connections = worldService.GetConnectionStats();
|
|
||||||
var uptime = telemetry.Uptime;
|
|
||||||
|
|
||||||
return SendOkAsync(new
|
|
||||||
{
|
{
|
||||||
process = new
|
Get("/api/telemetry");
|
||||||
{
|
AllowAnonymous();
|
||||||
uptimeSeconds = uptime.TotalSeconds,
|
}
|
||||||
cpuPercent = Math.Round(telemetry.CpuPercent, 1),
|
|
||||||
workingSetMb = Math.Round(telemetry.WorkingSetBytes / 1_048_576.0, 1),
|
public override Task HandleAsync(CancellationToken cancellationToken)
|
||||||
gcMemoryMb = Math.Round(telemetry.GcMemoryBytes / 1_048_576.0, 1),
|
{
|
||||||
threadCount = telemetry.ThreadCount,
|
var status = worldService.GetStatus();
|
||||||
processorCount = Environment.ProcessorCount,
|
var connections = worldService.GetConnectionStats();
|
||||||
},
|
var uptime = telemetry.Uptime;
|
||||||
simulation = new
|
|
||||||
{
|
return SendOkAsync(new
|
||||||
sequence = status.Sequence,
|
{
|
||||||
connectedClients = connections.ConnectedClients,
|
process = new
|
||||||
deltaHistoryCount = connections.DeltaHistoryCount,
|
{
|
||||||
tickIntervalMs = 200,
|
uptimeSeconds = uptime.TotalSeconds,
|
||||||
},
|
cpuPercent = Math.Round(telemetry.CpuPercent, 1),
|
||||||
runtime = new
|
workingSetMb = Math.Round(telemetry.WorkingSetBytes / 1_048_576.0, 1),
|
||||||
{
|
gcMemoryMb = Math.Round(telemetry.GcMemoryBytes / 1_048_576.0, 1),
|
||||||
frameworkDescription = RuntimeInformation.FrameworkDescription,
|
threadCount = telemetry.ThreadCount,
|
||||||
osDescription = RuntimeInformation.OSDescription,
|
processorCount = Environment.ProcessorCount,
|
||||||
gcGen0 = GC.CollectionCount(0),
|
},
|
||||||
gcGen1 = GC.CollectionCount(1),
|
simulation = new
|
||||||
gcGen2 = GC.CollectionCount(2),
|
{
|
||||||
},
|
sequence = status.Sequence,
|
||||||
}, cancellationToken);
|
connectedClients = connections.ConnectedClients,
|
||||||
}
|
deltaHistoryCount = connections.DeltaHistoryCount,
|
||||||
|
tickIntervalMs = 200,
|
||||||
|
},
|
||||||
|
runtime = new
|
||||||
|
{
|
||||||
|
frameworkDescription = RuntimeInformation.FrameworkDescription,
|
||||||
|
osDescription = RuntimeInformation.OSDescription,
|
||||||
|
gcGen0 = GC.CollectionCount(0),
|
||||||
|
gcGen1 = GC.CollectionCount(1),
|
||||||
|
gcGen2 = GC.CollectionCount(2),
|
||||||
|
},
|
||||||
|
}, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ namespace SpaceGame.Api.Universe.Api;
|
|||||||
|
|
||||||
public sealed class GetWorldHandler(WorldService worldService) : EndpointWithoutRequest
|
public sealed class GetWorldHandler(WorldService worldService) : EndpointWithoutRequest
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
{
|
||||||
Get("/api/world");
|
Get("/api/world");
|
||||||
AllowAnonymous();
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task HandleAsync(CancellationToken cancellationToken) =>
|
public override Task HandleAsync(CancellationToken cancellationToken) =>
|
||||||
SendOkAsync(worldService.GetSnapshot(), cancellationToken);
|
SendOkAsync(worldService.GetSnapshot(), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ namespace SpaceGame.Api.Universe.Api;
|
|||||||
|
|
||||||
public sealed class GetWorldHealthHandler(WorldService worldService) : EndpointWithoutRequest
|
public sealed class GetWorldHealthHandler(WorldService worldService) : EndpointWithoutRequest
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Get("/api/world/health");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task HandleAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var status = worldService.GetStatus();
|
|
||||||
return SendOkAsync(new
|
|
||||||
{
|
{
|
||||||
ok = true,
|
Get("/api/world/health");
|
||||||
sequence = status.Sequence,
|
AllowAnonymous();
|
||||||
generatedAtUtc = status.GeneratedAtUtc,
|
}
|
||||||
}, cancellationToken);
|
|
||||||
}
|
public override Task HandleAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var status = worldService.GetStatus();
|
||||||
|
return SendOkAsync(new
|
||||||
|
{
|
||||||
|
ok = true,
|
||||||
|
sequence = status.Sequence,
|
||||||
|
generatedAtUtc = status.GeneratedAtUtc,
|
||||||
|
}, cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ namespace SpaceGame.Api.Universe.Api;
|
|||||||
|
|
||||||
public sealed class ResetWorldHandler(WorldService worldService) : EndpointWithoutRequest
|
public sealed class ResetWorldHandler(WorldService worldService) : EndpointWithoutRequest
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
{
|
||||||
Post("/api/world/reset");
|
Post("/api/world/reset");
|
||||||
AllowAnonymous();
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task HandleAsync(CancellationToken cancellationToken) =>
|
public override Task HandleAsync(CancellationToken cancellationToken) =>
|
||||||
SendOkAsync(worldService.Reset(), cancellationToken);
|
SendOkAsync(worldService.Reset(), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ namespace SpaceGame.Api.Universe.Api;
|
|||||||
|
|
||||||
public sealed class RootRedirectHandler : EndpointWithoutRequest
|
public sealed class RootRedirectHandler : EndpointWithoutRequest
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
{
|
||||||
Get("/");
|
Get("/");
|
||||||
AllowAnonymous();
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task HandleAsync(CancellationToken cancellationToken)
|
public override Task HandleAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
HttpContext.Response.Redirect("/api/world");
|
HttpContext.Response.Redirect("/api/world");
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,49 +5,49 @@ namespace SpaceGame.Api.Universe.Api;
|
|||||||
|
|
||||||
public sealed class StreamWorldHandler(WorldService worldService) : EndpointWithoutRequest
|
public sealed class StreamWorldHandler(WorldService worldService) : EndpointWithoutRequest
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions SseJsonOptions = new(JsonSerializerDefaults.Web);
|
private static readonly JsonSerializerOptions SseJsonOptions = new(JsonSerializerDefaults.Web);
|
||||||
|
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
|
||||||
Get("/api/world/stream");
|
|
||||||
AllowAnonymous();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task HandleAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
HttpContext.Response.Headers.Append("Cache-Control", "no-cache");
|
|
||||||
HttpContext.Response.Headers.Append("Content-Type", "text/event-stream");
|
|
||||||
|
|
||||||
var afterSequenceRaw = HttpContext.Request.Query["afterSequence"].ToString();
|
|
||||||
_ = long.TryParse(afterSequenceRaw, out var afterSequence);
|
|
||||||
|
|
||||||
var scopeKind = HttpContext.Request.Query["scopeKind"].ToString();
|
|
||||||
if (string.IsNullOrWhiteSpace(scopeKind))
|
|
||||||
{
|
{
|
||||||
scopeKind = HttpContext.Request.Query["scope"].ToString();
|
Get("/api/world/stream");
|
||||||
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(scopeKind))
|
public override async Task HandleAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
scopeKind = "universe";
|
HttpContext.Response.Headers.Append("Cache-Control", "no-cache");
|
||||||
|
HttpContext.Response.Headers.Append("Content-Type", "text/event-stream");
|
||||||
|
|
||||||
|
var afterSequenceRaw = HttpContext.Request.Query["afterSequence"].ToString();
|
||||||
|
_ = long.TryParse(afterSequenceRaw, out var afterSequence);
|
||||||
|
|
||||||
|
var scopeKind = HttpContext.Request.Query["scopeKind"].ToString();
|
||||||
|
if (string.IsNullOrWhiteSpace(scopeKind))
|
||||||
|
{
|
||||||
|
scopeKind = HttpContext.Request.Query["scope"].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(scopeKind))
|
||||||
|
{
|
||||||
|
scopeKind = "universe";
|
||||||
|
}
|
||||||
|
|
||||||
|
var systemId = HttpContext.Request.Query["systemId"].ToString();
|
||||||
|
var bubbleId = HttpContext.Request.Query["bubbleId"].ToString();
|
||||||
|
var scope = new ObserverScope(
|
||||||
|
scopeKind,
|
||||||
|
string.IsNullOrWhiteSpace(systemId) ? null : systemId,
|
||||||
|
string.IsNullOrWhiteSpace(bubbleId) ? null : bubbleId);
|
||||||
|
var stream = worldService.Subscribe(scope, afterSequence, cancellationToken);
|
||||||
|
|
||||||
|
await HttpContext.Response.WriteAsync(": connected\n\n", cancellationToken);
|
||||||
|
await HttpContext.Response.Body.FlushAsync(cancellationToken);
|
||||||
|
|
||||||
|
await foreach (var delta in stream.ReadAllAsync(cancellationToken))
|
||||||
|
{
|
||||||
|
var payload = JsonSerializer.Serialize(delta, SseJsonOptions);
|
||||||
|
await HttpContext.Response.WriteAsync($"event: world-delta\ndata: {payload}\n\n", cancellationToken);
|
||||||
|
await HttpContext.Response.Body.FlushAsync(cancellationToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var systemId = HttpContext.Request.Query["systemId"].ToString();
|
|
||||||
var bubbleId = HttpContext.Request.Query["bubbleId"].ToString();
|
|
||||||
var scope = new ObserverScope(
|
|
||||||
scopeKind,
|
|
||||||
string.IsNullOrWhiteSpace(systemId) ? null : systemId,
|
|
||||||
string.IsNullOrWhiteSpace(bubbleId) ? null : bubbleId);
|
|
||||||
var stream = worldService.Subscribe(scope, afterSequence, cancellationToken);
|
|
||||||
|
|
||||||
await HttpContext.Response.WriteAsync(": connected\n\n", cancellationToken);
|
|
||||||
await HttpContext.Response.Body.FlushAsync(cancellationToken);
|
|
||||||
|
|
||||||
await foreach (var delta in stream.ReadAllAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
var payload = JsonSerializer.Serialize(delta, SseJsonOptions);
|
|
||||||
await HttpContext.Response.WriteAsync($"event: world-delta\ndata: {payload}\n\n", cancellationToken);
|
|
||||||
await HttpContext.Response.Body.FlushAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ namespace SpaceGame.Api.Universe.Api;
|
|||||||
|
|
||||||
public sealed class UpdateBalanceHandler(WorldService worldService) : Endpoint<BalanceDefinition>
|
public sealed class UpdateBalanceHandler(WorldService worldService) : Endpoint<BalanceDefinition>
|
||||||
{
|
{
|
||||||
public override void Configure()
|
public override void Configure()
|
||||||
{
|
{
|
||||||
Put("/api/balance");
|
Put("/api/balance");
|
||||||
AllowAnonymous();
|
AllowAnonymous();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task HandleAsync(BalanceDefinition req, CancellationToken cancellationToken)
|
public override Task HandleAsync(BalanceDefinition req, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var applied = worldService.UpdateBalance(req);
|
var applied = worldService.UpdateBalance(req);
|
||||||
return SendOkAsync(applied, cancellationToken);
|
return SendOkAsync(applied, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,292 +5,292 @@ namespace SpaceGame.Api.Universe.Scenario;
|
|||||||
|
|
||||||
internal sealed class DataCatalogLoader(string dataRoot)
|
internal sealed class DataCatalogLoader(string dataRoot)
|
||||||
{
|
{
|
||||||
private readonly JsonSerializerOptions _jsonOptions = new()
|
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||||
{
|
|
||||||
PropertyNameCaseInsensitive = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
internal ScenarioCatalog LoadCatalog()
|
|
||||||
{
|
|
||||||
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 items = NormalizeItems(Read<List<ItemDefinition>>("items.json"));
|
|
||||||
var balance = Read<BalanceDefinition>("balance.json");
|
|
||||||
var recipes = BuildRecipes(items, ships, modules);
|
|
||||||
var moduleRecipes = BuildModuleRecipes(modules);
|
|
||||||
var productionGraph = ProductionGraphBuilder.Build(items, recipes, modules);
|
|
||||||
|
|
||||||
return new ScenarioCatalog(
|
|
||||||
authoredSystems,
|
|
||||||
scenario,
|
|
||||||
balance,
|
|
||||||
modules.ToDictionary(definition => definition.Id, StringComparer.Ordinal),
|
|
||||||
ships.ToDictionary(definition => definition.Id, StringComparer.Ordinal),
|
|
||||||
items.ToDictionary(definition => definition.Id, StringComparer.Ordinal),
|
|
||||||
recipes.ToDictionary(definition => definition.Id, StringComparer.Ordinal),
|
|
||||||
moduleRecipes.ToDictionary(definition => definition.ModuleId, StringComparer.Ordinal),
|
|
||||||
productionGraph);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal ScenarioDefinition NormalizeScenarioToAvailableSystems(
|
|
||||||
ScenarioDefinition scenario,
|
|
||||||
IReadOnlyList<string> availableSystemIds)
|
|
||||||
{
|
|
||||||
if (availableSystemIds.Count == 0)
|
|
||||||
{
|
{
|
||||||
return scenario;
|
PropertyNameCaseInsensitive = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
internal ScenarioCatalog LoadCatalog()
|
||||||
|
{
|
||||||
|
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 items = NormalizeItems(Read<List<ItemDefinition>>("items.json"));
|
||||||
|
var balance = Read<BalanceDefinition>("balance.json");
|
||||||
|
var recipes = BuildRecipes(items, ships, modules);
|
||||||
|
var moduleRecipes = BuildModuleRecipes(modules);
|
||||||
|
var productionGraph = ProductionGraphBuilder.Build(items, recipes, modules);
|
||||||
|
|
||||||
|
return new ScenarioCatalog(
|
||||||
|
authoredSystems,
|
||||||
|
scenario,
|
||||||
|
balance,
|
||||||
|
modules.ToDictionary(definition => definition.Id, StringComparer.Ordinal),
|
||||||
|
ships.ToDictionary(definition => definition.Id, StringComparer.Ordinal),
|
||||||
|
items.ToDictionary(definition => definition.Id, StringComparer.Ordinal),
|
||||||
|
recipes.ToDictionary(definition => definition.Id, StringComparer.Ordinal),
|
||||||
|
moduleRecipes.ToDictionary(definition => definition.ModuleId, StringComparer.Ordinal),
|
||||||
|
productionGraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fallbackSystemId = availableSystemIds.Contains("sol", StringComparer.Ordinal)
|
internal ScenarioDefinition NormalizeScenarioToAvailableSystems(
|
||||||
? "sol"
|
ScenarioDefinition scenario,
|
||||||
: availableSystemIds[0];
|
IReadOnlyList<string> availableSystemIds)
|
||||||
|
|
||||||
string ResolveSystemId(string systemId) =>
|
|
||||||
availableSystemIds.Contains(systemId, StringComparer.Ordinal) ? systemId : fallbackSystemId;
|
|
||||||
|
|
||||||
return new ScenarioDefinition
|
|
||||||
{
|
{
|
||||||
InitialStations = scenario.InitialStations
|
if (availableSystemIds.Count == 0)
|
||||||
.Select(station => new InitialStationDefinition
|
|
||||||
{
|
{
|
||||||
SystemId = ResolveSystemId(station.SystemId),
|
return scenario;
|
||||||
Label = station.Label,
|
}
|
||||||
Color = station.Color,
|
|
||||||
Objective = station.Objective,
|
var fallbackSystemId = availableSystemIds.Contains("sol", StringComparer.Ordinal)
|
||||||
StartingModules = station.StartingModules.ToList(),
|
? "sol"
|
||||||
FactionId = station.FactionId,
|
: availableSystemIds[0];
|
||||||
PlanetIndex = station.PlanetIndex,
|
|
||||||
LagrangeSide = station.LagrangeSide,
|
string ResolveSystemId(string systemId) =>
|
||||||
Position = station.Position?.ToArray(),
|
availableSystemIds.Contains(systemId, StringComparer.Ordinal) ? systemId : fallbackSystemId;
|
||||||
})
|
|
||||||
.ToList(),
|
return new ScenarioDefinition
|
||||||
ShipFormations = scenario.ShipFormations
|
|
||||||
.Select(formation => new ShipFormationDefinition
|
|
||||||
{
|
{
|
||||||
ShipId = formation.ShipId,
|
InitialStations = scenario.InitialStations
|
||||||
Count = formation.Count,
|
.Select(station => new InitialStationDefinition
|
||||||
Center = formation.Center.ToArray(),
|
{
|
||||||
SystemId = ResolveSystemId(formation.SystemId),
|
SystemId = ResolveSystemId(station.SystemId),
|
||||||
FactionId = formation.FactionId,
|
Label = station.Label,
|
||||||
StartingInventory = new Dictionary<string, float>(formation.StartingInventory, StringComparer.Ordinal),
|
Color = station.Color,
|
||||||
})
|
Objective = station.Objective,
|
||||||
.ToList(),
|
StartingModules = station.StartingModules.ToList(),
|
||||||
PatrolRoutes = scenario.PatrolRoutes
|
FactionId = station.FactionId,
|
||||||
.Select(route => new PatrolRouteDefinition
|
PlanetIndex = station.PlanetIndex,
|
||||||
{
|
LagrangeSide = station.LagrangeSide,
|
||||||
SystemId = ResolveSystemId(route.SystemId),
|
Position = station.Position?.ToArray(),
|
||||||
Points = route.Points.Select(point => point.ToArray()).ToList(),
|
})
|
||||||
})
|
.ToList(),
|
||||||
.ToList(),
|
ShipFormations = scenario.ShipFormations
|
||||||
MiningDefaults = new MiningDefaultsDefinition
|
.Select(formation => new ShipFormationDefinition
|
||||||
{
|
{
|
||||||
NodeSystemId = ResolveSystemId(scenario.MiningDefaults.NodeSystemId),
|
ShipId = formation.ShipId,
|
||||||
RefinerySystemId = ResolveSystemId(scenario.MiningDefaults.RefinerySystemId),
|
Count = formation.Count,
|
||||||
},
|
Center = formation.Center.ToArray(),
|
||||||
};
|
SystemId = ResolveSystemId(formation.SystemId),
|
||||||
}
|
FactionId = formation.FactionId,
|
||||||
|
StartingInventory = new Dictionary<string, float>(formation.StartingInventory, StringComparer.Ordinal),
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
PatrolRoutes = scenario.PatrolRoutes
|
||||||
|
.Select(route => new PatrolRouteDefinition
|
||||||
|
{
|
||||||
|
SystemId = ResolveSystemId(route.SystemId),
|
||||||
|
Points = route.Points.Select(point => point.ToArray()).ToList(),
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
MiningDefaults = new MiningDefaultsDefinition
|
||||||
|
{
|
||||||
|
NodeSystemId = ResolveSystemId(scenario.MiningDefaults.NodeSystemId),
|
||||||
|
RefinerySystemId = ResolveSystemId(scenario.MiningDefaults.RefinerySystemId),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private T Read<T>(string fileName)
|
private T Read<T>(string fileName)
|
||||||
{
|
|
||||||
var path = Path.Combine(dataRoot, fileName);
|
|
||||||
var json = File.ReadAllText(path);
|
|
||||||
return JsonSerializer.Deserialize<T>(json, _jsonOptions)
|
|
||||||
?? throw new InvalidOperationException($"Unable to read {fileName}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<ModuleRecipeDefinition> BuildModuleRecipes(IEnumerable<ModuleDefinition> modules) =>
|
|
||||||
modules
|
|
||||||
.Where(module => module.Construction is not null || module.Production.Count > 0)
|
|
||||||
.Select(module => new ModuleRecipeDefinition
|
|
||||||
{
|
|
||||||
ModuleId = module.Id,
|
|
||||||
Duration = module.Construction?.ProductionTime ?? module.Production[0].Time,
|
|
||||||
Inputs = (module.Construction?.Requirements ?? module.Production[0].Wares)
|
|
||||||
.Select(input => new RecipeInputDefinition
|
|
||||||
{
|
|
||||||
ItemId = input.ItemId,
|
|
||||||
Amount = input.Amount,
|
|
||||||
})
|
|
||||||
.ToList(),
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
private static List<RecipeDefinition> BuildRecipes(IEnumerable<ItemDefinition> items, IEnumerable<ShipDefinition> ships, IReadOnlyCollection<ModuleDefinition> modules)
|
|
||||||
{
|
|
||||||
var recipes = new List<RecipeDefinition>();
|
|
||||||
var preferredProducerByItemId = modules
|
|
||||||
.Where(module => module.Products.Count > 0)
|
|
||||||
.GroupBy(module => module.Products[0], StringComparer.Ordinal)
|
|
||||||
.ToDictionary(
|
|
||||||
group => group.Key,
|
|
||||||
group => group.OrderBy(module => module.Id, StringComparer.Ordinal).First().Id,
|
|
||||||
StringComparer.Ordinal);
|
|
||||||
|
|
||||||
foreach (var item in items)
|
|
||||||
{
|
{
|
||||||
if (item.Production.Count > 0)
|
var path = Path.Combine(dataRoot, fileName);
|
||||||
{
|
var json = File.ReadAllText(path);
|
||||||
foreach (var production in item.Production)
|
return JsonSerializer.Deserialize<T>(json, _jsonOptions)
|
||||||
|
?? throw new InvalidOperationException($"Unable to read {fileName}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ModuleRecipeDefinition> BuildModuleRecipes(IEnumerable<ModuleDefinition> modules) =>
|
||||||
|
modules
|
||||||
|
.Where(module => module.Construction is not null || module.Production.Count > 0)
|
||||||
|
.Select(module => new ModuleRecipeDefinition
|
||||||
{
|
{
|
||||||
recipes.Add(new RecipeDefinition
|
ModuleId = module.Id,
|
||||||
{
|
Duration = module.Construction?.ProductionTime ?? module.Production[0].Time,
|
||||||
Id = $"{item.Id}-{production.Method}-production",
|
Inputs = (module.Construction?.Requirements ?? module.Production[0].Wares)
|
||||||
Label = production.Name == "Universal" ? item.Name : $"{item.Name} ({production.Name})",
|
.Select(input => new RecipeInputDefinition
|
||||||
FacilityCategory = InferFacilityCategory(item),
|
{
|
||||||
Duration = production.Time,
|
|
||||||
Priority = InferRecipePriority(item),
|
|
||||||
RequiredModules = InferRequiredModules(item, preferredProducerByItemId),
|
|
||||||
Inputs = production.Wares
|
|
||||||
.Select(input => new RecipeInputDefinition
|
|
||||||
{
|
|
||||||
ItemId = input.ItemId,
|
ItemId = input.ItemId,
|
||||||
Amount = input.Amount,
|
Amount = input.Amount,
|
||||||
})
|
})
|
||||||
.ToList(),
|
.ToList(),
|
||||||
Outputs =
|
})
|
||||||
[
|
.ToList();
|
||||||
new RecipeOutputDefinition
|
|
||||||
|
private static List<RecipeDefinition> BuildRecipes(IEnumerable<ItemDefinition> items, IEnumerable<ShipDefinition> ships, IReadOnlyCollection<ModuleDefinition> modules)
|
||||||
|
{
|
||||||
|
var recipes = new List<RecipeDefinition>();
|
||||||
|
var preferredProducerByItemId = modules
|
||||||
|
.Where(module => module.Products.Count > 0)
|
||||||
|
.GroupBy(module => module.Products[0], StringComparer.Ordinal)
|
||||||
|
.ToDictionary(
|
||||||
|
group => group.Key,
|
||||||
|
group => group.OrderBy(module => module.Id, StringComparer.Ordinal).First().Id,
|
||||||
|
StringComparer.Ordinal);
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (item.Production.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var production in item.Production)
|
||||||
|
{
|
||||||
|
recipes.Add(new RecipeDefinition
|
||||||
|
{
|
||||||
|
Id = $"{item.Id}-{production.Method}-production",
|
||||||
|
Label = production.Name == "Universal" ? item.Name : $"{item.Name} ({production.Name})",
|
||||||
|
FacilityCategory = InferFacilityCategory(item),
|
||||||
|
Duration = production.Time,
|
||||||
|
Priority = InferRecipePriority(item),
|
||||||
|
RequiredModules = InferRequiredModules(item, preferredProducerByItemId),
|
||||||
|
Inputs = production.Wares
|
||||||
|
.Select(input => new RecipeInputDefinition
|
||||||
|
{
|
||||||
|
ItemId = input.ItemId,
|
||||||
|
Amount = input.Amount,
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
Outputs =
|
||||||
|
[
|
||||||
|
new RecipeOutputDefinition
|
||||||
{
|
{
|
||||||
ItemId = item.Id,
|
ItemId = item.Id,
|
||||||
Amount = production.Amount,
|
Amount = production.Amount,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.Construction is null)
|
if (item.Construction is null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
recipes.Add(new RecipeDefinition
|
recipes.Add(new RecipeDefinition
|
||||||
{
|
{
|
||||||
Id = item.Construction.RecipeId ?? $"{item.Id}-production",
|
Id = item.Construction.RecipeId ?? $"{item.Id}-production",
|
||||||
Label = item.Name,
|
Label = item.Name,
|
||||||
FacilityCategory = item.Construction.FacilityCategory,
|
FacilityCategory = item.Construction.FacilityCategory,
|
||||||
Duration = item.Construction.CycleTime,
|
Duration = item.Construction.CycleTime,
|
||||||
Priority = item.Construction.Priority,
|
Priority = item.Construction.Priority,
|
||||||
RequiredModules = item.Construction.RequiredModules.ToList(),
|
RequiredModules = item.Construction.RequiredModules.ToList(),
|
||||||
Inputs = item.Construction.Requirements
|
Inputs = item.Construction.Requirements
|
||||||
.Select(input => new RecipeInputDefinition
|
.Select(input => new RecipeInputDefinition
|
||||||
{
|
{
|
||||||
ItemId = input.ItemId,
|
ItemId = input.ItemId,
|
||||||
Amount = input.Amount,
|
Amount = input.Amount,
|
||||||
})
|
})
|
||||||
.ToList(),
|
.ToList(),
|
||||||
Outputs =
|
Outputs =
|
||||||
[
|
[
|
||||||
new RecipeOutputDefinition
|
new RecipeOutputDefinition
|
||||||
{
|
{
|
||||||
ItemId = item.Id,
|
ItemId = item.Id,
|
||||||
Amount = item.Construction.BatchSize,
|
Amount = item.Construction.BatchSize,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var ship in ships)
|
||||||
|
{
|
||||||
|
if (ship.Construction is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
recipes.Add(new RecipeDefinition
|
||||||
|
{
|
||||||
|
Id = ship.Construction.RecipeId ?? $"{ship.Id}-construction",
|
||||||
|
Label = $"{ship.Label} Construction",
|
||||||
|
FacilityCategory = ship.Construction.FacilityCategory,
|
||||||
|
Duration = ship.Construction.CycleTime,
|
||||||
|
Priority = ship.Construction.Priority,
|
||||||
|
RequiredModules = ship.Construction.RequiredModules.ToList(),
|
||||||
|
Inputs = ship.Construction.Requirements
|
||||||
|
.Select(input => new RecipeInputDefinition
|
||||||
|
{
|
||||||
|
ItemId = input.ItemId,
|
||||||
|
Amount = input.Amount,
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
ShipOutputId = ship.Id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipes;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var ship in ships)
|
private static string InferFacilityCategory(ItemDefinition item) =>
|
||||||
{
|
item.Group switch
|
||||||
if (ship.Construction is null)
|
|
||||||
{
|
{
|
||||||
continue;
|
"agricultural" or "food" or "pharmaceutical" or "water" => "farm",
|
||||||
}
|
_ => "station",
|
||||||
|
};
|
||||||
|
|
||||||
recipes.Add(new RecipeDefinition
|
private static List<string> InferRequiredModules(ItemDefinition item, IReadOnlyDictionary<string, string> preferredProducerByItemId)
|
||||||
{
|
{
|
||||||
Id = ship.Construction.RecipeId ?? $"{ship.Id}-construction",
|
if (preferredProducerByItemId.TryGetValue(item.Id, out var moduleId))
|
||||||
Label = $"{ship.Label} Construction",
|
{
|
||||||
FacilityCategory = ship.Construction.FacilityCategory,
|
return [moduleId];
|
||||||
Duration = ship.Construction.CycleTime,
|
}
|
||||||
Priority = ship.Construction.Priority,
|
|
||||||
RequiredModules = ship.Construction.RequiredModules.ToList(),
|
return [];
|
||||||
Inputs = ship.Construction.Requirements
|
|
||||||
.Select(input => new RecipeInputDefinition
|
|
||||||
{
|
|
||||||
ItemId = input.ItemId,
|
|
||||||
Amount = input.Amount,
|
|
||||||
})
|
|
||||||
.ToList(),
|
|
||||||
ShipOutputId = ship.Id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return recipes;
|
private static int InferRecipePriority(ItemDefinition item) =>
|
||||||
}
|
item.Group switch
|
||||||
|
{
|
||||||
|
"energy" => 140,
|
||||||
|
"water" => 130,
|
||||||
|
"food" => 120,
|
||||||
|
"agricultural" => 110,
|
||||||
|
"refined" => 100,
|
||||||
|
"hightech" => 90,
|
||||||
|
"shiptech" => 80,
|
||||||
|
"pharmaceutical" => 70,
|
||||||
|
_ => 60,
|
||||||
|
};
|
||||||
|
|
||||||
private static string InferFacilityCategory(ItemDefinition item) =>
|
private static List<ItemDefinition> NormalizeItems(List<ItemDefinition> items)
|
||||||
item.Group switch
|
|
||||||
{
|
{
|
||||||
"agricultural" or "food" or "pharmaceutical" or "water" => "farm",
|
foreach (var item in items)
|
||||||
_ => "station",
|
{
|
||||||
};
|
if (string.IsNullOrWhiteSpace(item.Type))
|
||||||
|
{
|
||||||
|
item.Type = string.IsNullOrWhiteSpace(item.Group) ? "material" : item.Group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static List<string> InferRequiredModules(ItemDefinition item, IReadOnlyDictionary<string, string> preferredProducerByItemId)
|
return items;
|
||||||
{
|
|
||||||
if (preferredProducerByItemId.TryGetValue(item.Id, out var moduleId))
|
|
||||||
{
|
|
||||||
return [moduleId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
private static List<ModuleDefinition> NormalizeModules(List<ModuleDefinition> modules)
|
||||||
}
|
|
||||||
|
|
||||||
private static int InferRecipePriority(ItemDefinition item) =>
|
|
||||||
item.Group switch
|
|
||||||
{
|
{
|
||||||
"energy" => 140,
|
foreach (var module in modules)
|
||||||
"water" => 130,
|
{
|
||||||
"food" => 120,
|
if (module.Products.Count == 0 && !string.IsNullOrWhiteSpace(module.Product))
|
||||||
"agricultural" => 110,
|
{
|
||||||
"refined" => 100,
|
module.Products = [module.Product];
|
||||||
"hightech" => 90,
|
}
|
||||||
"shiptech" => 80,
|
|
||||||
"pharmaceutical" => 70,
|
|
||||||
_ => 60,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static List<ItemDefinition> NormalizeItems(List<ItemDefinition> items)
|
if (string.IsNullOrWhiteSpace(module.ProductionMode))
|
||||||
{
|
{
|
||||||
foreach (var item in items)
|
module.ProductionMode = string.Equals(module.Type, "buildmodule", StringComparison.Ordinal)
|
||||||
{
|
? "commanded"
|
||||||
if (string.IsNullOrWhiteSpace(item.Type))
|
: "passive";
|
||||||
{
|
}
|
||||||
item.Type = string.IsNullOrWhiteSpace(item.Group) ? "material" : item.Group;
|
|
||||||
}
|
if (module.WorkforceNeeded <= 0f)
|
||||||
|
{
|
||||||
|
module.WorkforceNeeded = module.WorkForce?.Max ?? 0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<ModuleDefinition> NormalizeModules(List<ModuleDefinition> modules)
|
|
||||||
{
|
|
||||||
foreach (var module in modules)
|
|
||||||
{
|
|
||||||
if (module.Products.Count == 0 && !string.IsNullOrWhiteSpace(module.Product))
|
|
||||||
{
|
|
||||||
module.Products = [module.Product];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(module.ProductionMode))
|
|
||||||
{
|
|
||||||
module.ProductionMode = string.Equals(module.Type, "buildmodule", StringComparison.Ordinal)
|
|
||||||
? "commanded"
|
|
||||||
: "passive";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module.WorkforceNeeded <= 0f)
|
|
||||||
{
|
|
||||||
module.WorkforceNeeded = module.WorkForce?.Max ?? 0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return modules;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed record ScenarioCatalog(
|
internal sealed record ScenarioCatalog(
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ namespace SpaceGame.Api.Universe.Scenario;
|
|||||||
|
|
||||||
internal static class LoaderSupport
|
internal static class LoaderSupport
|
||||||
{
|
{
|
||||||
internal const string DefaultFactionId = "sol-dominion";
|
internal const string DefaultFactionId = "sol-dominion";
|
||||||
internal const int WorldSeed = 1;
|
internal const int WorldSeed = 1;
|
||||||
internal const float MinimumFactionCredits = 0f;
|
internal const float MinimumFactionCredits = 0f;
|
||||||
internal const float MinimumRefineryOre = 0f;
|
internal const float MinimumRefineryOre = 0f;
|
||||||
internal const float MinimumRefineryStock = 0f;
|
internal const float MinimumRefineryStock = 0f;
|
||||||
internal const float MinimumShipyardStock = 0f;
|
internal const float MinimumShipyardStock = 0f;
|
||||||
internal const float MinimumSystemSeparation = 3.2f;
|
internal const float MinimumSystemSeparation = 3.2f;
|
||||||
internal const float LocalSpaceRadius = 10_000f;
|
internal const float LocalSpaceRadius = 10_000f;
|
||||||
|
|
||||||
internal static readonly string[] GeneratedSystemNames =
|
internal static readonly string[] GeneratedSystemNames =
|
||||||
[
|
[
|
||||||
"Aquila Verge",
|
"Aquila Verge",
|
||||||
"Orion Fold",
|
"Orion Fold",
|
||||||
"Draco Span",
|
"Draco Span",
|
||||||
"Lyra Shoal",
|
"Lyra Shoal",
|
||||||
@@ -48,9 +48,9 @@ internal static class LoaderSupport
|
|||||||
"Telescopium Strand",
|
"Telescopium Strand",
|
||||||
];
|
];
|
||||||
|
|
||||||
internal static readonly StarProfile[] StarProfiles =
|
internal static readonly StarProfile[] StarProfiles =
|
||||||
[
|
[
|
||||||
new("main-sequence", "#ffd27a", "#ffb14a", 696340f),
|
new("main-sequence", "#ffd27a", "#ffb14a", 696340f),
|
||||||
new("blue-white", "#9dc6ff", "#66a0ff", 930000f),
|
new("blue-white", "#9dc6ff", "#66a0ff", 930000f),
|
||||||
new("white-dwarf", "#f1f5ff", "#b8caff", 12000f),
|
new("white-dwarf", "#f1f5ff", "#b8caff", 12000f),
|
||||||
new("brown-dwarf", "#b97d56", "#8a5438", 70000f),
|
new("brown-dwarf", "#b97d56", "#8a5438", 70000f),
|
||||||
@@ -59,9 +59,9 @@ internal static class LoaderSupport
|
|||||||
new("binary-white-dwarf", "#edf3ff", "#c8d6ff", 14000f),
|
new("binary-white-dwarf", "#edf3ff", "#c8d6ff", 14000f),
|
||||||
];
|
];
|
||||||
|
|
||||||
internal static readonly PlanetProfile[] PlanetProfiles =
|
internal static readonly PlanetProfile[] PlanetProfiles =
|
||||||
[
|
[
|
||||||
new("barren", "sphere", "#bca48f", 2800f, 0.22f, 0, false),
|
new("barren", "sphere", "#bca48f", 2800f, 0.22f, 0, false),
|
||||||
new("terrestrial", "sphere", "#58a36c", 6400f, 0.28f, 1, false),
|
new("terrestrial", "sphere", "#58a36c", 6400f, 0.28f, 1, false),
|
||||||
new("oceanic", "sphere", "#4f84c4", 7000f, 0.30f, 2, false),
|
new("oceanic", "sphere", "#4f84c4", 7000f, 0.30f, 2, false),
|
||||||
new("desert", "sphere", "#d4a373", 5200f, 0.26f, 0, false),
|
new("desert", "sphere", "#d4a373", 5200f, 0.26f, 0, false),
|
||||||
@@ -71,85 +71,85 @@ internal static class LoaderSupport
|
|||||||
new("lava", "sphere", "#db6846", 3200f, 0.20f, 0, false),
|
new("lava", "sphere", "#db6846", 3200f, 0.20f, 0, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
internal static Vector3 ToVector(float[] values) => new(values[0], values[1], values[2]);
|
internal static Vector3 ToVector(float[] values) => new(values[0], values[1], values[2]);
|
||||||
|
|
||||||
internal static Vector3 NormalizeScenarioPoint(SystemRuntime system, float[] values)
|
internal static Vector3 NormalizeScenarioPoint(SystemRuntime system, float[] values)
|
||||||
{
|
|
||||||
var raw = ToVector(values);
|
|
||||||
var relativeToSystem = new Vector3(
|
|
||||||
raw.X - system.Position.X,
|
|
||||||
raw.Y - system.Position.Y,
|
|
||||||
raw.Z - system.Position.Z);
|
|
||||||
|
|
||||||
return relativeToSystem.LengthSquared() < raw.LengthSquared()
|
|
||||||
? relativeToSystem
|
|
||||||
: raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool HasInstalledModules(StationRuntime station, params string[] modules) =>
|
|
||||||
modules.All(moduleId => station.Modules.Any(candidate => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal)));
|
|
||||||
|
|
||||||
internal static bool HasCapabilities(ShipDefinition definition, params string[] capabilities) =>
|
|
||||||
capabilities.All(capability => definition.Capabilities.Contains(capability, StringComparer.Ordinal));
|
|
||||||
|
|
||||||
internal static void AddStationModule(StationRuntime station, IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions, string moduleId)
|
|
||||||
{
|
|
||||||
if (!moduleDefinitions.TryGetValue(moduleId, out var definition))
|
|
||||||
{
|
{
|
||||||
return;
|
var raw = ToVector(values);
|
||||||
|
var relativeToSystem = new Vector3(
|
||||||
|
raw.X - system.Position.X,
|
||||||
|
raw.Y - system.Position.Y,
|
||||||
|
raw.Z - system.Position.Z);
|
||||||
|
|
||||||
|
return relativeToSystem.LengthSquared() < raw.LengthSquared()
|
||||||
|
? relativeToSystem
|
||||||
|
: raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
station.Modules.Add(new StationModuleRuntime
|
internal static bool HasInstalledModules(StationRuntime station, params string[] modules) =>
|
||||||
|
modules.All(moduleId => station.Modules.Any(candidate => string.Equals(candidate.ModuleId, moduleId, StringComparison.Ordinal)));
|
||||||
|
|
||||||
|
internal static bool HasCapabilities(ShipDefinition definition, params string[] capabilities) =>
|
||||||
|
capabilities.All(capability => definition.Capabilities.Contains(capability, StringComparer.Ordinal));
|
||||||
|
|
||||||
|
internal static void AddStationModule(StationRuntime station, IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions, string moduleId)
|
||||||
{
|
{
|
||||||
Id = $"{station.Id}-module-{station.Modules.Count + 1}",
|
if (!moduleDefinitions.TryGetValue(moduleId, out var definition))
|
||||||
ModuleId = moduleId,
|
{
|
||||||
Health = definition.Hull,
|
return;
|
||||||
MaxHealth = definition.Hull,
|
}
|
||||||
});
|
|
||||||
station.Radius = GetStationRadius(moduleDefinitions, station);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static float GetStationRadius(IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions, StationRuntime station)
|
station.Modules.Add(new StationModuleRuntime
|
||||||
{
|
{
|
||||||
var totalArea = station.Modules
|
Id = $"{station.Id}-module-{station.Modules.Count + 1}",
|
||||||
.Select(module => moduleDefinitions.TryGetValue(module.ModuleId, out var definition) ? definition.Radius * definition.Radius : 0f)
|
ModuleId = moduleId,
|
||||||
.Sum();
|
Health = definition.Hull,
|
||||||
return MathF.Max(24f, MathF.Sqrt(MathF.Max(totalArea, 1f)));
|
MaxHealth = definition.Hull,
|
||||||
}
|
});
|
||||||
|
station.Radius = GetStationRadius(moduleDefinitions, station);
|
||||||
internal static Vector3 Add(Vector3 left, Vector3 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
|
|
||||||
|
|
||||||
internal static Vector3 Scale(Vector3 vector, float scale) => new(vector.X * scale, vector.Y * scale, vector.Z * scale);
|
|
||||||
|
|
||||||
internal static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
|
||||||
modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal));
|
|
||||||
|
|
||||||
internal static float ComputeWorkforceRatio(float population, float workforceRequired)
|
|
||||||
{
|
|
||||||
if (workforceRequired <= 0.01f)
|
|
||||||
{
|
|
||||||
return 1f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var staffedRatio = MathF.Min(1f, population / workforceRequired);
|
internal static float GetStationRadius(IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions, StationRuntime station)
|
||||||
return 0.1f + (0.9f * staffedRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static float GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
|
|
||||||
inventory.TryGetValue(itemId, out var amount) ? amount : 0f;
|
|
||||||
|
|
||||||
internal static float DegreesToRadians(float degrees) => degrees * (MathF.PI / 180f);
|
|
||||||
|
|
||||||
internal static Vector3 NormalizeOrFallback(Vector3 vector, Vector3 fallback)
|
|
||||||
{
|
|
||||||
var length = MathF.Sqrt(vector.LengthSquared());
|
|
||||||
if (length <= 0.0001f)
|
|
||||||
{
|
{
|
||||||
return fallback;
|
var totalArea = station.Modules
|
||||||
|
.Select(module => moduleDefinitions.TryGetValue(module.ModuleId, out var definition) ? definition.Radius * definition.Radius : 0f)
|
||||||
|
.Sum();
|
||||||
|
return MathF.Max(24f, MathF.Sqrt(MathF.Max(totalArea, 1f)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return vector.Divide(length);
|
internal static Vector3 Add(Vector3 left, Vector3 right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
|
||||||
}
|
|
||||||
|
internal static Vector3 Scale(Vector3 vector, float scale) => new(vector.X * scale, vector.Y * scale, vector.Z * scale);
|
||||||
|
|
||||||
|
internal static int CountModules(IEnumerable<string> modules, string moduleId) =>
|
||||||
|
modules.Count(candidate => string.Equals(candidate, moduleId, StringComparison.Ordinal));
|
||||||
|
|
||||||
|
internal static float ComputeWorkforceRatio(float population, float workforceRequired)
|
||||||
|
{
|
||||||
|
if (workforceRequired <= 0.01f)
|
||||||
|
{
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var staffedRatio = MathF.Min(1f, population / workforceRequired);
|
||||||
|
return 0.1f + (0.9f * staffedRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static float GetInventoryAmount(IReadOnlyDictionary<string, float> inventory, string itemId) =>
|
||||||
|
inventory.TryGetValue(itemId, out var amount) ? amount : 0f;
|
||||||
|
|
||||||
|
internal static float DegreesToRadians(float degrees) => degrees * (MathF.PI / 180f);
|
||||||
|
|
||||||
|
internal static Vector3 NormalizeOrFallback(Vector3 vector, Vector3 fallback)
|
||||||
|
{
|
||||||
|
var length = MathF.Sqrt(vector.LengthSquared());
|
||||||
|
if (length <= 0.0001f)
|
||||||
|
{
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vector.Divide(length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed record StarProfile(
|
internal sealed record StarProfile(
|
||||||
@@ -167,5 +167,5 @@ internal sealed record PlanetProfile(
|
|||||||
int BaseMoonCount,
|
int BaseMoonCount,
|
||||||
bool CanHaveRing)
|
bool CanHaveRing)
|
||||||
{
|
{
|
||||||
public float OrbitGapMax => OrbitGapMin + MathF.Max(0.12f, OrbitGapMin * 0.45f);
|
public float OrbitGapMax => OrbitGapMin + MathF.Max(0.12f, OrbitGapMin * 0.45f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,24 @@ namespace SpaceGame.Api.Universe.Scenario;
|
|||||||
|
|
||||||
public sealed class ScenarioLoader
|
public sealed class ScenarioLoader
|
||||||
{
|
{
|
||||||
private readonly WorldBuilder _worldBuilder;
|
private readonly WorldBuilder _worldBuilder;
|
||||||
|
|
||||||
public ScenarioLoader(string contentRootPath, WorldGenerationOptions? worldGeneration = null)
|
public ScenarioLoader(string contentRootPath, WorldGenerationOptions? worldGeneration = null)
|
||||||
{
|
{
|
||||||
var generationOptions = worldGeneration ?? new WorldGenerationOptions();
|
var generationOptions = worldGeneration ?? new WorldGenerationOptions();
|
||||||
var dataRoot = Path.GetFullPath(Path.Combine(contentRootPath, "..", "..", "shared", "data"));
|
var dataRoot = Path.GetFullPath(Path.Combine(contentRootPath, "..", "..", "shared", "data"));
|
||||||
var dataLoader = new DataCatalogLoader(dataRoot);
|
var dataLoader = new DataCatalogLoader(dataRoot);
|
||||||
var generationService = new SystemGenerationService();
|
var generationService = new SystemGenerationService();
|
||||||
var spatialBuilder = new SpatialBuilder();
|
var spatialBuilder = new SpatialBuilder();
|
||||||
var seedingService = new WorldSeedingService();
|
var seedingService = new WorldSeedingService();
|
||||||
|
|
||||||
_worldBuilder = new WorldBuilder(
|
_worldBuilder = new WorldBuilder(
|
||||||
generationOptions,
|
generationOptions,
|
||||||
dataLoader,
|
dataLoader,
|
||||||
generationService,
|
generationService,
|
||||||
spatialBuilder,
|
spatialBuilder,
|
||||||
seedingService);
|
seedingService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimulationWorld Load() => _worldBuilder.Build();
|
public SimulationWorld Load() => _worldBuilder.Build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,305 +4,305 @@ namespace SpaceGame.Api.Universe.Scenario;
|
|||||||
|
|
||||||
internal sealed class SpatialBuilder
|
internal sealed class SpatialBuilder
|
||||||
{
|
{
|
||||||
internal ScenarioSpatialLayout BuildLayout(IReadOnlyList<SystemRuntime> systems, BalanceDefinition balance)
|
internal ScenarioSpatialLayout BuildLayout(IReadOnlyList<SystemRuntime> systems, BalanceDefinition balance)
|
||||||
{
|
|
||||||
var systemGraphs = systems.ToDictionary(
|
|
||||||
system => system.Definition.Id,
|
|
||||||
BuildSystemSpatialGraph,
|
|
||||||
StringComparer.Ordinal);
|
|
||||||
var celestials = systemGraphs.Values.SelectMany(graph => graph.Celestials).ToList();
|
|
||||||
var nodes = new List<ResourceNodeRuntime>();
|
|
||||||
var nodeIdCounter = 0;
|
|
||||||
|
|
||||||
foreach (var system in systems)
|
|
||||||
{
|
{
|
||||||
var systemGraph = systemGraphs[system.Definition.Id];
|
var systemGraphs = systems.ToDictionary(
|
||||||
foreach (var node in system.Definition.ResourceNodes)
|
system => system.Definition.Id,
|
||||||
{
|
BuildSystemSpatialGraph,
|
||||||
var anchorCelestial = ResolveResourceNodeAnchor(systemGraph, node);
|
StringComparer.Ordinal);
|
||||||
nodes.Add(new ResourceNodeRuntime
|
var celestials = systemGraphs.Values.SelectMany(graph => graph.Celestials).ToList();
|
||||||
|
var nodes = new List<ResourceNodeRuntime>();
|
||||||
|
var nodeIdCounter = 0;
|
||||||
|
|
||||||
|
foreach (var system in systems)
|
||||||
{
|
{
|
||||||
Id = $"node-{++nodeIdCounter}",
|
var systemGraph = systemGraphs[system.Definition.Id];
|
||||||
SystemId = system.Definition.Id,
|
foreach (var node in system.Definition.ResourceNodes)
|
||||||
Position = ComputeResourceNodePosition(anchorCelestial, node, balance.YPlane),
|
{
|
||||||
SourceKind = node.SourceKind,
|
var anchorCelestial = ResolveResourceNodeAnchor(systemGraph, node);
|
||||||
ItemId = node.ItemId,
|
nodes.Add(new ResourceNodeRuntime
|
||||||
CelestialId = anchorCelestial?.Id,
|
{
|
||||||
OrbitRadius = node.RadiusOffset,
|
Id = $"node-{++nodeIdCounter}",
|
||||||
OrbitPhase = node.Angle,
|
SystemId = system.Definition.Id,
|
||||||
OrbitInclination = DegreesToRadians(node.InclinationDegrees),
|
Position = ComputeResourceNodePosition(anchorCelestial, node, balance.YPlane),
|
||||||
OreRemaining = node.OreAmount,
|
SourceKind = node.SourceKind,
|
||||||
MaxOre = node.OreAmount,
|
ItemId = node.ItemId,
|
||||||
});
|
CelestialId = anchorCelestial?.Id,
|
||||||
}
|
OrbitRadius = node.RadiusOffset,
|
||||||
|
OrbitPhase = node.Angle,
|
||||||
|
OrbitInclination = DegreesToRadians(node.InclinationDegrees),
|
||||||
|
OreRemaining = node.OreAmount,
|
||||||
|
MaxOre = node.OreAmount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ScenarioSpatialLayout(systemGraphs, celestials, nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ScenarioSpatialLayout(systemGraphs, celestials, nodes);
|
private static SystemSpatialGraph BuildSystemSpatialGraph(SystemRuntime system)
|
||||||
}
|
|
||||||
|
|
||||||
private static SystemSpatialGraph BuildSystemSpatialGraph(SystemRuntime system)
|
|
||||||
{
|
|
||||||
var celestials = new List<CelestialRuntime>();
|
|
||||||
var lagrangeNodesByPlanetIndex = new Dictionary<int, Dictionary<string, CelestialRuntime>>();
|
|
||||||
|
|
||||||
for (var starIndex = 0; starIndex < system.Definition.Stars.Count; starIndex += 1)
|
|
||||||
{
|
{
|
||||||
AddCelestial(
|
var celestials = new List<CelestialRuntime>();
|
||||||
celestials,
|
var lagrangeNodesByPlanetIndex = new Dictionary<int, Dictionary<string, CelestialRuntime>>();
|
||||||
id: $"node-{system.Definition.Id}-star-{starIndex + 1}",
|
|
||||||
systemId: system.Definition.Id,
|
for (var starIndex = 0; starIndex < system.Definition.Stars.Count; starIndex += 1)
|
||||||
kind: SpatialNodeKind.Star,
|
{
|
||||||
position: Vector3.Zero,
|
AddCelestial(
|
||||||
localSpaceRadius: LocalSpaceRadius);
|
celestials,
|
||||||
|
id: $"node-{system.Definition.Id}-star-{starIndex + 1}",
|
||||||
|
systemId: system.Definition.Id,
|
||||||
|
kind: SpatialNodeKind.Star,
|
||||||
|
position: Vector3.Zero,
|
||||||
|
localSpaceRadius: LocalSpaceRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
var primaryStarNodeId = $"node-{system.Definition.Id}-star-1";
|
||||||
|
|
||||||
|
for (var planetIndex = 0; planetIndex < system.Definition.Planets.Count; planetIndex += 1)
|
||||||
|
{
|
||||||
|
var planet = system.Definition.Planets[planetIndex];
|
||||||
|
var planetNodeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}";
|
||||||
|
var planetPosition = ComputePlanetPosition(planet);
|
||||||
|
var planetCelestial = AddCelestial(
|
||||||
|
celestials,
|
||||||
|
id: planetNodeId,
|
||||||
|
systemId: system.Definition.Id,
|
||||||
|
kind: SpatialNodeKind.Planet,
|
||||||
|
position: planetPosition,
|
||||||
|
localSpaceRadius: LocalSpaceRadius,
|
||||||
|
parentNodeId: primaryStarNodeId);
|
||||||
|
|
||||||
|
var lagrangeNodes = new Dictionary<string, CelestialRuntime>(StringComparer.Ordinal);
|
||||||
|
foreach (var point in EnumeratePlanetLagrangePoints(planetPosition, planet))
|
||||||
|
{
|
||||||
|
var lagrangeCelestial = AddCelestial(
|
||||||
|
celestials,
|
||||||
|
id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-{point.Designation.ToLowerInvariant()}",
|
||||||
|
systemId: system.Definition.Id,
|
||||||
|
kind: SpatialNodeKind.LagrangePoint,
|
||||||
|
position: point.Position,
|
||||||
|
localSpaceRadius: LocalSpaceRadius,
|
||||||
|
parentNodeId: planetCelestial.Id,
|
||||||
|
orbitReferenceId: point.Designation);
|
||||||
|
lagrangeNodes[point.Designation] = lagrangeCelestial;
|
||||||
|
}
|
||||||
|
|
||||||
|
lagrangeNodesByPlanetIndex[planetIndex] = lagrangeNodes;
|
||||||
|
|
||||||
|
for (var moonIndex = 0; moonIndex < planet.Moons.Count; moonIndex += 1)
|
||||||
|
{
|
||||||
|
var moon = planet.Moons[moonIndex];
|
||||||
|
var moonPosition = ComputeMoonPosition(planetPosition, moon);
|
||||||
|
AddCelestial(
|
||||||
|
celestials,
|
||||||
|
id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-moon-{moonIndex + 1}",
|
||||||
|
systemId: system.Definition.Id,
|
||||||
|
kind: SpatialNodeKind.Moon,
|
||||||
|
position: moonPosition,
|
||||||
|
localSpaceRadius: LocalSpaceRadius,
|
||||||
|
parentNodeId: planetCelestial.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SystemSpatialGraph(system.Definition.Id, celestials, lagrangeNodesByPlanetIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
var primaryStarNodeId = $"node-{system.Definition.Id}-star-1";
|
private static CelestialRuntime AddCelestial(
|
||||||
|
ICollection<CelestialRuntime> celestials,
|
||||||
for (var planetIndex = 0; planetIndex < system.Definition.Planets.Count; planetIndex += 1)
|
string id,
|
||||||
|
string systemId,
|
||||||
|
SpatialNodeKind kind,
|
||||||
|
Vector3 position,
|
||||||
|
float localSpaceRadius,
|
||||||
|
string? parentNodeId = null,
|
||||||
|
string? orbitReferenceId = null)
|
||||||
{
|
{
|
||||||
var planet = system.Definition.Planets[planetIndex];
|
var celestial = new CelestialRuntime
|
||||||
var planetNodeId = $"node-{system.Definition.Id}-planet-{planetIndex + 1}";
|
{
|
||||||
var planetPosition = ComputePlanetPosition(planet);
|
Id = id,
|
||||||
var planetCelestial = AddCelestial(
|
SystemId = systemId,
|
||||||
celestials,
|
Kind = kind,
|
||||||
id: planetNodeId,
|
Position = position,
|
||||||
systemId: system.Definition.Id,
|
LocalSpaceRadius = localSpaceRadius,
|
||||||
kind: SpatialNodeKind.Planet,
|
ParentNodeId = parentNodeId,
|
||||||
position: planetPosition,
|
OrbitReferenceId = orbitReferenceId,
|
||||||
localSpaceRadius: LocalSpaceRadius,
|
};
|
||||||
parentNodeId: primaryStarNodeId);
|
|
||||||
|
|
||||||
var lagrangeNodes = new Dictionary<string, CelestialRuntime>(StringComparer.Ordinal);
|
celestials.Add(celestial);
|
||||||
foreach (var point in EnumeratePlanetLagrangePoints(planetPosition, planet))
|
return celestial;
|
||||||
{
|
|
||||||
var lagrangeCelestial = AddCelestial(
|
|
||||||
celestials,
|
|
||||||
id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-{point.Designation.ToLowerInvariant()}",
|
|
||||||
systemId: system.Definition.Id,
|
|
||||||
kind: SpatialNodeKind.LagrangePoint,
|
|
||||||
position: point.Position,
|
|
||||||
localSpaceRadius: LocalSpaceRadius,
|
|
||||||
parentNodeId: planetCelestial.Id,
|
|
||||||
orbitReferenceId: point.Designation);
|
|
||||||
lagrangeNodes[point.Designation] = lagrangeCelestial;
|
|
||||||
}
|
|
||||||
|
|
||||||
lagrangeNodesByPlanetIndex[planetIndex] = lagrangeNodes;
|
|
||||||
|
|
||||||
for (var moonIndex = 0; moonIndex < planet.Moons.Count; moonIndex += 1)
|
|
||||||
{
|
|
||||||
var moon = planet.Moons[moonIndex];
|
|
||||||
var moonPosition = ComputeMoonPosition(planetPosition, moon);
|
|
||||||
AddCelestial(
|
|
||||||
celestials,
|
|
||||||
id: $"node-{system.Definition.Id}-planet-{planetIndex + 1}-moon-{moonIndex + 1}",
|
|
||||||
systemId: system.Definition.Id,
|
|
||||||
kind: SpatialNodeKind.Moon,
|
|
||||||
position: moonPosition,
|
|
||||||
localSpaceRadius: LocalSpaceRadius,
|
|
||||||
parentNodeId: planetCelestial.Id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SystemSpatialGraph(system.Definition.Id, celestials, lagrangeNodesByPlanetIndex);
|
private static IEnumerable<LagrangePointPlacement> EnumeratePlanetLagrangePoints(Vector3 planetPosition, PlanetDefinition planet)
|
||||||
}
|
|
||||||
|
|
||||||
private static CelestialRuntime AddCelestial(
|
|
||||||
ICollection<CelestialRuntime> celestials,
|
|
||||||
string id,
|
|
||||||
string systemId,
|
|
||||||
SpatialNodeKind kind,
|
|
||||||
Vector3 position,
|
|
||||||
float localSpaceRadius,
|
|
||||||
string? parentNodeId = null,
|
|
||||||
string? orbitReferenceId = null)
|
|
||||||
{
|
|
||||||
var celestial = new CelestialRuntime
|
|
||||||
{
|
{
|
||||||
Id = id,
|
var radial = NormalizeOrFallback(planetPosition, new Vector3(1f, 0f, 0f));
|
||||||
SystemId = systemId,
|
var tangential = new Vector3(-radial.Z, 0f, radial.X);
|
||||||
Kind = kind,
|
var orbitRadiusKm = MathF.Sqrt(planetPosition.X * planetPosition.X + planetPosition.Z * planetPosition.Z);
|
||||||
Position = position,
|
var offset = ComputePlanetLocalLagrangeOffset(orbitRadiusKm, planet);
|
||||||
LocalSpaceRadius = localSpaceRadius,
|
var triangularAngle = MathF.PI / 3f;
|
||||||
ParentNodeId = parentNodeId,
|
|
||||||
OrbitReferenceId = orbitReferenceId,
|
yield return new LagrangePointPlacement("L1", Add(planetPosition, Scale(radial, -offset)));
|
||||||
|
yield return new LagrangePointPlacement("L2", Add(planetPosition, Scale(radial, offset)));
|
||||||
|
yield return new LagrangePointPlacement("L3", Scale(radial, -orbitRadiusKm));
|
||||||
|
yield return new LagrangePointPlacement(
|
||||||
|
"L4",
|
||||||
|
Add(
|
||||||
|
Scale(radial, orbitRadiusKm * MathF.Cos(triangularAngle)),
|
||||||
|
Scale(tangential, orbitRadiusKm * MathF.Sin(triangularAngle))));
|
||||||
|
yield return new LagrangePointPlacement(
|
||||||
|
"L5",
|
||||||
|
Add(
|
||||||
|
Scale(radial, orbitRadiusKm * MathF.Cos(triangularAngle)),
|
||||||
|
Scale(tangential, -orbitRadiusKm * MathF.Sin(triangularAngle))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float ComputePlanetLocalLagrangeOffset(float orbitRadiusKm, PlanetDefinition planet)
|
||||||
|
{
|
||||||
|
var planetMassProxy = EstimatePlanetMassRatio(planet);
|
||||||
|
var hillLikeOffset = orbitRadiusKm * MathF.Cbrt(MathF.Max(planetMassProxy / 3f, 1e-9f));
|
||||||
|
var minimumOffset = MathF.Max(planet.Size * 4f, 25000f);
|
||||||
|
return MathF.Max(minimumOffset, hillLikeOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float EstimatePlanetMassRatio(PlanetDefinition planet)
|
||||||
|
{
|
||||||
|
var earthRadiusRatio = MathF.Max(planet.Size / 6371f, 0.05f);
|
||||||
|
var densityFactor = planet.PlanetType switch
|
||||||
|
{
|
||||||
|
"gas-giant" => 0.24f,
|
||||||
|
"ice-giant" => 0.18f,
|
||||||
|
"oceanic" => 0.95f,
|
||||||
|
"ice" => 0.7f,
|
||||||
|
_ => 1f,
|
||||||
|
};
|
||||||
|
|
||||||
|
var earthMasses = MathF.Pow(earthRadiusRatio, 3f) * densityFactor;
|
||||||
|
return earthMasses / 332_946f;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static StationPlacement ResolveStationPlacement(
|
||||||
|
InitialStationDefinition plan,
|
||||||
|
SystemRuntime system,
|
||||||
|
SystemSpatialGraph graph,
|
||||||
|
IReadOnlyCollection<CelestialRuntime> existingCelestials)
|
||||||
|
{
|
||||||
|
if (plan.PlanetIndex is int planetIndex &&
|
||||||
|
graph.LagrangeNodesByPlanetIndex.TryGetValue(planetIndex, out var lagrangeNodes))
|
||||||
|
{
|
||||||
|
var designation = ResolveLagrangeDesignation(plan.LagrangeSide);
|
||||||
|
if (lagrangeNodes.TryGetValue(designation, out var lagrangeCelestial))
|
||||||
|
{
|
||||||
|
return new StationPlacement(lagrangeCelestial, lagrangeCelestial.Position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan.Position is { Length: 3 })
|
||||||
|
{
|
||||||
|
var targetPosition = NormalizeScenarioPoint(system, plan.Position);
|
||||||
|
var preferredCelestial = existingCelestials
|
||||||
|
.Where(c => c.SystemId == system.Definition.Id && c.Kind == SpatialNodeKind.LagrangePoint)
|
||||||
|
.OrderBy(c => c.Position.DistanceTo(targetPosition))
|
||||||
|
.FirstOrDefault()
|
||||||
|
?? existingCelestials
|
||||||
|
.Where(c => c.SystemId == system.Definition.Id)
|
||||||
|
.OrderBy(c => c.Position.DistanceTo(targetPosition))
|
||||||
|
.First();
|
||||||
|
return new StationPlacement(preferredCelestial, preferredCelestial.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
var fallbackCelestial = graph.Celestials
|
||||||
|
.FirstOrDefault(c => c.Kind == SpatialNodeKind.LagrangePoint && string.IsNullOrEmpty(c.OccupyingStructureId))
|
||||||
|
?? graph.Celestials.First(c => c.Kind == SpatialNodeKind.Planet);
|
||||||
|
return new StationPlacement(fallbackCelestial, fallbackCelestial.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolveLagrangeDesignation(int? lagrangeSide) => lagrangeSide switch
|
||||||
|
{
|
||||||
|
< 0 => "L4",
|
||||||
|
> 0 => "L5",
|
||||||
|
_ => "L1",
|
||||||
};
|
};
|
||||||
|
|
||||||
celestials.Add(celestial);
|
private static CelestialRuntime? ResolveResourceNodeAnchor(SystemSpatialGraph graph, ResourceNodeDefinition definition)
|
||||||
return celestial;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<LagrangePointPlacement> EnumeratePlanetLagrangePoints(Vector3 planetPosition, PlanetDefinition planet)
|
|
||||||
{
|
|
||||||
var radial = NormalizeOrFallback(planetPosition, new Vector3(1f, 0f, 0f));
|
|
||||||
var tangential = new Vector3(-radial.Z, 0f, radial.X);
|
|
||||||
var orbitRadiusKm = MathF.Sqrt(planetPosition.X * planetPosition.X + planetPosition.Z * planetPosition.Z);
|
|
||||||
var offset = ComputePlanetLocalLagrangeOffset(orbitRadiusKm, planet);
|
|
||||||
var triangularAngle = MathF.PI / 3f;
|
|
||||||
|
|
||||||
yield return new LagrangePointPlacement("L1", Add(planetPosition, Scale(radial, -offset)));
|
|
||||||
yield return new LagrangePointPlacement("L2", Add(planetPosition, Scale(radial, offset)));
|
|
||||||
yield return new LagrangePointPlacement("L3", Scale(radial, -orbitRadiusKm));
|
|
||||||
yield return new LagrangePointPlacement(
|
|
||||||
"L4",
|
|
||||||
Add(
|
|
||||||
Scale(radial, orbitRadiusKm * MathF.Cos(triangularAngle)),
|
|
||||||
Scale(tangential, orbitRadiusKm * MathF.Sin(triangularAngle))));
|
|
||||||
yield return new LagrangePointPlacement(
|
|
||||||
"L5",
|
|
||||||
Add(
|
|
||||||
Scale(radial, orbitRadiusKm * MathF.Cos(triangularAngle)),
|
|
||||||
Scale(tangential, -orbitRadiusKm * MathF.Sin(triangularAngle))));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float ComputePlanetLocalLagrangeOffset(float orbitRadiusKm, PlanetDefinition planet)
|
|
||||||
{
|
|
||||||
var planetMassProxy = EstimatePlanetMassRatio(planet);
|
|
||||||
var hillLikeOffset = orbitRadiusKm * MathF.Cbrt(MathF.Max(planetMassProxy / 3f, 1e-9f));
|
|
||||||
var minimumOffset = MathF.Max(planet.Size * 4f, 25000f);
|
|
||||||
return MathF.Max(minimumOffset, hillLikeOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static float EstimatePlanetMassRatio(PlanetDefinition planet)
|
|
||||||
{
|
|
||||||
var earthRadiusRatio = MathF.Max(planet.Size / 6371f, 0.05f);
|
|
||||||
var densityFactor = planet.PlanetType switch
|
|
||||||
{
|
{
|
||||||
"gas-giant" => 0.24f,
|
if (!string.IsNullOrWhiteSpace(definition.AnchorReference))
|
||||||
"ice-giant" => 0.18f,
|
{
|
||||||
"oceanic" => 0.95f,
|
var anchorId = definition.AnchorReference.ToLowerInvariant() switch
|
||||||
"ice" => 0.7f,
|
{
|
||||||
_ => 1f,
|
var reference when reference.StartsWith("star-", StringComparison.Ordinal)
|
||||||
};
|
=> $"node-{graph.SystemId}-{reference}",
|
||||||
|
var reference when reference.StartsWith("planet-", StringComparison.Ordinal)
|
||||||
|
=> $"node-{graph.SystemId}-{reference}",
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
var earthMasses = MathF.Pow(earthRadiusRatio, 3f) * densityFactor;
|
if (anchorId is not null)
|
||||||
return earthMasses / 332_946f;
|
{
|
||||||
}
|
return graph.Celestials.FirstOrDefault(c => string.Equals(c.Id, anchorId, StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal static StationPlacement ResolveStationPlacement(
|
if (definition.AnchorPlanetIndex is not int planetIndex || planetIndex < 0)
|
||||||
InitialStationDefinition plan,
|
{
|
||||||
SystemRuntime system,
|
return null;
|
||||||
SystemSpatialGraph graph,
|
}
|
||||||
IReadOnlyCollection<CelestialRuntime> existingCelestials)
|
|
||||||
{
|
if (definition.AnchorMoonIndex is int moonIndex && moonIndex >= 0)
|
||||||
if (plan.PlanetIndex is int planetIndex &&
|
{
|
||||||
graph.LagrangeNodesByPlanetIndex.TryGetValue(planetIndex, out var lagrangeNodes))
|
var moonNodeId = $"node-{graph.SystemId}-planet-{planetIndex + 1}-moon-{moonIndex + 1}";
|
||||||
{
|
return graph.Celestials.FirstOrDefault(c => c.Id == moonNodeId);
|
||||||
var designation = ResolveLagrangeDesignation(plan.LagrangeSide);
|
}
|
||||||
if (lagrangeNodes.TryGetValue(designation, out var lagrangeCelestial))
|
|
||||||
{
|
var planetNodeId = $"node-{graph.SystemId}-planet-{planetIndex + 1}";
|
||||||
return new StationPlacement(lagrangeCelestial, lagrangeCelestial.Position);
|
return graph.Celestials.FirstOrDefault(c => c.Id == planetNodeId);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan.Position is { Length: 3 })
|
private static Vector3 ComputeResourceNodePosition(CelestialRuntime? anchorCelestial, ResourceNodeDefinition definition, float yPlane)
|
||||||
{
|
{
|
||||||
var targetPosition = NormalizeScenarioPoint(system, plan.Position);
|
var verticalOffset = MathF.Sin(DegreesToRadians(definition.InclinationDegrees)) * MathF.Min(definition.RadiusOffset * 0.04f, 25000f);
|
||||||
var preferredCelestial = existingCelestials
|
var offset = new Vector3(
|
||||||
.Where(c => c.SystemId == system.Definition.Id && c.Kind == SpatialNodeKind.LagrangePoint)
|
MathF.Cos(definition.Angle) * definition.RadiusOffset,
|
||||||
.OrderBy(c => c.Position.DistanceTo(targetPosition))
|
verticalOffset,
|
||||||
.FirstOrDefault()
|
MathF.Sin(definition.Angle) * definition.RadiusOffset);
|
||||||
?? existingCelestials
|
|
||||||
.Where(c => c.SystemId == system.Definition.Id)
|
if (anchorCelestial is null)
|
||||||
.OrderBy(c => c.Position.DistanceTo(targetPosition))
|
{
|
||||||
.First();
|
return new Vector3(offset.X, yPlane + offset.Y, offset.Z);
|
||||||
return new StationPlacement(preferredCelestial, preferredCelestial.Position);
|
}
|
||||||
|
|
||||||
|
return Add(anchorCelestial.Position, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fallbackCelestial = graph.Celestials
|
private static Vector3 ComputePlanetPosition(PlanetDefinition planet)
|
||||||
.FirstOrDefault(c => c.Kind == SpatialNodeKind.LagrangePoint && string.IsNullOrEmpty(c.OccupyingStructureId))
|
|
||||||
?? graph.Celestials.First(c => c.Kind == SpatialNodeKind.Planet);
|
|
||||||
return new StationPlacement(fallbackCelestial, fallbackCelestial.Position);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ResolveLagrangeDesignation(int? lagrangeSide) => lagrangeSide switch
|
|
||||||
{
|
|
||||||
< 0 => "L4",
|
|
||||||
> 0 => "L5",
|
|
||||||
_ => "L1",
|
|
||||||
};
|
|
||||||
|
|
||||||
private static CelestialRuntime? ResolveResourceNodeAnchor(SystemSpatialGraph graph, ResourceNodeDefinition definition)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(definition.AnchorReference))
|
|
||||||
{
|
{
|
||||||
var anchorId = definition.AnchorReference.ToLowerInvariant() switch
|
var angle = DegreesToRadians(planet.OrbitPhaseAtEpoch);
|
||||||
{
|
var orbitRadiusKm = SimulationUnits.AuToKilometers(planet.OrbitRadius);
|
||||||
var reference when reference.StartsWith("star-", StringComparison.Ordinal)
|
return new Vector3(MathF.Cos(angle) * orbitRadiusKm, 0f, MathF.Sin(angle) * orbitRadiusKm);
|
||||||
=> $"node-{graph.SystemId}-{reference}",
|
|
||||||
var reference when reference.StartsWith("planet-", StringComparison.Ordinal)
|
|
||||||
=> $"node-{graph.SystemId}-{reference}",
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (anchorId is not null)
|
|
||||||
{
|
|
||||||
return graph.Celestials.FirstOrDefault(c => string.Equals(c.Id, anchorId, StringComparison.Ordinal));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (definition.AnchorPlanetIndex is not int planetIndex || planetIndex < 0)
|
private static Vector3 ComputeMoonPosition(Vector3 planetPosition, MoonDefinition moon)
|
||||||
{
|
{
|
||||||
return null;
|
var angle = DegreesToRadians(moon.OrbitPhaseAtEpoch);
|
||||||
|
var local = new Vector3(MathF.Cos(angle) * moon.OrbitRadius, 0f, MathF.Sin(angle) * moon.OrbitRadius);
|
||||||
|
return Add(planetPosition, local);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (definition.AnchorMoonIndex is int moonIndex && moonIndex >= 0)
|
internal static ShipSpatialStateRuntime CreateInitialShipSpatialState(string systemId, Vector3 position, IReadOnlyCollection<CelestialRuntime> celestials)
|
||||||
{
|
{
|
||||||
var moonNodeId = $"node-{graph.SystemId}-planet-{planetIndex + 1}-moon-{moonIndex + 1}";
|
var nearestCelestial = celestials
|
||||||
return graph.Celestials.FirstOrDefault(c => c.Id == moonNodeId);
|
.Where(c => c.SystemId == systemId)
|
||||||
|
.OrderBy(c => c.Position.DistanceTo(position))
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
return new ShipSpatialStateRuntime
|
||||||
|
{
|
||||||
|
CurrentSystemId = systemId,
|
||||||
|
SpaceLayer = SpaceLayerKinds.LocalSpace,
|
||||||
|
CurrentCelestialId = nearestCelestial?.Id,
|
||||||
|
LocalPosition = position,
|
||||||
|
SystemPosition = position,
|
||||||
|
MovementRegime = MovementRegimeKinds.LocalFlight,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var planetNodeId = $"node-{graph.SystemId}-planet-{planetIndex + 1}";
|
|
||||||
return graph.Celestials.FirstOrDefault(c => c.Id == planetNodeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Vector3 ComputeResourceNodePosition(CelestialRuntime? anchorCelestial, ResourceNodeDefinition definition, float yPlane)
|
|
||||||
{
|
|
||||||
var verticalOffset = MathF.Sin(DegreesToRadians(definition.InclinationDegrees)) * MathF.Min(definition.RadiusOffset * 0.04f, 25000f);
|
|
||||||
var offset = new Vector3(
|
|
||||||
MathF.Cos(definition.Angle) * definition.RadiusOffset,
|
|
||||||
verticalOffset,
|
|
||||||
MathF.Sin(definition.Angle) * definition.RadiusOffset);
|
|
||||||
|
|
||||||
if (anchorCelestial is null)
|
|
||||||
{
|
|
||||||
return new Vector3(offset.X, yPlane + offset.Y, offset.Z);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Add(anchorCelestial.Position, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Vector3 ComputePlanetPosition(PlanetDefinition planet)
|
|
||||||
{
|
|
||||||
var angle = DegreesToRadians(planet.OrbitPhaseAtEpoch);
|
|
||||||
var orbitRadiusKm = SimulationUnits.AuToKilometers(planet.OrbitRadius);
|
|
||||||
return new Vector3(MathF.Cos(angle) * orbitRadiusKm, 0f, MathF.Sin(angle) * orbitRadiusKm);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Vector3 ComputeMoonPosition(Vector3 planetPosition, MoonDefinition moon)
|
|
||||||
{
|
|
||||||
var angle = DegreesToRadians(moon.OrbitPhaseAtEpoch);
|
|
||||||
var local = new Vector3(MathF.Cos(angle) * moon.OrbitRadius, 0f, MathF.Sin(angle) * moon.OrbitRadius);
|
|
||||||
return Add(planetPosition, local);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static ShipSpatialStateRuntime CreateInitialShipSpatialState(string systemId, Vector3 position, IReadOnlyCollection<CelestialRuntime> celestials)
|
|
||||||
{
|
|
||||||
var nearestCelestial = celestials
|
|
||||||
.Where(c => c.SystemId == systemId)
|
|
||||||
.OrderBy(c => c.Position.DistanceTo(position))
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
return new ShipSpatialStateRuntime
|
|
||||||
{
|
|
||||||
CurrentSystemId = systemId,
|
|
||||||
SpaceLayer = SpaceLayerKinds.LocalSpace,
|
|
||||||
CurrentCelestialId = nearestCelestial?.Id,
|
|
||||||
LocalPosition = position,
|
|
||||||
SystemPosition = position,
|
|
||||||
MovementRegime = MovementRegimeKinds.LocalFlight,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed record ScenarioSpatialLayout(
|
internal sealed record ScenarioSpatialLayout(
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,335 +9,335 @@ internal sealed class WorldBuilder(
|
|||||||
SpatialBuilder spatialBuilder,
|
SpatialBuilder spatialBuilder,
|
||||||
WorldSeedingService seedingService)
|
WorldSeedingService seedingService)
|
||||||
{
|
{
|
||||||
internal SimulationWorld Build()
|
internal SimulationWorld Build()
|
||||||
{
|
|
||||||
var catalog = dataLoader.LoadCatalog();
|
|
||||||
var systems = generationService.ExpandSystems(
|
|
||||||
generationService.InjectSpecialSystems(catalog.AuthoredSystems),
|
|
||||||
worldGeneration.TargetSystemCount);
|
|
||||||
|
|
||||||
Console.WriteLine("TEST");
|
|
||||||
Console.WriteLine(string.Join(',', systems.Select(s => s.Id)));
|
|
||||||
|
|
||||||
var scenario = dataLoader.NormalizeScenarioToAvailableSystems(
|
|
||||||
catalog.Scenario,
|
|
||||||
systems.Select(system => system.Id).ToList());
|
|
||||||
|
|
||||||
Console.WriteLine(string.Join(',', systems.Select(s => s.Id)));
|
|
||||||
|
|
||||||
var systemRuntimes = systems
|
|
||||||
.Select(definition => new SystemRuntime
|
|
||||||
{
|
|
||||||
Definition = definition,
|
|
||||||
Position = ToVector(definition.Position),
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
var systemsById = systemRuntimes.ToDictionary(system => system.Definition.Id, StringComparer.Ordinal);
|
|
||||||
var spatialLayout = spatialBuilder.BuildLayout(systemRuntimes, catalog.Balance);
|
|
||||||
|
|
||||||
var stations = CreateStations(
|
|
||||||
scenario,
|
|
||||||
systemsById,
|
|
||||||
spatialLayout.SystemGraphs,
|
|
||||||
spatialLayout.Celestials,
|
|
||||||
catalog.ModuleDefinitions,
|
|
||||||
catalog.ItemDefinitions);
|
|
||||||
|
|
||||||
seedingService.InitializeStationStockpiles(stations);
|
|
||||||
var refinery = seedingService.SelectRefineryStation(stations, scenario);
|
|
||||||
var patrolRoutes = BuildPatrolRoutes(scenario, systemsById);
|
|
||||||
var ships = CreateShips(scenario, systemsById, spatialLayout.Celestials, catalog.Balance, catalog.ShipDefinitions, patrolRoutes, stations, refinery);
|
|
||||||
|
|
||||||
if (worldGeneration.AiControllerFactionCount < int.MaxValue)
|
|
||||||
{
|
{
|
||||||
var aiFactionIds = stations
|
var catalog = dataLoader.LoadCatalog();
|
||||||
.Select(s => s.FactionId)
|
var systems = generationService.ExpandSystems(
|
||||||
.Concat(ships.Select(s => s.FactionId))
|
generationService.InjectSpecialSystems(catalog.AuthoredSystems),
|
||||||
.Where(id => !string.IsNullOrWhiteSpace(id) && !string.Equals(id, DefaultFactionId, StringComparison.Ordinal))
|
worldGeneration.TargetSystemCount);
|
||||||
.Distinct(StringComparer.Ordinal)
|
|
||||||
.OrderBy(id => id, StringComparer.Ordinal)
|
|
||||||
.Take(worldGeneration.AiControllerFactionCount)
|
|
||||||
.ToHashSet(StringComparer.Ordinal);
|
|
||||||
aiFactionIds.Add(DefaultFactionId);
|
|
||||||
stations = stations.Where(s => aiFactionIds.Contains(s.FactionId)).ToList();
|
|
||||||
ships = ships.Where(s => aiFactionIds.Contains(s.FactionId)).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
var factions = seedingService.CreateFactions(stations, ships);
|
Console.WriteLine("TEST");
|
||||||
seedingService.BootstrapFactionEconomy(factions, stations);
|
Console.WriteLine(string.Join(',', systems.Select(s => s.Id)));
|
||||||
var policies = seedingService.CreatePolicies(factions);
|
|
||||||
var commanders = seedingService.CreateCommanders(factions, stations, ships);
|
|
||||||
var nowUtc = DateTimeOffset.UtcNow;
|
|
||||||
var playerFaction = worldGeneration.GeneratePlayerFaction
|
|
||||||
? seedingService.CreatePlayerFaction(factions, stations, ships, commanders, policies, nowUtc)
|
|
||||||
: null;
|
|
||||||
var claims = seedingService.CreateClaims(stations, spatialLayout.Celestials, nowUtc);
|
|
||||||
var bootstrapWorld = new SimulationWorld
|
|
||||||
{
|
|
||||||
Label = "Split Viewer / Bootstrap World",
|
|
||||||
Seed = WorldSeed,
|
|
||||||
Balance = catalog.Balance,
|
|
||||||
Systems = systemRuntimes,
|
|
||||||
Celestials = spatialLayout.Celestials,
|
|
||||||
Nodes = spatialLayout.Nodes,
|
|
||||||
Wrecks = [],
|
|
||||||
Stations = stations,
|
|
||||||
Ships = ships,
|
|
||||||
Factions = factions,
|
|
||||||
PlayerFaction = playerFaction,
|
|
||||||
Commanders = commanders,
|
|
||||||
Claims = claims,
|
|
||||||
ConstructionSites = [],
|
|
||||||
MarketOrders = [],
|
|
||||||
Policies = policies,
|
|
||||||
ShipDefinitions = new Dictionary<string, ShipDefinition>(catalog.ShipDefinitions, StringComparer.Ordinal),
|
|
||||||
ItemDefinitions = new Dictionary<string, ItemDefinition>(catalog.ItemDefinitions, StringComparer.Ordinal),
|
|
||||||
ModuleDefinitions = new Dictionary<string, ModuleDefinition>(catalog.ModuleDefinitions, StringComparer.Ordinal),
|
|
||||||
ModuleRecipes = new Dictionary<string, ModuleRecipeDefinition>(catalog.ModuleRecipes, StringComparer.Ordinal),
|
|
||||||
Recipes = new Dictionary<string, RecipeDefinition>(catalog.Recipes, StringComparer.Ordinal),
|
|
||||||
ProductionGraph = catalog.ProductionGraph,
|
|
||||||
OrbitalTimeSeconds = WorldSeed * 97d,
|
|
||||||
GeneratedAtUtc = nowUtc,
|
|
||||||
};
|
|
||||||
var (constructionSites, marketOrders) = seedingService.CreateConstructionSites(bootstrapWorld);
|
|
||||||
|
|
||||||
var world = new SimulationWorld
|
var scenario = dataLoader.NormalizeScenarioToAvailableSystems(
|
||||||
{
|
catalog.Scenario,
|
||||||
Label = "Split Viewer / Simulation World",
|
systems.Select(system => system.Id).ToList());
|
||||||
Seed = WorldSeed,
|
|
||||||
Balance = catalog.Balance,
|
|
||||||
Systems = systemRuntimes,
|
|
||||||
Celestials = spatialLayout.Celestials,
|
|
||||||
Nodes = spatialLayout.Nodes,
|
|
||||||
Wrecks = [],
|
|
||||||
Stations = stations,
|
|
||||||
Ships = ships,
|
|
||||||
Factions = factions,
|
|
||||||
PlayerFaction = playerFaction,
|
|
||||||
Geopolitics = null,
|
|
||||||
Commanders = commanders,
|
|
||||||
Claims = claims,
|
|
||||||
ConstructionSites = constructionSites,
|
|
||||||
MarketOrders = marketOrders,
|
|
||||||
Policies = policies,
|
|
||||||
ShipDefinitions = new Dictionary<string, ShipDefinition>(catalog.ShipDefinitions, StringComparer.Ordinal),
|
|
||||||
ItemDefinitions = new Dictionary<string, ItemDefinition>(catalog.ItemDefinitions, StringComparer.Ordinal),
|
|
||||||
ModuleDefinitions = new Dictionary<string, ModuleDefinition>(catalog.ModuleDefinitions, StringComparer.Ordinal),
|
|
||||||
ModuleRecipes = new Dictionary<string, ModuleRecipeDefinition>(catalog.ModuleRecipes, StringComparer.Ordinal),
|
|
||||||
Recipes = new Dictionary<string, RecipeDefinition>(catalog.Recipes, StringComparer.Ordinal),
|
|
||||||
ProductionGraph = catalog.ProductionGraph,
|
|
||||||
OrbitalTimeSeconds = WorldSeed * 97d,
|
|
||||||
GeneratedAtUtc = DateTimeOffset.UtcNow,
|
|
||||||
};
|
|
||||||
|
|
||||||
var geopolitics = new GeopoliticalSimulationService();
|
Console.WriteLine(string.Join(',', systems.Select(s => s.Id)));
|
||||||
geopolitics.Update(world, 0f, []);
|
|
||||||
return world;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<StationRuntime> CreateStations(
|
var systemRuntimes = systems
|
||||||
ScenarioDefinition scenario,
|
.Select(definition => new SystemRuntime
|
||||||
IReadOnlyDictionary<string, SystemRuntime> systemsById,
|
|
||||||
IReadOnlyDictionary<string, SystemSpatialGraph> systemGraphs,
|
|
||||||
IReadOnlyCollection<CelestialRuntime> celestials,
|
|
||||||
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
|
|
||||||
IReadOnlyDictionary<string, ItemDefinition> itemDefinitions)
|
|
||||||
{
|
|
||||||
var stations = new List<StationRuntime>();
|
|
||||||
var stationIdCounter = 0;
|
|
||||||
|
|
||||||
foreach (var plan in scenario.InitialStations)
|
|
||||||
{
|
|
||||||
if (!systemsById.TryGetValue(plan.SystemId, out var system))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var placement = SpatialBuilder.ResolveStationPlacement(plan, system, systemGraphs[system.Definition.Id], celestials);
|
|
||||||
var station = new StationRuntime
|
|
||||||
{
|
|
||||||
Id = $"station-{++stationIdCounter}",
|
|
||||||
SystemId = system.Definition.Id,
|
|
||||||
Label = plan.Label,
|
|
||||||
Color = plan.Color,
|
|
||||||
Objective = StationSimulationService.NormalizeStationObjective(plan.Objective),
|
|
||||||
Position = placement.Position,
|
|
||||||
FactionId = plan.FactionId ?? DefaultFactionId,
|
|
||||||
CelestialId = placement.AnchorCelestial.Id,
|
|
||||||
Health = 600f,
|
|
||||||
MaxHealth = 600f,
|
|
||||||
};
|
|
||||||
|
|
||||||
stations.Add(station);
|
|
||||||
placement.AnchorCelestial.OccupyingStructureId = station.Id;
|
|
||||||
|
|
||||||
var startingModules = BuildStartingModules(plan, moduleDefinitions, itemDefinitions);
|
|
||||||
|
|
||||||
foreach (var moduleId in startingModules)
|
|
||||||
{
|
|
||||||
AddStationModule(station, moduleDefinitions, moduleId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stations;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IReadOnlyList<string> BuildStartingModules(
|
|
||||||
InitialStationDefinition plan,
|
|
||||||
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
|
|
||||||
IReadOnlyDictionary<string, ItemDefinition> itemDefinitions)
|
|
||||||
{
|
|
||||||
var startingModules = new List<string>(plan.StartingModules.Count > 0
|
|
||||||
? plan.StartingModules
|
|
||||||
: ["module_arg_dock_m_01_lowtech", "module_gen_prod_energycells_01", "module_arg_stor_container_m_01"]);
|
|
||||||
|
|
||||||
EnsureStartingModule(startingModules, "module_arg_dock_m_01_lowtech");
|
|
||||||
|
|
||||||
var objectiveModuleId = GetObjectiveStartingModuleId(plan.Objective);
|
|
||||||
if (!string.IsNullOrWhiteSpace(objectiveModuleId))
|
|
||||||
{
|
|
||||||
EnsureStartingModule(startingModules, objectiveModuleId);
|
|
||||||
|
|
||||||
if (!string.Equals(objectiveModuleId, "module_gen_prod_energycells_01", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
EnsureStartingModule(startingModules, "module_gen_prod_energycells_01");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var storageModuleId in GetRequiredStartingStorageModules(objectiveModuleId, moduleDefinitions, itemDefinitions))
|
|
||||||
{
|
|
||||||
EnsureStartingModule(startingModules, storageModuleId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return startingModules;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? GetObjectiveStartingModuleId(string? objective) =>
|
|
||||||
StationSimulationService.NormalizeStationObjective(objective) switch
|
|
||||||
{
|
|
||||||
"power" => "module_gen_prod_energycells_01",
|
|
||||||
"refinery" => "module_gen_ref_ore_01",
|
|
||||||
"graphene" => "module_gen_prod_graphene_01",
|
|
||||||
"siliconwafers" => "module_gen_prod_siliconwafers_01",
|
|
||||||
"hullparts" => "module_gen_prod_hullparts_01",
|
|
||||||
"claytronics" => "module_gen_prod_claytronics_01",
|
|
||||||
"quantumtubes" => "module_gen_prod_quantumtubes_01",
|
|
||||||
"antimattercells" => "module_gen_prod_antimattercells_01",
|
|
||||||
"superfluidcoolant" => "module_gen_prod_superfluidcoolant_01",
|
|
||||||
"water" => "module_gen_prod_water_01",
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static IEnumerable<string> GetRequiredStartingStorageModules(
|
|
||||||
string moduleId,
|
|
||||||
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
|
|
||||||
IReadOnlyDictionary<string, ItemDefinition> itemDefinitions)
|
|
||||||
{
|
|
||||||
if (!moduleDefinitions.TryGetValue(moduleId, out var moduleDefinition))
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var wareId in moduleDefinition.Production
|
|
||||||
.SelectMany(production => production.Wares.Select(ware => ware.ItemId))
|
|
||||||
.Concat(moduleDefinition.Products)
|
|
||||||
.Distinct(StringComparer.Ordinal))
|
|
||||||
{
|
|
||||||
if (!itemDefinitions.TryGetValue(wareId, out var itemDefinition))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var storageModuleId = itemDefinition.CargoKind switch
|
|
||||||
{
|
|
||||||
"solid" => "module_arg_stor_solid_m_01",
|
|
||||||
"liquid" => "module_arg_stor_liquid_m_01",
|
|
||||||
_ => "module_arg_stor_container_m_01",
|
|
||||||
};
|
|
||||||
|
|
||||||
yield return storageModuleId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void EnsureStartingModule(List<string> modules, string moduleId)
|
|
||||||
{
|
|
||||||
if (!modules.Contains(moduleId, StringComparer.Ordinal))
|
|
||||||
{
|
|
||||||
modules.Add(moduleId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Dictionary<string, List<Vector3>> BuildPatrolRoutes(
|
|
||||||
ScenarioDefinition scenario,
|
|
||||||
IReadOnlyDictionary<string, SystemRuntime> systemsById)
|
|
||||||
{
|
|
||||||
return scenario.PatrolRoutes
|
|
||||||
.GroupBy(route => route.SystemId, StringComparer.Ordinal)
|
|
||||||
.ToDictionary(
|
|
||||||
group => group.Key,
|
|
||||||
group => group
|
|
||||||
.SelectMany(route => route.Points)
|
|
||||||
.Select(point => NormalizeScenarioPoint(systemsById[group.Key], point))
|
|
||||||
.ToList(),
|
|
||||||
StringComparer.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<ShipRuntime> CreateShips(
|
|
||||||
ScenarioDefinition scenario,
|
|
||||||
IReadOnlyDictionary<string, SystemRuntime> systemsById,
|
|
||||||
IReadOnlyCollection<CelestialRuntime> celestials,
|
|
||||||
BalanceDefinition balance,
|
|
||||||
IReadOnlyDictionary<string, ShipDefinition> shipDefinitions,
|
|
||||||
IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
|
|
||||||
IReadOnlyCollection<StationRuntime> stations,
|
|
||||||
StationRuntime? refinery)
|
|
||||||
{
|
|
||||||
var ships = new List<ShipRuntime>();
|
|
||||||
var shipIdCounter = 0;
|
|
||||||
|
|
||||||
foreach (var formation in scenario.ShipFormations)
|
|
||||||
{
|
|
||||||
if (!shipDefinitions.TryGetValue(formation.ShipId, out var definition))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var index = 0; index < formation.Count; index += 1)
|
|
||||||
{
|
|
||||||
var offset = new Vector3((index % 3) * 18f, balance.YPlane, (index / 3) * 18f);
|
|
||||||
var position = Add(NormalizeScenarioPoint(systemsById[formation.SystemId], formation.Center), offset);
|
|
||||||
|
|
||||||
ships.Add(new ShipRuntime
|
|
||||||
{
|
|
||||||
Id = $"ship-{++shipIdCounter}",
|
|
||||||
SystemId = formation.SystemId,
|
|
||||||
Definition = definition,
|
|
||||||
FactionId = formation.FactionId ?? DefaultFactionId,
|
|
||||||
Position = position,
|
|
||||||
TargetPosition = position,
|
|
||||||
SpatialState = SpatialBuilder.CreateInitialShipSpatialState(formation.SystemId, position, celestials),
|
|
||||||
DefaultBehavior = WorldSeedingService.CreateBehavior(
|
|
||||||
definition,
|
|
||||||
formation.SystemId,
|
|
||||||
formation.FactionId ?? DefaultFactionId,
|
|
||||||
scenario,
|
|
||||||
patrolRoutes,
|
|
||||||
stations,
|
|
||||||
refinery),
|
|
||||||
Skills = WorldSeedingService.CreateSkills(definition),
|
|
||||||
Health = definition.MaxHealth,
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach (var (itemId, amount) in formation.StartingInventory)
|
|
||||||
{
|
|
||||||
if (amount > 0f)
|
|
||||||
{
|
{
|
||||||
ships[^1].Inventory[itemId] = amount;
|
Definition = definition,
|
||||||
}
|
Position = ToVector(definition.Position),
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
var systemsById = systemRuntimes.ToDictionary(system => system.Definition.Id, StringComparer.Ordinal);
|
||||||
|
var spatialLayout = spatialBuilder.BuildLayout(systemRuntimes, catalog.Balance);
|
||||||
|
|
||||||
|
var stations = CreateStations(
|
||||||
|
scenario,
|
||||||
|
systemsById,
|
||||||
|
spatialLayout.SystemGraphs,
|
||||||
|
spatialLayout.Celestials,
|
||||||
|
catalog.ModuleDefinitions,
|
||||||
|
catalog.ItemDefinitions);
|
||||||
|
|
||||||
|
seedingService.InitializeStationStockpiles(stations);
|
||||||
|
var refinery = seedingService.SelectRefineryStation(stations, scenario);
|
||||||
|
var patrolRoutes = BuildPatrolRoutes(scenario, systemsById);
|
||||||
|
var ships = CreateShips(scenario, systemsById, spatialLayout.Celestials, catalog.Balance, catalog.ShipDefinitions, patrolRoutes, stations, refinery);
|
||||||
|
|
||||||
|
if (worldGeneration.AiControllerFactionCount < int.MaxValue)
|
||||||
|
{
|
||||||
|
var aiFactionIds = stations
|
||||||
|
.Select(s => s.FactionId)
|
||||||
|
.Concat(ships.Select(s => s.FactionId))
|
||||||
|
.Where(id => !string.IsNullOrWhiteSpace(id) && !string.Equals(id, DefaultFactionId, StringComparison.Ordinal))
|
||||||
|
.Distinct(StringComparer.Ordinal)
|
||||||
|
.OrderBy(id => id, StringComparer.Ordinal)
|
||||||
|
.Take(worldGeneration.AiControllerFactionCount)
|
||||||
|
.ToHashSet(StringComparer.Ordinal);
|
||||||
|
aiFactionIds.Add(DefaultFactionId);
|
||||||
|
stations = stations.Where(s => aiFactionIds.Contains(s.FactionId)).ToList();
|
||||||
|
ships = ships.Where(s => aiFactionIds.Contains(s.FactionId)).ToList();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
var factions = seedingService.CreateFactions(stations, ships);
|
||||||
|
seedingService.BootstrapFactionEconomy(factions, stations);
|
||||||
|
var policies = seedingService.CreatePolicies(factions);
|
||||||
|
var commanders = seedingService.CreateCommanders(factions, stations, ships);
|
||||||
|
var nowUtc = DateTimeOffset.UtcNow;
|
||||||
|
var playerFaction = worldGeneration.GeneratePlayerFaction
|
||||||
|
? seedingService.CreatePlayerFaction(factions, stations, ships, commanders, policies, nowUtc)
|
||||||
|
: null;
|
||||||
|
var claims = seedingService.CreateClaims(stations, spatialLayout.Celestials, nowUtc);
|
||||||
|
var bootstrapWorld = new SimulationWorld
|
||||||
|
{
|
||||||
|
Label = "Split Viewer / Bootstrap World",
|
||||||
|
Seed = WorldSeed,
|
||||||
|
Balance = catalog.Balance,
|
||||||
|
Systems = systemRuntimes,
|
||||||
|
Celestials = spatialLayout.Celestials,
|
||||||
|
Nodes = spatialLayout.Nodes,
|
||||||
|
Wrecks = [],
|
||||||
|
Stations = stations,
|
||||||
|
Ships = ships,
|
||||||
|
Factions = factions,
|
||||||
|
PlayerFaction = playerFaction,
|
||||||
|
Commanders = commanders,
|
||||||
|
Claims = claims,
|
||||||
|
ConstructionSites = [],
|
||||||
|
MarketOrders = [],
|
||||||
|
Policies = policies,
|
||||||
|
ShipDefinitions = new Dictionary<string, ShipDefinition>(catalog.ShipDefinitions, StringComparer.Ordinal),
|
||||||
|
ItemDefinitions = new Dictionary<string, ItemDefinition>(catalog.ItemDefinitions, StringComparer.Ordinal),
|
||||||
|
ModuleDefinitions = new Dictionary<string, ModuleDefinition>(catalog.ModuleDefinitions, StringComparer.Ordinal),
|
||||||
|
ModuleRecipes = new Dictionary<string, ModuleRecipeDefinition>(catalog.ModuleRecipes, StringComparer.Ordinal),
|
||||||
|
Recipes = new Dictionary<string, RecipeDefinition>(catalog.Recipes, StringComparer.Ordinal),
|
||||||
|
ProductionGraph = catalog.ProductionGraph,
|
||||||
|
OrbitalTimeSeconds = WorldSeed * 97d,
|
||||||
|
GeneratedAtUtc = nowUtc,
|
||||||
|
};
|
||||||
|
var (constructionSites, marketOrders) = seedingService.CreateConstructionSites(bootstrapWorld);
|
||||||
|
|
||||||
|
var world = new SimulationWorld
|
||||||
|
{
|
||||||
|
Label = "Split Viewer / Simulation World",
|
||||||
|
Seed = WorldSeed,
|
||||||
|
Balance = catalog.Balance,
|
||||||
|
Systems = systemRuntimes,
|
||||||
|
Celestials = spatialLayout.Celestials,
|
||||||
|
Nodes = spatialLayout.Nodes,
|
||||||
|
Wrecks = [],
|
||||||
|
Stations = stations,
|
||||||
|
Ships = ships,
|
||||||
|
Factions = factions,
|
||||||
|
PlayerFaction = playerFaction,
|
||||||
|
Geopolitics = null,
|
||||||
|
Commanders = commanders,
|
||||||
|
Claims = claims,
|
||||||
|
ConstructionSites = constructionSites,
|
||||||
|
MarketOrders = marketOrders,
|
||||||
|
Policies = policies,
|
||||||
|
ShipDefinitions = new Dictionary<string, ShipDefinition>(catalog.ShipDefinitions, StringComparer.Ordinal),
|
||||||
|
ItemDefinitions = new Dictionary<string, ItemDefinition>(catalog.ItemDefinitions, StringComparer.Ordinal),
|
||||||
|
ModuleDefinitions = new Dictionary<string, ModuleDefinition>(catalog.ModuleDefinitions, StringComparer.Ordinal),
|
||||||
|
ModuleRecipes = new Dictionary<string, ModuleRecipeDefinition>(catalog.ModuleRecipes, StringComparer.Ordinal),
|
||||||
|
Recipes = new Dictionary<string, RecipeDefinition>(catalog.Recipes, StringComparer.Ordinal),
|
||||||
|
ProductionGraph = catalog.ProductionGraph,
|
||||||
|
OrbitalTimeSeconds = WorldSeed * 97d,
|
||||||
|
GeneratedAtUtc = DateTimeOffset.UtcNow,
|
||||||
|
};
|
||||||
|
|
||||||
|
var geopolitics = new GeopoliticalSimulationService();
|
||||||
|
geopolitics.Update(world, 0f, []);
|
||||||
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ships;
|
private static List<StationRuntime> CreateStations(
|
||||||
}
|
ScenarioDefinition scenario,
|
||||||
|
IReadOnlyDictionary<string, SystemRuntime> systemsById,
|
||||||
|
IReadOnlyDictionary<string, SystemSpatialGraph> systemGraphs,
|
||||||
|
IReadOnlyCollection<CelestialRuntime> celestials,
|
||||||
|
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
|
||||||
|
IReadOnlyDictionary<string, ItemDefinition> itemDefinitions)
|
||||||
|
{
|
||||||
|
var stations = new List<StationRuntime>();
|
||||||
|
var stationIdCounter = 0;
|
||||||
|
|
||||||
|
foreach (var plan in scenario.InitialStations)
|
||||||
|
{
|
||||||
|
if (!systemsById.TryGetValue(plan.SystemId, out var system))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var placement = SpatialBuilder.ResolveStationPlacement(plan, system, systemGraphs[system.Definition.Id], celestials);
|
||||||
|
var station = new StationRuntime
|
||||||
|
{
|
||||||
|
Id = $"station-{++stationIdCounter}",
|
||||||
|
SystemId = system.Definition.Id,
|
||||||
|
Label = plan.Label,
|
||||||
|
Color = plan.Color,
|
||||||
|
Objective = StationSimulationService.NormalizeStationObjective(plan.Objective),
|
||||||
|
Position = placement.Position,
|
||||||
|
FactionId = plan.FactionId ?? DefaultFactionId,
|
||||||
|
CelestialId = placement.AnchorCelestial.Id,
|
||||||
|
Health = 600f,
|
||||||
|
MaxHealth = 600f,
|
||||||
|
};
|
||||||
|
|
||||||
|
stations.Add(station);
|
||||||
|
placement.AnchorCelestial.OccupyingStructureId = station.Id;
|
||||||
|
|
||||||
|
var startingModules = BuildStartingModules(plan, moduleDefinitions, itemDefinitions);
|
||||||
|
|
||||||
|
foreach (var moduleId in startingModules)
|
||||||
|
{
|
||||||
|
AddStationModule(station, moduleDefinitions, moduleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> BuildStartingModules(
|
||||||
|
InitialStationDefinition plan,
|
||||||
|
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
|
||||||
|
IReadOnlyDictionary<string, ItemDefinition> itemDefinitions)
|
||||||
|
{
|
||||||
|
var startingModules = new List<string>(plan.StartingModules.Count > 0
|
||||||
|
? plan.StartingModules
|
||||||
|
: ["module_arg_dock_m_01_lowtech", "module_gen_prod_energycells_01", "module_arg_stor_container_m_01"]);
|
||||||
|
|
||||||
|
EnsureStartingModule(startingModules, "module_arg_dock_m_01_lowtech");
|
||||||
|
|
||||||
|
var objectiveModuleId = GetObjectiveStartingModuleId(plan.Objective);
|
||||||
|
if (!string.IsNullOrWhiteSpace(objectiveModuleId))
|
||||||
|
{
|
||||||
|
EnsureStartingModule(startingModules, objectiveModuleId);
|
||||||
|
|
||||||
|
if (!string.Equals(objectiveModuleId, "module_gen_prod_energycells_01", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
EnsureStartingModule(startingModules, "module_gen_prod_energycells_01");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var storageModuleId in GetRequiredStartingStorageModules(objectiveModuleId, moduleDefinitions, itemDefinitions))
|
||||||
|
{
|
||||||
|
EnsureStartingModule(startingModules, storageModuleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return startingModules;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetObjectiveStartingModuleId(string? objective) =>
|
||||||
|
StationSimulationService.NormalizeStationObjective(objective) switch
|
||||||
|
{
|
||||||
|
"power" => "module_gen_prod_energycells_01",
|
||||||
|
"refinery" => "module_gen_ref_ore_01",
|
||||||
|
"graphene" => "module_gen_prod_graphene_01",
|
||||||
|
"siliconwafers" => "module_gen_prod_siliconwafers_01",
|
||||||
|
"hullparts" => "module_gen_prod_hullparts_01",
|
||||||
|
"claytronics" => "module_gen_prod_claytronics_01",
|
||||||
|
"quantumtubes" => "module_gen_prod_quantumtubes_01",
|
||||||
|
"antimattercells" => "module_gen_prod_antimattercells_01",
|
||||||
|
"superfluidcoolant" => "module_gen_prod_superfluidcoolant_01",
|
||||||
|
"water" => "module_gen_prod_water_01",
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetRequiredStartingStorageModules(
|
||||||
|
string moduleId,
|
||||||
|
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
|
||||||
|
IReadOnlyDictionary<string, ItemDefinition> itemDefinitions)
|
||||||
|
{
|
||||||
|
if (!moduleDefinitions.TryGetValue(moduleId, out var moduleDefinition))
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var wareId in moduleDefinition.Production
|
||||||
|
.SelectMany(production => production.Wares.Select(ware => ware.ItemId))
|
||||||
|
.Concat(moduleDefinition.Products)
|
||||||
|
.Distinct(StringComparer.Ordinal))
|
||||||
|
{
|
||||||
|
if (!itemDefinitions.TryGetValue(wareId, out var itemDefinition))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var storageModuleId = itemDefinition.CargoKind switch
|
||||||
|
{
|
||||||
|
"solid" => "module_arg_stor_solid_m_01",
|
||||||
|
"liquid" => "module_arg_stor_liquid_m_01",
|
||||||
|
_ => "module_arg_stor_container_m_01",
|
||||||
|
};
|
||||||
|
|
||||||
|
yield return storageModuleId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureStartingModule(List<string> modules, string moduleId)
|
||||||
|
{
|
||||||
|
if (!modules.Contains(moduleId, StringComparer.Ordinal))
|
||||||
|
{
|
||||||
|
modules.Add(moduleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, List<Vector3>> BuildPatrolRoutes(
|
||||||
|
ScenarioDefinition scenario,
|
||||||
|
IReadOnlyDictionary<string, SystemRuntime> systemsById)
|
||||||
|
{
|
||||||
|
return scenario.PatrolRoutes
|
||||||
|
.GroupBy(route => route.SystemId, StringComparer.Ordinal)
|
||||||
|
.ToDictionary(
|
||||||
|
group => group.Key,
|
||||||
|
group => group
|
||||||
|
.SelectMany(route => route.Points)
|
||||||
|
.Select(point => NormalizeScenarioPoint(systemsById[group.Key], point))
|
||||||
|
.ToList(),
|
||||||
|
StringComparer.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ShipRuntime> CreateShips(
|
||||||
|
ScenarioDefinition scenario,
|
||||||
|
IReadOnlyDictionary<string, SystemRuntime> systemsById,
|
||||||
|
IReadOnlyCollection<CelestialRuntime> celestials,
|
||||||
|
BalanceDefinition balance,
|
||||||
|
IReadOnlyDictionary<string, ShipDefinition> shipDefinitions,
|
||||||
|
IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
|
||||||
|
IReadOnlyCollection<StationRuntime> stations,
|
||||||
|
StationRuntime? refinery)
|
||||||
|
{
|
||||||
|
var ships = new List<ShipRuntime>();
|
||||||
|
var shipIdCounter = 0;
|
||||||
|
|
||||||
|
foreach (var formation in scenario.ShipFormations)
|
||||||
|
{
|
||||||
|
if (!shipDefinitions.TryGetValue(formation.ShipId, out var definition))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var index = 0; index < formation.Count; index += 1)
|
||||||
|
{
|
||||||
|
var offset = new Vector3((index % 3) * 18f, balance.YPlane, (index / 3) * 18f);
|
||||||
|
var position = Add(NormalizeScenarioPoint(systemsById[formation.SystemId], formation.Center), offset);
|
||||||
|
|
||||||
|
ships.Add(new ShipRuntime
|
||||||
|
{
|
||||||
|
Id = $"ship-{++shipIdCounter}",
|
||||||
|
SystemId = formation.SystemId,
|
||||||
|
Definition = definition,
|
||||||
|
FactionId = formation.FactionId ?? DefaultFactionId,
|
||||||
|
Position = position,
|
||||||
|
TargetPosition = position,
|
||||||
|
SpatialState = SpatialBuilder.CreateInitialShipSpatialState(formation.SystemId, position, celestials),
|
||||||
|
DefaultBehavior = WorldSeedingService.CreateBehavior(
|
||||||
|
definition,
|
||||||
|
formation.SystemId,
|
||||||
|
formation.FactionId ?? DefaultFactionId,
|
||||||
|
scenario,
|
||||||
|
patrolRoutes,
|
||||||
|
stations,
|
||||||
|
refinery),
|
||||||
|
Skills = WorldSeedingService.CreateSkills(definition),
|
||||||
|
Health = definition.MaxHealth,
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var (itemId, amount) in formation.StartingInventory)
|
||||||
|
{
|
||||||
|
if (amount > 0f)
|
||||||
|
{
|
||||||
|
ships[^1].Inventory[itemId] = amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ships;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,5 +2,5 @@ namespace SpaceGame.Api.Universe.Simulation;
|
|||||||
|
|
||||||
public sealed class OrbitalSimulationOptions
|
public sealed class OrbitalSimulationOptions
|
||||||
{
|
{
|
||||||
public double SimulatedSecondsPerRealSecond { get; init; } = 0d;
|
public double SimulatedSecondsPerRealSecond { get; init; } = 0d;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ namespace SpaceGame.Api.Universe.Simulation;
|
|||||||
|
|
||||||
public sealed class SimulationHostedService(WorldService worldService) : BackgroundService
|
public sealed class SimulationHostedService(WorldService worldService) : BackgroundService
|
||||||
{
|
{
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
|
||||||
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(200));
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken))
|
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(200));
|
||||||
{
|
try
|
||||||
worldService.Tick(0.2f);
|
{
|
||||||
}
|
while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken))
|
||||||
|
{
|
||||||
|
worldService.Tick(0.2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,41 +4,41 @@ namespace SpaceGame.Api.Universe.Simulation;
|
|||||||
|
|
||||||
public sealed class TelemetryService : IDisposable
|
public sealed class TelemetryService : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Process _process = Process.GetCurrentProcess();
|
private readonly Process _process = Process.GetCurrentProcess();
|
||||||
private readonly Timer _timer;
|
private readonly Timer _timer;
|
||||||
private double _cpuPercent;
|
private double _cpuPercent;
|
||||||
private DateTime _lastSampleTime;
|
private DateTime _lastSampleTime;
|
||||||
private TimeSpan _lastCpuTime;
|
private TimeSpan _lastCpuTime;
|
||||||
|
|
||||||
public TelemetryService()
|
public TelemetryService()
|
||||||
{
|
{
|
||||||
_process.Refresh();
|
_process.Refresh();
|
||||||
_lastSampleTime = DateTime.UtcNow;
|
_lastSampleTime = DateTime.UtcNow;
|
||||||
_lastCpuTime = _process.TotalProcessorTime;
|
_lastCpuTime = _process.TotalProcessorTime;
|
||||||
_timer = new Timer(Sample, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
|
_timer = new Timer(Sample, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Sample(object? _)
|
private void Sample(object? _)
|
||||||
{
|
{
|
||||||
_process.Refresh();
|
_process.Refresh();
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var cpu = _process.TotalProcessorTime;
|
var cpu = _process.TotalProcessorTime;
|
||||||
var elapsed = (now - _lastSampleTime).TotalSeconds;
|
var elapsed = (now - _lastSampleTime).TotalSeconds;
|
||||||
var cpuUsed = (cpu - _lastCpuTime).TotalSeconds;
|
var cpuUsed = (cpu - _lastCpuTime).TotalSeconds;
|
||||||
Volatile.Write(ref _cpuPercent, elapsed > 0 ? cpuUsed / elapsed / Environment.ProcessorCount * 100.0 : 0);
|
Volatile.Write(ref _cpuPercent, elapsed > 0 ? cpuUsed / elapsed / Environment.ProcessorCount * 100.0 : 0);
|
||||||
_lastSampleTime = now;
|
_lastSampleTime = now;
|
||||||
_lastCpuTime = cpu;
|
_lastCpuTime = cpu;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double CpuPercent => Volatile.Read(ref _cpuPercent);
|
public double CpuPercent => Volatile.Read(ref _cpuPercent);
|
||||||
public long WorkingSetBytes => _process.WorkingSet64;
|
public long WorkingSetBytes => _process.WorkingSet64;
|
||||||
public long GcMemoryBytes => GC.GetTotalMemory(false);
|
public long GcMemoryBytes => GC.GetTotalMemory(false);
|
||||||
public int ThreadCount => _process.Threads.Count;
|
public int ThreadCount => _process.Threads.Count;
|
||||||
public TimeSpan Uptime => DateTime.UtcNow - _process.StartTime.ToUniversalTime();
|
public TimeSpan Uptime => DateTime.UtcNow - _process.StartTime.ToUniversalTime();
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_timer.Dispose();
|
_timer.Dispose();
|
||||||
_process.Dispose();
|
_process.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ namespace SpaceGame.Api.Universe.Simulation;
|
|||||||
|
|
||||||
public sealed class WorldGenerationOptions
|
public sealed class WorldGenerationOptions
|
||||||
{
|
{
|
||||||
public int TargetSystemCount { get; init; }
|
public int TargetSystemCount { get; init; }
|
||||||
public int AiControllerFactionCount { get; init; }
|
public int AiControllerFactionCount { get; init; }
|
||||||
public bool GeneratePlayerFaction { get; init; }
|
public bool GeneratePlayerFaction { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,507 +8,507 @@ public sealed class WorldService(
|
|||||||
IOptions<WorldGenerationOptions> worldGenerationOptions,
|
IOptions<WorldGenerationOptions> worldGenerationOptions,
|
||||||
IOptions<OrbitalSimulationOptions> orbitalSimulationOptions)
|
IOptions<OrbitalSimulationOptions> orbitalSimulationOptions)
|
||||||
{
|
{
|
||||||
private const int DeltaHistoryLimit = 256;
|
private const int DeltaHistoryLimit = 256;
|
||||||
|
|
||||||
private readonly Lock _sync = new();
|
private readonly Lock _sync = new();
|
||||||
private readonly OrbitalSimulationSnapshot _orbitalSimulation = new(orbitalSimulationOptions.Value.SimulatedSecondsPerRealSecond);
|
private readonly OrbitalSimulationSnapshot _orbitalSimulation = new(orbitalSimulationOptions.Value.SimulatedSecondsPerRealSecond);
|
||||||
private readonly ScenarioLoader _loader = new(environment.ContentRootPath, worldGenerationOptions.Value);
|
private readonly ScenarioLoader _loader = new(environment.ContentRootPath, worldGenerationOptions.Value);
|
||||||
private readonly SimulationEngine _engine = new(orbitalSimulationOptions.Value);
|
private readonly SimulationEngine _engine = new(orbitalSimulationOptions.Value);
|
||||||
private readonly PlayerFactionService _playerFaction = new();
|
private readonly PlayerFactionService _playerFaction = new();
|
||||||
private readonly Dictionary<Guid, SubscriptionState> _subscribers = [];
|
private readonly Dictionary<Guid, SubscriptionState> _subscribers = [];
|
||||||
private readonly Queue<WorldDelta> _history = [];
|
private readonly Queue<WorldDelta> _history = [];
|
||||||
private SimulationWorld _world = new ScenarioLoader(environment.ContentRootPath, worldGenerationOptions.Value).Load();
|
private SimulationWorld _world = new ScenarioLoader(environment.ContentRootPath, worldGenerationOptions.Value).Load();
|
||||||
private long _sequence;
|
private long _sequence;
|
||||||
private BalanceDefinition? _balanceOverride;
|
private BalanceDefinition? _balanceOverride;
|
||||||
|
|
||||||
public WorldSnapshot GetSnapshot()
|
public WorldSnapshot GetSnapshot()
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
{
|
||||||
return _engine.BuildSnapshot(_world, _sequence);
|
lock (_sync)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public (long Sequence, DateTimeOffset GeneratedAtUtc) GetStatus()
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
return (_sequence, _world.GeneratedAtUtc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public (int ConnectedClients, int DeltaHistoryCount) GetConnectionStats()
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
return (_subscribers.Count, _history.Count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BalanceDefinition GetBalance()
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
var b = _world.Balance;
|
|
||||||
return new BalanceDefinition
|
|
||||||
{
|
|
||||||
SimulationSpeedMultiplier = b.SimulationSpeedMultiplier,
|
|
||||||
YPlane = b.YPlane,
|
|
||||||
ArrivalThreshold = b.ArrivalThreshold,
|
|
||||||
MiningRate = b.MiningRate,
|
|
||||||
MiningCycleSeconds = b.MiningCycleSeconds,
|
|
||||||
TransferRate = b.TransferRate,
|
|
||||||
DockingDuration = b.DockingDuration,
|
|
||||||
UndockingDuration = b.UndockingDuration,
|
|
||||||
UndockDistance = b.UndockDistance,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BalanceDefinition UpdateBalance(BalanceDefinition balance)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_balanceOverride = SanitizeBalance(balance);
|
|
||||||
ApplyBalance(_world, _balanceOverride);
|
|
||||||
return GetBalance();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShipSnapshot? EnqueueShipOrder(string shipId, ShipOrderCommandRequest request)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
var ship = _playerFaction.EnqueueDirectShipOrder(_world, shipId, request);
|
|
||||||
if (ship is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetShipSnapshotUnsafe(ship.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShipSnapshot? RemoveShipOrder(string shipId, string orderId)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
var ship = _playerFaction.RemoveDirectShipOrder(_world, shipId, orderId);
|
|
||||||
if (ship is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetShipSnapshotUnsafe(ship.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShipSnapshot? UpdateShipDefaultBehavior(string shipId, ShipDefaultBehaviorCommandRequest request)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
var ship = _playerFaction.ConfigureDirectShipBehavior(_world, shipId, request);
|
|
||||||
if (ship is null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetShipSnapshotUnsafe(ship.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? GetPlayerFaction()
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.EnsureDomain(_world);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? CreatePlayerOrganization(PlayerOrganizationCommandRequest request)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.CreateOrganization(_world, request);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? DeletePlayerOrganization(string organizationId)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.DeleteOrganization(_world, organizationId);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? UpdatePlayerOrganizationMembership(string organizationId, PlayerOrganizationMembershipCommandRequest request)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.UpdateOrganizationMembership(_world, organizationId, request);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? UpsertPlayerDirective(string? directiveId, PlayerDirectiveCommandRequest request)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.UpsertDirective(_world, directiveId, request);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? DeletePlayerDirective(string directiveId)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.DeleteDirective(_world, directiveId);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? UpsertPlayerPolicy(string? policyId, PlayerPolicyCommandRequest request)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.UpsertPolicy(_world, policyId, request);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? UpsertPlayerAutomationPolicy(string? automationPolicyId, PlayerAutomationPolicyCommandRequest request)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.UpsertAutomationPolicy(_world, automationPolicyId, request);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? UpsertPlayerReinforcementPolicy(string? reinforcementPolicyId, PlayerReinforcementPolicyCommandRequest request)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.UpsertReinforcementPolicy(_world, reinforcementPolicyId, request);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? UpsertPlayerProductionProgram(string? productionProgramId, PlayerProductionProgramCommandRequest request)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.UpsertProductionProgram(_world, productionProgramId, request);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? UpsertPlayerAssignment(string assetId, PlayerAssetAssignmentCommandRequest request)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.UpsertAssignment(_world, assetId, request);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerFactionSnapshot? UpdatePlayerStrategicIntent(PlayerStrategicIntentCommandRequest request)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
_playerFaction.UpdateStrategicIntent(_world, request);
|
|
||||||
return GetPlayerFactionSnapshotUnsafe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChannelReader<WorldDelta> Subscribe(ObserverScope scope, long afterSequence, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var channel = Channel.CreateUnbounded<WorldDelta>(new UnboundedChannelOptions
|
|
||||||
{
|
|
||||||
SingleReader = true,
|
|
||||||
SingleWriter = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
Guid subscriberId;
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
subscriberId = Guid.NewGuid();
|
|
||||||
_subscribers.Add(subscriberId, new SubscriptionState(scope, channel));
|
|
||||||
|
|
||||||
foreach (var delta in _history.Where((candidate) => candidate.Sequence > afterSequence))
|
|
||||||
{
|
|
||||||
var filtered = FilterDeltaForScope(delta, scope);
|
|
||||||
if (HasMeaningfulDelta(filtered))
|
|
||||||
{
|
{
|
||||||
channel.Writer.TryWrite(filtered);
|
return _engine.BuildSnapshot(_world, _sequence);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cancellationToken.Register(() => Unsubscribe(subscriberId));
|
public (long Sequence, DateTimeOffset GeneratedAtUtc) GetStatus()
|
||||||
return channel.Reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Tick(float deltaSeconds)
|
|
||||||
{
|
|
||||||
WorldDelta? delta = null;
|
|
||||||
lock (_sync)
|
|
||||||
{
|
{
|
||||||
delta = _engine.Tick(_world, deltaSeconds, ++_sequence);
|
lock (_sync)
|
||||||
if (!HasMeaningfulDelta(delta))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_history.Enqueue(delta);
|
|
||||||
while (_history.Count > DeltaHistoryLimit)
|
|
||||||
{
|
|
||||||
_history.Dequeue();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var subscriber in _subscribers.Values.ToList())
|
|
||||||
{
|
|
||||||
var filtered = FilterDeltaForScope(delta, subscriber.Scope);
|
|
||||||
if (HasMeaningfulDelta(filtered))
|
|
||||||
{
|
{
|
||||||
subscriber.Channel.Writer.TryWrite(filtered);
|
return (_sequence, _world.GeneratedAtUtc);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public WorldSnapshot Reset()
|
public (int ConnectedClients, int DeltaHistoryCount) GetConnectionStats()
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
{
|
||||||
_world = _loader.Load();
|
lock (_sync)
|
||||||
if (_balanceOverride is not null)
|
{
|
||||||
{
|
return (_subscribers.Count, _history.Count);
|
||||||
ApplyBalance(_world, _balanceOverride);
|
}
|
||||||
}
|
|
||||||
_sequence += 1;
|
|
||||||
_history.Clear();
|
|
||||||
|
|
||||||
var resetDelta = new WorldDelta(
|
|
||||||
_sequence,
|
|
||||||
_world.TickIntervalMs,
|
|
||||||
_world.OrbitalTimeSeconds,
|
|
||||||
_orbitalSimulation,
|
|
||||||
DateTimeOffset.UtcNow,
|
|
||||||
true,
|
|
||||||
[new SimulationEventRecord("world", "world", "reset", "World reset requested", DateTimeOffset.UtcNow, "world", "universe", "world")],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
|
|
||||||
_history.Enqueue(resetDelta);
|
|
||||||
foreach (var subscriber in _subscribers.Values.ToList())
|
|
||||||
{
|
|
||||||
subscriber.Channel.Writer.TryWrite(FilterDeltaForScope(resetDelta, subscriber.Scope));
|
|
||||||
}
|
|
||||||
|
|
||||||
return _engine.BuildSnapshot(_world, _sequence);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void ApplyBalance(SimulationWorld world, BalanceDefinition balance) =>
|
public BalanceDefinition GetBalance()
|
||||||
world.Balance = new BalanceDefinition
|
|
||||||
{
|
{
|
||||||
SimulationSpeedMultiplier = balance.SimulationSpeedMultiplier,
|
lock (_sync)
|
||||||
YPlane = balance.YPlane,
|
{
|
||||||
ArrivalThreshold = balance.ArrivalThreshold,
|
var b = _world.Balance;
|
||||||
MiningRate = balance.MiningRate,
|
return new BalanceDefinition
|
||||||
MiningCycleSeconds = balance.MiningCycleSeconds,
|
{
|
||||||
TransferRate = balance.TransferRate,
|
SimulationSpeedMultiplier = b.SimulationSpeedMultiplier,
|
||||||
DockingDuration = balance.DockingDuration,
|
YPlane = b.YPlane,
|
||||||
UndockingDuration = balance.UndockingDuration,
|
ArrivalThreshold = b.ArrivalThreshold,
|
||||||
UndockDistance = balance.UndockDistance,
|
MiningRate = b.MiningRate,
|
||||||
};
|
MiningCycleSeconds = b.MiningCycleSeconds,
|
||||||
|
TransferRate = b.TransferRate,
|
||||||
private static BalanceDefinition SanitizeBalance(BalanceDefinition candidate)
|
DockingDuration = b.DockingDuration,
|
||||||
{
|
UndockingDuration = b.UndockingDuration,
|
||||||
static float finiteOr(float value, float fallback) =>
|
UndockDistance = b.UndockDistance,
|
||||||
float.IsFinite(value) ? value : fallback;
|
};
|
||||||
|
}
|
||||||
return new BalanceDefinition
|
|
||||||
{
|
|
||||||
SimulationSpeedMultiplier = MathF.Max(0.01f, finiteOr(candidate.SimulationSpeedMultiplier, 1f)),
|
|
||||||
YPlane = MathF.Max(0f, finiteOr(candidate.YPlane, 0f)),
|
|
||||||
ArrivalThreshold = MathF.Max(0.1f, finiteOr(candidate.ArrivalThreshold, 16f)),
|
|
||||||
MiningRate = MathF.Max(0f, finiteOr(candidate.MiningRate, 10f)),
|
|
||||||
MiningCycleSeconds = MathF.Max(0.1f, finiteOr(candidate.MiningCycleSeconds, 10f)),
|
|
||||||
TransferRate = MathF.Max(0f, finiteOr(candidate.TransferRate, 56f)),
|
|
||||||
DockingDuration = MathF.Max(0.1f, finiteOr(candidate.DockingDuration, 1.2f)),
|
|
||||||
UndockingDuration = MathF.Max(0.1f, finiteOr(candidate.UndockingDuration, 1.2f)),
|
|
||||||
UndockDistance = MathF.Max(0f, finiteOr(candidate.UndockDistance, 42f)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private ShipSnapshot? GetShipSnapshotUnsafe(string shipId) =>
|
|
||||||
_engine.BuildSnapshot(_world, _sequence).Ships.FirstOrDefault(ship => ship.Id == shipId);
|
|
||||||
|
|
||||||
private PlayerFactionSnapshot? GetPlayerFactionSnapshotUnsafe() =>
|
|
||||||
_engine.BuildSnapshot(_world, _sequence).PlayerFaction;
|
|
||||||
|
|
||||||
private static bool HasMeaningfulDelta(WorldDelta delta) =>
|
|
||||||
delta.RequiresSnapshotRefresh
|
|
||||||
|| delta.Events.Count > 0
|
|
||||||
|| delta.Celestials.Count > 0
|
|
||||||
|| delta.Nodes.Count > 0
|
|
||||||
|| delta.Stations.Count > 0
|
|
||||||
|| delta.Claims.Count > 0
|
|
||||||
|| delta.ConstructionSites.Count > 0
|
|
||||||
|| delta.MarketOrders.Count > 0
|
|
||||||
|| delta.Policies.Count > 0
|
|
||||||
|| delta.Ships.Count > 0
|
|
||||||
|| delta.Factions.Count > 0
|
|
||||||
|| delta.PlayerFaction is not null
|
|
||||||
|| delta.Geopolitics is not null;
|
|
||||||
|
|
||||||
private void Unsubscribe(Guid subscriberId)
|
|
||||||
{
|
|
||||||
lock (_sync)
|
|
||||||
{
|
|
||||||
if (!_subscribers.Remove(subscriberId, out var subscription))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
subscription.Channel.Writer.TryComplete();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private WorldDelta FilterDeltaForScope(WorldDelta delta, ObserverScope scope)
|
public BalanceDefinition UpdateBalance(BalanceDefinition balance)
|
||||||
{
|
|
||||||
if (string.Equals(scope.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
return delta with
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_balanceOverride = SanitizeBalance(balance);
|
||||||
|
ApplyBalance(_world, _balanceOverride);
|
||||||
|
return GetBalance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShipSnapshot? EnqueueShipOrder(string shipId, ShipOrderCommandRequest request)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
var ship = _playerFaction.EnqueueDirectShipOrder(_world, shipId, request);
|
||||||
|
if (ship is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetShipSnapshotUnsafe(ship.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShipSnapshot? RemoveShipOrder(string shipId, string orderId)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
var ship = _playerFaction.RemoveDirectShipOrder(_world, shipId, orderId);
|
||||||
|
if (ship is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetShipSnapshotUnsafe(ship.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ShipSnapshot? UpdateShipDefaultBehavior(string shipId, ShipDefaultBehaviorCommandRequest request)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
var ship = _playerFaction.ConfigureDirectShipBehavior(_world, shipId, request);
|
||||||
|
if (ship is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetShipSnapshotUnsafe(ship.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? GetPlayerFaction()
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.EnsureDomain(_world);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? CreatePlayerOrganization(PlayerOrganizationCommandRequest request)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.CreateOrganization(_world, request);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? DeletePlayerOrganization(string organizationId)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.DeleteOrganization(_world, organizationId);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? UpdatePlayerOrganizationMembership(string organizationId, PlayerOrganizationMembershipCommandRequest request)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.UpdateOrganizationMembership(_world, organizationId, request);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? UpsertPlayerDirective(string? directiveId, PlayerDirectiveCommandRequest request)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.UpsertDirective(_world, directiveId, request);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? DeletePlayerDirective(string directiveId)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.DeleteDirective(_world, directiveId);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? UpsertPlayerPolicy(string? policyId, PlayerPolicyCommandRequest request)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.UpsertPolicy(_world, policyId, request);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? UpsertPlayerAutomationPolicy(string? automationPolicyId, PlayerAutomationPolicyCommandRequest request)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.UpsertAutomationPolicy(_world, automationPolicyId, request);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? UpsertPlayerReinforcementPolicy(string? reinforcementPolicyId, PlayerReinforcementPolicyCommandRequest request)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.UpsertReinforcementPolicy(_world, reinforcementPolicyId, request);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? UpsertPlayerProductionProgram(string? productionProgramId, PlayerProductionProgramCommandRequest request)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.UpsertProductionProgram(_world, productionProgramId, request);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? UpsertPlayerAssignment(string assetId, PlayerAssetAssignmentCommandRequest request)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.UpsertAssignment(_world, assetId, request);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerFactionSnapshot? UpdatePlayerStrategicIntent(PlayerStrategicIntentCommandRequest request)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_playerFaction.UpdateStrategicIntent(_world, request);
|
||||||
|
return GetPlayerFactionSnapshotUnsafe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChannelReader<WorldDelta> Subscribe(ObserverScope scope, long afterSequence, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var channel = Channel.CreateUnbounded<WorldDelta>(new UnboundedChannelOptions
|
||||||
|
{
|
||||||
|
SingleReader = true,
|
||||||
|
SingleWriter = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
Guid subscriberId;
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
subscriberId = Guid.NewGuid();
|
||||||
|
_subscribers.Add(subscriberId, new SubscriptionState(scope, channel));
|
||||||
|
|
||||||
|
foreach (var delta in _history.Where((candidate) => candidate.Sequence > afterSequence))
|
||||||
|
{
|
||||||
|
var filtered = FilterDeltaForScope(delta, scope);
|
||||||
|
if (HasMeaningfulDelta(filtered))
|
||||||
|
{
|
||||||
|
channel.Writer.TryWrite(filtered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancellationToken.Register(() => Unsubscribe(subscriberId));
|
||||||
|
return channel.Reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Tick(float deltaSeconds)
|
||||||
|
{
|
||||||
|
WorldDelta? delta = null;
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
delta = _engine.Tick(_world, deltaSeconds, ++_sequence);
|
||||||
|
if (!HasMeaningfulDelta(delta))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_history.Enqueue(delta);
|
||||||
|
while (_history.Count > DeltaHistoryLimit)
|
||||||
|
{
|
||||||
|
_history.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var subscriber in _subscribers.Values.ToList())
|
||||||
|
{
|
||||||
|
var filtered = FilterDeltaForScope(delta, subscriber.Scope);
|
||||||
|
if (HasMeaningfulDelta(filtered))
|
||||||
|
{
|
||||||
|
subscriber.Channel.Writer.TryWrite(filtered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldSnapshot Reset()
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_world = _loader.Load();
|
||||||
|
if (_balanceOverride is not null)
|
||||||
|
{
|
||||||
|
ApplyBalance(_world, _balanceOverride);
|
||||||
|
}
|
||||||
|
_sequence += 1;
|
||||||
|
_history.Clear();
|
||||||
|
|
||||||
|
var resetDelta = new WorldDelta(
|
||||||
|
_sequence,
|
||||||
|
_world.TickIntervalMs,
|
||||||
|
_world.OrbitalTimeSeconds,
|
||||||
|
_orbitalSimulation,
|
||||||
|
DateTimeOffset.UtcNow,
|
||||||
|
true,
|
||||||
|
[new SimulationEventRecord("world", "world", "reset", "World reset requested", DateTimeOffset.UtcNow, "world", "universe", "world")],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
|
||||||
|
_history.Enqueue(resetDelta);
|
||||||
|
foreach (var subscriber in _subscribers.Values.ToList())
|
||||||
|
{
|
||||||
|
subscriber.Channel.Writer.TryWrite(FilterDeltaForScope(resetDelta, subscriber.Scope));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _engine.BuildSnapshot(_world, _sequence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyBalance(SimulationWorld world, BalanceDefinition balance) =>
|
||||||
|
world.Balance = new BalanceDefinition
|
||||||
{
|
{
|
||||||
Events = delta.Events.Select((evt) => EnrichEventScope(evt)).ToList(),
|
SimulationSpeedMultiplier = balance.SimulationSpeedMultiplier,
|
||||||
Scope = scope,
|
YPlane = balance.YPlane,
|
||||||
|
ArrivalThreshold = balance.ArrivalThreshold,
|
||||||
|
MiningRate = balance.MiningRate,
|
||||||
|
MiningCycleSeconds = balance.MiningCycleSeconds,
|
||||||
|
TransferRate = balance.TransferRate,
|
||||||
|
DockingDuration = balance.DockingDuration,
|
||||||
|
UndockingDuration = balance.UndockingDuration,
|
||||||
|
UndockDistance = balance.UndockDistance,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static BalanceDefinition SanitizeBalance(BalanceDefinition candidate)
|
||||||
|
{
|
||||||
|
static float finiteOr(float value, float fallback) =>
|
||||||
|
float.IsFinite(value) ? value : fallback;
|
||||||
|
|
||||||
|
return new BalanceDefinition
|
||||||
|
{
|
||||||
|
SimulationSpeedMultiplier = MathF.Max(0.01f, finiteOr(candidate.SimulationSpeedMultiplier, 1f)),
|
||||||
|
YPlane = MathF.Max(0f, finiteOr(candidate.YPlane, 0f)),
|
||||||
|
ArrivalThreshold = MathF.Max(0.1f, finiteOr(candidate.ArrivalThreshold, 16f)),
|
||||||
|
MiningRate = MathF.Max(0f, finiteOr(candidate.MiningRate, 10f)),
|
||||||
|
MiningCycleSeconds = MathF.Max(0.1f, finiteOr(candidate.MiningCycleSeconds, 10f)),
|
||||||
|
TransferRate = MathF.Max(0f, finiteOr(candidate.TransferRate, 56f)),
|
||||||
|
DockingDuration = MathF.Max(0.1f, finiteOr(candidate.DockingDuration, 1.2f)),
|
||||||
|
UndockingDuration = MathF.Max(0.1f, finiteOr(candidate.UndockingDuration, 1.2f)),
|
||||||
|
UndockDistance = MathF.Max(0f, finiteOr(candidate.UndockDistance, 42f)),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var systemFilter = scope.SystemId;
|
private ShipSnapshot? GetShipSnapshotUnsafe(string shipId) =>
|
||||||
if (string.Equals(scope.ScopeKind, "local-celestial", StringComparison.OrdinalIgnoreCase) && systemFilter is null && scope.CelestialId is not null)
|
_engine.BuildSnapshot(_world, _sequence).Ships.FirstOrDefault(ship => ship.Id == shipId);
|
||||||
|
|
||||||
|
private PlayerFactionSnapshot? GetPlayerFactionSnapshotUnsafe() =>
|
||||||
|
_engine.BuildSnapshot(_world, _sequence).PlayerFaction;
|
||||||
|
|
||||||
|
private static bool HasMeaningfulDelta(WorldDelta delta) =>
|
||||||
|
delta.RequiresSnapshotRefresh
|
||||||
|
|| delta.Events.Count > 0
|
||||||
|
|| delta.Celestials.Count > 0
|
||||||
|
|| delta.Nodes.Count > 0
|
||||||
|
|| delta.Stations.Count > 0
|
||||||
|
|| delta.Claims.Count > 0
|
||||||
|
|| delta.ConstructionSites.Count > 0
|
||||||
|
|| delta.MarketOrders.Count > 0
|
||||||
|
|| delta.Policies.Count > 0
|
||||||
|
|| delta.Ships.Count > 0
|
||||||
|
|| delta.Factions.Count > 0
|
||||||
|
|| delta.PlayerFaction is not null
|
||||||
|
|| delta.Geopolitics is not null;
|
||||||
|
|
||||||
|
private void Unsubscribe(Guid subscriberId)
|
||||||
{
|
{
|
||||||
systemFilter = ResolveCelestialSystemId(scope.CelestialId);
|
lock (_sync)
|
||||||
|
{
|
||||||
|
if (!_subscribers.Remove(subscriberId, out var subscription))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscription.Channel.Writer.TryComplete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return delta with
|
private WorldDelta FilterDeltaForScope(WorldDelta delta, ObserverScope scope)
|
||||||
{
|
{
|
||||||
Events = delta.Events
|
if (string.Equals(scope.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase))
|
||||||
.Select((evt) => EnrichEventScope(evt))
|
{
|
||||||
.Where((evt) => IsEventVisibleToScope(evt, scope, systemFilter))
|
return delta with
|
||||||
.ToList(),
|
{
|
||||||
Celestials = delta.Celestials.Where((celestial) => systemFilter is null || celestial.SystemId == systemFilter).ToList(),
|
Events = delta.Events.Select((evt) => EnrichEventScope(evt)).ToList(),
|
||||||
Nodes = delta.Nodes.Where((node) => systemFilter is null || node.SystemId == systemFilter).ToList(),
|
Scope = scope,
|
||||||
Stations = delta.Stations.Where((station) => systemFilter is null || station.SystemId == systemFilter).ToList(),
|
};
|
||||||
Claims = delta.Claims.Where((claim) => systemFilter is null || claim.SystemId == systemFilter).ToList(),
|
}
|
||||||
ConstructionSites = delta.ConstructionSites.Where((site) => systemFilter is null || site.SystemId == systemFilter).ToList(),
|
|
||||||
MarketOrders = delta.MarketOrders.Where((order) => IsOrderVisibleToScope(order, systemFilter)).ToList(),
|
|
||||||
Policies = string.Equals(scope.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase) ? delta.Policies : [],
|
|
||||||
Ships = delta.Ships.Where((ship) => systemFilter is null || ship.SystemId == systemFilter).ToList(),
|
|
||||||
Factions = string.Equals(scope.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase) ? delta.Factions : [],
|
|
||||||
PlayerFaction = string.Equals(scope.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase) ? delta.PlayerFaction : null,
|
|
||||||
Geopolitics = string.Equals(scope.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase) ? delta.Geopolitics : null,
|
|
||||||
Scope = scope,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private SimulationEventRecord EnrichEventScope(SimulationEventRecord evt)
|
var systemFilter = scope.SystemId;
|
||||||
{
|
if (string.Equals(scope.ScopeKind, "local-celestial", StringComparison.OrdinalIgnoreCase) && systemFilter is null && scope.CelestialId is not null)
|
||||||
if (!string.Equals(evt.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase) || evt.ScopeEntityId is not null)
|
{
|
||||||
{
|
systemFilter = ResolveCelestialSystemId(scope.CelestialId);
|
||||||
return evt;
|
}
|
||||||
|
|
||||||
|
return delta with
|
||||||
|
{
|
||||||
|
Events = delta.Events
|
||||||
|
.Select((evt) => EnrichEventScope(evt))
|
||||||
|
.Where((evt) => IsEventVisibleToScope(evt, scope, systemFilter))
|
||||||
|
.ToList(),
|
||||||
|
Celestials = delta.Celestials.Where((celestial) => systemFilter is null || celestial.SystemId == systemFilter).ToList(),
|
||||||
|
Nodes = delta.Nodes.Where((node) => systemFilter is null || node.SystemId == systemFilter).ToList(),
|
||||||
|
Stations = delta.Stations.Where((station) => systemFilter is null || station.SystemId == systemFilter).ToList(),
|
||||||
|
Claims = delta.Claims.Where((claim) => systemFilter is null || claim.SystemId == systemFilter).ToList(),
|
||||||
|
ConstructionSites = delta.ConstructionSites.Where((site) => systemFilter is null || site.SystemId == systemFilter).ToList(),
|
||||||
|
MarketOrders = delta.MarketOrders.Where((order) => IsOrderVisibleToScope(order, systemFilter)).ToList(),
|
||||||
|
Policies = string.Equals(scope.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase) ? delta.Policies : [],
|
||||||
|
Ships = delta.Ships.Where((ship) => systemFilter is null || ship.SystemId == systemFilter).ToList(),
|
||||||
|
Factions = string.Equals(scope.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase) ? delta.Factions : [],
|
||||||
|
PlayerFaction = string.Equals(scope.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase) ? delta.PlayerFaction : null,
|
||||||
|
Geopolitics = string.Equals(scope.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase) ? delta.Geopolitics : null,
|
||||||
|
Scope = scope,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return evt.EntityKind switch
|
private SimulationEventRecord EnrichEventScope(SimulationEventRecord evt)
|
||||||
{
|
{
|
||||||
"ship" => WithEntityScope(evt, "system", _world.Ships.FirstOrDefault((ship) => ship.Id == evt.EntityId)?.SystemId),
|
if (!string.Equals(evt.ScopeKind, "universe", StringComparison.OrdinalIgnoreCase) || evt.ScopeEntityId is not null)
|
||||||
"station" => WithEntityScope(evt, "system", _world.Stations.FirstOrDefault((station) => station.Id == evt.EntityId)?.SystemId),
|
{
|
||||||
"node" => WithEntityScope(evt, "system", _world.Nodes.FirstOrDefault((node) => node.Id == evt.EntityId)?.SystemId),
|
return evt;
|
||||||
"celestial" => WithEntityScope(evt, "system", _world.Celestials.FirstOrDefault((c) => c.Id == evt.EntityId)?.SystemId),
|
}
|
||||||
"claim" => WithEntityScope(evt, "system", _world.Claims.FirstOrDefault((claim) => claim.Id == evt.EntityId)?.SystemId),
|
|
||||||
"construction-site" => WithEntityScope(evt, "system", _world.ConstructionSites.FirstOrDefault((site) => site.Id == evt.EntityId)?.SystemId),
|
|
||||||
"market-order" => WithEntityScope(evt, "system", ResolveMarketOrderSystemId(evt.EntityId)),
|
|
||||||
_ => evt,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SimulationEventRecord WithEntityScope(SimulationEventRecord evt, string scopeKind, string? scopeEntityId) =>
|
return evt.EntityKind switch
|
||||||
evt with
|
{
|
||||||
{
|
"ship" => WithEntityScope(evt, "system", _world.Ships.FirstOrDefault((ship) => ship.Id == evt.EntityId)?.SystemId),
|
||||||
Family = evt.Kind.Contains("power", StringComparison.Ordinal) ? "power" :
|
"station" => WithEntityScope(evt, "system", _world.Stations.FirstOrDefault((station) => station.Id == evt.EntityId)?.SystemId),
|
||||||
evt.Kind.Contains("construction", StringComparison.Ordinal) ? "construction" :
|
"node" => WithEntityScope(evt, "system", _world.Nodes.FirstOrDefault((node) => node.Id == evt.EntityId)?.SystemId),
|
||||||
evt.Kind.Contains("population", StringComparison.Ordinal) ? "population" :
|
"celestial" => WithEntityScope(evt, "system", _world.Celestials.FirstOrDefault((c) => c.Id == evt.EntityId)?.SystemId),
|
||||||
evt.Kind.Contains("claim", StringComparison.Ordinal) ? "claim" :
|
"claim" => WithEntityScope(evt, "system", _world.Claims.FirstOrDefault((claim) => claim.Id == evt.EntityId)?.SystemId),
|
||||||
"simulation",
|
"construction-site" => WithEntityScope(evt, "system", _world.ConstructionSites.FirstOrDefault((site) => site.Id == evt.EntityId)?.SystemId),
|
||||||
ScopeKind = scopeKind,
|
"market-order" => WithEntityScope(evt, "system", ResolveMarketOrderSystemId(evt.EntityId)),
|
||||||
ScopeEntityId = scopeEntityId,
|
_ => evt,
|
||||||
};
|
};
|
||||||
|
|
||||||
private string? ResolveCelestialSystemId(string celestialId) =>
|
|
||||||
_world.Celestials.FirstOrDefault((c) => c.Id == celestialId)?.SystemId;
|
|
||||||
|
|
||||||
private string? ResolveMarketOrderSystemId(string orderId)
|
|
||||||
{
|
|
||||||
var order = _world.MarketOrders.FirstOrDefault((candidate) => candidate.Id == orderId);
|
|
||||||
if (order?.StationId is not null)
|
|
||||||
{
|
|
||||||
return _world.Stations.FirstOrDefault((station) => station.Id == order.StationId)?.SystemId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (order?.ConstructionSiteId is not null)
|
private static SimulationEventRecord WithEntityScope(SimulationEventRecord evt, string scopeKind, string? scopeEntityId) =>
|
||||||
|
evt with
|
||||||
|
{
|
||||||
|
Family = evt.Kind.Contains("power", StringComparison.Ordinal) ? "power" :
|
||||||
|
evt.Kind.Contains("construction", StringComparison.Ordinal) ? "construction" :
|
||||||
|
evt.Kind.Contains("population", StringComparison.Ordinal) ? "population" :
|
||||||
|
evt.Kind.Contains("claim", StringComparison.Ordinal) ? "claim" :
|
||||||
|
"simulation",
|
||||||
|
ScopeKind = scopeKind,
|
||||||
|
ScopeEntityId = scopeEntityId,
|
||||||
|
};
|
||||||
|
|
||||||
|
private string? ResolveCelestialSystemId(string celestialId) =>
|
||||||
|
_world.Celestials.FirstOrDefault((c) => c.Id == celestialId)?.SystemId;
|
||||||
|
|
||||||
|
private string? ResolveMarketOrderSystemId(string orderId)
|
||||||
{
|
{
|
||||||
return _world.ConstructionSites.FirstOrDefault((site) => site.Id == order.ConstructionSiteId)?.SystemId;
|
var order = _world.MarketOrders.FirstOrDefault((candidate) => candidate.Id == orderId);
|
||||||
|
if (order?.StationId is not null)
|
||||||
|
{
|
||||||
|
return _world.Stations.FirstOrDefault((station) => station.Id == order.StationId)?.SystemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order?.ConstructionSiteId is not null)
|
||||||
|
{
|
||||||
|
return _world.ConstructionSites.FirstOrDefault((site) => site.Id == order.ConstructionSiteId)?.SystemId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
private bool IsOrderVisibleToScope(MarketOrderDelta order, string? systemFilter)
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsOrderVisibleToScope(MarketOrderDelta order, string? systemFilter)
|
|
||||||
{
|
|
||||||
if (systemFilter is null)
|
|
||||||
{
|
{
|
||||||
return true;
|
if (systemFilter is null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.StationId is not null)
|
||||||
|
{
|
||||||
|
return _world.Stations.Any((station) => station.Id == order.StationId && station.SystemId == systemFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.ConstructionSiteId is not null)
|
||||||
|
{
|
||||||
|
return _world.ConstructionSites.Any((site) => site.Id == order.ConstructionSiteId && site.SystemId == systemFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (order.StationId is not null)
|
private static bool IsEventVisibleToScope(SimulationEventRecord evt, ObserverScope scope, string? systemFilter)
|
||||||
{
|
{
|
||||||
return _world.Stations.Any((station) => station.Id == order.StationId && station.SystemId == systemFilter);
|
return scope.ScopeKind switch
|
||||||
|
{
|
||||||
|
"universe" => true,
|
||||||
|
"system" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
|
||||||
|
"local-celestial" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (order.ConstructionSiteId is not null)
|
private sealed record SubscriptionState(ObserverScope Scope, Channel<WorldDelta> Channel);
|
||||||
{
|
|
||||||
return _world.ConstructionSites.Any((site) => site.Id == order.ConstructionSiteId && site.SystemId == systemFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsEventVisibleToScope(SimulationEventRecord evt, ObserverScope scope, string? systemFilter)
|
|
||||||
{
|
|
||||||
return scope.ScopeKind switch
|
|
||||||
{
|
|
||||||
"universe" => true,
|
|
||||||
"system" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
|
|
||||||
"local-celestial" => evt.ScopeKind == "universe" || evt.ScopeEntityId == systemFilter,
|
|
||||||
_ => true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed record SubscriptionState(ObserverScope Scope, Channel<WorldDelta> Channel);
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user