Deepen faction economy and station planning
This commit is contained in:
@@ -12,17 +12,22 @@ internal sealed class CommanderPlanningService
|
||||
|
||||
private static readonly IReadOnlyList<GoapGoal<FactionPlanningState>> _factionGoals =
|
||||
[
|
||||
new ExterminateRivalGoal(),
|
||||
new EnsureWarIndustryGoal(),
|
||||
new ExpandTerritoryGoal(),
|
||||
new EnsureWarFleetGoal(),
|
||||
new EnsureWaterSecurityGoal(),
|
||||
new EnsureMiningCapacityGoal(),
|
||||
new EnsureConstructionCapacityGoal(),
|
||||
];
|
||||
|
||||
private static readonly IReadOnlyList<GoapAction<ShipPlanningState>> _shipActions =
|
||||
[
|
||||
new SetAttackObjectiveAction(),
|
||||
new SetMiningObjectiveAction(),
|
||||
new SetPatrolObjectiveAction(),
|
||||
new SetConstructionObjectiveAction(),
|
||||
new SetTradeObjectiveAction(),
|
||||
new SetIdleObjectiveAction(),
|
||||
];
|
||||
|
||||
@@ -93,6 +98,19 @@ internal sealed class CommanderPlanningService
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdateShipCommander(SimulationEngine engine, SimulationWorld world, CommanderRuntime commander)
|
||||
@@ -124,9 +142,20 @@ internal sealed class CommanderPlanningService
|
||||
internal FactionPlanningState BuildFactionPlanningState(SimulationWorld world, string factionId)
|
||||
{
|
||||
var stations = world.Stations.Where(s => s.FactionId == factionId).ToList();
|
||||
var economy = FactionEconomyAnalyzer.Build(world, factionId);
|
||||
var refinedMetals = economy.GetCommodity("refinedmetals");
|
||||
var hullparts = economy.GetCommodity("hullparts");
|
||||
var claytronics = economy.GetCommodity("claytronics");
|
||||
var water = economy.GetCommodity("water");
|
||||
|
||||
return new FactionPlanningState
|
||||
{
|
||||
EnemyFactionCount = world.Factions.Count(f => f.Id != factionId),
|
||||
EnemyShipCount = world.Ships.Count(s =>
|
||||
s.Health > 0f &&
|
||||
!string.Equals(s.FactionId, factionId, StringComparison.Ordinal)),
|
||||
EnemyStationCount = world.Stations.Count(s =>
|
||||
!string.Equals(s.FactionId, factionId, StringComparison.Ordinal)),
|
||||
MilitaryShipCount = world.Ships.Count(s =>
|
||||
s.FactionId == factionId &&
|
||||
string.Equals(s.Definition.Kind, "military", StringComparison.Ordinal)),
|
||||
@@ -142,8 +171,19 @@ internal sealed class CommanderPlanningService
|
||||
ControlledSystemCount = StationSimulationService.GetFactionControlledSystemsCount(world, factionId),
|
||||
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 = stations.Sum(s => GetInventoryAmount(s.Inventory, "ore")),
|
||||
RefinedMetalsStockpile = stations.Sum(s => GetInventoryAmount(s.Inventory, "refinedmetals")),
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -156,13 +196,52 @@ internal sealed class CommanderPlanningService
|
||||
c.FactionId == commander.FactionId &&
|
||||
string.Equals(c.Kind, CommanderKind.Faction, StringComparison.Ordinal));
|
||||
|
||||
var enemyTarget = SelectEnemyTarget(world, ship);
|
||||
var tradeRoute = SelectTradeRoute(world, ship.FactionId);
|
||||
var expansionProject = FactionIndustryPlanner.GetActiveExpansionProject(world, ship.FactionId);
|
||||
if (commander.ActiveBehavior is not null)
|
||||
{
|
||||
commander.ActiveBehavior.AreaSystemId = enemyTarget?.SystemId;
|
||||
commander.ActiveBehavior.TargetEntityId = enemyTarget?.EntityId;
|
||||
if (string.Equals(ship.Definition.Kind, "transport", StringComparison.Ordinal))
|
||||
{
|
||||
commander.ActiveBehavior.ItemId = tradeRoute?.ItemId;
|
||||
commander.ActiveBehavior.StationId = tradeRoute?.SourceStationId;
|
||||
commander.ActiveBehavior.TargetEntityId = tradeRoute?.DestinationStationId;
|
||||
}
|
||||
else if (string.Equals(ship.Definition.Kind, "construction", StringComparison.Ordinal) && expansionProject is not null)
|
||||
{
|
||||
commander.ActiveBehavior.StationId = expansionProject.SupportStationId;
|
||||
commander.ActiveBehavior.TargetEntityId = expansionProject.SiteId;
|
||||
commander.ActiveBehavior.ModuleId = expansionProject.ModuleId;
|
||||
commander.ActiveBehavior.AreaSystemId = expansionProject.SystemId;
|
||||
}
|
||||
else if (string.Equals(ship.Definition.Kind, "construction", StringComparison.Ordinal))
|
||||
{
|
||||
commander.ActiveBehavior.TargetEntityId = null;
|
||||
commander.ActiveBehavior.ModuleId = null;
|
||||
commander.ActiveBehavior.AreaSystemId = ship.SystemId;
|
||||
}
|
||||
}
|
||||
|
||||
return new ShipPlanningState
|
||||
{
|
||||
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,
|
||||
TargetEnemyEntityId = enemyTarget?.EntityId,
|
||||
TradeItemId = tradeRoute?.ItemId,
|
||||
TradeSourceStationId = tradeRoute?.SourceStationId,
|
||||
TradeDestinationStationId = tradeRoute?.DestinationStationId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -170,11 +249,15 @@ internal sealed class CommanderPlanningService
|
||||
{
|
||||
var actions = new List<GoapAction<FactionPlanningState>>();
|
||||
|
||||
actions.Add(new PlanWarIndustryAction());
|
||||
actions.Add(new PlanCommoditySupplyAction("water"));
|
||||
|
||||
foreach (var (shipId, def) in world.ShipDefinitions)
|
||||
{
|
||||
actions.Add(new OrderShipProductionAction(def.Kind, shipId));
|
||||
}
|
||||
|
||||
actions.Add(new LaunchExterminationCampaignAction());
|
||||
actions.Add(new ExpandToSystemAction());
|
||||
return actions;
|
||||
}
|
||||
@@ -184,4 +267,137 @@ internal sealed class CommanderPlanningService
|
||||
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;
|
||||
}
|
||||
|
||||
private static (string EntityId, string SystemId)? SelectEnemyTarget(SimulationWorld world, ShipRuntime ship)
|
||||
{
|
||||
var hostileShip = world.Ships
|
||||
.Where(candidate =>
|
||||
candidate.Health > 0f &&
|
||||
!string.Equals(candidate.FactionId, ship.FactionId, StringComparison.Ordinal))
|
||||
.OrderBy(candidate => candidate.SystemId == ship.SystemId ? 0 : 1)
|
||||
.ThenBy(candidate => candidate.Position.DistanceTo(ship.Position))
|
||||
.Select(candidate => (candidate.Id, candidate.SystemId))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (hostileShip != default)
|
||||
{
|
||||
return hostileShip;
|
||||
}
|
||||
|
||||
var hostileStation = world.Stations
|
||||
.Where(candidate => !string.Equals(candidate.FactionId, ship.FactionId, StringComparison.Ordinal))
|
||||
.OrderBy(candidate => candidate.SystemId == ship.SystemId ? 0 : 1)
|
||||
.ThenBy(candidate => candidate.Position.DistanceTo(ship.Position))
|
||||
.Select(candidate => (candidate.Id, candidate.SystemId))
|
||||
.FirstOrDefault();
|
||||
|
||||
return hostileStation == default ? null : hostileStation;
|
||||
}
|
||||
|
||||
private static (string ItemId, string SourceStationId, string DestinationStationId)? SelectTradeRoute(SimulationWorld world, string factionId)
|
||||
{
|
||||
var stationsById = world.Stations
|
||||
.Where(station => string.Equals(station.FactionId, factionId, StringComparison.Ordinal))
|
||||
.ToDictionary(station => station.Id, StringComparer.Ordinal);
|
||||
|
||||
foreach (var demand in world.MarketOrders
|
||||
.Where(order =>
|
||||
string.Equals(order.FactionId, factionId, StringComparison.Ordinal)
|
||||
&& order.Kind == MarketOrderKinds.Buy
|
||||
&& order.RemainingAmount > 0.01f
|
||||
&& order.StationId is not null)
|
||||
.OrderByDescending(order => order.Valuation))
|
||||
{
|
||||
if (!stationsById.TryGetValue(demand.StationId!, out var destination))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CanStationAcceptAdditionalItem(world, destination, demand.ItemId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var source = stationsById.Values
|
||||
.Where(station =>
|
||||
station.Id != destination.Id
|
||||
&& GetInventoryAmount(station.Inventory, demand.ItemId) > 1f)
|
||||
.OrderByDescending(station => GetInventoryAmount(station.Inventory, demand.ItemId))
|
||||
.FirstOrDefault();
|
||||
if (source is not null)
|
||||
{
|
||||
return (demand.ItemId, source.Id, destination.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool CanStationAcceptAdditionalItem(SimulationWorld world, StationRuntime station, string itemId)
|
||||
{
|
||||
if (!world.ItemDefinitions.TryGetValue(itemId, out var definition))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var requiredModule = GetStorageRequirement(definition.CargoKind);
|
||||
if (requiredModule is not null && !station.InstalledModules.Contains(requiredModule, StringComparer.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var capacity = GetStationStorageCapacity(station, definition.CargoKind);
|
||||
if (capacity <= 0.01f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var used = station.Inventory
|
||||
.Where(entry => world.ItemDefinitions.TryGetValue(entry.Key, out var item) && string.Equals(item.CargoKind, definition.CargoKind, StringComparison.Ordinal))
|
||||
.Sum(entry => entry.Value);
|
||||
|
||||
return used <= capacity - 1f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,20 +12,89 @@ public sealed class FactionPlanningState
|
||||
public int ControlledSystemCount { get; set; }
|
||||
public int TargetSystemCount { get; set; }
|
||||
public bool HasShipFactory { get; set; }
|
||||
public int EnemyFactionCount { get; set; }
|
||||
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 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 HasWarIndustrySupplyChain =>
|
||||
HasRefinedMetalsProduction && HasHullpartsProduction && HasClaytronicsProduction;
|
||||
|
||||
public FactionPlanningState Clone() => (FactionPlanningState)MemberwiseClone();
|
||||
|
||||
internal static int ComputeTargetWarships(FactionPlanningState state)
|
||||
{
|
||||
var expansionDeficit = Math.Max(0, state.TargetSystemCount - state.ControlledSystemCount);
|
||||
return Math.Max(2, (state.ControlledSystemCount * 2) + (expansionDeficit * 3));
|
||||
return Math.Max(3, (state.ControlledSystemCount * 2) + (expansionDeficit * 3) + Math.Min(4, state.EnemyFactionCount + state.EnemyStationCount));
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Goals ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
public sealed class EnsureWarIndustryGoal : GoapGoal<FactionPlanningState>
|
||||
{
|
||||
public override string Name => "ensure-war-industry";
|
||||
|
||||
public override bool IsSatisfied(FactionPlanningState state) =>
|
||||
state.EnemyFactionCount <= 0 || (state.HasWarIndustrySupplyChain && state.HasShipFactory);
|
||||
|
||||
public override float ComputePriority(FactionPlanningState state, SimulationWorld world, CommanderRuntime commander)
|
||||
{
|
||||
if (state.EnemyFactionCount <= 0)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
var missingStages =
|
||||
(state.HasRefinedMetalsProduction ? 0 : 1) +
|
||||
(state.HasHullpartsProduction ? 0 : 1) +
|
||||
(state.HasClaytronicsProduction ? 0 : 1) +
|
||||
(state.HasShipFactory ? 0 : 1);
|
||||
|
||||
return missingStages <= 0 ? 0f : 125f + (missingStages * 18f);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EnsureWaterSecurityGoal : GoapGoal<FactionPlanningState>
|
||||
{
|
||||
public override string Name => "ensure-water-security";
|
||||
|
||||
public override bool IsSatisfied(FactionPlanningState state) =>
|
||||
state.HasWaterProduction && state.WaterShortageHorizonSeconds >= 300f;
|
||||
|
||||
public override float ComputePriority(FactionPlanningState state, SimulationWorld world, CommanderRuntime commander)
|
||||
{
|
||||
if (state.HasWaterProduction && state.WaterShortageHorizonSeconds >= 300f)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
if (float.IsPositiveInfinity(state.WaterShortageHorizonSeconds))
|
||||
{
|
||||
return state.HasWaterProduction ? 0f : 85f;
|
||||
}
|
||||
|
||||
return 55f + MathF.Max(0f, 300f - state.WaterShortageHorizonSeconds) * 0.2f;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EnsureWarFleetGoal : GoapGoal<FactionPlanningState>
|
||||
{
|
||||
public override string Name => "ensure-war-fleet";
|
||||
@@ -40,6 +109,24 @@ public sealed class EnsureWarFleetGoal : GoapGoal<FactionPlanningState>
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ExterminateRivalGoal : GoapGoal<FactionPlanningState>
|
||||
{
|
||||
public override string Name => "exterminate-rival";
|
||||
|
||||
public override bool IsSatisfied(FactionPlanningState state) =>
|
||||
state.EnemyFactionCount <= 0 || (state.EnemyShipCount <= 0 && state.EnemyStationCount <= 0);
|
||||
|
||||
public override float ComputePriority(FactionPlanningState state, SimulationWorld world, CommanderRuntime commander)
|
||||
{
|
||||
if (state.EnemyFactionCount <= 0)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
return 140f + (state.EnemyStationCount * 25f) + (state.EnemyShipCount * 6f);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ExpandTerritoryGoal : GoapGoal<FactionPlanningState>
|
||||
{
|
||||
public override string Name => "expand-territory";
|
||||
@@ -100,7 +187,8 @@ public sealed class OrderShipProductionAction : GoapAction<FactionPlanningState>
|
||||
public override string Name => $"order-{shipId}-production";
|
||||
public override float Cost => 1f;
|
||||
|
||||
public override bool CheckPreconditions(FactionPlanningState state) => state.HasShipFactory;
|
||||
public override bool CheckPreconditions(FactionPlanningState state) =>
|
||||
state.HasShipFactory && state.HasWarIndustrySupplyChain;
|
||||
|
||||
public override FactionPlanningState ApplyEffects(FactionPlanningState state)
|
||||
{
|
||||
@@ -121,13 +209,86 @@ public sealed class OrderShipProductionAction : GoapAction<FactionPlanningState>
|
||||
}
|
||||
}
|
||||
|
||||
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.ConstructorShipCount > 0 && state.MilitaryShipCount >= 2 && state.HasWarIndustrySupplyChain;
|
||||
|
||||
public override FactionPlanningState ApplyEffects(FactionPlanningState state)
|
||||
{
|
||||
@@ -140,3 +301,28 @@ public sealed class ExpandToSystemAction : GoapAction<FactionPlanningState>
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,15 @@ public sealed record FactionGoapStateSnapshot(
|
||||
int TargetSystemCount,
|
||||
bool HasShipFactory,
|
||||
float OreStockpile,
|
||||
float RefinedMetalsStockpile);
|
||||
float RefinedMetalsStockpile,
|
||||
float RefinedMetalsProductionRate,
|
||||
float HullpartsStockpile,
|
||||
float HullpartsProductionRate,
|
||||
float ClaytronicsStockpile,
|
||||
float ClaytronicsProductionRate,
|
||||
float WaterStockpile,
|
||||
float WaterProductionRate,
|
||||
float WaterShortageHorizonSeconds);
|
||||
|
||||
public sealed record FactionGoapPrioritySnapshot(string GoalName, float Priority);
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ public sealed class CommanderBehaviorRuntime
|
||||
{
|
||||
public required string Kind { get; set; }
|
||||
public string? Phase { get; set; }
|
||||
public string? TargetEntityId { get; set; }
|
||||
public string? ItemId { get; set; }
|
||||
public string? NodeId { get; set; }
|
||||
public string? StationId { get; set; }
|
||||
public string? ModuleId { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user