improvement on gm windows, ai
This commit is contained in:
@@ -457,6 +457,18 @@ internal sealed class ObjectiveStepFactory
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed record StepExecutionAssessment(
|
||||
FactionPlanStepStatus Status,
|
||||
string StatusReason,
|
||||
string? BlockingReason = null,
|
||||
StepExecutionBinding? Binding = null,
|
||||
IndustryExpansionProject? ExpectedProject = null);
|
||||
|
||||
internal sealed record StepExecutionBinding(
|
||||
string Kind,
|
||||
string? TargetId,
|
||||
string Summary);
|
||||
|
||||
internal sealed class FactionObjectiveExecutor
|
||||
{
|
||||
internal void Execute(
|
||||
@@ -466,6 +478,7 @@ internal sealed class FactionObjectiveExecutor
|
||||
FactionPlanningState state)
|
||||
{
|
||||
var blackboard = commander.FactionBlackboard ?? throw new InvalidOperationException("Faction blackboard must exist before objectives are executed.");
|
||||
var activeProject = FactionIndustryPlanner.GetActiveExpansionProject(world, commander.FactionId);
|
||||
|
||||
commander.ActiveGoalName = null;
|
||||
commander.ActiveActionName = null;
|
||||
@@ -480,8 +493,8 @@ internal sealed class FactionObjectiveExecutor
|
||||
EvaluateObjective(world, commander, objective);
|
||||
foreach (var step in objective.Steps.OrderByDescending(step => step.Priority))
|
||||
{
|
||||
EvaluateStep(world, commander, objective, step, blackboard, state);
|
||||
EmitTasks(engine, world, commander, objective, step, blackboard, state, touchedTaskIds, assignedAssetIds);
|
||||
var assessment = EvaluateStep(world, commander, objective, step, blackboard, state, activeProject);
|
||||
EmitTasks(engine, world, commander, objective, step, assessment, touchedTaskIds, assignedAssetIds);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,102 +544,300 @@ internal sealed class FactionObjectiveExecutor
|
||||
objective.State = FactionObjectiveState.Active;
|
||||
}
|
||||
|
||||
private static void EvaluateStep(
|
||||
private static StepExecutionAssessment EvaluateStep(
|
||||
SimulationWorld world,
|
||||
CommanderRuntime commander,
|
||||
FactionObjectiveRuntime objective,
|
||||
FactionPlanStepRuntime step,
|
||||
FactionBlackboardRuntime blackboard,
|
||||
FactionPlanningState state)
|
||||
FactionPlanningState state,
|
||||
IndustryExpansionProject? activeProject)
|
||||
{
|
||||
step.LastEvaluatedCycle = commander.PlanningCycle;
|
||||
step.BlockingReason = null;
|
||||
step.StatusReason = null;
|
||||
step.ExecutionBindingKind = null;
|
||||
step.ExecutionBindingTargetId = null;
|
||||
step.ExecutionBindingSummary = null;
|
||||
|
||||
StepExecutionAssessment assessment;
|
||||
if (step.DependencyStepIds.Count > 0 && HasIncompleteDependencies(commander, step))
|
||||
{
|
||||
step.Status = FactionPlanStepStatus.Blocked;
|
||||
step.BlockingReason = "Waiting for prerequisite objective steps to complete.";
|
||||
return;
|
||||
assessment = new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Blocked,
|
||||
"Blocked on prerequisite objective steps.",
|
||||
BlockingReason: "Waiting for prerequisite objective steps to complete.");
|
||||
}
|
||||
else
|
||||
{
|
||||
assessment = step.Kind switch
|
||||
{
|
||||
FactionPlanStepKind.EnsureCommodityProduction => EvaluateCommodityStep(world, commander, step, blackboard, activeProject),
|
||||
FactionPlanStepKind.EnsureShipyardSite => EvaluateShipyardStep(world, commander, step, state, activeProject),
|
||||
FactionPlanStepKind.ProduceFleet => EvaluateFleetProductionStep(commander, step, blackboard, state),
|
||||
FactionPlanStepKind.AttackFactionAssets => EvaluateAttackStep(commander, step, blackboard, state),
|
||||
FactionPlanStepKind.EnsureWaterSupply => EvaluateWaterStep(world, commander, step, blackboard, activeProject),
|
||||
FactionPlanStepKind.EnsureMiningCapacity => EvaluateCapacityStep(commander, step, blackboard.HasShipyard, state.MinerShipCount, 2, "mining"),
|
||||
FactionPlanStepKind.EnsureConstructionCapacity => EvaluateCapacityStep(commander, step, blackboard.HasShipyard, state.ConstructorShipCount, 1, "construction"),
|
||||
FactionPlanStepKind.EnsureTransportCapacity => EvaluateCapacityStep(commander, step, blackboard.HasShipyard, state.TransportShipCount, 1, "transport"),
|
||||
FactionPlanStepKind.MonitorExpansionProject => EvaluateWarIndustryMonitorStep(world, commander, step, blackboard, activeProject),
|
||||
_ => new StepExecutionAssessment(FactionPlanStepStatus.Failed, "Unknown step kind."),
|
||||
};
|
||||
}
|
||||
|
||||
switch (step.Kind)
|
||||
{
|
||||
case FactionPlanStepKind.EnsureCommodityProduction:
|
||||
EvaluateCommodityStep(step, blackboard);
|
||||
break;
|
||||
case FactionPlanStepKind.EnsureShipyardSite:
|
||||
if (state.HasShipFactory)
|
||||
{
|
||||
step.Status = FactionPlanStepStatus.Complete;
|
||||
step.ProducedFacts.Add("shipyard-online");
|
||||
}
|
||||
else
|
||||
{
|
||||
step.Status = blackboard.HasActiveExpansionProject && string.Equals(blackboard.ActiveExpansionModuleId, step.ModuleId, StringComparison.Ordinal)
|
||||
? FactionPlanStepStatus.Running
|
||||
: FactionPlanStepStatus.Ready;
|
||||
}
|
||||
break;
|
||||
case FactionPlanStepKind.ProduceFleet:
|
||||
step.Status = state.MilitaryShipCount >= blackboard.TargetWarshipCount
|
||||
? FactionPlanStepStatus.Complete
|
||||
: FactionPlanStepStatus.Running;
|
||||
break;
|
||||
case FactionPlanStepKind.AttackFactionAssets:
|
||||
if (blackboard.EnemyFactionCount <= 0)
|
||||
{
|
||||
step.Status = FactionPlanStepStatus.Complete;
|
||||
}
|
||||
else if (state.MilitaryShipCount < Math.Max(2, blackboard.TargetWarshipCount / 2))
|
||||
{
|
||||
step.Status = FactionPlanStepStatus.Blocked;
|
||||
step.BlockingReason = "Insufficient military strength to commit to a faction attack objective.";
|
||||
}
|
||||
else
|
||||
{
|
||||
step.Status = FactionPlanStepStatus.Running;
|
||||
}
|
||||
break;
|
||||
case FactionPlanStepKind.EnsureWaterSupply:
|
||||
step.Status = IsCommodityOperational(blackboard, "water", 300f)
|
||||
? FactionPlanStepStatus.Complete
|
||||
: blackboard.HasActiveExpansionProject && string.Equals(blackboard.ActiveExpansionCommodityId, "water", StringComparison.Ordinal)
|
||||
? FactionPlanStepStatus.Running
|
||||
: FactionPlanStepStatus.Ready;
|
||||
break;
|
||||
case FactionPlanStepKind.EnsureMiningCapacity:
|
||||
step.Status = state.MinerShipCount >= 2 ? FactionPlanStepStatus.Complete : FactionPlanStepStatus.Running;
|
||||
break;
|
||||
case FactionPlanStepKind.EnsureConstructionCapacity:
|
||||
step.Status = state.ConstructorShipCount >= 1 ? FactionPlanStepStatus.Complete : FactionPlanStepStatus.Running;
|
||||
break;
|
||||
case FactionPlanStepKind.EnsureTransportCapacity:
|
||||
step.Status = state.TransportShipCount >= 1 ? FactionPlanStepStatus.Complete : FactionPlanStepStatus.Running;
|
||||
break;
|
||||
case FactionPlanStepKind.MonitorExpansionProject:
|
||||
step.Status = blackboard.HasWarIndustrySupplyChain ? FactionPlanStepStatus.Complete : FactionPlanStepStatus.Running;
|
||||
break;
|
||||
}
|
||||
ApplyAssessment(step, assessment);
|
||||
return assessment;
|
||||
}
|
||||
|
||||
private static void EvaluateCommodityStep(
|
||||
private static StepExecutionAssessment EvaluateCommodityStep(
|
||||
SimulationWorld world,
|
||||
CommanderRuntime commander,
|
||||
FactionPlanStepRuntime step,
|
||||
FactionBlackboardRuntime blackboard)
|
||||
FactionBlackboardRuntime blackboard,
|
||||
IndustryExpansionProject? activeProject)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(step.CommodityId))
|
||||
{
|
||||
step.Status = FactionPlanStepStatus.Failed;
|
||||
step.BlockingReason = "Commodity planning step is missing a target commodity.";
|
||||
return;
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Failed,
|
||||
"Commodity step is missing a required commodity.",
|
||||
BlockingReason: "Commodity planning step is missing a target commodity.");
|
||||
}
|
||||
|
||||
var completed = IsCommodityOperational(blackboard, step.CommodityId, 240f);
|
||||
|
||||
step.Status = completed ? FactionPlanStepStatus.Complete : FactionPlanStepStatus.Ready;
|
||||
if (completed)
|
||||
if (IsCommodityOperational(blackboard, step.CommodityId, 240f))
|
||||
{
|
||||
step.ProducedFacts.Add($"commodity-online:{step.CommodityId}");
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Complete,
|
||||
$"Commodity {step.CommodityId} is operational in the faction economy.");
|
||||
}
|
||||
|
||||
var expectedProject = FactionIndustryPlanner.AnalyzeCommodityNeed(world, commander.FactionId, step.CommodityId, ignoreActiveExpansionProject: true);
|
||||
return EvaluateExpansionRequirement(step, expectedProject, activeProject);
|
||||
}
|
||||
|
||||
private static StepExecutionAssessment EvaluateShipyardStep(
|
||||
SimulationWorld world,
|
||||
CommanderRuntime commander,
|
||||
FactionPlanStepRuntime step,
|
||||
FactionPlanningState state,
|
||||
IndustryExpansionProject? activeProject)
|
||||
{
|
||||
if (state.HasShipFactory)
|
||||
{
|
||||
step.ProducedFacts.Add("shipyard-online");
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Complete,
|
||||
"Faction already has an online shipyard.");
|
||||
}
|
||||
|
||||
var expectedProject = FactionIndustryPlanner.CreateShipyardFoundationProject(world, commander.FactionId, ignoreActiveExpansionProject: true);
|
||||
return EvaluateExpansionRequirement(step, expectedProject, activeProject);
|
||||
}
|
||||
|
||||
private static StepExecutionAssessment EvaluateWaterStep(
|
||||
SimulationWorld world,
|
||||
CommanderRuntime commander,
|
||||
FactionPlanStepRuntime step,
|
||||
FactionBlackboardRuntime blackboard,
|
||||
IndustryExpansionProject? activeProject)
|
||||
{
|
||||
if (IsCommodityOperational(blackboard, "water", 300f))
|
||||
{
|
||||
step.ProducedFacts.Add("commodity-online:water");
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Complete,
|
||||
"Water supply is operational.");
|
||||
}
|
||||
|
||||
var expectedProject = FactionIndustryPlanner.AnalyzeCommodityNeed(world, commander.FactionId, "water", ignoreActiveExpansionProject: true);
|
||||
return EvaluateExpansionRequirement(step, expectedProject, activeProject);
|
||||
}
|
||||
|
||||
private static StepExecutionAssessment EvaluateFleetProductionStep(
|
||||
CommanderRuntime commander,
|
||||
FactionPlanStepRuntime step,
|
||||
FactionBlackboardRuntime blackboard,
|
||||
FactionPlanningState state)
|
||||
{
|
||||
if (state.MilitaryShipCount >= blackboard.TargetWarshipCount)
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Complete,
|
||||
"Target war fleet size has been reached.");
|
||||
}
|
||||
|
||||
if (!blackboard.HasShipyard)
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Blocked,
|
||||
"Fleet production requires an online shipyard.",
|
||||
BlockingReason: "Fleet production requires an online shipyard.");
|
||||
}
|
||||
|
||||
if (TryFindIssuedTaskBinding(commander, step, out var binding))
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Running,
|
||||
"Military fleet production is already bound to an issued ship-production task.",
|
||||
Binding: binding);
|
||||
}
|
||||
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Ready,
|
||||
"Shipyard is available; military fleet production can begin.");
|
||||
}
|
||||
|
||||
private static StepExecutionAssessment EvaluateAttackStep(
|
||||
CommanderRuntime commander,
|
||||
FactionPlanStepRuntime step,
|
||||
FactionBlackboardRuntime blackboard,
|
||||
FactionPlanningState state)
|
||||
{
|
||||
if (blackboard.EnemyFactionCount <= 0)
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Complete,
|
||||
"No hostile faction remains to attack.");
|
||||
}
|
||||
|
||||
if (state.MilitaryShipCount < Math.Max(2, blackboard.TargetWarshipCount / 2))
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Blocked,
|
||||
"Insufficient military strength to begin the attack objective.",
|
||||
BlockingReason: "Insufficient military strength to commit to a faction attack objective.");
|
||||
}
|
||||
|
||||
if (TryFindIssuedTaskBinding(commander, step, out var binding))
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Running,
|
||||
"Attack objective is already bound to a matching combat task.",
|
||||
Binding: binding);
|
||||
}
|
||||
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Ready,
|
||||
"Combat strength is available; attack execution can begin.");
|
||||
}
|
||||
|
||||
private static StepExecutionAssessment EvaluateCapacityStep(
|
||||
CommanderRuntime commander,
|
||||
FactionPlanStepRuntime step,
|
||||
bool hasShipyard,
|
||||
int currentCount,
|
||||
int requiredCount,
|
||||
string shipRole)
|
||||
{
|
||||
if (currentCount >= requiredCount)
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Complete,
|
||||
$"Faction already meets the required {shipRole} ship capacity.");
|
||||
}
|
||||
|
||||
if (!hasShipyard)
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Blocked,
|
||||
$"No shipyard is currently assigned to produce {shipRole} ships.",
|
||||
BlockingReason: $"Ship capacity expansion for {shipRole} requires an online shipyard.");
|
||||
}
|
||||
|
||||
if (TryFindIssuedTaskBinding(commander, step, out var binding))
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Running,
|
||||
$"{shipRole} ship production is already bound to a matching issued task.",
|
||||
Binding: binding);
|
||||
}
|
||||
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Ready,
|
||||
$"Shipyard capacity is available; {shipRole} ship production can begin.");
|
||||
}
|
||||
|
||||
private static StepExecutionAssessment EvaluateWarIndustryMonitorStep(
|
||||
SimulationWorld world,
|
||||
CommanderRuntime commander,
|
||||
FactionPlanStepRuntime step,
|
||||
FactionBlackboardRuntime blackboard,
|
||||
IndustryExpansionProject? activeProject)
|
||||
{
|
||||
if (blackboard.HasWarIndustrySupplyChain)
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Complete,
|
||||
"War-industry supply chain is operational.");
|
||||
}
|
||||
|
||||
foreach (var commodityId in new[] { "refinedmetals", "hullparts", "claytronics" })
|
||||
{
|
||||
if (IsCommodityOperational(blackboard, commodityId, 240f))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var expectedProject = FactionIndustryPlanner.AnalyzeCommodityNeed(world, commander.FactionId, commodityId, ignoreActiveExpansionProject: true);
|
||||
return EvaluateExpansionRequirement(step, expectedProject, activeProject);
|
||||
}
|
||||
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Ready,
|
||||
"War-industry prerequisites are unresolved but no matching active project is bound yet.");
|
||||
}
|
||||
|
||||
private static StepExecutionAssessment EvaluateExpansionRequirement(
|
||||
FactionPlanStepRuntime step,
|
||||
IndustryExpansionProject? expectedProject,
|
||||
IndustryExpansionProject? activeProject)
|
||||
{
|
||||
if (expectedProject is null)
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Blocked,
|
||||
"Unable to derive a valid expansion plan for the step outcome.",
|
||||
BlockingReason: BuildMissingPlanReason(step));
|
||||
}
|
||||
|
||||
if (activeProject is not null && ProjectsSemanticallyMatch(expectedProject, activeProject))
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Running,
|
||||
$"Running on matching active expansion project {DescribeProject(activeProject)}.",
|
||||
Binding: new StepExecutionBinding(
|
||||
"expansion-project",
|
||||
activeProject.SiteId,
|
||||
$"Matched active project {DescribeProject(activeProject)}."),
|
||||
ExpectedProject: activeProject);
|
||||
}
|
||||
|
||||
if (activeProject is not null)
|
||||
{
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Blocked,
|
||||
$"Blocked by unrelated active expansion {DescribeProject(activeProject)}; step requires {DescribeProject(expectedProject)}.",
|
||||
BlockingReason: $"Active expansion {DescribeProject(activeProject)} does not satisfy required outcome {DescribeProject(expectedProject)}.",
|
||||
ExpectedProject: expectedProject);
|
||||
}
|
||||
|
||||
return new StepExecutionAssessment(
|
||||
FactionPlanStepStatus.Ready,
|
||||
$"Ready to start required expansion {DescribeProject(expectedProject)}.",
|
||||
ExpectedProject: expectedProject);
|
||||
}
|
||||
|
||||
private static void ApplyAssessment(
|
||||
FactionPlanStepRuntime step,
|
||||
StepExecutionAssessment assessment)
|
||||
{
|
||||
step.Status = assessment.Status;
|
||||
step.StatusReason = assessment.StatusReason;
|
||||
step.BlockingReason = assessment.BlockingReason;
|
||||
step.ExecutionBindingKind = assessment.Binding?.Kind;
|
||||
step.ExecutionBindingTargetId = assessment.Binding?.TargetId;
|
||||
step.ExecutionBindingSummary = assessment.Binding?.Summary;
|
||||
}
|
||||
|
||||
private static bool IsCommodityOperational(
|
||||
@@ -653,8 +864,7 @@ internal sealed class FactionObjectiveExecutor
|
||||
CommanderRuntime commander,
|
||||
FactionObjectiveRuntime objective,
|
||||
FactionPlanStepRuntime step,
|
||||
FactionBlackboardRuntime blackboard,
|
||||
FactionPlanningState state,
|
||||
StepExecutionAssessment assessment,
|
||||
ISet<string> touchedTaskIds,
|
||||
ISet<string> assignedAssetIds)
|
||||
{
|
||||
@@ -670,128 +880,12 @@ internal sealed class FactionObjectiveExecutor
|
||||
switch (step.Kind)
|
||||
{
|
||||
case FactionPlanStepKind.EnsureCommodityProduction:
|
||||
if (blackboard.HasActiveExpansionProject)
|
||||
{
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: blackboard.ActiveExpansionCommodityId ?? step.CommodityId,
|
||||
moduleId: blackboard.ActiveExpansionModuleId,
|
||||
targetSystemId: blackboard.ActiveExpansionSystemId,
|
||||
targetSiteId: blackboard.ActiveExpansionSiteId,
|
||||
blockingReason: step.BlockingReason,
|
||||
notes: step.Notes ?? "Expansion project already active for faction.");
|
||||
AssignConstructionAssets(world, commander, objective, step, step.IssuedTaskIds, assignedAssetIds);
|
||||
step.Status = FactionPlanStepStatus.Running;
|
||||
return;
|
||||
}
|
||||
|
||||
if (step.CommodityId is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var project = FactionIndustryPlanner.AnalyzeCommodityNeed(world, commander.FactionId, step.CommodityId);
|
||||
if (project is not null)
|
||||
{
|
||||
FactionIndustryPlanner.EnsureExpansionSite(world, commander.FactionId, project);
|
||||
step.TargetSiteId = project.SiteId;
|
||||
step.Status = FactionPlanStepStatus.Running;
|
||||
step.Notes = $"Queued expansion project for {project.CommodityId}.";
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: project.CommodityId,
|
||||
moduleId: project.ModuleId,
|
||||
targetSystemId: project.SystemId,
|
||||
targetSiteId: project.SiteId,
|
||||
blockingReason: null,
|
||||
notes: step.Notes);
|
||||
AssignConstructionAssets(world, commander, objective, step, step.IssuedTaskIds, assignedAssetIds);
|
||||
}
|
||||
else
|
||||
{
|
||||
step.Status = FactionPlanStepStatus.Blocked;
|
||||
step.BlockingReason = $"Unable to derive an expansion project for {step.CommodityId}.";
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: step.CommodityId,
|
||||
moduleId: step.ModuleId,
|
||||
targetSystemId: null,
|
||||
targetSiteId: null,
|
||||
blockingReason: step.BlockingReason,
|
||||
notes: step.Notes);
|
||||
}
|
||||
EmitExpansionExecution(world, commander, objective, step, assessment, touchedTaskIds, assignedAssetIds);
|
||||
break;
|
||||
case FactionPlanStepKind.EnsureShipyardSite:
|
||||
if (blackboard.HasActiveExpansionProject)
|
||||
{
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: blackboard.ActiveExpansionCommodityId,
|
||||
moduleId: blackboard.ActiveExpansionModuleId ?? step.ModuleId,
|
||||
targetSystemId: blackboard.ActiveExpansionSystemId,
|
||||
targetSiteId: blackboard.ActiveExpansionSiteId,
|
||||
blockingReason: step.BlockingReason,
|
||||
notes: step.Notes ?? "Shipyard support project waiting on current expansion site.");
|
||||
AssignConstructionAssets(world, commander, objective, step, step.IssuedTaskIds, assignedAssetIds);
|
||||
step.Status = FactionPlanStepStatus.Running;
|
||||
return;
|
||||
}
|
||||
|
||||
var shipyardProject = FactionIndustryPlanner.CreateShipyardFoundationProject(world, commander.FactionId);
|
||||
if (shipyardProject is not null)
|
||||
{
|
||||
FactionIndustryPlanner.EnsureExpansionSite(world, commander.FactionId, shipyardProject);
|
||||
step.TargetSiteId = shipyardProject.SiteId;
|
||||
step.Status = FactionPlanStepStatus.Running;
|
||||
step.Notes = "Queued shipyard foundation project.";
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: shipyardProject.CommodityId,
|
||||
moduleId: shipyardProject.ModuleId,
|
||||
targetSystemId: shipyardProject.SystemId,
|
||||
targetSiteId: shipyardProject.SiteId,
|
||||
blockingReason: null,
|
||||
notes: step.Notes);
|
||||
AssignConstructionAssets(world, commander, objective, step, step.IssuedTaskIds, assignedAssetIds);
|
||||
}
|
||||
else
|
||||
{
|
||||
step.Status = FactionPlanStepStatus.Blocked;
|
||||
step.BlockingReason = "Unable to identify a viable shipyard foundation project.";
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: step.CommodityId,
|
||||
moduleId: step.ModuleId,
|
||||
targetSystemId: null,
|
||||
targetSiteId: null,
|
||||
blockingReason: step.BlockingReason,
|
||||
notes: step.Notes);
|
||||
}
|
||||
EmitExpansionExecution(world, commander, objective, step, assessment, touchedTaskIds, assignedAssetIds);
|
||||
break;
|
||||
case FactionPlanStepKind.ProduceFleet:
|
||||
if (!blackboard.HasShipyard)
|
||||
{
|
||||
step.Status = FactionPlanStepStatus.Blocked;
|
||||
step.BlockingReason = "Fleet production requires an online shipyard.";
|
||||
}
|
||||
UpsertShipProductionTask(
|
||||
commander,
|
||||
objective,
|
||||
@@ -801,78 +895,30 @@ internal sealed class FactionObjectiveExecutor
|
||||
blockingReason: step.BlockingReason,
|
||||
notes: step.Notes ?? "Maintain military ship production until war fleet target is satisfied.");
|
||||
AssignShipyardAssets(world, commander, objective, step);
|
||||
PromoteShipProductionStepToRunning(step, "military");
|
||||
break;
|
||||
case FactionPlanStepKind.AttackFactionAssets:
|
||||
UpsertAttackTask(commander, objective, step, touchedTaskIds);
|
||||
AssignCombatAssets(world, commander, objective, step, step.IssuedTaskIds, assignedAssetIds);
|
||||
PromoteCombatStepToRunning(step);
|
||||
break;
|
||||
case FactionPlanStepKind.EnsureWaterSupply:
|
||||
if (blackboard.HasActiveExpansionProject)
|
||||
{
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: blackboard.ActiveExpansionCommodityId,
|
||||
moduleId: blackboard.ActiveExpansionModuleId,
|
||||
targetSystemId: blackboard.ActiveExpansionSystemId,
|
||||
targetSiteId: blackboard.ActiveExpansionSiteId,
|
||||
blockingReason: step.BlockingReason,
|
||||
notes: step.Notes ?? "Water support project waiting on current expansion site.");
|
||||
AssignConstructionAssets(world, commander, objective, step, step.IssuedTaskIds, assignedAssetIds);
|
||||
step.Status = FactionPlanStepStatus.Running;
|
||||
return;
|
||||
}
|
||||
|
||||
var waterProject = FactionIndustryPlanner.AnalyzeCommodityNeed(world, commander.FactionId, "water");
|
||||
if (waterProject is not null)
|
||||
{
|
||||
FactionIndustryPlanner.EnsureExpansionSite(world, commander.FactionId, waterProject);
|
||||
step.Status = FactionPlanStepStatus.Running;
|
||||
step.Notes = "Queued water expansion project.";
|
||||
step.TargetSiteId = waterProject.SiteId;
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: waterProject.CommodityId,
|
||||
moduleId: waterProject.ModuleId,
|
||||
targetSystemId: waterProject.SystemId,
|
||||
targetSiteId: waterProject.SiteId,
|
||||
blockingReason: null,
|
||||
notes: step.Notes);
|
||||
AssignConstructionAssets(world, commander, objective, step, step.IssuedTaskIds, assignedAssetIds);
|
||||
}
|
||||
else
|
||||
{
|
||||
step.Status = FactionPlanStepStatus.Blocked;
|
||||
step.BlockingReason = "Unable to derive an expansion project for water.";
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: "water",
|
||||
moduleId: step.ModuleId,
|
||||
targetSystemId: null,
|
||||
targetSiteId: null,
|
||||
blockingReason: step.BlockingReason,
|
||||
notes: step.Notes);
|
||||
}
|
||||
EmitExpansionExecution(world, commander, objective, step, assessment, touchedTaskIds, assignedAssetIds);
|
||||
break;
|
||||
case FactionPlanStepKind.EnsureMiningCapacity:
|
||||
UpsertShipProductionTask(commander, objective, step, touchedTaskIds, "mining", step.BlockingReason, "Maintain mining ship production until logistical capacity is healthy.");
|
||||
AssignShipyardAssets(world, commander, objective, step);
|
||||
PromoteShipProductionStepToRunning(step, "mining");
|
||||
break;
|
||||
case FactionPlanStepKind.EnsureConstructionCapacity:
|
||||
UpsertShipProductionTask(commander, objective, step, touchedTaskIds, "construction", step.BlockingReason, "Maintain construction ship production until expansion support is healthy.");
|
||||
AssignShipyardAssets(world, commander, objective, step);
|
||||
PromoteShipProductionStepToRunning(step, "construction");
|
||||
break;
|
||||
case FactionPlanStepKind.EnsureTransportCapacity:
|
||||
UpsertShipProductionTask(commander, objective, step, touchedTaskIds, "transport", step.BlockingReason, "Maintain transport ship production until logistical throughput is healthy.");
|
||||
AssignShipyardAssets(world, commander, objective, step);
|
||||
PromoteShipProductionStepToRunning(step, "transport");
|
||||
break;
|
||||
case FactionPlanStepKind.MonitorExpansionProject:
|
||||
UpsertWarIndustryTask(commander, objective, step, touchedTaskIds);
|
||||
@@ -880,6 +926,111 @@ internal sealed class FactionObjectiveExecutor
|
||||
}
|
||||
}
|
||||
|
||||
private static void EmitExpansionExecution(
|
||||
SimulationWorld world,
|
||||
CommanderRuntime commander,
|
||||
FactionObjectiveRuntime objective,
|
||||
FactionPlanStepRuntime step,
|
||||
StepExecutionAssessment assessment,
|
||||
ISet<string> touchedTaskIds,
|
||||
ISet<string> assignedAssetIds)
|
||||
{
|
||||
var project = assessment.ExpectedProject;
|
||||
if (project is null)
|
||||
{
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: step.CommodityId,
|
||||
moduleId: step.ModuleId,
|
||||
targetSystemId: null,
|
||||
targetSiteId: null,
|
||||
blockingReason: step.BlockingReason,
|
||||
notes: step.StatusReason);
|
||||
return;
|
||||
}
|
||||
|
||||
if (step.Status == FactionPlanStepStatus.Ready)
|
||||
{
|
||||
FactionIndustryPlanner.EnsureExpansionSite(world, commander.FactionId, project);
|
||||
project = project with { SiteId = project.SiteId ?? FindMatchingSiteId(world, commander.FactionId, project) };
|
||||
step.TargetSiteId = project.SiteId;
|
||||
step.Status = FactionPlanStepStatus.Running;
|
||||
step.StatusReason = $"Started required expansion {DescribeProject(project)}.";
|
||||
step.ExecutionBindingKind = "expansion-project";
|
||||
step.ExecutionBindingTargetId = project.SiteId;
|
||||
step.ExecutionBindingSummary = $"Started site {project.SiteId ?? "pending"} for {DescribeProject(project)}.";
|
||||
}
|
||||
|
||||
if (step.Status == FactionPlanStepStatus.Running)
|
||||
{
|
||||
step.TargetSiteId ??= project.SiteId;
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: project.CommodityId,
|
||||
moduleId: project.ModuleId,
|
||||
targetSystemId: project.SystemId,
|
||||
targetSiteId: project.SiteId,
|
||||
blockingReason: step.BlockingReason,
|
||||
notes: step.StatusReason);
|
||||
AssignConstructionAssets(world, commander, objective, step, step.IssuedTaskIds, assignedAssetIds);
|
||||
return;
|
||||
}
|
||||
|
||||
UpsertExpansionTask(
|
||||
commander,
|
||||
objective,
|
||||
step,
|
||||
touchedTaskIds,
|
||||
commodityId: project.CommodityId,
|
||||
moduleId: project.ModuleId,
|
||||
targetSystemId: project.SystemId,
|
||||
targetSiteId: project.SiteId,
|
||||
blockingReason: step.BlockingReason,
|
||||
notes: step.StatusReason);
|
||||
}
|
||||
|
||||
private static void PromoteShipProductionStepToRunning(FactionPlanStepRuntime step, string shipRole)
|
||||
{
|
||||
if (step.Status != FactionPlanStepStatus.Ready || step.AssignedAssetIds.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
step.Status = FactionPlanStepStatus.Running;
|
||||
step.StatusReason = $"{titleCase(shipRole)} ship production is bound to shipyard assets.";
|
||||
step.ExecutionBindingKind = "shipyard-production";
|
||||
step.ExecutionBindingTargetId = step.AssignedAssetIds.FirstOrDefault();
|
||||
step.ExecutionBindingSummary = $"Using shipyard assets {string.Join(", ", step.AssignedAssetIds.OrderBy(id => id, StringComparer.Ordinal))}.";
|
||||
}
|
||||
|
||||
private static void PromoteCombatStepToRunning(FactionPlanStepRuntime step)
|
||||
{
|
||||
if (step.Status != FactionPlanStepStatus.Ready)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (step.AssignedAssetIds.Count <= 0)
|
||||
{
|
||||
step.Status = FactionPlanStepStatus.Blocked;
|
||||
step.BlockingReason = "No combat ships were available for the attack step.";
|
||||
step.StatusReason = step.BlockingReason;
|
||||
return;
|
||||
}
|
||||
|
||||
step.Status = FactionPlanStepStatus.Running;
|
||||
step.StatusReason = "Attack step is bound to assigned combat ships.";
|
||||
step.ExecutionBindingKind = "combat-assets";
|
||||
step.ExecutionBindingTargetId = step.AssignedAssetIds.FirstOrDefault();
|
||||
step.ExecutionBindingSummary = $"Using combat ships {string.Join(", ", step.AssignedAssetIds.OrderBy(id => id, StringComparer.Ordinal))}.";
|
||||
}
|
||||
|
||||
private static void ReconcileStaleTasks(CommanderRuntime commander, ISet<string> touchedTaskIds)
|
||||
{
|
||||
foreach (var task in commander.IssuedTasks)
|
||||
@@ -1056,6 +1207,71 @@ internal sealed class FactionObjectiveExecutor
|
||||
_ => FactionIssuedTaskState.Cancelled,
|
||||
};
|
||||
|
||||
private static bool ProjectsSemanticallyMatch(
|
||||
IndustryExpansionProject expectedProject,
|
||||
IndustryExpansionProject activeProject) =>
|
||||
string.Equals(expectedProject.CommodityId, activeProject.CommodityId, StringComparison.Ordinal)
|
||||
&& string.Equals(expectedProject.ModuleId, activeProject.ModuleId, StringComparison.Ordinal)
|
||||
&& string.Equals(expectedProject.SystemId, activeProject.SystemId, StringComparison.Ordinal)
|
||||
&& string.Equals(expectedProject.CelestialId, activeProject.CelestialId, StringComparison.Ordinal);
|
||||
|
||||
private static string DescribeProject(IndustryExpansionProject project) =>
|
||||
$"{project.CommodityId}/{project.ModuleId} @ {project.SystemId}:{project.CelestialId}";
|
||||
|
||||
private static string BuildMissingPlanReason(FactionPlanStepRuntime step) =>
|
||||
step.Kind switch
|
||||
{
|
||||
FactionPlanStepKind.EnsureCommodityProduction => $"Unable to derive an expansion project for required commodity {step.CommodityId}.",
|
||||
FactionPlanStepKind.EnsureWaterSupply => "Unable to derive an expansion project for water supply.",
|
||||
FactionPlanStepKind.EnsureShipyardSite => "Unable to identify a viable shipyard foundation project.",
|
||||
_ => "Unable to derive the required execution plan for this step.",
|
||||
};
|
||||
|
||||
private static bool TryFindIssuedTaskBinding(
|
||||
CommanderRuntime commander,
|
||||
FactionPlanStepRuntime step,
|
||||
out StepExecutionBinding binding)
|
||||
{
|
||||
var task = commander.IssuedTasks.FirstOrDefault(candidate =>
|
||||
string.Equals(candidate.StepId, step.Id, StringComparison.Ordinal)
|
||||
&& candidate.State == FactionIssuedTaskState.Active);
|
||||
if (task is null)
|
||||
{
|
||||
binding = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetId = task.TargetSiteId
|
||||
?? task.TargetFactionId
|
||||
?? task.AssignedAssetIds.OrderBy(id => id, StringComparer.Ordinal).FirstOrDefault();
|
||||
binding = new StepExecutionBinding(
|
||||
"issued-task",
|
||||
targetId,
|
||||
$"Reusing active issued task {task.Kind} ({task.Id}).");
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string? FindMatchingSiteId(
|
||||
SimulationWorld world,
|
||||
string factionId,
|
||||
IndustryExpansionProject project) =>
|
||||
world.ConstructionSites
|
||||
.Where(site =>
|
||||
string.Equals(site.FactionId, factionId, StringComparison.Ordinal)
|
||||
&& string.Equals(site.TargetKind, "station-foundation", StringComparison.Ordinal)
|
||||
&& site.State is not ConstructionSiteStateKinds.Completed and not ConstructionSiteStateKinds.Destroyed
|
||||
&& string.Equals(site.TargetDefinitionId, project.CommodityId, StringComparison.Ordinal)
|
||||
&& string.Equals(site.BlueprintId, project.ModuleId, StringComparison.Ordinal)
|
||||
&& string.Equals(site.SystemId, project.SystemId, StringComparison.Ordinal)
|
||||
&& string.Equals(site.CelestialId, project.CelestialId, StringComparison.Ordinal))
|
||||
.Select(site => site.Id)
|
||||
.FirstOrDefault();
|
||||
|
||||
private static string titleCase(string value) =>
|
||||
string.IsNullOrWhiteSpace(value)
|
||||
? value
|
||||
: char.ToUpperInvariant(value[0]) + value[1..];
|
||||
|
||||
private static void AssignCombatAssets(
|
||||
SimulationWorld world,
|
||||
CommanderRuntime commander,
|
||||
|
||||
Reference in New Issue
Block a user