457 lines
15 KiB
C#
457 lines
15 KiB
C#
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)
|
|
{
|
|
foreach (var station in stations)
|
|
{
|
|
InitializeStationPopulation(station);
|
|
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(60f, station.Population * 1.5f);
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
|
|
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-expansionist",
|
|
};
|
|
|
|
commander.Goals.Add("control-all-systems");
|
|
commander.Goals.Add("control-five-systems-fast");
|
|
commander.Goals.Add("expand-industrial-base");
|
|
commander.Goals.Add("grow-war-fleet");
|
|
commander.Goals.Add("deter-pirate-harassment");
|
|
commander.Goals.Add("contest-rival-expansion");
|
|
|
|
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-default",
|
|
};
|
|
|
|
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-default",
|
|
ActiveBehavior = CopyBehavior(ship.DefaultBehavior),
|
|
ActiveTask = CopyTask(ship.ControllerTask, null),
|
|
};
|
|
|
|
if (ship.Order is not null)
|
|
{
|
|
commander.ActiveOrder = CopyOrder(ship.Order);
|
|
}
|
|
|
|
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 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",
|
|
StationId = homeStation.Id,
|
|
Phase = "travel-to-station",
|
|
};
|
|
}
|
|
|
|
if (HasCapabilities(definition, "mining") && homeStation is not null)
|
|
{
|
|
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))
|
|
{
|
|
return new DefaultBehaviorRuntime
|
|
{
|
|
Kind = "patrol",
|
|
StationId = homeStation?.Id,
|
|
PatrolPoints = route,
|
|
PatrolIndex = 0,
|
|
};
|
|
}
|
|
|
|
return new DefaultBehaviorRuntime
|
|
{
|
|
Kind = "idle",
|
|
};
|
|
}
|
|
|
|
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,
|
|
},
|
|
_ => new FactionRuntime
|
|
{
|
|
Id = factionId,
|
|
Label = ToFactionLabel(factionId),
|
|
Color = "#c7d2e0",
|
|
Credits = MinimumFactionCredits,
|
|
},
|
|
};
|
|
}
|
|
|
|
private static void InitializeStationPopulation(StationRuntime station)
|
|
{
|
|
var habitatModules = CountModules(station.InstalledModules, "module_arg_hab_m_01");
|
|
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..]));
|
|
}
|
|
|
|
private static DefaultBehaviorRuntime CreateResourceHarvestBehavior(string kind, string areaSystemId, string stationId) => new()
|
|
{
|
|
Kind = kind,
|
|
AreaSystemId = areaSystemId,
|
|
StationId = stationId,
|
|
Phase = "travel-to-node",
|
|
};
|
|
|
|
private static CommanderBehaviorRuntime CopyBehavior(DefaultBehaviorRuntime behavior) => new()
|
|
{
|
|
Kind = behavior.Kind,
|
|
AreaSystemId = behavior.AreaSystemId,
|
|
TargetEntityId = behavior.TargetEntityId,
|
|
ItemId = behavior.ItemId,
|
|
ModuleId = behavior.ModuleId,
|
|
NodeId = behavior.NodeId,
|
|
Phase = behavior.Phase,
|
|
PatrolIndex = behavior.PatrolIndex,
|
|
StationId = behavior.StationId,
|
|
};
|
|
|
|
private static CommanderOrderRuntime CopyOrder(ShipOrderRuntime order) => new()
|
|
{
|
|
Kind = order.Kind,
|
|
Status = order.Status,
|
|
DestinationSystemId = order.DestinationSystemId,
|
|
DestinationPosition = order.DestinationPosition,
|
|
};
|
|
|
|
private static CommanderTaskRuntime CopyTask(ControllerTaskRuntime task, string? targetNodeId) => new()
|
|
{
|
|
Kind = task.Kind.ToContractValue(),
|
|
Status = task.Status,
|
|
TargetEntityId = task.TargetEntityId,
|
|
TargetNodeId = targetNodeId ?? task.TargetNodeId,
|
|
TargetPosition = task.TargetPosition,
|
|
TargetSystemId = task.TargetSystemId,
|
|
Threshold = task.Threshold,
|
|
};
|
|
}
|