Refactor world bootstrap and allow empty startup worlds
This commit is contained in:
145
apps/backend/Universe/Scenario/StarterStationLayoutResolver.cs
Normal file
145
apps/backend/Universe/Scenario/StarterStationLayoutResolver.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using SpaceGame.Api.Shared.Runtime;
|
||||
|
||||
namespace SpaceGame.Api.Universe.Scenario;
|
||||
|
||||
internal static class StarterStationLayoutResolver
|
||||
{
|
||||
internal static string ResolveDockModuleId(
|
||||
string? factionId,
|
||||
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions) =>
|
||||
SelectPreferredModule(
|
||||
moduleDefinitions.Values.Where(definition => definition.ModuleType == ModuleType.DockArea),
|
||||
factionId,
|
||||
"starter dock module").Id;
|
||||
|
||||
internal static string ResolvePowerModuleId(
|
||||
string? factionId,
|
||||
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions) =>
|
||||
ResolveProducerModuleId("energycells", factionId, moduleDefinitions);
|
||||
|
||||
internal static string? ResolveObjectiveModuleId(
|
||||
string? objective,
|
||||
string? factionId,
|
||||
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
|
||||
{
|
||||
var targetWareId = ResolveObjectiveWareId(objective);
|
||||
return targetWareId is null ? null : ResolveProducerModuleId(targetWareId, factionId, moduleDefinitions);
|
||||
}
|
||||
|
||||
internal static IEnumerable<string> ResolveRequiredStorageModuleIds(
|
||||
string moduleId,
|
||||
string? factionId,
|
||||
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions,
|
||||
IReadOnlyDictionary<string, ItemDefinition> itemDefinitions)
|
||||
{
|
||||
if (!moduleDefinitions.TryGetValue(moduleId, out var moduleDefinition))
|
||||
{
|
||||
throw new InvalidOperationException($"Module '{moduleId}' is not defined in static data.");
|
||||
}
|
||||
|
||||
foreach (var wareId in moduleDefinition.BuildRecipes
|
||||
.SelectMany(production => production.Wares.Select(ware => ware.ItemId))
|
||||
.Concat(moduleDefinition.ProductItemIds)
|
||||
.Distinct(StringComparer.Ordinal))
|
||||
{
|
||||
if (!itemDefinitions.TryGetValue(wareId, out var itemDefinition))
|
||||
{
|
||||
throw new InvalidOperationException($"Module '{moduleId}' references unknown ware '{wareId}'.");
|
||||
}
|
||||
|
||||
if (itemDefinition.CargoKind is not { } storageKind)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return ResolveStorageModuleId(storageKind, factionId, moduleDefinitions);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ResolveObjectiveWareId(string? objective) =>
|
||||
StationSimulationService.NormalizeStationObjective(objective) switch
|
||||
{
|
||||
"power" => "energycells",
|
||||
"refinery" => "refinedmetals",
|
||||
"graphene" => "graphene",
|
||||
"siliconwafers" => "siliconwafers",
|
||||
"hullparts" => "hullparts",
|
||||
"claytronics" => "claytronics",
|
||||
"quantumtubes" => "quantumtubes",
|
||||
"antimattercells" => "antimattercells",
|
||||
"superfluidcoolant" => "superfluidcoolant",
|
||||
"water" => "water",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
private static string ResolveProducerModuleId(
|
||||
string wareId,
|
||||
string? factionId,
|
||||
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions) =>
|
||||
SelectPreferredModule(
|
||||
moduleDefinitions.Values
|
||||
.OfType<ProductionModuleDefinition>()
|
||||
.Where(definition => definition.ProductItemIds.Contains(wareId, StringComparer.Ordinal)),
|
||||
factionId,
|
||||
$"producer module for ware '{wareId}'").Id;
|
||||
|
||||
private static string ResolveStorageModuleId(
|
||||
StorageKind storageKind,
|
||||
string? factionId,
|
||||
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions) =>
|
||||
SelectPreferredModule(
|
||||
moduleDefinitions.Values
|
||||
.OfType<StorageModuleDefinition>()
|
||||
.Where(definition => definition.StorageKind == storageKind),
|
||||
factionId,
|
||||
$"storage module for cargo kind '{storageKind.ToDataValue()}'").Id;
|
||||
|
||||
private static T SelectPreferredModule<T>(
|
||||
IEnumerable<T> candidates,
|
||||
string? factionId,
|
||||
string context)
|
||||
where T : ModuleDefinition
|
||||
{
|
||||
var ordered = candidates
|
||||
.OrderBy(definition => ComputeOwnerRank(definition, factionId))
|
||||
.ThenBy(definition => ComputeModuleRank(definition))
|
||||
.ThenBy(definition => definition.Id, StringComparer.Ordinal)
|
||||
.ToList();
|
||||
|
||||
return ordered.FirstOrDefault()
|
||||
?? throw new InvalidOperationException($"Unable to resolve {context}.");
|
||||
}
|
||||
|
||||
private static int ComputeOwnerRank(ModuleDefinition definition, string? factionId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(factionId))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return definition.Owners.Contains(factionId, StringComparer.Ordinal) ? 0 : 1;
|
||||
}
|
||||
|
||||
private static int ComputeModuleRank(ModuleDefinition definition)
|
||||
{
|
||||
if (definition.ModuleType is ModuleType.DockArea or ModuleType.Storage)
|
||||
{
|
||||
if (definition.Id.Contains("_m_", StringComparison.Ordinal))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (definition.Id.Contains("_s_", StringComparison.Ordinal))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (definition.Id.Contains("_l_", StringComparison.Ordinal))
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user