579 lines
22 KiB
C#
579 lines
22 KiB
C#
using SpaceGame.Api.Shared.Runtime;
|
|
using static SpaceGame.Api.Universe.Scenario.LoaderSupport;
|
|
|
|
namespace SpaceGame.Api.Universe.Scenario;
|
|
|
|
internal sealed class WorldSeedingService
|
|
{
|
|
internal List<FactionRuntime> CreateFactions(
|
|
IReadOnlyCollection<StationRuntime> stations,
|
|
IReadOnlyCollection<ShipRuntime> ships)
|
|
{
|
|
var factionIds = stations
|
|
.Select(station => station.FactionId)
|
|
.Concat(ships.Select(ship => ship.FactionId))
|
|
.Where(factionId => !string.IsNullOrWhiteSpace(factionId))
|
|
.Distinct(StringComparer.Ordinal)
|
|
.OrderBy(factionId => factionId, StringComparer.Ordinal)
|
|
.ToList();
|
|
|
|
if (factionIds.Count == 0)
|
|
{
|
|
factionIds.Add(DefaultFactionId);
|
|
}
|
|
|
|
return factionIds.Select(CreateFaction).ToList();
|
|
}
|
|
|
|
internal void BootstrapFactionEconomy(
|
|
IReadOnlyCollection<FactionRuntime> factions,
|
|
IReadOnlyCollection<StationRuntime> stations)
|
|
{
|
|
foreach (var faction in factions)
|
|
{
|
|
faction.Credits = MathF.Max(faction.Credits, MinimumFactionCredits);
|
|
|
|
var ownedStations = stations
|
|
.Where(station => station.FactionId == faction.Id)
|
|
.ToList();
|
|
|
|
var refineries = ownedStations
|
|
.Where(station => string.Equals(StationSimulationService.DetermineStationRole(station), "refinery", StringComparison.Ordinal))
|
|
.ToList();
|
|
|
|
if (refineries.Count > 0)
|
|
{
|
|
foreach (var refinery in refineries)
|
|
{
|
|
refinery.Inventory["refinedmetals"] = MathF.Max(GetInventoryAmount(refinery.Inventory, "refinedmetals"), MinimumRefineryStock);
|
|
}
|
|
|
|
if (refineries.All(station => GetInventoryAmount(station.Inventory, "ore") < MinimumRefineryOre))
|
|
{
|
|
refineries[0].Inventory["ore"] = MinimumRefineryOre;
|
|
}
|
|
}
|
|
|
|
foreach (var shipyard in ownedStations.Where(station => HasInstalledModules(station, "module_gen_build_l_01")))
|
|
{
|
|
shipyard.Inventory["refinedmetals"] = MathF.Max(GetInventoryAmount(shipyard.Inventory, "refinedmetals"), MinimumShipyardStock);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void InitializeStationStockpiles(
|
|
IReadOnlyCollection<StationRuntime> stations,
|
|
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
|
|
{
|
|
foreach (var station in stations)
|
|
{
|
|
InitializeStationPopulation(station, moduleDefinitions);
|
|
}
|
|
}
|
|
|
|
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,
|
|
DateTimeOffset nowUtc)
|
|
{
|
|
var stationsByCelestialId = stations
|
|
.Where(station => station.CelestialId is not null)
|
|
.ToDictionary(station => station.CelestialId!, StringComparer.Ordinal);
|
|
var claims = new List<ClaimRuntime>();
|
|
|
|
foreach (var celestial in celestials.Where(c => c.Kind == SpatialNodeKind.LagrangePoint))
|
|
{
|
|
if (!stationsByCelestialId.TryGetValue(celestial.Id, out var station))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
claims.Add(new ClaimRuntime
|
|
{
|
|
Id = $"claim-{celestial.Id}",
|
|
FactionId = station.FactionId,
|
|
SystemId = celestial.SystemId,
|
|
CelestialId = celestial.Id,
|
|
PlacedAtUtc = nowUtc,
|
|
ActivatesAtUtc = nowUtc.AddSeconds(8),
|
|
State = ClaimStateKinds.Activating,
|
|
Health = 100f,
|
|
});
|
|
}
|
|
|
|
return claims;
|
|
}
|
|
|
|
internal (List<ConstructionSiteRuntime> ConstructionSites, List<MarketOrderRuntime> MarketOrders) CreateConstructionSites(
|
|
SimulationWorld world)
|
|
{
|
|
var sites = new List<ConstructionSiteRuntime>();
|
|
var orders = new List<MarketOrderRuntime>();
|
|
|
|
foreach (var station in world.Stations)
|
|
{
|
|
if (HasSatisfiedStarterObjectiveLayout(world, station))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var moduleId = InfrastructureSimulationService.GetNextStationModuleToBuild(station, world);
|
|
if (moduleId is null || station.CelestialId is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var claim = world.Claims.FirstOrDefault(candidate => candidate.CelestialId == station.CelestialId);
|
|
if (claim is null || !world.ModuleRecipes.TryGetValue(moduleId, out var recipe))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var site = new ConstructionSiteRuntime
|
|
{
|
|
Id = $"site-{station.Id}",
|
|
FactionId = station.FactionId,
|
|
SystemId = station.SystemId,
|
|
CelestialId = station.CelestialId,
|
|
TargetKind = "station-module",
|
|
TargetDefinitionId = "station",
|
|
BlueprintId = moduleId,
|
|
ClaimId = claim.Id,
|
|
StationId = station.Id,
|
|
State = claim.State == ClaimStateKinds.Active ? ConstructionSiteStateKinds.Active : ConstructionSiteStateKinds.Planned,
|
|
};
|
|
|
|
foreach (var input in recipe.Inputs)
|
|
{
|
|
site.RequiredItems[input.ItemId] = input.Amount;
|
|
site.DeliveredItems[input.ItemId] = 0f;
|
|
|
|
var orderId = $"market-order-{station.Id}-{moduleId}-{input.ItemId}";
|
|
site.MarketOrderIds.Add(orderId);
|
|
station.MarketOrderIds.Add(orderId);
|
|
orders.Add(new MarketOrderRuntime
|
|
{
|
|
Id = orderId,
|
|
FactionId = station.FactionId,
|
|
StationId = station.Id,
|
|
ConstructionSiteId = site.Id,
|
|
Kind = MarketOrderKinds.Buy,
|
|
ItemId = input.ItemId,
|
|
Amount = input.Amount,
|
|
RemainingAmount = input.Amount,
|
|
Valuation = 1f,
|
|
State = MarketOrderStateKinds.Open,
|
|
});
|
|
}
|
|
|
|
sites.Add(site);
|
|
}
|
|
|
|
return (sites, orders);
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
if (objectiveModuleId is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!station.InstalledModules.Contains("module_arg_dock_m_01_lowtech", 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))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var storageModuleId in GetRequiredStorageModulesForInstalledObjective(world, objectiveModuleId))
|
|
{
|
|
if (!station.InstalledModules.Contains(storageModuleId, StringComparer.Ordinal))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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.Production
|
|
.SelectMany(production => production.Wares.Select(ware => ware.ItemId))
|
|
.Concat(moduleDefinition.Products)
|
|
.Distinct(StringComparer.Ordinal))
|
|
{
|
|
if (!world.ItemDefinitions.TryGetValue(wareId, out var itemDefinition))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
yield return itemDefinition.CargoKind switch
|
|
{
|
|
"solid" => "module_arg_stor_solid_m_01",
|
|
"liquid" => "module_arg_stor_liquid_m_01",
|
|
_ => "module_arg_stor_container_m_01",
|
|
};
|
|
}
|
|
}
|
|
|
|
internal List<PolicySetRuntime> CreatePolicies(IReadOnlyCollection<FactionRuntime> factions)
|
|
{
|
|
var policies = new List<PolicySetRuntime>(factions.Count);
|
|
foreach (var faction in factions)
|
|
{
|
|
var policyId = $"policy-{faction.Id}";
|
|
faction.DefaultPolicySetId = policyId;
|
|
policies.Add(new PolicySetRuntime
|
|
{
|
|
Id = policyId,
|
|
OwnerKind = "faction",
|
|
OwnerId = faction.Id,
|
|
});
|
|
}
|
|
|
|
return policies;
|
|
}
|
|
|
|
internal List<CommanderRuntime> CreateCommanders(
|
|
IReadOnlyCollection<FactionRuntime> factions,
|
|
IReadOnlyCollection<StationRuntime> stations,
|
|
IReadOnlyCollection<ShipRuntime> ships)
|
|
{
|
|
var commanders = new List<CommanderRuntime>();
|
|
var factionCommanders = new Dictionary<string, CommanderRuntime>(StringComparer.Ordinal);
|
|
var factionsById = factions.ToDictionary(faction => faction.Id, StringComparer.Ordinal);
|
|
|
|
foreach (var faction in factions)
|
|
{
|
|
var commander = new CommanderRuntime
|
|
{
|
|
Id = $"commander-faction-{faction.Id}",
|
|
Kind = CommanderKind.Faction,
|
|
FactionId = faction.Id,
|
|
ControlledEntityId = faction.Id,
|
|
PolicySetId = faction.DefaultPolicySetId,
|
|
Doctrine = "strategic-control",
|
|
};
|
|
|
|
commanders.Add(commander);
|
|
factionCommanders[faction.Id] = commander;
|
|
faction.CommanderIds.Add(commander.Id);
|
|
}
|
|
|
|
foreach (var station in stations)
|
|
{
|
|
if (!factionCommanders.TryGetValue(station.FactionId, out var parentCommander))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var commander = new CommanderRuntime
|
|
{
|
|
Id = $"commander-station-{station.Id}",
|
|
Kind = CommanderKind.Station,
|
|
FactionId = station.FactionId,
|
|
ParentCommanderId = parentCommander.Id,
|
|
ControlledEntityId = station.Id,
|
|
PolicySetId = parentCommander.PolicySetId,
|
|
Doctrine = "station-control",
|
|
};
|
|
|
|
station.CommanderId = commander.Id;
|
|
station.PolicySetId = parentCommander.PolicySetId;
|
|
parentCommander.SubordinateCommanderIds.Add(commander.Id);
|
|
factionsById[station.FactionId].CommanderIds.Add(commander.Id);
|
|
commanders.Add(commander);
|
|
}
|
|
|
|
foreach (var ship in ships)
|
|
{
|
|
if (!factionCommanders.TryGetValue(ship.FactionId, out var parentCommander))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var commander = new CommanderRuntime
|
|
{
|
|
Id = $"commander-ship-{ship.Id}",
|
|
Kind = CommanderKind.Ship,
|
|
FactionId = ship.FactionId,
|
|
ParentCommanderId = parentCommander.Id,
|
|
ControlledEntityId = ship.Id,
|
|
PolicySetId = parentCommander.PolicySetId,
|
|
Doctrine = "ship-control",
|
|
};
|
|
|
|
ship.CommanderId = commander.Id;
|
|
ship.PolicySetId = parentCommander.PolicySetId;
|
|
parentCommander.SubordinateCommanderIds.Add(commander.Id);
|
|
factionsById[ship.FactionId].CommanderIds.Add(commander.Id);
|
|
commanders.Add(commander);
|
|
}
|
|
|
|
return commanders;
|
|
}
|
|
|
|
internal PlayerFactionRuntime CreatePlayerFaction(
|
|
IReadOnlyCollection<FactionRuntime> factions,
|
|
IReadOnlyCollection<StationRuntime> stations,
|
|
IReadOnlyCollection<ShipRuntime> ships,
|
|
IReadOnlyCollection<CommanderRuntime> commanders,
|
|
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 player = new PlayerFactionRuntime
|
|
{
|
|
Id = "player-faction",
|
|
Label = $"{sovereignFaction.Label} Command",
|
|
SovereignFactionId = sovereignFaction.Id,
|
|
CreatedAtUtc = nowUtc,
|
|
UpdatedAtUtc = nowUtc,
|
|
};
|
|
|
|
foreach (var shipId in ships.Where(ship => ship.FactionId == sovereignFaction.Id).Select(ship => ship.Id))
|
|
{
|
|
player.AssetRegistry.ShipIds.Add(shipId);
|
|
}
|
|
|
|
foreach (var stationId in stations.Where(station => station.FactionId == sovereignFaction.Id).Select(station => station.Id))
|
|
{
|
|
player.AssetRegistry.StationIds.Add(stationId);
|
|
}
|
|
|
|
foreach (var commanderId in commanders.Where(commander => commander.FactionId == sovereignFaction.Id).Select(commander => commander.Id))
|
|
{
|
|
player.AssetRegistry.CommanderIds.Add(commanderId);
|
|
}
|
|
|
|
foreach (var policy in policies.Where(policy => string.Equals(policy.OwnerId, sovereignFaction.Id, StringComparison.Ordinal)))
|
|
{
|
|
player.AssetRegistry.PolicySetIds.Add(policy.Id);
|
|
}
|
|
|
|
player.Policies.Add(new PlayerFactionPolicyRuntime
|
|
{
|
|
Id = "player-core-policy",
|
|
Label = "Core Empire Policy",
|
|
ScopeKind = "player-faction",
|
|
ScopeId = player.Id,
|
|
PolicySetId = sovereignFaction.DefaultPolicySetId,
|
|
TradeAccessPolicy = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.TradeAccessPolicy ?? "owner-and-allies",
|
|
DockingAccessPolicy = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.DockingAccessPolicy ?? "owner-and-allies",
|
|
ConstructionAccessPolicy = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.ConstructionAccessPolicy ?? "owner-only",
|
|
OperationalRangePolicy = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.OperationalRangePolicy ?? "unrestricted",
|
|
CombatEngagementPolicy = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.CombatEngagementPolicy ?? "defensive",
|
|
AvoidHostileSystems = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.AvoidHostileSystems ?? true,
|
|
FleeHullRatio = policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId)?.FleeHullRatio ?? 0.35f,
|
|
UpdatedAtUtc = nowUtc,
|
|
});
|
|
|
|
if (policies.FirstOrDefault(policy => policy.Id == sovereignFaction.DefaultPolicySetId) is { } defaultPolicy)
|
|
{
|
|
foreach (var systemId in defaultPolicy.BlacklistedSystemIds)
|
|
{
|
|
player.Policies[0].BlacklistedSystemIds.Add(systemId);
|
|
}
|
|
}
|
|
|
|
player.AutomationPolicies.Add(new PlayerAutomationPolicyRuntime
|
|
{
|
|
Id = "player-core-automation",
|
|
Label = "Core Automation",
|
|
ScopeKind = "player-faction",
|
|
ScopeId = player.Id,
|
|
BehaviorKind = "idle",
|
|
UpdatedAtUtc = nowUtc,
|
|
});
|
|
|
|
player.Reserves.Add(new PlayerReserveGroupRuntime
|
|
{
|
|
Id = "player-core-reserve",
|
|
Label = "Strategic Reserve",
|
|
ReserveKind = "military",
|
|
UpdatedAtUtc = nowUtc,
|
|
});
|
|
player.AssetRegistry.ReserveIds.Add("player-core-reserve");
|
|
|
|
return player;
|
|
}
|
|
|
|
internal static DefaultBehaviorRuntime CreateBehavior(
|
|
ShipDefinition definition,
|
|
string systemId,
|
|
string factionId,
|
|
ScenarioDefinition scenario,
|
|
IReadOnlyDictionary<string, List<Vector3>> patrolRoutes,
|
|
IReadOnlyCollection<StationRuntime> stations,
|
|
StationRuntime? refinery)
|
|
{
|
|
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",
|
|
HomeSystemId = homeStation.SystemId,
|
|
HomeStationId = homeStation.Id,
|
|
PreferredConstructionSiteId = null,
|
|
};
|
|
}
|
|
|
|
if (HasCapabilities(definition, "mining") && homeStation is not null)
|
|
{
|
|
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,
|
|
};
|
|
}
|
|
|
|
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 void InitializeStationPopulation(
|
|
StationRuntime station,
|
|
IReadOnlyDictionary<string, ModuleDefinition> moduleDefinitions)
|
|
{
|
|
var habitatModules = CountModules(station.InstalledModules, moduleDefinitions, ModuleType.Habitation);
|
|
station.PopulationCapacity = 40f + (habitatModules * 220f);
|
|
station.WorkforceRequired = MathF.Max(12f, station.Modules.Count * 14f);
|
|
station.Population = habitatModules > 0
|
|
? MathF.Min(station.PopulationCapacity * 0.65f, station.WorkforceRequired * 1.05f)
|
|
: MathF.Min(28f, station.PopulationCapacity);
|
|
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..]));
|
|
}
|
|
|
|
}
|