Refactor world bootstrap and allow empty startup worlds
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
using SpaceGame.Api.Universe.Bootstrap;
|
||||
using static SpaceGame.Api.Universe.Scenario.LoaderSupport;
|
||||
|
||||
namespace SpaceGame.Api.Universe.Scenario;
|
||||
|
||||
public sealed class WorldSeedingService
|
||||
public sealed class WorldSeedingService(IStaticDataProvider staticData)
|
||||
{
|
||||
internal List<FactionRuntime> CreateFactions(
|
||||
IReadOnlyCollection<StationRuntime> stations,
|
||||
@@ -18,7 +19,7 @@ public sealed class WorldSeedingService
|
||||
|
||||
if (factionIds.Count == 0)
|
||||
{
|
||||
factionIds.Add(DefaultFactionId);
|
||||
return [];
|
||||
}
|
||||
|
||||
return factionIds.Select(CreateFaction).ToList();
|
||||
@@ -70,15 +71,6 @@ public sealed class WorldSeedingService
|
||||
}
|
||||
}
|
||||
|
||||
internal StationRuntime? SelectRefineryStation(IReadOnlyCollection<StationRuntime> stations, ScenarioDefinition scenario)
|
||||
{
|
||||
return stations.FirstOrDefault(station =>
|
||||
string.Equals(StationSimulationService.DetermineStationRole(station), "refinery", StringComparison.Ordinal) &&
|
||||
station.SystemId == scenario.MiningDefaults.RefinerySystemId)
|
||||
?? stations.FirstOrDefault(station =>
|
||||
string.Equals(StationSimulationService.DetermineStationRole(station), "refinery", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
internal List<ClaimRuntime> CreateClaims(
|
||||
IReadOnlyCollection<StationRuntime> stations,
|
||||
IReadOnlyCollection<CelestialRuntime> celestials,
|
||||
@@ -183,39 +175,32 @@ public sealed class WorldSeedingService
|
||||
private static bool HasSatisfiedStarterObjectiveLayout(SimulationWorld world, StationRuntime station)
|
||||
{
|
||||
var role = StationSimulationService.DetermineStationRole(station);
|
||||
var objectiveModuleId = role switch
|
||||
{
|
||||
"power" => "module_gen_prod_energycells_01",
|
||||
"refinery" => "module_gen_prod_refinedmetals_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,
|
||||
};
|
||||
var objectiveModuleId = StarterStationLayoutResolver.ResolveObjectiveModuleId(role, station.FactionId, world.ModuleDefinitions);
|
||||
|
||||
if (objectiveModuleId is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!station.InstalledModules.Contains("module_arg_dock_m_01_lowtech", StringComparer.Ordinal)
|
||||
var requiredDockModuleId = StarterStationLayoutResolver.ResolveDockModuleId(station.FactionId, world.ModuleDefinitions);
|
||||
if (!station.InstalledModules.Contains(requiredDockModuleId, StringComparer.Ordinal)
|
||||
|| !station.InstalledModules.Contains(objectiveModuleId, StringComparer.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(objectiveModuleId, "module_gen_prod_energycells_01", StringComparison.Ordinal)
|
||||
&& !station.InstalledModules.Contains("module_gen_prod_energycells_01", StringComparer.Ordinal))
|
||||
var powerModuleId = StarterStationLayoutResolver.ResolvePowerModuleId(station.FactionId, world.ModuleDefinitions);
|
||||
if (!string.Equals(objectiveModuleId, powerModuleId, StringComparison.Ordinal)
|
||||
&& !station.InstalledModules.Contains(powerModuleId, StringComparer.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var storageModuleId in GetRequiredStorageModulesForInstalledObjective(world, objectiveModuleId))
|
||||
foreach (var storageModuleId in StarterStationLayoutResolver.ResolveRequiredStorageModuleIds(
|
||||
objectiveModuleId,
|
||||
station.FactionId,
|
||||
world.ModuleDefinitions,
|
||||
world.ItemDefinitions))
|
||||
{
|
||||
if (!station.InstalledModules.Contains(storageModuleId, StringComparer.Ordinal))
|
||||
{
|
||||
@@ -226,30 +211,6 @@ public sealed class WorldSeedingService
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetRequiredStorageModulesForInstalledObjective(SimulationWorld world, string moduleId)
|
||||
{
|
||||
if (!world.ModuleDefinitions.TryGetValue(moduleId, out var moduleDefinition))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var wareId in moduleDefinition.BuildRecipes
|
||||
.SelectMany(production => production.Wares.Select(ware => ware.ItemId))
|
||||
.Concat(moduleDefinition.ProductItemIds)
|
||||
.Distinct(StringComparer.Ordinal))
|
||||
{
|
||||
if (!world.ItemDefinitions.TryGetValue(wareId, out var itemDefinition))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport.GetStorageRequirement(world.ModuleDefinitions, itemDefinition.CargoKind) is { } storageModuleId)
|
||||
{
|
||||
yield return storageModuleId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal List<PolicySetRuntime> CreatePolicies(IReadOnlyCollection<FactionRuntime> factions)
|
||||
{
|
||||
var policies = new List<PolicySetRuntime>(factions.Count);
|
||||
@@ -355,8 +316,8 @@ public sealed class WorldSeedingService
|
||||
IReadOnlyCollection<PolicySetRuntime> policies,
|
||||
DateTimeOffset nowUtc)
|
||||
{
|
||||
var sovereignFaction = factions.FirstOrDefault(faction => string.Equals(faction.Id, DefaultFactionId, StringComparison.Ordinal))
|
||||
?? factions.OrderBy(faction => faction.Id, StringComparer.Ordinal).First();
|
||||
var sovereignFaction = factions.OrderBy(faction => faction.Id, StringComparer.Ordinal).FirstOrDefault()
|
||||
?? throw new InvalidOperationException("Cannot create a player faction without at least one faction in the world.");
|
||||
|
||||
var player = new PlayerFactionRuntime
|
||||
{
|
||||
@@ -434,122 +395,55 @@ public sealed class WorldSeedingService
|
||||
return player;
|
||||
}
|
||||
|
||||
internal static DefaultBehaviorRuntime CreateBehavior(
|
||||
ShipDefinition definition,
|
||||
string systemId,
|
||||
string factionId,
|
||||
ScenarioDefinition scenario,
|
||||
IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
|
||||
IReadOnlyCollection<StationRuntime> stations,
|
||||
StationRuntime? refinery)
|
||||
private FactionRuntime CreateFaction(string factionId)
|
||||
{
|
||||
var homeStation = stations.FirstOrDefault(station =>
|
||||
string.Equals(station.FactionId, factionId, StringComparison.Ordinal)
|
||||
&& string.Equals(station.SystemId, systemId, StringComparison.Ordinal))
|
||||
?? stations.FirstOrDefault(station => string.Equals(station.FactionId, factionId, StringComparison.Ordinal))
|
||||
?? refinery;
|
||||
|
||||
if (string.Equals(definition.Kind, "construction", StringComparison.Ordinal) && homeStation is not null)
|
||||
if (!staticData.FactionDefinitions.TryGetValue(factionId, out var definition))
|
||||
{
|
||||
return new DefaultBehaviorRuntime
|
||||
{
|
||||
Kind = "construct-station",
|
||||
HomeSystemId = homeStation.SystemId,
|
||||
HomeStationId = homeStation.Id,
|
||||
PreferredConstructionSiteId = null,
|
||||
};
|
||||
throw new InvalidOperationException($"Faction '{factionId}' is not defined in static data.");
|
||||
}
|
||||
|
||||
if (HasCapabilities(definition, "mining") && homeStation is not null)
|
||||
return new FactionRuntime
|
||||
{
|
||||
return new DefaultBehaviorRuntime
|
||||
{
|
||||
Kind = definition.CargoCapacity >= 120f ? "expert-auto-mine" : "advanced-auto-mine",
|
||||
HomeSystemId = homeStation.SystemId,
|
||||
HomeStationId = homeStation.Id,
|
||||
AreaSystemId = scenario.MiningDefaults.NodeSystemId,
|
||||
MaxSystemRange = definition.CargoCapacity >= 120f ? 3 : 1,
|
||||
};
|
||||
}
|
||||
|
||||
if (string.Equals(definition.Kind, "transport", StringComparison.Ordinal))
|
||||
{
|
||||
return new DefaultBehaviorRuntime
|
||||
{
|
||||
Kind = "advanced-auto-trade",
|
||||
HomeSystemId = homeStation?.SystemId ?? systemId,
|
||||
HomeStationId = homeStation?.Id,
|
||||
MaxSystemRange = 2,
|
||||
};
|
||||
}
|
||||
|
||||
if (string.Equals(definition.Kind, "military", StringComparison.Ordinal) && patrolRoutes.TryGetValue(systemId, out var route))
|
||||
{
|
||||
return new DefaultBehaviorRuntime
|
||||
{
|
||||
Kind = "patrol",
|
||||
HomeSystemId = homeStation?.SystemId ?? systemId,
|
||||
HomeStationId = homeStation?.Id,
|
||||
AreaSystemId = systemId,
|
||||
PatrolPoints = route,
|
||||
PatrolIndex = 0,
|
||||
};
|
||||
}
|
||||
|
||||
return new DefaultBehaviorRuntime
|
||||
{
|
||||
Kind = "idle",
|
||||
HomeSystemId = homeStation?.SystemId ?? systemId,
|
||||
HomeStationId = homeStation?.Id,
|
||||
Id = definition.Id,
|
||||
Label = definition.Label,
|
||||
Color = ResolveFactionColor(definition),
|
||||
Credits = MinimumFactionCredits,
|
||||
};
|
||||
}
|
||||
|
||||
internal static ShipSkillProfileRuntime CreateSkills(ShipDefinition definition)
|
||||
{
|
||||
return definition.Kind switch
|
||||
{
|
||||
"transport" => new ShipSkillProfileRuntime { Navigation = 3, Trade = 4, Mining = 1, Combat = 1, Construction = 1 },
|
||||
"construction" => new ShipSkillProfileRuntime { Navigation = 3, Trade = 1, Mining = 1, Combat = 1, Construction = 4 },
|
||||
"military" => new ShipSkillProfileRuntime { Navigation = 4, Trade = 1, Mining = 1, Combat = 4, Construction = 1 },
|
||||
_ when HasCapabilities(definition, "mining") => new ShipSkillProfileRuntime { Navigation = 3, Trade = 1, Mining = 4, Combat = 1, Construction = 1 },
|
||||
_ => new ShipSkillProfileRuntime { Navigation = 3, Trade = 2, Mining = 1, Combat = 1, Construction = 1 },
|
||||
};
|
||||
}
|
||||
|
||||
private static FactionRuntime CreateFaction(string factionId)
|
||||
{
|
||||
return factionId switch
|
||||
{
|
||||
DefaultFactionId => new FactionRuntime
|
||||
{
|
||||
Id = factionId,
|
||||
Label = "Sol Dominion",
|
||||
Color = "#7ed4ff",
|
||||
Credits = MinimumFactionCredits,
|
||||
},
|
||||
"asterion-league" => new FactionRuntime
|
||||
{
|
||||
Id = factionId,
|
||||
Label = "Asterion League",
|
||||
Color = "#ff8f70",
|
||||
Credits = MinimumFactionCredits,
|
||||
},
|
||||
"nadir-syndicate" => new FactionRuntime
|
||||
{
|
||||
Id = factionId,
|
||||
Label = "Nadir Syndicate",
|
||||
Color = "#91e6a8",
|
||||
Credits = MinimumFactionCredits,
|
||||
},
|
||||
_ => new FactionRuntime
|
||||
{
|
||||
Id = factionId,
|
||||
Label = ToFactionLabel(factionId),
|
||||
Color = "#c7d2e0",
|
||||
Credits = MinimumFactionCredits,
|
||||
},
|
||||
};
|
||||
}
|
||||
private static string ResolveFactionColor(FactionDefinition definition) =>
|
||||
definition.Id switch
|
||||
{
|
||||
"alliance" => "#c084fc",
|
||||
"antigone" => "#f97316",
|
||||
"argon" => "#3b82f6",
|
||||
"boron" => "#14b8a6",
|
||||
"freesplit" => "#ef4444",
|
||||
"hatikvah" => "#84cc16",
|
||||
"holyorder" => "#d97706",
|
||||
"loanshark" => "#f59e0b",
|
||||
"ministry" => "#a3e635",
|
||||
"paranid" => "#eab308",
|
||||
"pioneers" => "#60a5fa",
|
||||
"scaleplate" => "#94a3b8",
|
||||
"scavenger" => "#64748b",
|
||||
"split" => "#b91c1c",
|
||||
"teladi" => "#22c55e",
|
||||
"terran" => "#38bdf8",
|
||||
"trinity" => "#2dd4bf",
|
||||
"xenon" => "#9ca3af",
|
||||
_ => definition.RaceId switch
|
||||
{
|
||||
"argon" => "#3b82f6",
|
||||
"boron" => "#14b8a6",
|
||||
"paranid" => "#eab308",
|
||||
"split" => "#b91c1c",
|
||||
"teladi" => "#22c55e",
|
||||
"terran" => "#38bdf8",
|
||||
"xenon" => "#9ca3af",
|
||||
_ => "#94a3b8",
|
||||
},
|
||||
};
|
||||
|
||||
private static void InitializeStationPopulation(
|
||||
StationRuntime station,
|
||||
@@ -563,12 +457,4 @@ public sealed class WorldSeedingService
|
||||
station.WorkforceEffectiveRatio = ComputeWorkforceRatio(station.Population, station.WorkforceRequired);
|
||||
}
|
||||
|
||||
private static string ToFactionLabel(string factionId)
|
||||
{
|
||||
return string.Join(" ",
|
||||
factionId
|
||||
.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Select(segment => char.ToUpperInvariant(segment[0]) + segment[1..]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user