Deepen faction economy and station planning
This commit is contained in:
@@ -20,6 +20,7 @@ internal sealed class DataCatalogLoader(string dataRoot)
|
||||
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,
|
||||
@@ -29,7 +30,8 @@ internal sealed class DataCatalogLoader(string dataRoot)
|
||||
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));
|
||||
moduleRecipes.ToDictionary(definition => definition.ModuleId, StringComparer.Ordinal),
|
||||
productionGraph);
|
||||
}
|
||||
|
||||
internal ScenarioDefinition NormalizeScenarioToAvailableSystems(
|
||||
@@ -56,6 +58,7 @@ internal sealed class DataCatalogLoader(string dataRoot)
|
||||
SystemId = ResolveSystemId(station.SystemId),
|
||||
Label = station.Label,
|
||||
Color = station.Color,
|
||||
Objective = station.Objective,
|
||||
StartingModules = station.StartingModules.ToList(),
|
||||
FactionId = station.FactionId,
|
||||
PlanetIndex = station.PlanetIndex,
|
||||
@@ -71,6 +74,7 @@ internal sealed class DataCatalogLoader(string dataRoot)
|
||||
Center = formation.Center.ToArray(),
|
||||
SystemId = ResolveSystemId(formation.SystemId),
|
||||
FactionId = formation.FactionId,
|
||||
StartingInventory = new Dictionary<string, float>(formation.StartingInventory, StringComparer.Ordinal),
|
||||
})
|
||||
.ToList(),
|
||||
PatrolRoutes = scenario.PatrolRoutes
|
||||
@@ -297,4 +301,5 @@ internal sealed record ScenarioCatalog(
|
||||
IReadOnlyDictionary<string, ShipDefinition> ShipDefinitions,
|
||||
IReadOnlyDictionary<string, ItemDefinition> ItemDefinitions,
|
||||
IReadOnlyDictionary<string, RecipeDefinition> Recipes,
|
||||
IReadOnlyDictionary<string, ModuleRecipeDefinition> ModuleRecipes);
|
||||
IReadOnlyDictionary<string, ModuleRecipeDefinition> ModuleRecipes,
|
||||
ProductionGraph ProductionGraph);
|
||||
|
||||
@@ -39,7 +39,7 @@ internal sealed class WorldBuilder(
|
||||
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 ships = CreateShips(scenario, systemsById, spatialLayout.Celestials, catalog.Balance, catalog.ShipDefinitions, patrolRoutes, stations, refinery);
|
||||
|
||||
var factions = seedingService.CreateFactions(stations, ships);
|
||||
seedingService.BootstrapFactionEconomy(factions, stations);
|
||||
@@ -47,7 +47,32 @@ internal sealed class WorldBuilder(
|
||||
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);
|
||||
var bootstrapWorld = new SimulationWorld
|
||||
{
|
||||
Label = "Split Viewer / Bootstrap 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 = [],
|
||||
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);
|
||||
|
||||
return new SimulationWorld
|
||||
{
|
||||
@@ -70,6 +95,7 @@ internal sealed class WorldBuilder(
|
||||
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,
|
||||
};
|
||||
@@ -99,9 +125,12 @@ internal sealed class WorldBuilder(
|
||||
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);
|
||||
@@ -142,6 +171,7 @@ internal sealed class WorldBuilder(
|
||||
BalanceDefinition balance,
|
||||
IReadOnlyDictionary<string, ShipDefinition> shipDefinitions,
|
||||
IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
|
||||
IReadOnlyCollection<StationRuntime> stations,
|
||||
StationRuntime? refinery)
|
||||
{
|
||||
var ships = new List<ShipRuntime>();
|
||||
@@ -168,10 +198,25 @@ internal sealed class WorldBuilder(
|
||||
Position = position,
|
||||
TargetPosition = position,
|
||||
SpatialState = SpatialBuilder.CreateInitialShipSpatialState(formation.SystemId, position, celestials),
|
||||
DefaultBehavior = WorldSeedingService.CreateBehavior(definition, formation.SystemId, scenario, patrolRoutes, refinery),
|
||||
DefaultBehavior = WorldSeedingService.CreateBehavior(
|
||||
definition,
|
||||
formation.SystemId,
|
||||
formation.FactionId ?? DefaultFactionId,
|
||||
scenario,
|
||||
patrolRoutes,
|
||||
stations,
|
||||
refinery),
|
||||
ControllerTask = new ControllerTaskRuntime { Kind = ControllerTaskKind.Idle, Threshold = balance.ArrivalThreshold, Status = WorkStatus.Pending },
|
||||
Health = definition.MaxHealth,
|
||||
});
|
||||
|
||||
foreach (var (itemId, amount) in formation.StartingInventory)
|
||||
{
|
||||
if (amount > 0f)
|
||||
{
|
||||
ships[^1].Inventory[itemId] = amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ internal sealed class WorldSeedingService
|
||||
.ToList();
|
||||
|
||||
var refineries = ownedStations
|
||||
.Where(station => HasInstalledModules(station, "module_gen_prod_refinedmetals_01", "module_gen_prod_energycells_01", "module_arg_stor_liquid_m_01"))
|
||||
.Where(station => string.Equals(StationSimulationService.DetermineStationRole(station), "refinery", StringComparison.Ordinal))
|
||||
.ToList();
|
||||
|
||||
if (refineries.Count > 0)
|
||||
@@ -65,10 +65,32 @@ internal sealed class WorldSeedingService
|
||||
foreach (var station in stations)
|
||||
{
|
||||
InitializeStationPopulation(station);
|
||||
station.Inventory["refinedmetals"] = 120f;
|
||||
if (station.InstalledModules.Contains("module_gen_prod_energycells_01", StringComparer.Ordinal))
|
||||
{
|
||||
station.Inventory["energycells"] = MathF.Max(GetInventoryAmount(station.Inventory, "energycells"), 240f);
|
||||
}
|
||||
|
||||
if (station.InstalledModules.Contains("module_gen_prod_refinedmetals_01", StringComparer.Ordinal))
|
||||
{
|
||||
station.Inventory["ore"] = MathF.Max(GetInventoryAmount(station.Inventory, "ore"), 220f);
|
||||
}
|
||||
|
||||
if (station.InstalledModules.Contains("module_gen_prod_hullparts_01", StringComparer.Ordinal))
|
||||
{
|
||||
station.Inventory["refinedmetals"] = MathF.Max(GetInventoryAmount(station.Inventory, "refinedmetals"), 240f);
|
||||
station.Inventory["graphene"] = MathF.Max(GetInventoryAmount(station.Inventory, "graphene"), 80f);
|
||||
}
|
||||
|
||||
if (station.InstalledModules.Contains("module_gen_prod_claytronics_01", StringComparer.Ordinal))
|
||||
{
|
||||
station.Inventory["antimattercells"] = MathF.Max(GetInventoryAmount(station.Inventory, "antimattercells"), 90f);
|
||||
station.Inventory["microchips"] = MathF.Max(GetInventoryAmount(station.Inventory, "microchips"), 120f);
|
||||
station.Inventory["quantumtubes"] = MathF.Max(GetInventoryAmount(station.Inventory, "quantumtubes"), 90f);
|
||||
}
|
||||
|
||||
if (station.Population > 0f)
|
||||
{
|
||||
station.Inventory["water"] = MathF.Max(80f, station.Population * 1.5f);
|
||||
station.Inventory["water"] = MathF.Max(60f, station.Population * 1.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,10 +98,10 @@ internal sealed class WorldSeedingService
|
||||
internal StationRuntime? SelectRefineryStation(IReadOnlyCollection<StationRuntime> stations, ScenarioDefinition scenario)
|
||||
{
|
||||
return stations.FirstOrDefault(station =>
|
||||
HasInstalledModules(station, "module_gen_prod_energycells_01", "module_arg_stor_liquid_m_01") &&
|
||||
string.Equals(StationSimulationService.DetermineStationRole(station), "refinery", StringComparison.Ordinal) &&
|
||||
station.SystemId == scenario.MiningDefaults.RefinerySystemId)
|
||||
?? stations.FirstOrDefault(station =>
|
||||
HasInstalledModules(station, "module_gen_prod_energycells_01", "module_arg_stor_liquid_m_01"));
|
||||
string.Equals(StationSimulationService.DetermineStationRole(station), "refinery", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
internal List<ClaimRuntime> CreateClaims(
|
||||
@@ -116,23 +138,21 @@ internal sealed class WorldSeedingService
|
||||
}
|
||||
|
||||
internal (List<ConstructionSiteRuntime> ConstructionSites, List<MarketOrderRuntime> MarketOrders) CreateConstructionSites(
|
||||
IReadOnlyCollection<StationRuntime> stations,
|
||||
IReadOnlyCollection<ClaimRuntime> claims,
|
||||
IReadOnlyDictionary<string, ModuleRecipeDefinition> moduleRecipes)
|
||||
SimulationWorld world)
|
||||
{
|
||||
var sites = new List<ConstructionSiteRuntime>();
|
||||
var orders = new List<MarketOrderRuntime>();
|
||||
|
||||
foreach (var station in stations)
|
||||
foreach (var station in world.Stations)
|
||||
{
|
||||
var moduleId = GetNextConstructionSiteModule(station, moduleRecipes);
|
||||
var moduleId = InfrastructureSimulationService.GetNextStationModuleToBuild(station, world);
|
||||
if (moduleId is null || station.CelestialId is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var claim = claims.FirstOrDefault(candidate => candidate.CelestialId == station.CelestialId);
|
||||
if (claim is null || !moduleRecipes.TryGetValue(moduleId, out var recipe))
|
||||
var claim = world.Claims.FirstOrDefault(candidate => candidate.CelestialId == station.CelestialId);
|
||||
if (claim is null || !world.ModuleRecipes.TryGetValue(moduleId, out var recipe))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -294,23 +314,40 @@ internal sealed class WorldSeedingService
|
||||
internal static DefaultBehaviorRuntime CreateBehavior(
|
||||
ShipDefinition definition,
|
||||
string systemId,
|
||||
string factionId,
|
||||
ScenarioDefinition scenario,
|
||||
IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
|
||||
IReadOnlyCollection<StationRuntime> stations,
|
||||
StationRuntime? refinery)
|
||||
{
|
||||
if (string.Equals(definition.Kind, "construction", StringComparison.Ordinal) && refinery is not null)
|
||||
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)
|
||||
{
|
||||
return new DefaultBehaviorRuntime
|
||||
{
|
||||
Kind = "construct-station",
|
||||
StationId = refinery.Id,
|
||||
StationId = homeStation.Id,
|
||||
Phase = "travel-to-station",
|
||||
};
|
||||
}
|
||||
|
||||
if (HasCapabilities(definition, "mining") && refinery is not null)
|
||||
if (HasCapabilities(definition, "mining") && homeStation is not null)
|
||||
{
|
||||
return CreateResourceHarvestBehavior("auto-mine", scenario.MiningDefaults.NodeSystemId, refinery.Id);
|
||||
return CreateResourceHarvestBehavior("auto-mine", scenario.MiningDefaults.NodeSystemId, homeStation.Id);
|
||||
}
|
||||
|
||||
if (string.Equals(definition.Kind, "transport", StringComparison.Ordinal))
|
||||
{
|
||||
return new DefaultBehaviorRuntime
|
||||
{
|
||||
Kind = "trade-haul",
|
||||
Phase = "travel-to-source",
|
||||
};
|
||||
}
|
||||
|
||||
if (string.Equals(definition.Kind, "military", StringComparison.Ordinal) && patrolRoutes.TryGetValue(systemId, out var route))
|
||||
@@ -318,6 +355,7 @@ internal sealed class WorldSeedingService
|
||||
return new DefaultBehaviorRuntime
|
||||
{
|
||||
Kind = "patrol",
|
||||
StationId = homeStation?.Id,
|
||||
PatrolPoints = route,
|
||||
PatrolIndex = 0,
|
||||
};
|
||||
@@ -340,6 +378,13 @@ internal sealed class WorldSeedingService
|
||||
Color = "#7ed4ff",
|
||||
Credits = MinimumFactionCredits,
|
||||
},
|
||||
"asterion-league" => new FactionRuntime
|
||||
{
|
||||
Id = factionId,
|
||||
Label = "Asterion League",
|
||||
Color = "#ff8f70",
|
||||
Credits = MinimumFactionCredits,
|
||||
},
|
||||
_ => new FactionRuntime
|
||||
{
|
||||
Id = factionId,
|
||||
@@ -350,31 +395,6 @@ internal sealed class WorldSeedingService
|
||||
};
|
||||
}
|
||||
|
||||
private static string? GetNextConstructionSiteModule(
|
||||
StationRuntime station,
|
||||
IReadOnlyDictionary<string, ModuleRecipeDefinition> moduleRecipes)
|
||||
{
|
||||
foreach (var (moduleId, targetCount) in new (string ModuleId, int TargetCount)[]
|
||||
{
|
||||
("module_gen_prod_refinedmetals_01", 1),
|
||||
("module_arg_stor_container_m_01", 1),
|
||||
("module_gen_prod_hullparts_01", 2),
|
||||
("module_gen_prod_advancedelectronics_01", 1),
|
||||
("module_gen_build_l_01", 1),
|
||||
("module_gen_prod_energycells_01", 2),
|
||||
("module_arg_dock_m_01_lowtech", 2),
|
||||
})
|
||||
{
|
||||
if (CountModules(station.InstalledModules, moduleId) < targetCount
|
||||
&& moduleRecipes.ContainsKey(moduleId))
|
||||
{
|
||||
return moduleId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void InitializeStationPopulation(StationRuntime station)
|
||||
{
|
||||
var habitatModules = CountModules(station.InstalledModules, "module_arg_hab_m_01");
|
||||
@@ -406,6 +426,8 @@ internal sealed class WorldSeedingService
|
||||
{
|
||||
Kind = behavior.Kind,
|
||||
AreaSystemId = behavior.AreaSystemId,
|
||||
TargetEntityId = behavior.TargetEntityId,
|
||||
ItemId = behavior.ItemId,
|
||||
ModuleId = behavior.ModuleId,
|
||||
NodeId = behavior.NodeId,
|
||||
Phase = behavior.Phase,
|
||||
|
||||
Reference in New Issue
Block a user