Refactor runtime bootstrap and ship control flows

This commit is contained in:
2026-04-03 01:12:26 -04:00
parent 0bb72bee35
commit 706e1cda8f
129 changed files with 9588 additions and 3548 deletions

View File

@@ -1,5 +1,6 @@
using SpaceGame.Api.Industry.Planning;
using SpaceGame.Api.Stations.Simulation;
using static SpaceGame.Api.Shared.Runtime.ShipBehaviorKinds;
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
namespace SpaceGame.Api.Factions.AI;
@@ -13,8 +14,12 @@ internal sealed class CommanderPlanningService
private const int MaxDecisionLogEntries = 40;
private const int MaxOutcomeEntries = 32;
private const int MaxAiOrdersPerShip = 2;
private const string MilitaryShipCategory = "military";
private const string MiningShipCategory = "mining";
private const string TransportShipCategory = "transport";
private const string ConstructionShipCategory = "construction";
internal void UpdateCommanders(SimulationWorld world, float deltaSeconds, ICollection<SimulationEventRecord> events)
internal void UpdateCommanders(SimulationWorld world, IPlayerStateStore playerStateStore, float deltaSeconds, ICollection<SimulationEventRecord> events)
{
EnsureHierarchy(world);
@@ -33,7 +38,7 @@ internal sealed class CommanderPlanningService
foreach (var commander in world.Commanders.Where(candidate => candidate.IsAlive && candidate.Kind == CommanderKind.Faction).ToList())
{
if (PlayerFactionService.IsPlayerFaction(world, commander.FactionId))
if (PlayerFactionService.IsPlayerFaction(playerStateStore, commander.FactionId))
{
continue;
}
@@ -48,7 +53,7 @@ internal sealed class CommanderPlanningService
foreach (var commander in world.Commanders.Where(candidate => candidate.IsAlive && candidate.Kind == CommanderKind.Fleet).ToList())
{
if (PlayerFactionService.IsPlayerFaction(world, commander.FactionId))
if (PlayerFactionService.IsPlayerFaction(playerStateStore, commander.FactionId))
{
continue;
}
@@ -63,7 +68,7 @@ internal sealed class CommanderPlanningService
foreach (var commander in world.Commanders.Where(candidate => candidate.IsAlive && candidate.Kind == CommanderKind.Station).ToList())
{
if (PlayerFactionService.IsPlayerFaction(world, commander.FactionId))
if (PlayerFactionService.IsPlayerFaction(playerStateStore, commander.FactionId))
{
continue;
}
@@ -78,7 +83,7 @@ internal sealed class CommanderPlanningService
foreach (var commander in world.Commanders.Where(candidate => candidate.IsAlive && candidate.Kind == CommanderKind.Ship).ToList())
{
if (PlayerFactionService.IsPlayerFaction(world, commander.FactionId))
if (PlayerFactionService.IsPlayerFaction(playerStateStore, commander.FactionId))
{
continue;
}
@@ -268,7 +273,7 @@ internal sealed class CommanderPlanningService
CommanderRuntime factionCommander,
IReadOnlyDictionary<string, CommanderRuntime> stationCommanders)
{
if (string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal))
if (IsMilitaryShip(ship.Definition))
{
return factionCommander;
}
@@ -456,8 +461,8 @@ internal sealed class CommanderPlanningService
ship.Id,
nextAssignment is null ? "assignment-cleared" : "assignment-updated",
nextAssignment is null
? $"{ship.Definition.Label} returned to default behavior."
: $"{ship.Definition.Label} assigned to {nextAssignment.Kind}.",
? $"{ship.Definition.Name} returned to default behavior."
: $"{ship.Definition.Name} assigned to {nextAssignment.Kind}.",
DateTimeOffset.UtcNow));
}
}
@@ -586,10 +591,10 @@ internal sealed class CommanderPlanningService
var frontCount = Math.Max(1,
threatAssessment.ThreatSignals.Count(signal => signal.ScopeKind is "controlled-system" or "contested-system")
+ (expansionProject is null ? 0 : 1));
var militaryShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && ship.Definition.Kind == "military");
var minerShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && HasShipCapabilities(ship.Definition, "mining"));
var transportShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && ship.Definition.Kind == "transport");
var constructorShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && ship.Definition.Kind == "construction");
var militaryShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && IsMilitaryShip(ship.Definition));
var minerShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && IsMiningShip(ship.Definition));
var transportShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && IsTransportShip(ship.Definition));
var constructorShipCount = world.Ships.Count(ship => ship.FactionId == faction.Id && ship.Health > 0f && IsConstructionShip(ship.Definition));
var hasShipyard = world.Stations.Any(station =>
string.Equals(station.FactionId, faction.Id, StringComparison.Ordinal) &&
station.InstalledModules.Contains("module_gen_build_l_01", StringComparer.Ordinal));
@@ -1365,9 +1370,9 @@ internal sealed class CommanderPlanningService
Id = $"{campaign.Id}-protect-station-{station.Id}",
CampaignId = campaign.Id,
TheaterId = theater?.Id,
Kind = "protect-station",
Kind = ProtectStation,
DelegationKind = "ship",
BehaviorKind = "protect-station",
BehaviorKind = ProtectStation,
Status = "active",
Priority = campaign.Priority + 8f,
HomeSystemId = station.SystemId,
@@ -1389,7 +1394,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "patrol-front",
DelegationKind = "ship",
BehaviorKind = "patrol",
BehaviorKind = Patrol,
Status = "active",
Priority = campaign.Priority + 2f,
HomeSystemId = campaign.TargetSystemId,
@@ -1414,7 +1419,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "police-front",
DelegationKind = "ship",
BehaviorKind = "police",
BehaviorKind = Police,
Status = "active",
Priority = campaign.Priority + 1f,
HomeSystemId = campaign.TargetSystemId,
@@ -1454,7 +1459,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "strike-station",
DelegationKind = "ship",
BehaviorKind = "attack-target",
BehaviorKind = AttackTarget,
Status = "active",
Priority = campaign.Priority + 10f,
TargetSystemId = enemyStation.SystemId,
@@ -1478,7 +1483,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "hold-front",
DelegationKind = "ship",
BehaviorKind = "protect-position",
BehaviorKind = ProtectPosition,
Status = "active",
Priority = campaign.Priority + 3f,
TargetSystemId = campaign.TargetSystemId,
@@ -1500,7 +1505,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "fleet-sustainment",
DelegationKind = "ship",
BehaviorKind = "supply-fleet",
BehaviorKind = SupplyFleet,
Status = "active",
Priority = campaign.Priority + 1.5f,
HomeSystemId = campaign.TargetSystemId,
@@ -1539,7 +1544,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "construct-site",
DelegationKind = "ship",
BehaviorKind = "construct-station",
BehaviorKind = ConstructStation,
Status = "active",
Priority = campaign.Priority + 8f,
HomeSystemId = expansionProject.SystemId,
@@ -1564,7 +1569,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "supply-site",
DelegationKind = "ship",
BehaviorKind = "find-build-tasks",
BehaviorKind = FindBuildTasks,
Status = "active",
Priority = campaign.Priority + 4f,
HomeSystemId = expansionProject.SystemId,
@@ -1589,7 +1594,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "guard-site",
DelegationKind = "ship",
BehaviorKind = "protect-position",
BehaviorKind = ProtectPosition,
Status = "active",
Priority = campaign.Priority + 2f,
TargetSystemId = expansionProject.SystemId,
@@ -1614,7 +1619,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "mine-expansion-input",
DelegationKind = "ship",
BehaviorKind = "expert-auto-mine",
BehaviorKind = ExpertAutoMine,
Status = "active",
Priority = campaign.Priority + 1f,
HomeSystemId = expansionProject.SystemId,
@@ -1655,7 +1660,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "trade-shortage",
DelegationKind = "ship",
BehaviorKind = "fill-shortages",
BehaviorKind = FillShortages,
Status = "active",
Priority = campaign.Priority + 5f,
HomeSystemId = anchorStation?.SystemId,
@@ -1680,7 +1685,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "mine-shortage",
DelegationKind = "ship",
BehaviorKind = "expert-auto-mine",
BehaviorKind = ExpertAutoMine,
Status = "active",
Priority = campaign.Priority + 3f,
HomeSystemId = anchorStation?.SystemId,
@@ -1703,7 +1708,7 @@ internal sealed class CommanderPlanningService
TheaterId = theater?.Id,
Kind = "revisit-stations",
DelegationKind = "ship",
BehaviorKind = "revisit-known-stations",
BehaviorKind = RevisitKnownStations,
Status = "active",
Priority = campaign.Priority + 0.5f,
HomeSystemId = anchorStation?.SystemId,
@@ -1743,7 +1748,7 @@ internal sealed class CommanderPlanningService
CampaignId = campaign.Id,
Kind = "feed-shipyard",
DelegationKind = "ship",
BehaviorKind = "fill-shortages",
BehaviorKind = FillShortages,
Status = "active",
Priority = campaign.Priority + 4f,
HomeSystemId = shipyard.SystemId,
@@ -1768,7 +1773,7 @@ internal sealed class CommanderPlanningService
CampaignId = campaign.Id,
Kind = "mine-bottleneck",
DelegationKind = "ship",
BehaviorKind = "expert-auto-mine",
BehaviorKind = ExpertAutoMine,
Status = "active",
Priority = campaign.Priority + 2f,
HomeSystemId = shipyard.SystemId,
@@ -1838,7 +1843,9 @@ internal sealed class CommanderPlanningService
var reservedCommanderIds = new HashSet<string>(StringComparer.Ordinal);
var availableMilitaryCommanders = commanders.Count(commander =>
commander.Kind == CommanderKind.Ship &&
world.Ships.FirstOrDefault(ship => ship.Id == commander.ControlledEntityId) is { Definition.Kind: "military", Health: > 0f });
world.Ships.FirstOrDefault(ship => ship.Id == commander.ControlledEntityId) is { } commanderShip
&& commanderShip.Health > 0f
&& IsMilitaryShip(commanderShip.Definition));
var committedMilitaryCommanders = 0;
foreach (var objective in objectives
@@ -1921,11 +1928,11 @@ internal sealed class CommanderPlanningService
return objective.BehaviorKind switch
{
"construct-station" => string.Equals(ship.Definition.Kind, "construction", StringComparison.Ordinal),
"find-build-tasks" => string.Equals(ship.Definition.Kind, "transport", StringComparison.Ordinal),
"fill-shortages" or "advanced-auto-trade" or "revisit-known-stations" or "supply-fleet" => string.Equals(ship.Definition.Kind, "transport", StringComparison.Ordinal),
"local-auto-mine" or "advanced-auto-mine" or "expert-auto-mine" => HasShipCapabilities(ship.Definition, "mining"),
"patrol" or "police" or "protect-position" or "protect-ship" or "protect-station" or "attack-target" => string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal),
ConstructStation => IsConstructionShip(ship.Definition),
FindBuildTasks => IsTransportShip(ship.Definition),
FillShortages or AdvancedAutoTrade or RevisitKnownStations or SupplyFleet => IsTransportShip(ship.Definition),
LocalAutoMine or AdvancedAutoMine or ExpertAutoMine => IsMiningShip(ship.Definition),
Patrol or Police or ProtectPosition or ProtectShip or ProtectStation or AttackTarget => IsMilitaryShip(ship.Definition),
_ => true,
};
}
@@ -1992,7 +1999,7 @@ internal sealed class CommanderPlanningService
Kind = "military-fleet",
Status = economicAssessment.MilitaryShipCount >= economicAssessment.TargetMilitaryShipCount ? "stable" : "active",
Priority = 80f + (threatAssessment.ThreatSignals.Count * 4f),
ShipKind = "military",
ShipKind = MilitaryShipCategory,
TargetCount = economicAssessment.TargetMilitaryShipCount,
CurrentCount = economicAssessment.MilitaryShipCount,
Notes = "Maintain enough military hulls for all active fronts.",
@@ -2004,7 +2011,7 @@ internal sealed class CommanderPlanningService
Kind = "mining-fleet",
Status = economicAssessment.MinerShipCount >= economicAssessment.TargetMinerShipCount ? "stable" : "active",
Priority = 60f,
ShipKind = "mining",
ShipKind = MiningShipCategory,
TargetCount = economicAssessment.TargetMinerShipCount,
CurrentCount = economicAssessment.MinerShipCount,
Notes = "Maintain raw resource extraction capacity.",
@@ -2016,7 +2023,7 @@ internal sealed class CommanderPlanningService
Kind = "logistics-fleet",
Status = economicAssessment.TransportShipCount >= economicAssessment.TargetTransportShipCount ? "stable" : "active",
Priority = 62f,
ShipKind = "transport",
ShipKind = TransportShipCategory,
TargetCount = economicAssessment.TargetTransportShipCount,
CurrentCount = economicAssessment.TransportShipCount,
Notes = "Maintain logistics throughput across stations and fronts.",
@@ -2028,7 +2035,7 @@ internal sealed class CommanderPlanningService
Kind = "construction-fleet",
Status = economicAssessment.ConstructorShipCount >= economicAssessment.TargetConstructorShipCount ? "stable" : "active",
Priority = expansionProject is null ? 35f : 68f,
ShipKind = "construction",
ShipKind = ConstructionShipCategory,
TargetCount = economicAssessment.TargetConstructorShipCount,
CurrentCount = economicAssessment.ConstructorShipCount,
Notes = "Maintain construction capacity for frontier growth.",
@@ -2347,10 +2354,10 @@ internal sealed class CommanderPlanningService
Kind = "fleet-command",
BehaviorKind = campaign.Kind switch
{
"offense" => "attack-target",
"defense" => "protect-position",
"expansion" => "protect-position",
_ => "patrol",
"offense" => AttackTarget,
"defense" => ProtectPosition,
"expansion" => ProtectPosition,
_ => Patrol,
},
Status = campaign.Status,
Priority = campaign.Priority,
@@ -2380,7 +2387,7 @@ internal sealed class CommanderPlanningService
{
ObjectiveId = $"objective-station-{station.Id}-ship-production",
Kind = "ship-production-focus",
BehaviorKind = "fill-shortages",
BehaviorKind = FillShortages,
Status = "active",
Priority = 55f,
HomeSystemId = station.SystemId,
@@ -2399,7 +2406,7 @@ internal sealed class CommanderPlanningService
{
ObjectiveId = $"objective-station-{station.Id}-commodity-focus-{bottleneckItem}",
Kind = "commodity-focus",
BehaviorKind = "fill-shortages",
BehaviorKind = FillShortages,
Status = "active",
Priority = 45f,
HomeSystemId = station.SystemId,
@@ -2418,7 +2425,7 @@ internal sealed class CommanderPlanningService
{
ObjectiveId = $"objective-station-{station.Id}-expansion-support",
Kind = "expansion-support",
BehaviorKind = "find-build-tasks",
BehaviorKind = FindBuildTasks,
Status = "active",
Priority = 40f,
HomeSystemId = station.SystemId,
@@ -2435,7 +2442,7 @@ internal sealed class CommanderPlanningService
{
ObjectiveId = $"objective-station-{station.Id}-oversight",
Kind = "station-oversight",
BehaviorKind = "fill-shortages",
BehaviorKind = FillShortages,
Status = "active",
Priority = 30f,
HomeSystemId = station.SystemId,
@@ -2460,7 +2467,7 @@ internal sealed class CommanderPlanningService
faction.StrategicState.Objectives.Any(objective =>
objective.CampaignId == campaign.Id &&
objective.CommanderId is not null &&
(IsCombatObjective(objective) || string.Equals(objective.BehaviorKind, "supply-fleet", StringComparison.Ordinal))))
(IsCombatObjective(objective) || string.Equals(objective.BehaviorKind, SupplyFleet, StringComparison.Ordinal))))
.Select(campaign => campaign.Id)
.ToHashSet(StringComparer.Ordinal);
@@ -2510,10 +2517,10 @@ internal sealed class CommanderPlanningService
Kind = "fleet-command",
BehaviorKind = campaign.Kind switch
{
"offense" => "attack-target",
"defense" => "protect-position",
"expansion" => "protect-position",
_ => "patrol",
"offense" => AttackTarget,
"defense" => ProtectPosition,
"expansion" => ProtectPosition,
_ => Patrol,
},
Status = campaign.Status,
Priority = campaign.Priority + 1f,
@@ -2581,7 +2588,7 @@ internal sealed class CommanderPlanningService
{
if (objective?.CampaignId is not null
&& fleetCommanders.TryGetValue(objective.CampaignId, out var fleetCommander)
&& (IsCombatObjective(objective) || string.Equals(objective.BehaviorKind, "supply-fleet", StringComparison.Ordinal)))
&& (IsCombatObjective(objective) || string.Equals(objective.BehaviorKind, SupplyFleet, StringComparison.Ordinal)))
{
return fleetCommander.Id;
}
@@ -2598,25 +2605,39 @@ internal sealed class CommanderPlanningService
private static DefaultBehaviorRuntime BuildFallbackBehavior(SimulationWorld world, ShipRuntime ship)
{
var homeStation = ResolveFallbackHomeStation(world, ship);
if (HasShipCapabilities(ship.Definition, "mining"))
if (IsMiningShip(ship.Definition))
{
if (homeStation is null)
{
return new DefaultBehaviorRuntime
{
Kind = LocalAutoMine,
HomeSystemId = ship.SystemId,
HomeStationId = null,
AreaSystemId = ship.SystemId,
ItemId = "ore",
Radius = 24f,
MaxSystemRange = 0,
};
}
return new DefaultBehaviorRuntime
{
Kind = ship.Definition.CargoCapacity >= 120f ? "expert-auto-mine" : "advanced-auto-mine",
Kind = ship.Definition.GetTotalCargoCapacity() >= 120f ? ExpertAutoMine : AdvancedAutoMine,
HomeSystemId = homeStation?.SystemId ?? ship.SystemId,
HomeStationId = homeStation?.Id,
AreaSystemId = homeStation?.SystemId ?? ship.SystemId,
PreferredItemId = null,
ItemId = null,
Radius = 24f,
MaxSystemRange = ship.Definition.CargoCapacity >= 120f ? 3 : 1,
MaxSystemRange = ship.Definition.GetTotalCargoCapacity() >= 120f ? 3 : 1,
};
}
if (string.Equals(ship.Definition.Kind, "transport", StringComparison.Ordinal))
if (IsTransportShip(ship.Definition))
{
return new DefaultBehaviorRuntime
{
Kind = "advanced-auto-trade",
Kind = AdvancedAutoTrade,
HomeSystemId = homeStation?.SystemId ?? ship.SystemId,
HomeStationId = homeStation?.Id,
AreaSystemId = homeStation?.SystemId ?? ship.SystemId,
@@ -2625,11 +2646,11 @@ internal sealed class CommanderPlanningService
};
}
if (string.Equals(ship.Definition.Kind, "construction", StringComparison.Ordinal))
if (IsConstructionShip(ship.Definition))
{
return new DefaultBehaviorRuntime
{
Kind = "construct-station",
Kind = ConstructStation,
HomeSystemId = homeStation?.SystemId ?? ship.SystemId,
HomeStationId = homeStation?.Id,
AreaSystemId = homeStation?.SystemId ?? ship.SystemId,
@@ -2638,13 +2659,13 @@ internal sealed class CommanderPlanningService
};
}
if (string.Equals(ship.Definition.Kind, "military", StringComparison.Ordinal))
if (IsMilitaryShip(ship.Definition))
{
var anchor = homeStation?.Position ?? ship.Position;
var patrolRadius = (homeStation?.Radius ?? 30f) + 90f;
return new DefaultBehaviorRuntime
{
Kind = "patrol",
Kind = Patrol,
HomeSystemId = homeStation?.SystemId ?? ship.SystemId,
HomeStationId = homeStation?.Id,
AreaSystemId = homeStation?.SystemId ?? ship.SystemId,
@@ -2660,7 +2681,7 @@ internal sealed class CommanderPlanningService
return new DefaultBehaviorRuntime
{
Kind = "idle",
Kind = Idle,
HomeSystemId = homeStation?.SystemId ?? ship.SystemId,
HomeStationId = homeStation?.Id,
AreaSystemId = homeStation?.SystemId ?? ship.SystemId,
@@ -2684,15 +2705,15 @@ internal sealed class CommanderPlanningService
var areaSystemId = objective.TargetSystemId ?? objective.HomeSystemId ?? fallback.AreaSystemId ?? ship.SystemId;
var radius = objective.BehaviorKind switch
{
"protect-position" or "protect-station" or "patrol" or "police" => MathF.Max(80f, fallback.Radius),
"follow-ship" or "protect-ship" => MathF.Max(18f, fallback.Radius * 0.6f),
"fill-shortages" or "advanced-auto-trade" or "find-build-tasks" => MathF.Max(20f, fallback.Radius),
ProtectPosition or ProtectStation or Patrol or Police => MathF.Max(80f, fallback.Radius),
FollowShip or ProtectShip => MathF.Max(18f, fallback.Radius * 0.6f),
FillShortages or AdvancedAutoTrade or FindBuildTasks => MathF.Max(20f, fallback.Radius),
_ => fallback.Radius,
};
var maxRange = objective.BehaviorKind switch
{
"attack-target" or "protect-position" or "protect-station" or "protect-ship" or "patrol" or "police" => Math.Max(1, fallback.MaxSystemRange),
"fill-shortages" or "advanced-auto-trade" or "find-build-tasks" or "supply-fleet" => Math.Max(2, fallback.MaxSystemRange),
AttackTarget or ProtectPosition or ProtectStation or ProtectShip or Patrol or Police => Math.Max(1, fallback.MaxSystemRange),
FillShortages or AdvancedAutoTrade or FindBuildTasks or SupplyFleet => Math.Max(2, fallback.MaxSystemRange),
_ => fallback.MaxSystemRange,
};
@@ -2703,16 +2724,16 @@ internal sealed class CommanderPlanningService
HomeStationId = objective.HomeStationId ?? fallback.HomeStationId,
AreaSystemId = areaSystemId,
TargetEntityId = objective.TargetEntityId,
PreferredItemId = objective.ItemId ?? fallback.PreferredItemId,
ItemId = objective.ItemId ?? fallback.ItemId,
PreferredNodeId = fallback.PreferredNodeId,
PreferredConstructionSiteId = objective.Kind is "construct-site" or "supply-site" ? objective.TargetEntityId : fallback.PreferredConstructionSiteId,
PreferredModuleId = fallback.PreferredModuleId,
TargetPosition = objective.TargetPosition ?? fallback.TargetPosition,
WaitSeconds = objective.BehaviorKind == "supply-fleet" ? 4f : fallback.WaitSeconds,
WaitSeconds = objective.BehaviorKind == SupplyFleet ? 4f : fallback.WaitSeconds,
Radius = radius,
MaxSystemRange = maxRange,
KnownStationsOnly = objective.BehaviorKind == "revisit-known-stations",
PatrolPoints = objective.BehaviorKind == "patrol"
KnownStationsOnly = objective.BehaviorKind == RevisitKnownStations,
PatrolPoints = objective.BehaviorKind == Patrol
? BuildPatrolPoints(objective.TargetPosition ?? fallback.TargetPosition ?? ship.Position, radius)
: [],
PatrolIndex = ship.DefaultBehavior.PatrolIndex,
@@ -2728,7 +2749,7 @@ internal sealed class CommanderPlanningService
target.HomeStationId = source.HomeStationId;
target.AreaSystemId = source.AreaSystemId;
target.TargetEntityId = source.TargetEntityId;
target.PreferredItemId = source.PreferredItemId;
target.ItemId = source.ItemId;
target.PreferredNodeId = source.PreferredNodeId;
target.PreferredConstructionSiteId = source.PreferredConstructionSiteId;
target.PreferredModuleId = source.PreferredModuleId;
@@ -2749,7 +2770,7 @@ internal sealed class CommanderPlanningService
&& string.Equals(left.HomeStationId, right.HomeStationId, StringComparison.Ordinal)
&& string.Equals(left.AreaSystemId, right.AreaSystemId, StringComparison.Ordinal)
&& string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
&& string.Equals(left.PreferredItemId, right.PreferredItemId, StringComparison.Ordinal)
&& string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
&& string.Equals(left.PreferredNodeId, right.PreferredNodeId, StringComparison.Ordinal)
&& string.Equals(left.PreferredConstructionSiteId, right.PreferredConstructionSiteId, StringComparison.Ordinal)
&& string.Equals(left.PreferredModuleId, right.PreferredModuleId, StringComparison.Ordinal)
@@ -2805,13 +2826,15 @@ internal sealed class CommanderPlanningService
{
Id = $"ai-order-{objective.Id}",
Kind = objective.StagingOrderKind,
SourceKind = ShipOrderSourceKind.Commander,
SourceId = objective.Id,
Priority = 90 + objective.ReinforcementLevel,
InterruptCurrentPlan = true,
Label = $"{objective.Kind} staging",
TargetEntityId = objective.TargetEntityId,
TargetSystemId = targetSystemId,
TargetPosition = targetPosition,
DestinationStationId = objective.BehaviorKind == "dock-and-wait" ? objective.TargetEntityId : null,
DestinationStationId = objective.BehaviorKind == DockAndWait ? objective.TargetEntityId : null,
ItemId = objective.ItemId,
WaitSeconds = 0f,
Radius = MathF.Max(12f, objective.ReinforcementLevel * 18f),
@@ -2885,6 +2908,8 @@ internal sealed class CommanderPlanningService
private static bool ShipOrdersEqual(ShipOrderRuntime left, ShipOrderRuntime right) =>
string.Equals(left.Id, right.Id, StringComparison.Ordinal)
&& string.Equals(left.Kind, right.Kind, StringComparison.Ordinal)
&& left.SourceKind == right.SourceKind
&& string.Equals(left.SourceId, right.SourceId, StringComparison.Ordinal)
&& left.Priority == right.Priority
&& left.InterruptCurrentPlan == right.InterruptCurrentPlan
&& string.Equals(left.Label, right.Label, StringComparison.Ordinal)
@@ -2920,7 +2945,7 @@ internal sealed class CommanderPlanningService
}
private static bool IsCombatObjective(FactionOperationalObjectiveRuntime objective) =>
objective.BehaviorKind is "attack-target" or "protect-position" or "protect-ship" or "protect-station" or "patrol" or "police";
objective.BehaviorKind is AttackTarget or ProtectPosition or ProtectShip or ProtectStation or Patrol or Police;
private static float EstimateFriendlyAssetValue(SimulationWorld world, string factionId, string systemId)
{