feat(ai): improving agents planning and memory
This commit is contained in:
@@ -6,8 +6,9 @@ internal sealed class CommanderPlanningService
|
||||
{
|
||||
private const float FactionCommanderReplanInterval = 10f;
|
||||
private const float ShipCommanderReplanInterval = 5f;
|
||||
private readonly FactionObjectivePlanner _objectivePlanner = new();
|
||||
private readonly FactionObjectiveExecutor _objectiveExecutor = new();
|
||||
|
||||
private static readonly GoapPlanner<FactionPlanningState> _factionPlanner = new(s => s.Clone());
|
||||
private static readonly GoapPlanner<ShipPlanningState> _shipPlanner = new(s => s.Clone());
|
||||
|
||||
private static readonly IReadOnlyList<GoapGoal<FactionPlanningState>> _factionGoals =
|
||||
@@ -76,12 +77,9 @@ internal sealed class CommanderPlanningService
|
||||
|
||||
commander.ReplanTimer = FactionCommanderReplanInterval;
|
||||
commander.NeedsReplan = false;
|
||||
commander.PlanningCycle += 1;
|
||||
|
||||
var state = BuildFactionPlanningState(world, commander.FactionId);
|
||||
var actions = BuildFactionActions(world);
|
||||
|
||||
// Clear stale directives — actions will re-assert what is still needed.
|
||||
commander.ActiveDirectives.Clear();
|
||||
|
||||
var rankedGoals = _factionGoals
|
||||
.Select(g => (goal: g, priority: g.ComputePriority(state, world, commander)))
|
||||
@@ -89,28 +87,15 @@ internal sealed class CommanderPlanningService
|
||||
.OrderByDescending(x => x.priority)
|
||||
.ToList();
|
||||
|
||||
commander.LastPlanningState = state;
|
||||
commander.LastGoalPriorities = rankedGoals.Select(x => (x.goal.Name, x.priority)).ToList();
|
||||
|
||||
// Execute the first action of each active goal's plan (top 3 to avoid conflicts).
|
||||
foreach (var (goal, _) in rankedGoals.Take(3))
|
||||
{
|
||||
var plan = _factionPlanner.Plan(state, goal, actions);
|
||||
plan?.CurrentAction?.Execute(engine, world, commander);
|
||||
}
|
||||
|
||||
if (FactionIndustryPlanner.GetActiveExpansionProject(world, commander.FactionId) is null)
|
||||
{
|
||||
if (rankedGoals.Any(entry => string.Equals(entry.goal.Name, "ensure-war-industry", StringComparison.Ordinal)))
|
||||
{
|
||||
TryQueueFactionExpansionProject(world, commander, SelectGoalDrivenWarIndustryProject(world, state, commander.FactionId));
|
||||
}
|
||||
else if (rankedGoals.Any(entry => string.Equals(entry.goal.Name, "ensure-water-security", StringComparison.Ordinal)))
|
||||
{
|
||||
TryQueueFactionExpansionProject(world, commander, FactionIndustryPlanner.AnalyzeCommodityNeed(world, commander.FactionId, "water"));
|
||||
}
|
||||
}
|
||||
|
||||
commander.LastStrategicAssessment = state;
|
||||
commander.LastStrategicPriorities = rankedGoals.Select(x => (x.goal.Name, x.priority)).ToList();
|
||||
_objectivePlanner.UpdateBlackboard(world, commander, state);
|
||||
_objectivePlanner.RefreshObjectives(
|
||||
world,
|
||||
commander,
|
||||
state,
|
||||
rankedGoals.Select(entry => (entry.goal.Name, entry.priority)).ToList());
|
||||
_objectiveExecutor.Execute(engine, world, commander, state);
|
||||
}
|
||||
|
||||
private void UpdateShipCommander(SimulationEngine engine, SimulationWorld world, CommanderRuntime commander)
|
||||
@@ -172,18 +157,30 @@ internal sealed class CommanderPlanningService
|
||||
TargetSystemCount = Math.Max(1, Math.Min(StationSimulationService.StrategicControlTargetSystems, world.Systems.Count)),
|
||||
HasShipFactory = stations.Any(s => s.InstalledModules.Contains("module_gen_build_l_01", StringComparer.Ordinal)),
|
||||
OreStockpile = economy.GetCommodity("ore").OnHand,
|
||||
RefinedMetalsStockpile = refinedMetals.OnHand,
|
||||
RefinedMetalsProductionRate = refinedMetals.ProjectedProductionRatePerSecond,
|
||||
RefinedMetalsShortageHorizonSeconds = refinedMetals.ProjectedShortageHorizonSeconds,
|
||||
HullpartsStockpile = hullparts.OnHand,
|
||||
HullpartsProductionRate = hullparts.ProjectedProductionRatePerSecond,
|
||||
HullpartsShortageHorizonSeconds = hullparts.ProjectedShortageHorizonSeconds,
|
||||
ClaytronicsStockpile = claytronics.OnHand,
|
||||
ClaytronicsProductionRate = claytronics.ProjectedProductionRatePerSecond,
|
||||
ClaytronicsShortageHorizonSeconds = claytronics.ProjectedShortageHorizonSeconds,
|
||||
WaterStockpile = water.OnHand,
|
||||
WaterProductionRate = water.ProjectedProductionRatePerSecond,
|
||||
WaterShortageHorizonSeconds = water.ProjectedShortageHorizonSeconds,
|
||||
RefinedMetalsAvailableStock = refinedMetals.AvailableStock,
|
||||
RefinedMetalsUsageRate = refinedMetals.OperationalUsageRatePerSecond,
|
||||
RefinedMetalsProjectedProductionRate = refinedMetals.ProjectedProductionRatePerSecond,
|
||||
RefinedMetalsProjectedNetRate = refinedMetals.ProjectedNetRatePerSecond,
|
||||
RefinedMetalsLevelSeconds = refinedMetals.LevelSeconds,
|
||||
RefinedMetalsLevel = refinedMetals.Level.ToString().ToLowerInvariant(),
|
||||
HullpartsAvailableStock = hullparts.AvailableStock,
|
||||
HullpartsUsageRate = hullparts.OperationalUsageRatePerSecond,
|
||||
HullpartsProjectedProductionRate = hullparts.ProjectedProductionRatePerSecond,
|
||||
HullpartsProjectedNetRate = hullparts.ProjectedNetRatePerSecond,
|
||||
HullpartsLevelSeconds = hullparts.LevelSeconds,
|
||||
HullpartsLevel = hullparts.Level.ToString().ToLowerInvariant(),
|
||||
ClaytronicsAvailableStock = claytronics.AvailableStock,
|
||||
ClaytronicsUsageRate = claytronics.OperationalUsageRatePerSecond,
|
||||
ClaytronicsProjectedProductionRate = claytronics.ProjectedProductionRatePerSecond,
|
||||
ClaytronicsProjectedNetRate = claytronics.ProjectedNetRatePerSecond,
|
||||
ClaytronicsLevelSeconds = claytronics.LevelSeconds,
|
||||
ClaytronicsLevel = claytronics.Level.ToString().ToLowerInvariant(),
|
||||
WaterAvailableStock = water.AvailableStock,
|
||||
WaterUsageRate = water.OperationalUsageRatePerSecond,
|
||||
WaterProjectedProductionRate = water.ProjectedProductionRatePerSecond,
|
||||
WaterProjectedNetRate = water.ProjectedNetRatePerSecond,
|
||||
WaterLevelSeconds = water.LevelSeconds,
|
||||
WaterLevel = water.Level.ToString().ToLowerInvariant(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -192,16 +189,23 @@ internal sealed class CommanderPlanningService
|
||||
ShipRuntime ship,
|
||||
CommanderRuntime commander)
|
||||
{
|
||||
var factionCommander = world.Commanders.FirstOrDefault(c =>
|
||||
c.FactionId == commander.FactionId &&
|
||||
string.Equals(c.Kind, CommanderKind.Faction, StringComparison.Ordinal));
|
||||
var factionCommander = FindFactionCommander(world, commander.FactionId);
|
||||
|
||||
var enemyTarget = SelectEnemyTarget(world, ship);
|
||||
var tradeRoute = SelectTradeRoute(world, ship.FactionId);
|
||||
var expansionTask = GetHighestPriorityIssuedTask(factionCommander, FactionIssuedTaskKind.ExpandIndustry);
|
||||
var attackTask = GetHighestPriorityIssuedTask(factionCommander, FactionIssuedTaskKind.AttackFactionAssets);
|
||||
var shipyardExpansionTask = factionCommander?.IssuedTasks
|
||||
.Where(task =>
|
||||
task.Kind == FactionIssuedTaskKind.ExpandIndustry
|
||||
&& task.State is FactionIssuedTaskState.Planned or FactionIssuedTaskState.Active
|
||||
&& string.Equals(task.ModuleId, "module_gen_build_l_01", StringComparison.Ordinal))
|
||||
.OrderByDescending(task => task.Priority)
|
||||
.FirstOrDefault();
|
||||
var expansionProject = FactionIndustryPlanner.GetActiveExpansionProject(world, ship.FactionId);
|
||||
if (commander.ActiveBehavior is not null)
|
||||
{
|
||||
commander.ActiveBehavior.AreaSystemId = enemyTarget?.SystemId;
|
||||
commander.ActiveBehavior.AreaSystemId = attackTask?.TargetSystemId ?? expansionTask?.TargetSystemId ?? enemyTarget?.SystemId;
|
||||
commander.ActiveBehavior.TargetEntityId = enemyTarget?.EntityId;
|
||||
if (string.Equals(ship.Definition.Kind, "transport", StringComparison.Ordinal))
|
||||
{
|
||||
@@ -209,12 +213,12 @@ internal sealed class CommanderPlanningService
|
||||
commander.ActiveBehavior.StationId = tradeRoute?.SourceStationId;
|
||||
commander.ActiveBehavior.TargetEntityId = tradeRoute?.DestinationStationId;
|
||||
}
|
||||
else if (string.Equals(ship.Definition.Kind, "construction", StringComparison.Ordinal) && expansionProject is not null)
|
||||
else if (string.Equals(ship.Definition.Kind, "construction", StringComparison.Ordinal) && (expansionTask is not null || expansionProject is not null))
|
||||
{
|
||||
commander.ActiveBehavior.StationId = expansionProject.SupportStationId;
|
||||
commander.ActiveBehavior.TargetEntityId = expansionProject.SiteId;
|
||||
commander.ActiveBehavior.ModuleId = expansionProject.ModuleId;
|
||||
commander.ActiveBehavior.AreaSystemId = expansionProject.SystemId;
|
||||
commander.ActiveBehavior.StationId = expansionProject?.SupportStationId;
|
||||
commander.ActiveBehavior.TargetEntityId = expansionTask?.TargetSiteId ?? expansionProject?.SiteId;
|
||||
commander.ActiveBehavior.ModuleId = expansionTask?.ModuleId ?? expansionProject?.ModuleId;
|
||||
commander.ActiveBehavior.AreaSystemId = expansionTask?.TargetSystemId ?? expansionProject?.SystemId;
|
||||
}
|
||||
else if (string.Equals(ship.Definition.Kind, "construction", StringComparison.Ordinal))
|
||||
{
|
||||
@@ -229,15 +233,13 @@ internal sealed class CommanderPlanningService
|
||||
ShipKind = ship.Definition.Kind,
|
||||
HasMiningCapability = HasShipCapabilities(ship.Definition, "mining"),
|
||||
FactionWantsOre = true,
|
||||
FactionWantsCombat = factionCommander?.ActiveDirectives.Contains("attack-rival", StringComparer.Ordinal) ?? false,
|
||||
FactionWantsExpansion = factionCommander?.ActiveDirectives
|
||||
.Contains("expand-territory", StringComparer.Ordinal) ?? false,
|
||||
FactionNeedsShipyard = !(factionCommander?.ActiveDirectives.Contains("bootstrap-war-industry", StringComparer.Ordinal) ?? false)
|
||||
? false
|
||||
: !world.Stations.Any(station =>
|
||||
string.Equals(station.FactionId, ship.FactionId, StringComparison.Ordinal)
|
||||
&& station.InstalledModules.Contains("module_gen_build_l_01", StringComparer.Ordinal)),
|
||||
TargetEnemySystemId = enemyTarget?.SystemId,
|
||||
FactionWantsCombat = attackTask is not null,
|
||||
FactionWantsExpansion = expansionTask is not null,
|
||||
FactionNeedsShipyard = shipyardExpansionTask is not null
|
||||
&& !world.Stations.Any(station =>
|
||||
string.Equals(station.FactionId, ship.FactionId, StringComparison.Ordinal)
|
||||
&& station.InstalledModules.Contains("module_gen_build_l_01", StringComparer.Ordinal)),
|
||||
TargetEnemySystemId = attackTask?.TargetSystemId ?? enemyTarget?.SystemId,
|
||||
TargetEnemyEntityId = enemyTarget?.EntityId,
|
||||
TradeItemId = tradeRoute?.ItemId,
|
||||
TradeSourceStationId = tradeRoute?.SourceStationId,
|
||||
@@ -245,70 +247,33 @@ internal sealed class CommanderPlanningService
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<GoapAction<FactionPlanningState>> BuildFactionActions(SimulationWorld world)
|
||||
{
|
||||
var actions = new List<GoapAction<FactionPlanningState>>();
|
||||
internal static CommanderRuntime? FindFactionCommander(SimulationWorld world, string factionId) =>
|
||||
world.Commanders.FirstOrDefault(c =>
|
||||
c.FactionId == factionId &&
|
||||
string.Equals(c.Kind, CommanderKind.Faction, StringComparison.Ordinal));
|
||||
|
||||
actions.Add(new PlanWarIndustryAction());
|
||||
actions.Add(new PlanCommoditySupplyAction("water"));
|
||||
internal static bool FactionCommanderHasIssuedTask(
|
||||
SimulationWorld world,
|
||||
string factionId,
|
||||
FactionIssuedTaskKind kind,
|
||||
string? shipRole = null) =>
|
||||
FindFactionCommander(world, factionId)?
|
||||
.IssuedTasks.Any(task =>
|
||||
task.Kind == kind
|
||||
&& task.State is FactionIssuedTaskState.Planned or FactionIssuedTaskState.Active or FactionIssuedTaskState.Blocked
|
||||
&& (shipRole is null || string.Equals(task.ShipRole, shipRole, StringComparison.Ordinal))) ?? false;
|
||||
|
||||
foreach (var (shipId, def) in world.ShipDefinitions)
|
||||
{
|
||||
actions.Add(new OrderShipProductionAction(def.Kind, shipId));
|
||||
}
|
||||
|
||||
actions.Add(new LaunchExterminationCampaignAction());
|
||||
actions.Add(new ExpandToSystemAction());
|
||||
return actions;
|
||||
}
|
||||
|
||||
internal static bool FactionCommanderHasDirective(SimulationWorld world, string factionId, string directive) =>
|
||||
world.Commanders.FirstOrDefault(c =>
|
||||
c.FactionId == factionId &&
|
||||
string.Equals(c.Kind, CommanderKind.Faction, StringComparison.Ordinal))
|
||||
?.ActiveDirectives.Contains(directive, StringComparer.Ordinal) ?? false;
|
||||
|
||||
private static void TryQueueFactionExpansionProject(
|
||||
SimulationWorld world,
|
||||
CommanderRuntime commander,
|
||||
IndustryExpansionProject? project)
|
||||
{
|
||||
if (project is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FactionIndustryPlanner.EnsureExpansionSite(world, commander.FactionId, project);
|
||||
commander.ActiveDirectives.Add($"expand-industry:{project.CommodityId}:{project.SystemId}:{project.CelestialId}");
|
||||
}
|
||||
|
||||
private static IndustryExpansionProject? SelectGoalDrivenWarIndustryProject(
|
||||
SimulationWorld world,
|
||||
FactionPlanningState state,
|
||||
string factionId)
|
||||
{
|
||||
if (!state.HasRefinedMetalsProduction || state.RefinedMetalsShortageHorizonSeconds < 240f)
|
||||
{
|
||||
return FactionIndustryPlanner.AnalyzeCommodityNeed(world, factionId, "refinedmetals");
|
||||
}
|
||||
|
||||
if (!state.HasHullpartsProduction || state.HullpartsShortageHorizonSeconds < 240f)
|
||||
{
|
||||
return FactionIndustryPlanner.AnalyzeCommodityNeed(world, factionId, "hullparts");
|
||||
}
|
||||
|
||||
if (!state.HasClaytronicsProduction || state.ClaytronicsShortageHorizonSeconds < 240f)
|
||||
{
|
||||
return FactionIndustryPlanner.AnalyzeCommodityNeed(world, factionId, "claytronics");
|
||||
}
|
||||
|
||||
if (!state.HasShipFactory)
|
||||
{
|
||||
return FactionIndustryPlanner.CreateShipyardFoundationProject(world, factionId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
internal static FactionIssuedTaskRuntime? GetHighestPriorityIssuedTask(
|
||||
CommanderRuntime? factionCommander,
|
||||
FactionIssuedTaskKind kind,
|
||||
string? shipRole = null) =>
|
||||
factionCommander?.IssuedTasks
|
||||
.Where(task =>
|
||||
task.Kind == kind
|
||||
&& task.State is FactionIssuedTaskState.Planned or FactionIssuedTaskState.Active or FactionIssuedTaskState.Blocked
|
||||
&& (shipRole is null || string.Equals(task.ShipRole, shipRole, StringComparison.Ordinal)))
|
||||
.OrderByDescending(task => task.Priority)
|
||||
.FirstOrDefault();
|
||||
|
||||
private static (string EntityId, string SystemId)? SelectEnemyTarget(SimulationWorld world, ShipRuntime ship)
|
||||
{
|
||||
|
||||
@@ -16,26 +16,40 @@ public sealed class FactionPlanningState
|
||||
public int EnemyShipCount { get; set; }
|
||||
public int EnemyStationCount { get; set; }
|
||||
public float OreStockpile { get; set; }
|
||||
public float RefinedMetalsStockpile { get; set; }
|
||||
public float RefinedMetalsProductionRate { get; set; }
|
||||
public float RefinedMetalsShortageHorizonSeconds { get; set; }
|
||||
public float HullpartsStockpile { get; set; }
|
||||
public float HullpartsProductionRate { get; set; }
|
||||
public float HullpartsShortageHorizonSeconds { get; set; }
|
||||
public float ClaytronicsStockpile { get; set; }
|
||||
public float ClaytronicsProductionRate { get; set; }
|
||||
public float ClaytronicsShortageHorizonSeconds { get; set; }
|
||||
public float WaterStockpile { get; set; }
|
||||
public float WaterProductionRate { get; set; }
|
||||
public float WaterShortageHorizonSeconds { get; set; }
|
||||
public float RefinedMetalsAvailableStock { get; set; }
|
||||
public float RefinedMetalsUsageRate { get; set; }
|
||||
public float RefinedMetalsProjectedProductionRate { get; set; }
|
||||
public float RefinedMetalsProjectedNetRate { get; set; }
|
||||
public float RefinedMetalsLevelSeconds { get; set; }
|
||||
public string RefinedMetalsLevel { get; set; } = "unknown";
|
||||
public float HullpartsAvailableStock { get; set; }
|
||||
public float HullpartsUsageRate { get; set; }
|
||||
public float HullpartsProjectedProductionRate { get; set; }
|
||||
public float HullpartsProjectedNetRate { get; set; }
|
||||
public float HullpartsLevelSeconds { get; set; }
|
||||
public string HullpartsLevel { get; set; } = "unknown";
|
||||
public float ClaytronicsAvailableStock { get; set; }
|
||||
public float ClaytronicsUsageRate { get; set; }
|
||||
public float ClaytronicsProjectedProductionRate { get; set; }
|
||||
public float ClaytronicsProjectedNetRate { get; set; }
|
||||
public float ClaytronicsLevelSeconds { get; set; }
|
||||
public string ClaytronicsLevel { get; set; } = "unknown";
|
||||
public float WaterAvailableStock { get; set; }
|
||||
public float WaterUsageRate { get; set; }
|
||||
public float WaterProjectedProductionRate { get; set; }
|
||||
public float WaterProjectedNetRate { get; set; }
|
||||
public float WaterLevelSeconds { get; set; }
|
||||
public string WaterLevel { get; set; } = "unknown";
|
||||
|
||||
public bool HasRefinedMetalsProduction => RefinedMetalsProductionRate > 0.01f;
|
||||
public bool HasHullpartsProduction => HullpartsProductionRate > 0.01f;
|
||||
public bool HasClaytronicsProduction => ClaytronicsProductionRate > 0.01f;
|
||||
public bool HasWaterProduction => WaterProductionRate > 0.01f;
|
||||
public bool HasRefinedMetalsProduction => RefinedMetalsProjectedProductionRate > 0.01f;
|
||||
public bool HasHullpartsProduction => HullpartsProjectedProductionRate > 0.01f;
|
||||
public bool HasClaytronicsProduction => ClaytronicsProjectedProductionRate > 0.01f;
|
||||
public bool HasWaterProduction => WaterProjectedProductionRate > 0.01f;
|
||||
|
||||
public bool HasWarIndustrySupplyChain =>
|
||||
HasRefinedMetalsProduction && HasHullpartsProduction && HasClaytronicsProduction;
|
||||
IsCommodityOperational(RefinedMetalsProjectedProductionRate, RefinedMetalsProjectedNetRate, RefinedMetalsLevelSeconds, RefinedMetalsLevel, 240f)
|
||||
&& IsCommodityOperational(HullpartsProjectedProductionRate, HullpartsProjectedNetRate, HullpartsLevelSeconds, HullpartsLevel, 240f)
|
||||
&& IsCommodityOperational(ClaytronicsProjectedProductionRate, ClaytronicsProjectedNetRate, ClaytronicsLevelSeconds, ClaytronicsLevel, 240f);
|
||||
|
||||
public FactionPlanningState Clone() => (FactionPlanningState)MemberwiseClone();
|
||||
|
||||
@@ -44,6 +58,39 @@ public sealed class FactionPlanningState
|
||||
var expansionDeficit = Math.Max(0, state.TargetSystemCount - state.ControlledSystemCount);
|
||||
return Math.Max(3, (state.ControlledSystemCount * 2) + (expansionDeficit * 3) + Math.Min(4, state.EnemyFactionCount + state.EnemyStationCount));
|
||||
}
|
||||
|
||||
internal static bool IsCommodityOperational(
|
||||
float projectedProductionRate,
|
||||
float projectedNetRate,
|
||||
float levelSeconds,
|
||||
string level,
|
||||
float targetLevelSeconds) =>
|
||||
projectedProductionRate > 0.01f
|
||||
&& projectedNetRate >= -0.01f
|
||||
&& levelSeconds >= targetLevelSeconds
|
||||
&& (string.Equals(level, "stable", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(level, "surplus", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
internal static float ComputeCommodityNeed(
|
||||
float projectedProductionRate,
|
||||
float usageRate,
|
||||
float projectedNetRate,
|
||||
float levelSeconds,
|
||||
string level,
|
||||
float targetLevelSeconds)
|
||||
{
|
||||
var levelWeight = level switch
|
||||
{
|
||||
"critical" => 140f,
|
||||
"low" => 80f,
|
||||
"stable" => 20f,
|
||||
_ => 0f,
|
||||
};
|
||||
var rateDeficit = MathF.Max(0f, usageRate - projectedProductionRate);
|
||||
var levelDeficit = MathF.Max(0f, targetLevelSeconds - levelSeconds) / MathF.Max(targetLevelSeconds, 1f);
|
||||
var instability = projectedNetRate < 0f ? MathF.Abs(projectedNetRate) * 80f : 0f;
|
||||
return levelWeight + (rateDeficit * 140f) + (levelDeficit * 120f) + instability;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Goals ─────────────────────────────────────────────────────────────────────
|
||||
@@ -63,12 +110,16 @@ public sealed class EnsureWarIndustryGoal : GoapGoal<FactionPlanningState>
|
||||
}
|
||||
|
||||
var missingStages =
|
||||
(state.HasRefinedMetalsProduction ? 0 : 1) +
|
||||
(state.HasHullpartsProduction ? 0 : 1) +
|
||||
(state.HasClaytronicsProduction ? 0 : 1) +
|
||||
(FactionPlanningState.IsCommodityOperational(state.RefinedMetalsProjectedProductionRate, state.RefinedMetalsProjectedNetRate, state.RefinedMetalsLevelSeconds, state.RefinedMetalsLevel, 240f) ? 0 : 1) +
|
||||
(FactionPlanningState.IsCommodityOperational(state.HullpartsProjectedProductionRate, state.HullpartsProjectedNetRate, state.HullpartsLevelSeconds, state.HullpartsLevel, 240f) ? 0 : 1) +
|
||||
(FactionPlanningState.IsCommodityOperational(state.ClaytronicsProjectedProductionRate, state.ClaytronicsProjectedNetRate, state.ClaytronicsLevelSeconds, state.ClaytronicsLevel, 240f) ? 0 : 1) +
|
||||
(state.HasShipFactory ? 0 : 1);
|
||||
var supplyNeed =
|
||||
FactionPlanningState.ComputeCommodityNeed(state.RefinedMetalsProjectedProductionRate, state.RefinedMetalsUsageRate, state.RefinedMetalsProjectedNetRate, state.RefinedMetalsLevelSeconds, state.RefinedMetalsLevel, 240f)
|
||||
+ FactionPlanningState.ComputeCommodityNeed(state.HullpartsProjectedProductionRate, state.HullpartsUsageRate, state.HullpartsProjectedNetRate, state.HullpartsLevelSeconds, state.HullpartsLevel, 240f)
|
||||
+ FactionPlanningState.ComputeCommodityNeed(state.ClaytronicsProjectedProductionRate, state.ClaytronicsUsageRate, state.ClaytronicsProjectedNetRate, state.ClaytronicsLevelSeconds, state.ClaytronicsLevel, 240f);
|
||||
|
||||
return missingStages <= 0 ? 0f : 125f + (missingStages * 18f);
|
||||
return missingStages <= 0 && supplyNeed <= 0.01f ? 0f : 110f + (missingStages * 22f) + (supplyNeed * 0.18f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,21 +128,22 @@ public sealed class EnsureWaterSecurityGoal : GoapGoal<FactionPlanningState>
|
||||
public override string Name => "ensure-water-security";
|
||||
|
||||
public override bool IsSatisfied(FactionPlanningState state) =>
|
||||
state.HasWaterProduction && state.WaterShortageHorizonSeconds >= 300f;
|
||||
FactionPlanningState.IsCommodityOperational(state.WaterProjectedProductionRate, state.WaterProjectedNetRate, state.WaterLevelSeconds, state.WaterLevel, 300f);
|
||||
|
||||
public override float ComputePriority(FactionPlanningState state, SimulationWorld world, CommanderRuntime commander)
|
||||
{
|
||||
if (state.HasWaterProduction && state.WaterShortageHorizonSeconds >= 300f)
|
||||
if (FactionPlanningState.IsCommodityOperational(state.WaterProjectedProductionRate, state.WaterProjectedNetRate, state.WaterLevelSeconds, state.WaterLevel, 300f))
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
if (float.IsPositiveInfinity(state.WaterShortageHorizonSeconds))
|
||||
{
|
||||
return state.HasWaterProduction ? 0f : 85f;
|
||||
}
|
||||
|
||||
return 55f + MathF.Max(0f, 300f - state.WaterShortageHorizonSeconds) * 0.2f;
|
||||
return 55f + FactionPlanningState.ComputeCommodityNeed(
|
||||
state.WaterProjectedProductionRate,
|
||||
state.WaterUsageRate,
|
||||
state.WaterProjectedNetRate,
|
||||
state.WaterLevelSeconds,
|
||||
state.WaterLevel,
|
||||
300f) * 0.25f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,159 +222,3 @@ public sealed class EnsureConstructionCapacityGoal : GoapGoal<FactionPlanningSta
|
||||
return deficit <= 0 ? 0f : 60f + (deficit * 10f);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Actions ───────────────────────────────────────────────────────────────────
|
||||
|
||||
public sealed class OrderShipProductionAction : GoapAction<FactionPlanningState>
|
||||
{
|
||||
private readonly string shipKind;
|
||||
private readonly string shipId;
|
||||
|
||||
public OrderShipProductionAction(string shipKind, string shipId)
|
||||
{
|
||||
this.shipKind = shipKind;
|
||||
this.shipId = shipId;
|
||||
}
|
||||
|
||||
public override string Name => $"order-{shipId}-production";
|
||||
public override float Cost => 1f;
|
||||
|
||||
public override bool CheckPreconditions(FactionPlanningState state) =>
|
||||
state.HasShipFactory && state.HasWarIndustrySupplyChain;
|
||||
|
||||
public override FactionPlanningState ApplyEffects(FactionPlanningState state)
|
||||
{
|
||||
switch (shipKind)
|
||||
{
|
||||
case "military": state.MilitaryShipCount++; break;
|
||||
case "mining": state.MinerShipCount++; break;
|
||||
case "transport": state.TransportShipCount++; break;
|
||||
case "construction": state.ConstructorShipCount++; break;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public override void Execute(SimulationEngine engine, SimulationWorld world, CommanderRuntime commander)
|
||||
{
|
||||
commander.ActiveDirectives.Add($"produce-{shipKind}-ships");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PlanWarIndustryAction : GoapAction<FactionPlanningState>
|
||||
{
|
||||
public override string Name => "plan-war-industry";
|
||||
public override float Cost => 2f;
|
||||
|
||||
public override bool CheckPreconditions(FactionPlanningState state) =>
|
||||
state.EnemyFactionCount > 0 && (!state.HasWarIndustrySupplyChain || !state.HasShipFactory);
|
||||
|
||||
public override FactionPlanningState ApplyEffects(FactionPlanningState state)
|
||||
{
|
||||
state.RefinedMetalsProductionRate = MathF.Max(state.RefinedMetalsProductionRate, 1f);
|
||||
state.HullpartsProductionRate = MathF.Max(state.HullpartsProductionRate, 1f);
|
||||
state.ClaytronicsProductionRate = MathF.Max(state.ClaytronicsProductionRate, 1f);
|
||||
state.HasShipFactory = true;
|
||||
return state;
|
||||
}
|
||||
|
||||
public override void Execute(SimulationEngine engine, SimulationWorld world, CommanderRuntime commander)
|
||||
{
|
||||
commander.ActiveDirectives.Add("bootstrap-war-industry");
|
||||
|
||||
if (FactionIndustryPlanner.AnalyzeShipyardNeed(world, commander.FactionId) is not { } project)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FactionIndustryPlanner.EnsureExpansionSite(world, commander.FactionId, project);
|
||||
commander.ActiveDirectives.Add($"expand-industry:{project.CommodityId}:{project.SystemId}:{project.CelestialId}");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PlanCommoditySupplyAction : GoapAction<FactionPlanningState>
|
||||
{
|
||||
private readonly string commodityId;
|
||||
|
||||
public PlanCommoditySupplyAction(string commodityId)
|
||||
{
|
||||
this.commodityId = commodityId;
|
||||
}
|
||||
|
||||
public override string Name => $"plan-{commodityId}-supply";
|
||||
public override float Cost => 2f;
|
||||
|
||||
public override bool CheckPreconditions(FactionPlanningState state) =>
|
||||
commodityId switch
|
||||
{
|
||||
"water" => !state.HasWaterProduction || state.WaterShortageHorizonSeconds < 300f,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public override FactionPlanningState ApplyEffects(FactionPlanningState state)
|
||||
{
|
||||
if (string.Equals(commodityId, "water", StringComparison.Ordinal))
|
||||
{
|
||||
state.WaterProductionRate = MathF.Max(state.WaterProductionRate, 1f);
|
||||
state.WaterShortageHorizonSeconds = MathF.Max(state.WaterShortageHorizonSeconds, 600f);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
public override void Execute(SimulationEngine engine, SimulationWorld world, CommanderRuntime commander)
|
||||
{
|
||||
if (FactionIndustryPlanner.AnalyzeCommodityNeed(world, commander.FactionId, commodityId) is not { } project)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FactionIndustryPlanner.EnsureExpansionSite(world, commander.FactionId, project);
|
||||
commander.ActiveDirectives.Add($"expand-industry:{project.CommodityId}:{project.SystemId}:{project.CelestialId}");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ExpandToSystemAction : GoapAction<FactionPlanningState>
|
||||
{
|
||||
public override string Name => "expand-to-system";
|
||||
public override float Cost => 3f;
|
||||
|
||||
public override bool CheckPreconditions(FactionPlanningState state) =>
|
||||
state.ConstructorShipCount > 0 && state.MilitaryShipCount >= 2 && state.HasWarIndustrySupplyChain;
|
||||
|
||||
public override FactionPlanningState ApplyEffects(FactionPlanningState state)
|
||||
{
|
||||
state.ControlledSystemCount++;
|
||||
return state;
|
||||
}
|
||||
|
||||
public override void Execute(SimulationEngine engine, SimulationWorld world, CommanderRuntime commander)
|
||||
{
|
||||
commander.ActiveDirectives.Add("expand-territory");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class LaunchExterminationCampaignAction : GoapAction<FactionPlanningState>
|
||||
{
|
||||
public override string Name => "launch-extermination-campaign";
|
||||
public override float Cost => 1f;
|
||||
|
||||
public override bool CheckPreconditions(FactionPlanningState state) =>
|
||||
state.EnemyFactionCount > 0
|
||||
&& state.HasShipFactory
|
||||
&& state.MilitaryShipCount >= Math.Max(2, FactionPlanningState.ComputeTargetWarships(state) / 2);
|
||||
|
||||
public override FactionPlanningState ApplyEffects(FactionPlanningState state)
|
||||
{
|
||||
state.EnemyShipCount = 0;
|
||||
state.EnemyStationCount = 0;
|
||||
state.EnemyFactionCount = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
public override void Execute(SimulationEngine engine, SimulationWorld world, CommanderRuntime commander)
|
||||
{
|
||||
commander.ActiveDirectives.Add("attack-rival");
|
||||
commander.ActiveDirectives.Add("produce-military-ships");
|
||||
}
|
||||
}
|
||||
|
||||
1169
apps/backend/Factions/AI/FactionObjectivePlanning.cs
Normal file
1169
apps/backend/Factions/AI/FactionObjectivePlanning.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
namespace SpaceGame.Api.Factions.Contracts;
|
||||
|
||||
public sealed record FactionGoapStateSnapshot(
|
||||
public sealed record FactionPlanningStateSnapshot(
|
||||
int MilitaryShipCount,
|
||||
int MinerShipCount,
|
||||
int TransportShipCount,
|
||||
@@ -9,17 +9,134 @@ public sealed record FactionGoapStateSnapshot(
|
||||
int TargetSystemCount,
|
||||
bool HasShipFactory,
|
||||
float OreStockpile,
|
||||
float RefinedMetalsStockpile,
|
||||
float RefinedMetalsProductionRate,
|
||||
float HullpartsStockpile,
|
||||
float HullpartsProductionRate,
|
||||
float ClaytronicsStockpile,
|
||||
float ClaytronicsProductionRate,
|
||||
float WaterStockpile,
|
||||
float WaterProductionRate,
|
||||
float WaterShortageHorizonSeconds);
|
||||
float RefinedMetalsAvailableStock,
|
||||
float RefinedMetalsUsageRate,
|
||||
float RefinedMetalsProjectedProductionRate,
|
||||
float RefinedMetalsProjectedNetRate,
|
||||
float RefinedMetalsLevelSeconds,
|
||||
string RefinedMetalsLevel,
|
||||
float HullpartsAvailableStock,
|
||||
float HullpartsUsageRate,
|
||||
float HullpartsProjectedProductionRate,
|
||||
float HullpartsProjectedNetRate,
|
||||
float HullpartsLevelSeconds,
|
||||
string HullpartsLevel,
|
||||
float ClaytronicsAvailableStock,
|
||||
float ClaytronicsUsageRate,
|
||||
float ClaytronicsProjectedProductionRate,
|
||||
float ClaytronicsProjectedNetRate,
|
||||
float ClaytronicsLevelSeconds,
|
||||
string ClaytronicsLevel,
|
||||
float WaterAvailableStock,
|
||||
float WaterUsageRate,
|
||||
float WaterProjectedProductionRate,
|
||||
float WaterProjectedNetRate,
|
||||
float WaterLevelSeconds,
|
||||
string WaterLevel);
|
||||
|
||||
public sealed record FactionGoapPrioritySnapshot(string GoalName, float Priority);
|
||||
public sealed record FactionStrategicPrioritySnapshot(string GoalName, float Priority);
|
||||
|
||||
public sealed record FactionCommoditySignalSnapshot(
|
||||
string ItemId,
|
||||
float AvailableStock,
|
||||
float OnHand,
|
||||
float ProductionRatePerSecond,
|
||||
float CommittedProductionRatePerSecond,
|
||||
float UsageRatePerSecond,
|
||||
float NetRatePerSecond,
|
||||
float ProjectedNetRatePerSecond,
|
||||
float LevelSeconds,
|
||||
string Level,
|
||||
float ProjectedProductionRatePerSecond,
|
||||
float BuyBacklog,
|
||||
float ReservedForConstruction);
|
||||
|
||||
public sealed record FactionThreatSignalSnapshot(
|
||||
string ScopeId,
|
||||
string ScopeKind,
|
||||
int EnemyShipCount,
|
||||
int EnemyStationCount);
|
||||
|
||||
public sealed record FactionBlackboardSnapshot(
|
||||
int PlanCycle,
|
||||
DateTimeOffset UpdatedAtUtc,
|
||||
int TargetWarshipCount,
|
||||
bool HasWarIndustrySupplyChain,
|
||||
bool HasShipyard,
|
||||
bool HasActiveExpansionProject,
|
||||
string? ActiveExpansionCommodityId,
|
||||
string? ActiveExpansionModuleId,
|
||||
string? ActiveExpansionSiteId,
|
||||
string? ActiveExpansionSystemId,
|
||||
int EnemyFactionCount,
|
||||
int EnemyShipCount,
|
||||
int EnemyStationCount,
|
||||
int MilitaryShipCount,
|
||||
int MinerShipCount,
|
||||
int TransportShipCount,
|
||||
int ConstructorShipCount,
|
||||
int ControlledSystemCount,
|
||||
IReadOnlyList<FactionCommoditySignalSnapshot> CommoditySignals,
|
||||
IReadOnlyList<FactionThreatSignalSnapshot> ThreatSignals);
|
||||
|
||||
public sealed record FactionPlanStepSnapshot(
|
||||
string Id,
|
||||
string Kind,
|
||||
string Status,
|
||||
float Priority,
|
||||
string? CommodityId,
|
||||
string? ModuleId,
|
||||
string? TargetFactionId,
|
||||
string? TargetSiteId,
|
||||
string? BlockingReason,
|
||||
string? Notes,
|
||||
int LastEvaluatedCycle,
|
||||
IReadOnlyList<string> DependencyStepIds,
|
||||
IReadOnlyList<string> RequiredFacts,
|
||||
IReadOnlyList<string> ProducedFacts,
|
||||
IReadOnlyList<string> AssignedAssets,
|
||||
IReadOnlyList<string> IssuedTaskIds);
|
||||
|
||||
public sealed record FactionIssuedTaskSnapshot(
|
||||
string Id,
|
||||
string Kind,
|
||||
string State,
|
||||
string ObjectiveId,
|
||||
string StepId,
|
||||
float Priority,
|
||||
string? ShipRole,
|
||||
string? CommodityId,
|
||||
string? ModuleId,
|
||||
string? TargetFactionId,
|
||||
string? TargetSystemId,
|
||||
string? TargetSiteId,
|
||||
int CreatedAtCycle,
|
||||
int UpdatedAtCycle,
|
||||
string? BlockingReason,
|
||||
string? Notes,
|
||||
IReadOnlyList<string> AssignedAssets);
|
||||
|
||||
public sealed record FactionObjectiveSnapshot(
|
||||
string Id,
|
||||
string Kind,
|
||||
string State,
|
||||
float Priority,
|
||||
string? ParentObjectiveId,
|
||||
string? TargetFactionId,
|
||||
string? TargetSystemId,
|
||||
string? TargetSiteId,
|
||||
string? TargetRegionId,
|
||||
string? CommodityId,
|
||||
string? ModuleId,
|
||||
int BudgetWeight,
|
||||
int SlotCost,
|
||||
int CreatedAtCycle,
|
||||
int UpdatedAtCycle,
|
||||
string? InvalidationReason,
|
||||
string? BlockingReason,
|
||||
IReadOnlyList<string> PrerequisiteObjectiveIds,
|
||||
IReadOnlyList<string> AssignedAssets,
|
||||
IReadOnlyList<FactionPlanStepSnapshot> Steps);
|
||||
|
||||
public sealed record FactionSnapshot(
|
||||
string Id,
|
||||
@@ -32,8 +149,11 @@ public sealed record FactionSnapshot(
|
||||
int ShipsBuilt,
|
||||
int ShipsLost,
|
||||
string? DefaultPolicySetId,
|
||||
FactionGoapStateSnapshot? GoapState,
|
||||
IReadOnlyList<FactionGoapPrioritySnapshot>? GoapPriorities);
|
||||
FactionPlanningStateSnapshot? StrategicAssessment,
|
||||
IReadOnlyList<FactionStrategicPrioritySnapshot>? StrategicPriorities,
|
||||
FactionBlackboardSnapshot? Blackboard,
|
||||
IReadOnlyList<FactionObjectiveSnapshot>? Objectives,
|
||||
IReadOnlyList<FactionIssuedTaskSnapshot>? IssuedTasks);
|
||||
|
||||
public sealed record FactionDelta(
|
||||
string Id,
|
||||
@@ -46,5 +166,8 @@ public sealed record FactionDelta(
|
||||
int ShipsBuilt,
|
||||
int ShipsLost,
|
||||
string? DefaultPolicySetId,
|
||||
FactionGoapStateSnapshot? GoapState,
|
||||
IReadOnlyList<FactionGoapPrioritySnapshot>? GoapPriorities);
|
||||
FactionPlanningStateSnapshot? StrategicAssessment,
|
||||
IReadOnlyList<FactionStrategicPrioritySnapshot>? StrategicPriorities,
|
||||
FactionBlackboardSnapshot? Blackboard,
|
||||
IReadOnlyList<FactionObjectiveSnapshot>? Objectives,
|
||||
IReadOnlyList<FactionIssuedTaskSnapshot>? IssuedTasks);
|
||||
|
||||
@@ -27,7 +27,6 @@ public sealed class CommanderRuntime
|
||||
public string? PolicySetId { get; set; }
|
||||
public string? Doctrine { get; set; }
|
||||
public List<string> Goals { get; } = [];
|
||||
public HashSet<string> ActiveDirectives { get; } = new(StringComparer.Ordinal);
|
||||
public string? ActiveGoalName { get; set; }
|
||||
public string? ActiveActionName { get; set; }
|
||||
public float ReplanTimer { get; set; }
|
||||
@@ -37,8 +36,194 @@ public sealed class CommanderRuntime
|
||||
public CommanderTaskRuntime? ActiveTask { get; set; }
|
||||
public HashSet<string> SubordinateCommanderIds { get; } = new(StringComparer.Ordinal);
|
||||
public bool IsAlive { get; set; } = true;
|
||||
public FactionPlanningState? LastPlanningState { get; set; }
|
||||
public IReadOnlyList<(string Name, float Priority)>? LastGoalPriorities { get; set; }
|
||||
public FactionPlanningState? LastStrategicAssessment { get; set; }
|
||||
public IReadOnlyList<(string Name, float Priority)>? LastStrategicPriorities { get; set; }
|
||||
public FactionBlackboardRuntime? FactionBlackboard { get; set; }
|
||||
public List<FactionObjectiveRuntime> Objectives { get; } = [];
|
||||
public List<FactionIssuedTaskRuntime> IssuedTasks { get; } = [];
|
||||
public int PlanningCycle { get; set; }
|
||||
}
|
||||
|
||||
public enum FactionObjectiveKind
|
||||
{
|
||||
DestroyFaction,
|
||||
BootstrapWarIndustry,
|
||||
BuildShipyard,
|
||||
BuildAttackFleet,
|
||||
EnsureCommoditySupply,
|
||||
EnsureWaterSecurity,
|
||||
EnsureMiningCapacity,
|
||||
EnsureConstructionCapacity,
|
||||
EnsureTransportCapacity,
|
||||
}
|
||||
|
||||
public enum FactionObjectiveState
|
||||
{
|
||||
Planned,
|
||||
Active,
|
||||
Blocked,
|
||||
Complete,
|
||||
Failed,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
public enum FactionPlanStepKind
|
||||
{
|
||||
EnsureCommodityProduction,
|
||||
EnsureShipyardSite,
|
||||
ProduceFleet,
|
||||
AttackFactionAssets,
|
||||
EnsureWaterSupply,
|
||||
EnsureMiningCapacity,
|
||||
EnsureConstructionCapacity,
|
||||
EnsureTransportCapacity,
|
||||
MonitorExpansionProject,
|
||||
}
|
||||
|
||||
public enum FactionPlanStepStatus
|
||||
{
|
||||
Planned,
|
||||
Ready,
|
||||
Running,
|
||||
Blocked,
|
||||
Complete,
|
||||
Failed,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
public enum FactionIssuedTaskKind
|
||||
{
|
||||
ExpandIndustry,
|
||||
ProduceShips,
|
||||
AttackFactionAssets,
|
||||
SustainWarIndustry,
|
||||
}
|
||||
|
||||
public enum FactionIssuedTaskState
|
||||
{
|
||||
Planned,
|
||||
Active,
|
||||
Blocked,
|
||||
Complete,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
public sealed class FactionObjectiveRuntime
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string MergeKey { get; init; }
|
||||
public required FactionObjectiveKind Kind { get; init; }
|
||||
public FactionObjectiveState State { get; set; } = FactionObjectiveState.Planned;
|
||||
public float Priority { get; set; }
|
||||
public string? ParentObjectiveId { get; set; }
|
||||
public string? TargetFactionId { get; set; }
|
||||
public string? TargetSystemId { get; set; }
|
||||
public string? TargetSiteId { get; set; }
|
||||
public string? TargetRegionId { get; set; }
|
||||
public string? CommodityId { get; set; }
|
||||
public string? ModuleId { get; set; }
|
||||
public int BudgetWeight { get; set; }
|
||||
public int SlotCost { get; set; } = 1;
|
||||
public int CreatedAtCycle { get; init; }
|
||||
public int UpdatedAtCycle { get; set; }
|
||||
public string? InvalidationReason { get; set; }
|
||||
public string? BlockingReason { get; set; }
|
||||
public HashSet<string> PrerequisiteObjectiveIds { get; } = new(StringComparer.Ordinal);
|
||||
public HashSet<string> AssignedAssetIds { get; } = new(StringComparer.Ordinal);
|
||||
public List<FactionPlanStepRuntime> Steps { get; } = [];
|
||||
}
|
||||
|
||||
public sealed class FactionPlanStepRuntime
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string ObjectiveId { get; init; }
|
||||
public required FactionPlanStepKind Kind { get; init; }
|
||||
public FactionPlanStepStatus Status { get; set; } = FactionPlanStepStatus.Planned;
|
||||
public float Priority { get; set; }
|
||||
public string? CommodityId { get; set; }
|
||||
public string? ModuleId { get; set; }
|
||||
public string? TargetFactionId { get; set; }
|
||||
public string? TargetSiteId { get; set; }
|
||||
public string? BlockingReason { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public int LastEvaluatedCycle { get; set; }
|
||||
public HashSet<string> DependencyStepIds { get; } = new(StringComparer.Ordinal);
|
||||
public HashSet<string> RequiredFacts { get; } = new(StringComparer.Ordinal);
|
||||
public HashSet<string> ProducedFacts { get; } = new(StringComparer.Ordinal);
|
||||
public HashSet<string> AssignedAssetIds { get; } = new(StringComparer.Ordinal);
|
||||
public HashSet<string> IssuedTaskIds { get; } = new(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public sealed class FactionIssuedTaskRuntime
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string MergeKey { get; init; }
|
||||
public required FactionIssuedTaskKind Kind { get; init; }
|
||||
public required string ObjectiveId { get; init; }
|
||||
public required string StepId { get; init; }
|
||||
public FactionIssuedTaskState State { get; set; } = FactionIssuedTaskState.Planned;
|
||||
public float Priority { get; set; }
|
||||
public string? ShipRole { get; set; }
|
||||
public string? CommodityId { get; set; }
|
||||
public string? ModuleId { get; set; }
|
||||
public string? TargetFactionId { get; set; }
|
||||
public string? TargetSystemId { get; set; }
|
||||
public string? TargetSiteId { get; set; }
|
||||
public int CreatedAtCycle { get; init; }
|
||||
public int UpdatedAtCycle { get; set; }
|
||||
public string? BlockingReason { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public HashSet<string> AssignedAssetIds { get; } = new(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public sealed class FactionBlackboardRuntime
|
||||
{
|
||||
public int PlanCycle { get; set; }
|
||||
public DateTimeOffset UpdatedAtUtc { get; set; }
|
||||
public int TargetWarshipCount { get; set; }
|
||||
public bool HasWarIndustrySupplyChain { get; set; }
|
||||
public bool HasShipyard { get; set; }
|
||||
public bool HasActiveExpansionProject { get; set; }
|
||||
public string? ActiveExpansionCommodityId { get; set; }
|
||||
public string? ActiveExpansionModuleId { get; set; }
|
||||
public string? ActiveExpansionSiteId { get; set; }
|
||||
public string? ActiveExpansionSystemId { get; set; }
|
||||
public int EnemyFactionCount { get; set; }
|
||||
public int EnemyShipCount { get; set; }
|
||||
public int EnemyStationCount { get; set; }
|
||||
public int MilitaryShipCount { get; set; }
|
||||
public int MinerShipCount { get; set; }
|
||||
public int TransportShipCount { get; set; }
|
||||
public int ConstructorShipCount { get; set; }
|
||||
public int ControlledSystemCount { get; set; }
|
||||
public List<FactionCommoditySignalRuntime> CommoditySignals { get; } = [];
|
||||
public List<FactionThreatSignalRuntime> ThreatSignals { get; } = [];
|
||||
public HashSet<string> AvailableShipIds { get; } = new(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public sealed class FactionCommoditySignalRuntime
|
||||
{
|
||||
public required string ItemId { get; init; }
|
||||
public float AvailableStock { get; set; }
|
||||
public float OnHand { get; set; }
|
||||
public float ProductionRatePerSecond { get; set; }
|
||||
public float CommittedProductionRatePerSecond { get; set; }
|
||||
public float UsageRatePerSecond { get; set; }
|
||||
public float NetRatePerSecond { get; set; }
|
||||
public float ProjectedNetRatePerSecond { get; set; }
|
||||
public float LevelSeconds { get; set; }
|
||||
public string Level { get; set; } = "unknown";
|
||||
public float ProjectedProductionRatePerSecond { get; set; }
|
||||
public float BuyBacklog { get; set; }
|
||||
public float ReservedForConstruction { get; set; }
|
||||
}
|
||||
|
||||
public sealed class FactionThreatSignalRuntime
|
||||
{
|
||||
public required string ScopeId { get; init; }
|
||||
public required string ScopeKind { get; init; }
|
||||
public int EnemyShipCount { get; set; }
|
||||
public int EnemyStationCount { get; set; }
|
||||
}
|
||||
|
||||
public sealed class CommanderBehaviorRuntime
|
||||
|
||||
Reference in New Issue
Block a user