183 lines
6.9 KiB
C#
183 lines
6.9 KiB
C#
using SpaceGame.Api.Data;
|
|
using SpaceGame.Api.Simulation.Model;
|
|
using static SpaceGame.Api.Simulation.LoaderSupport;
|
|
|
|
namespace SpaceGame.Api.Simulation;
|
|
|
|
internal sealed class WorldBuilder(
|
|
WorldGenerationOptions worldGeneration,
|
|
DataCatalogLoader dataLoader,
|
|
SystemGenerationService generationService,
|
|
SpatialBuilder spatialBuilder,
|
|
WorldSeedingService seedingService)
|
|
{
|
|
internal SimulationWorld Build()
|
|
{
|
|
var catalog = dataLoader.LoadCatalog();
|
|
var systems = generationService.ExpandSystems(
|
|
generationService.InjectSpecialSystems(catalog.AuthoredSystems),
|
|
worldGeneration.TargetSystemCount);
|
|
var scenario = dataLoader.NormalizeScenarioToAvailableSystems(
|
|
catalog.Scenario,
|
|
systems.Select(system => system.Id).ToList());
|
|
|
|
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);
|
|
|
|
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, refinery);
|
|
|
|
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 claims = seedingService.CreateClaims(stations, spatialLayout.Celestials, nowUtc);
|
|
var (constructionSites, marketOrders) = seedingService.CreateConstructionSites(stations, claims, catalog.ModuleRecipes);
|
|
|
|
return new SimulationWorld
|
|
{
|
|
Label = "Split Viewer / Simulation World",
|
|
Seed = WorldSeed,
|
|
Balance = catalog.Balance,
|
|
Systems = systemRuntimes,
|
|
Celestials = spatialLayout.Celestials,
|
|
Nodes = spatialLayout.Nodes,
|
|
Stations = stations,
|
|
Ships = ships,
|
|
Factions = factions,
|
|
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),
|
|
OrbitalTimeSeconds = WorldSeed * 97d,
|
|
GeneratedAtUtc = DateTimeOffset.UtcNow,
|
|
};
|
|
}
|
|
|
|
private static List<StationRuntime> CreateStations(
|
|
ScenarioDefinition scenario,
|
|
IReadOnlyDictionary<string, SystemRuntime> systemsById,
|
|
IReadOnlyDictionary<string, SystemSpatialGraph> systemGraphs,
|
|
IReadOnlyCollection<CelestialRuntime> celestials,
|
|
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
|
|
{
|
|
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,
|
|
Position = placement.Position,
|
|
FactionId = plan.FactionId ?? DefaultFactionId,
|
|
CelestialId = placement.AnchorCelestial.Id,
|
|
};
|
|
|
|
stations.Add(station);
|
|
placement.AnchorCelestial.OccupyingStructureId = station.Id;
|
|
|
|
var startingModules = plan.StartingModules.Count > 0
|
|
? plan.StartingModules
|
|
: ["module_arg_dock_m_01_lowtech", "module_gen_prod_energycells_01", "module_arg_stor_solid_m_01", "module_arg_stor_liquid_m_01"];
|
|
|
|
foreach (var moduleId in startingModules)
|
|
{
|
|
AddStationModule(station, moduleDefinitions, moduleId);
|
|
}
|
|
}
|
|
|
|
return stations;
|
|
}
|
|
|
|
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,
|
|
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, scenario, patrolRoutes, refinery),
|
|
ControllerTask = new ControllerTaskRuntime { Kind = ControllerTaskKind.Idle, Threshold = balance.ArrivalThreshold, Status = WorkStatus.Pending },
|
|
Health = definition.MaxHealth,
|
|
});
|
|
}
|
|
}
|
|
|
|
return ships;
|
|
}
|
|
}
|