feat: massive AI generation
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
using static SpaceGame.Api.Ships.Simulation.ShipControlService;
|
||||
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
|
||||
|
||||
namespace SpaceGame.Api.Stations.Simulation;
|
||||
@@ -80,7 +79,7 @@ internal sealed class StationLifecycleService
|
||||
TargetPosition = spawnPosition,
|
||||
SpatialState = CreateSpawnedShipSpatialState(station, spawnPosition),
|
||||
DefaultBehavior = CreateSpawnedShipBehavior(definition, station),
|
||||
ControllerTask = CreateIdleTask(world.Balance.ArrivalThreshold),
|
||||
Skills = WorldSeedingService.CreateSkills(definition),
|
||||
Health = definition.MaxHealth,
|
||||
};
|
||||
|
||||
@@ -109,13 +108,22 @@ internal sealed class StationLifecycleService
|
||||
{
|
||||
if (!string.Equals(definition.Kind, "military", StringComparison.Ordinal))
|
||||
{
|
||||
return new DefaultBehaviorRuntime { Kind = "idle" };
|
||||
return new DefaultBehaviorRuntime
|
||||
{
|
||||
Kind = string.Equals(definition.Kind, "transport", StringComparison.Ordinal) ? "advanced-auto-trade" : "idle",
|
||||
HomeSystemId = station.SystemId,
|
||||
HomeStationId = station.Id,
|
||||
MaxSystemRange = string.Equals(definition.Kind, "transport", StringComparison.Ordinal) ? 2 : 0,
|
||||
};
|
||||
}
|
||||
|
||||
var patrolRadius = station.Radius + 90f;
|
||||
return new DefaultBehaviorRuntime
|
||||
{
|
||||
Kind = "patrol",
|
||||
HomeSystemId = station.SystemId,
|
||||
HomeStationId = station.Id,
|
||||
AreaSystemId = station.SystemId,
|
||||
PatrolPoints =
|
||||
[
|
||||
new Vector3(station.Position.X + patrolRadius, station.Position.Y, station.Position.Z),
|
||||
@@ -150,7 +158,13 @@ internal sealed class StationLifecycleService
|
||||
ParentCommanderId = factionCommander.Id,
|
||||
ControlledEntityId = station.Id,
|
||||
PolicySetId = factionCommander.PolicySetId,
|
||||
Doctrine = "station-default",
|
||||
Doctrine = "station-control",
|
||||
Skills = new CommanderSkillProfileRuntime
|
||||
{
|
||||
Leadership = 3,
|
||||
Coordination = Math.Clamp(3 + (station.Modules.Count / 8), 3, 5),
|
||||
Strategy = 3,
|
||||
},
|
||||
};
|
||||
|
||||
station.CommanderId = commander.Id;
|
||||
@@ -179,25 +193,12 @@ internal sealed class StationLifecycleService
|
||||
ParentCommanderId = factionCommander.Id,
|
||||
ControlledEntityId = ship.Id,
|
||||
PolicySetId = factionCommander.PolicySetId,
|
||||
Doctrine = "ship-default",
|
||||
ActiveBehavior = new CommanderBehaviorRuntime
|
||||
Doctrine = "ship-control",
|
||||
Skills = new CommanderSkillProfileRuntime
|
||||
{
|
||||
Kind = ship.DefaultBehavior.Kind,
|
||||
AreaSystemId = ship.DefaultBehavior.AreaSystemId,
|
||||
TargetEntityId = ship.DefaultBehavior.TargetEntityId,
|
||||
ItemId = ship.DefaultBehavior.ItemId,
|
||||
StationId = ship.DefaultBehavior.StationId,
|
||||
ModuleId = ship.DefaultBehavior.ModuleId,
|
||||
NodeId = ship.DefaultBehavior.NodeId,
|
||||
Phase = ship.DefaultBehavior.Phase,
|
||||
PatrolIndex = ship.DefaultBehavior.PatrolIndex,
|
||||
},
|
||||
ActiveTask = new CommanderTaskRuntime
|
||||
{
|
||||
Kind = ShipTaskKinds.Idle,
|
||||
Status = WorkStatus.Pending,
|
||||
TargetSystemId = ship.SystemId,
|
||||
Threshold = 0f,
|
||||
Leadership = Math.Clamp((ship.Skills.Navigation + ship.Skills.Combat + 1) / 2, 2, 5),
|
||||
Coordination = Math.Clamp((ship.Skills.Trade + ship.Skills.Mining + 1) / 2, 2, 5),
|
||||
Strategy = Math.Clamp((ship.Skills.Combat + ship.Skills.Construction + 1) / 2, 2, 5),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ internal sealed class StationSimulationService
|
||||
var superfluidCoolantReserve = role == "superfluidcoolant" ? 120f : 0f;
|
||||
var quantumTubesReserve = role == "quantumtubes" ? 120f : 0f;
|
||||
var shipPartsReserve = HasStationModules(station, "module_gen_build_l_01")
|
||||
&& FactionCommanderHasIssuedTask(world, station.FactionId, FactionIssuedTaskKind.ProduceShips, "military")
|
||||
&& GetShipProductionPressure(world, station.FactionId, "military") > 0.2f
|
||||
? 90f
|
||||
: 0f;
|
||||
|
||||
@@ -104,6 +104,7 @@ internal sealed class StationSimulationService
|
||||
AddSupplyOrder(desiredOrders, station, "superfluidcoolant", ScaleSupplyTriggerByEconomy(economy, "superfluidcoolant", MathF.Max(superfluidCoolantReserve * 1.35f, superfluidCoolantReserve + 30f)), reserveFloor: superfluidCoolantReserve, valuationBase: ScaleSupplyValuation(economy, "superfluidcoolant", 0.9f));
|
||||
AddSupplyOrder(desiredOrders, station, "quantumtubes", ScaleSupplyTriggerByEconomy(economy, "quantumtubes", MathF.Max(quantumTubesReserve * 1.35f, quantumTubesReserve + 30f)), reserveFloor: quantumTubesReserve, valuationBase: ScaleSupplyValuation(economy, "quantumtubes", 0.9f));
|
||||
|
||||
desiredOrders = ApplyRegionalMarketModifiers(world, station, desiredOrders);
|
||||
ReconcileStationMarketOrders(world, station, desiredOrders);
|
||||
}
|
||||
|
||||
@@ -116,7 +117,7 @@ internal sealed class StationSimulationService
|
||||
var constructionClayReserve = GetConstructionDemandForItem(world, site, "claytronics");
|
||||
var constructionRefinedReserve = GetConstructionDemandForItem(world, site, "refinedmetals");
|
||||
var shipPartsReserve = HasStationModules(station, "module_gen_build_l_01")
|
||||
&& FactionCommanderHasIssuedTask(world, station.FactionId, FactionIssuedTaskKind.ProduceShips, "military")
|
||||
&& GetShipProductionPressure(world, station.FactionId, "military") > 0.2f
|
||||
? 90f
|
||||
: 0f;
|
||||
|
||||
@@ -257,8 +258,9 @@ internal sealed class StationSimulationService
|
||||
var priority = (float)recipe.Priority;
|
||||
|
||||
var expansionPressure = GetFactionExpansionPressure(world, station.FactionId);
|
||||
var fleetPressure = FactionCommanderHasIssuedTask(world, station.FactionId, FactionIssuedTaskKind.ProduceShips, "military") ? 1f : 0f;
|
||||
var fleetPressure = GetShipProductionPressure(world, station.FactionId, "military");
|
||||
priority += GetStationRecipePriorityAdjustment(world, station, recipe, expansionPressure, fleetPressure);
|
||||
priority += GetStrategicRecipeBias(world, station, recipe);
|
||||
|
||||
return priority;
|
||||
}
|
||||
@@ -321,6 +323,52 @@ internal sealed class StationSimulationService
|
||||
};
|
||||
}
|
||||
|
||||
private static float GetStrategicRecipeBias(SimulationWorld world, StationRuntime station, RecipeDefinition recipe)
|
||||
{
|
||||
var commander = station.CommanderId is null
|
||||
? null
|
||||
: world.Commanders.FirstOrDefault(candidate => candidate.Id == station.CommanderId);
|
||||
var assignment = commander?.Assignment;
|
||||
if (assignment is null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
var outputItemIds = recipe.Outputs
|
||||
.Select(output => output.ItemId)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
if (string.Equals(assignment.Kind, "ship-production-focus", StringComparison.Ordinal)
|
||||
&& recipe.ShipOutputId is not null
|
||||
&& world.ShipDefinitions.TryGetValue(recipe.ShipOutputId, out var shipDefinition)
|
||||
&& string.Equals(shipDefinition.Kind, "military", StringComparison.Ordinal))
|
||||
{
|
||||
return 260f;
|
||||
}
|
||||
|
||||
if (string.Equals(assignment.Kind, "commodity-focus", StringComparison.Ordinal)
|
||||
&& assignment.ItemId is not null
|
||||
&& outputItemIds.Contains(assignment.ItemId))
|
||||
{
|
||||
return 220f;
|
||||
}
|
||||
|
||||
if (string.Equals(assignment.Kind, "expansion-support", StringComparison.Ordinal)
|
||||
&& outputItemIds.Overlaps(["energycells", "refinedmetals", "hullparts", "claytronics"]))
|
||||
{
|
||||
return 180f;
|
||||
}
|
||||
|
||||
if (string.Equals(assignment.Kind, "station-oversight", StringComparison.Ordinal)
|
||||
&& assignment.ItemId is not null
|
||||
&& outputItemIds.Contains(assignment.ItemId))
|
||||
{
|
||||
return 90f;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
internal static bool RecipeAppliesToStation(StationRuntime station, RecipeDefinition recipe)
|
||||
{
|
||||
var categoryMatch = string.Equals(recipe.FacilityCategory, "station", StringComparison.Ordinal)
|
||||
@@ -338,7 +386,7 @@ internal sealed class StationSimulationService
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FactionCommanderHasIssuedTask(world, station.FactionId, FactionIssuedTaskKind.ProduceShips, shipDefinition.Kind))
|
||||
if (GetShipProductionPressure(world, station.FactionId, shipDefinition.Kind) <= 0.05f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -559,12 +607,20 @@ internal sealed class StationSimulationService
|
||||
var targetSystems = Math.Max(1, Math.Min(StrategicControlTargetSystems, world.Systems.Count));
|
||||
var controlledSystems = GetFactionControlledSystemsCount(world, factionId);
|
||||
var deficit = Math.Max(0, targetSystems - controlledSystems);
|
||||
return Math.Clamp(deficit / (float)targetSystems, 0f, 1f);
|
||||
var contestedSystems = world.Geopolitics?.Territory.ControlStates.Count(state =>
|
||||
state.IsContested
|
||||
&& (string.Equals(state.ControllerFactionId, factionId, StringComparison.Ordinal)
|
||||
|| string.Equals(state.PrimaryClaimantFactionId, factionId, StringComparison.Ordinal)
|
||||
|| state.ClaimantFactionIds.Contains(factionId, StringComparer.Ordinal))) ?? 0;
|
||||
var frontierSystems = world.Geopolitics?.Territory.Zones.Count(zone =>
|
||||
string.Equals(zone.FactionId, factionId, StringComparison.Ordinal)
|
||||
&& zone.Kind is "frontier" or "corridor" or "contested") ?? 0;
|
||||
return Math.Clamp((deficit / (float)targetSystems) + (contestedSystems * 0.12f) + (frontierSystems * 0.04f), 0f, 1f);
|
||||
}
|
||||
|
||||
internal static int GetFactionControlledSystemsCount(SimulationWorld world, string factionId)
|
||||
{
|
||||
return world.Systems.Count(system => FactionControlsSystem(world, factionId, system.Definition.Id));
|
||||
return GeopoliticalSimulationService.GetControlledSystems(world, factionId).Count;
|
||||
}
|
||||
|
||||
private static float ScaleReserveByEconomy(FactionEconomySnapshot economy, string itemId, float baseReserve)
|
||||
@@ -612,34 +668,66 @@ internal sealed class StationSimulationService
|
||||
: baseValuation;
|
||||
}
|
||||
|
||||
private static List<DesiredMarketOrder> ApplyRegionalMarketModifiers(SimulationWorld world, StationRuntime station, IReadOnlyCollection<DesiredMarketOrder> desiredOrders)
|
||||
{
|
||||
var region = GeopoliticalSimulationService.GetPrimaryEconomicRegion(world, station.FactionId, station.SystemId);
|
||||
if (region is null)
|
||||
{
|
||||
return desiredOrders.ToList();
|
||||
}
|
||||
|
||||
var security = world.Geopolitics?.EconomyRegions.SecurityAssessments.FirstOrDefault(assessment => string.Equals(assessment.RegionId, region.Id, StringComparison.Ordinal));
|
||||
var economic = world.Geopolitics?.EconomyRegions.EconomicAssessments.FirstOrDefault(assessment => string.Equals(assessment.RegionId, region.Id, StringComparison.Ordinal));
|
||||
var bottlenecks = world.Geopolitics?.EconomyRegions.Bottlenecks
|
||||
.Where(bottleneck => string.Equals(bottleneck.RegionId, region.Id, StringComparison.Ordinal))
|
||||
.ToDictionary(bottleneck => bottleneck.ItemId, StringComparer.Ordinal) ?? new Dictionary<string, RegionalBottleneckRuntime>(StringComparer.Ordinal);
|
||||
var riskMultiplier = 1f + ((security?.SupplyRisk ?? 0f) * 0.3f) + ((security?.AccessFriction ?? 0f) * 0.2f);
|
||||
var sustainmentFloor = 1f + MathF.Max(0f, 0.55f - (economic?.SustainmentScore ?? 1f));
|
||||
|
||||
return desiredOrders
|
||||
.Select(order =>
|
||||
{
|
||||
bottlenecks.TryGetValue(order.ItemId, out var bottleneck);
|
||||
var severity = bottleneck?.Severity ?? 0f;
|
||||
var buyBias = order.Kind == MarketOrderKinds.Buy ? 1f + (severity * 0.08f) : 1f;
|
||||
var sellBias = order.Kind == MarketOrderKinds.Sell && severity > 0f ? MathF.Max(0.35f, 1f - (severity * 0.07f)) : 1f;
|
||||
var amount = order.Amount * (order.Kind == MarketOrderKinds.Buy ? riskMultiplier * buyBias * sustainmentFloor : sellBias);
|
||||
var valuation = order.Valuation * (order.Kind == MarketOrderKinds.Buy
|
||||
? 1f + (severity * 0.06f) + ((security?.SupplyRisk ?? 0f) * 0.18f)
|
||||
: 1f + (severity * 0.04f));
|
||||
float? reserveThreshold = order.ReserveThreshold.HasValue
|
||||
? order.ReserveThreshold.Value * (1f + ((security?.SupplyRisk ?? 0f) * 0.15f))
|
||||
: null;
|
||||
return new DesiredMarketOrder(order.Kind, order.ItemId, amount, valuation, reserveThreshold);
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static float GetShipProductionPressure(SimulationWorld world, string factionId, string shipKind)
|
||||
{
|
||||
var factionCommander = FindFactionCommander(world, factionId);
|
||||
var task = GetHighestPriorityIssuedTask(factionCommander, FactionIssuedTaskKind.ProduceShips, shipKind);
|
||||
if (task is null)
|
||||
var economic = FindFactionEconomicAssessment(world, factionId);
|
||||
var threat = FindFactionThreatAssessment(world, factionId);
|
||||
if (economic is null || threat is null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
return task.State == FactionIssuedTaskState.Blocked ? 0.4f : 1f;
|
||||
return shipKind switch
|
||||
{
|
||||
"military" => threat.EnemyFactionCount > 0
|
||||
? economic.MilitaryShipCount < Math.Max(4, economic.ControlledSystemCount * 2) ? 1f : 0.25f
|
||||
: 0.1f,
|
||||
"construction" => economic.PrimaryExpansionSiteId is not null
|
||||
? economic.ConstructorShipCount < 1 ? 1f : 0.35f
|
||||
: economic.ConstructorShipCount < 1 ? 0.5f : 0f,
|
||||
"transport" => economic.TransportShipCount < Math.Max(2, economic.ControlledSystemCount) ? 0.8f : 0.2f,
|
||||
_ when shipKind == "mining" || shipKind == "miner" => economic.MinerShipCount < Math.Max(2, economic.ControlledSystemCount) ? 0.85f : 0.2f,
|
||||
_ => 0.15f,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool FactionControlsSystem(SimulationWorld world, string factionId, string systemId)
|
||||
{
|
||||
var totalLagrangePoints = world.Celestials.Count(node =>
|
||||
node.SystemId == systemId &&
|
||||
node.Kind == SpatialNodeKind.LagrangePoint);
|
||||
if (totalLagrangePoints == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var ownedLocations = world.Claims.Count(claim =>
|
||||
claim.SystemId == systemId &&
|
||||
claim.FactionId == factionId &&
|
||||
claim.State is ClaimStateKinds.Activating or ClaimStateKinds.Active);
|
||||
return ownedLocations > (totalLagrangePoints / 2f);
|
||||
}
|
||||
=> GeopoliticalSimulationService.FactionControlsSystem(world, factionId, systemId);
|
||||
|
||||
private sealed record DesiredMarketOrder(string Kind, string ItemId, float Amount, float Valuation, float? ReserveThreshold);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user