180 lines
12 KiB
C#
180 lines
12 KiB
C#
using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
|
|
using static SpaceGame.Api.Stations.Simulation.InfrastructureSimulationService;
|
|
|
|
namespace SpaceGame.Api.Ships.AI;
|
|
|
|
public sealed partial class ShipAiService
|
|
{
|
|
private static bool IsBehaviorBlockingFailure(string behaviorKind, string? failureReason) => failureReason switch
|
|
{
|
|
"missing-item" => true,
|
|
"no-suitable-buyer" => true,
|
|
"no-mineable-node" when string.Equals(behaviorKind, ShipBehaviorKinds.LocalAutoMine, StringComparison.Ordinal) => true,
|
|
_ => false,
|
|
};
|
|
|
|
private static (string BehaviorKind, string SourceId) ResolveBehaviorSource(SimulationWorld world, ShipRuntime ship)
|
|
{
|
|
var assignment = ResolveAssignment(world, ship);
|
|
return assignment is null
|
|
? (ship.DefaultBehavior.Kind, ship.DefaultBehavior.Kind)
|
|
: (assignment.BehaviorKind, assignment.ObjectiveId);
|
|
}
|
|
|
|
private IReadOnlyList<ShipSubTaskRuntime> BuildTradeSubTasks(ShipRuntime ship, TradeRoutePlan route)
|
|
{
|
|
return
|
|
[
|
|
CreateSubTask("sub-acquire-travel", ShipTaskKinds.Travel, $"Travel to {route.SourceStation.Label}", route.SourceStation.SystemId, route.SourceStation.Position, route.SourceStation.Id, MathF.Max(route.SourceStation.Radius + 12f, 12f), 0f),
|
|
CreateSubTask("sub-acquire-dock", ShipTaskKinds.Dock, $"Dock at {route.SourceStation.Label}", route.SourceStation.SystemId, route.SourceStation.Position, route.SourceStation.Id, 4f, 0f),
|
|
CreateSubTask("sub-acquire-load", ShipTaskKinds.LoadCargo, $"Load {route.ItemId}", route.SourceStation.SystemId, route.SourceStation.Position, route.SourceStation.Id, 0f, ship.Definition.GetTotalCargoCapacity(), itemId: route.ItemId),
|
|
CreateSubTask("sub-acquire-undock", ShipTaskKinds.Undock, $"Undock from {route.SourceStation.Label}", route.SourceStation.SystemId, route.SourceStation.Position, route.SourceStation.Id, MathF.Max(4f, 12f), 0f),
|
|
CreateSubTask("sub-route-travel", ShipTaskKinds.Travel, $"Travel to {route.DestinationStation.Label}", route.DestinationStation.SystemId, route.DestinationStation.Position, route.DestinationStation.Id, MathF.Max(route.DestinationStation.Radius + 12f, 12f), 0f),
|
|
CreateSubTask("sub-route-dock", ShipTaskKinds.Dock, $"Dock at {route.DestinationStation.Label}", route.DestinationStation.SystemId, route.DestinationStation.Position, route.DestinationStation.Id, 4f, 0f),
|
|
CreateSubTask("sub-route-unload", ShipTaskKinds.UnloadCargo, $"Unload {route.ItemId}", route.DestinationStation.SystemId, route.DestinationStation.Position, route.DestinationStation.Id, 0f, ship.Definition.GetTotalCargoCapacity(), itemId: route.ItemId),
|
|
CreateSubTask("sub-route-undock", ShipTaskKinds.Undock, $"Undock from {route.DestinationStation.Label}", route.DestinationStation.SystemId, route.DestinationStation.Position, route.DestinationStation.Id, MathF.Max(4f, 12f), 0f),
|
|
];
|
|
}
|
|
|
|
private IReadOnlyList<ShipSubTaskRuntime> BuildFleetSupplySubTasks(FleetSupplyPlan plan)
|
|
{
|
|
return
|
|
[
|
|
CreateSubTask("sub-fleet-source-travel", ShipTaskKinds.Travel, $"Travel to {plan.SourceStation.Label}", plan.SourceStation.SystemId, plan.SourceStation.Position, plan.SourceStation.Id, MathF.Max(plan.SourceStation.Radius + 12f, 12f), 0f),
|
|
CreateSubTask("sub-fleet-source-dock", ShipTaskKinds.Dock, $"Dock at {plan.SourceStation.Label}", plan.SourceStation.SystemId, plan.SourceStation.Position, plan.SourceStation.Id, 4f, 0f),
|
|
CreateSubTask("sub-fleet-source-load", ShipTaskKinds.LoadCargo, $"Load {plan.ItemId}", plan.SourceStation.SystemId, plan.SourceStation.Position, plan.SourceStation.Id, 0f, plan.Amount, itemId: plan.ItemId),
|
|
CreateSubTask("sub-fleet-source-undock", ShipTaskKinds.Undock, $"Undock from {plan.SourceStation.Label}", plan.SourceStation.SystemId, plan.SourceStation.Position, plan.SourceStation.Id, 12f, 0f),
|
|
CreateSubTask("sub-fleet-follow", ShipTaskKinds.FollowTarget, $"Rendezvous with {plan.TargetShip.Definition.Name}", plan.TargetShip.SystemId, plan.TargetShip.Position, plan.TargetShip.Id, plan.Radius, 6f),
|
|
CreateSubTask("sub-fleet-transfer", ShipTaskKinds.TransferCargoToShip, $"Transfer {plan.ItemId}", plan.TargetShip.SystemId, plan.TargetShip.Position, plan.TargetShip.Id, plan.Radius, plan.Amount, itemId: plan.ItemId),
|
|
];
|
|
}
|
|
|
|
private IReadOnlyList<ShipSubTaskRuntime> BuildConstructionSubTasks(ConstructionSiteRuntime site, StationRuntime supportStation)
|
|
{
|
|
var targetPosition = supportStation.Position;
|
|
return
|
|
[
|
|
CreateSubTask("sub-construction-travel", ShipTaskKinds.Travel, $"Travel to support station {supportStation.Label}", supportStation.SystemId, targetPosition, supportStation.Id, MathF.Max(supportStation.Radius + 18f, 18f), 0f),
|
|
CreateSubTask("sub-construction-deliver", ShipTaskKinds.DeliverConstruction, $"Deliver materials to {site.Id}", site.SystemId, supportStation.Position, site.Id, 12f, 0f),
|
|
CreateSubTask("sub-construction-build", ShipTaskKinds.BuildConstructionSite, $"Build {site.Id}", site.SystemId, supportStation.Position, site.Id, 12f, 0f),
|
|
];
|
|
}
|
|
|
|
private static IReadOnlyList<ShipSubTaskRuntime> BuildAttackSubTasks(string targetEntityId, string? targetSystemId, string summary)
|
|
{
|
|
return
|
|
[
|
|
CreateSubTask("sub-attack", ShipTaskKinds.AttackTarget, summary, targetSystemId ?? string.Empty, Vector3.Zero, targetEntityId, 26f, 0f),
|
|
];
|
|
}
|
|
|
|
private static IReadOnlyList<ShipSubTaskRuntime> BuildFlyToObjectSubTasks(string targetSystemId, Vector3 targetPosition, string targetEntityId, string summary)
|
|
{
|
|
return
|
|
[
|
|
CreateSubTask("sub-fly-object-travel", ShipTaskKinds.Travel, summary, targetSystemId, targetPosition, targetEntityId, 8f, 0f),
|
|
];
|
|
}
|
|
|
|
private static IReadOnlyList<ShipSubTaskRuntime> BuildFollowShipSubTasks(ShipRuntime targetShip, float radius, float durationSeconds, string summary) =>
|
|
BuildFollowSubTasks(targetShip.Id, targetShip.SystemId, targetShip.Position, radius, durationSeconds, summary);
|
|
|
|
private static IReadOnlyList<ShipSubTaskRuntime> BuildFollowSubTasks(string targetEntityId, string targetSystemId, Vector3 targetPosition, float radius, float durationSeconds, string summary)
|
|
{
|
|
return
|
|
[
|
|
CreateSubTask("sub-follow", ShipTaskKinds.FollowTarget, summary, targetSystemId, targetPosition, targetEntityId, radius, durationSeconds),
|
|
];
|
|
}
|
|
|
|
private static IReadOnlyList<ShipSubTaskRuntime> BuildHoldSubTasks(ShipRuntime ship, ShipOrderRuntime order)
|
|
{
|
|
return
|
|
[
|
|
CreateSubTask("sub-hold", ShipTaskKinds.HoldPosition, order.Label ?? "Hold position", order.TargetSystemId ?? ship.SystemId, order.TargetPosition ?? ship.Position, order.TargetEntityId, 0f, 3f),
|
|
];
|
|
}
|
|
|
|
private IReadOnlyList<ShipSubTaskRuntime> BuildMiningSubTasks(ShipRuntime ship, ResourceNodeRuntime node, StationRuntime homeStation)
|
|
{
|
|
var deposit = SelectMiningDeposit(node, ship.Id);
|
|
var extractionPosition = GetResourceHoldPosition(deposit?.Position ?? Vector3.Zero, ship.Id, 20f);
|
|
return
|
|
[
|
|
CreateSubTask("sub-mine-travel", ShipTaskKinds.Travel, $"Travel to {node.ItemId} field", node.SystemId, Vector3.Zero, node.AnchorId, 8f, 0f, targetAnchorId: node.AnchorId),
|
|
CreateSubTask("sub-mine", ShipTaskKinds.MineNode, $"Mine {node.ItemId}", node.SystemId, extractionPosition, node.Id, 8f, ship.Definition.GetTotalCargoCapacity(), itemId: node.ItemId, targetAnchorId: node.AnchorId, targetResourceNodeId: node.Id, targetResourceDepositId: deposit?.Id),
|
|
CreateSubTask("sub-deliver-travel", ShipTaskKinds.Travel, $"Travel to {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, MathF.Max(homeStation.Radius + 12f, 12f), 0f),
|
|
CreateSubTask("sub-deliver-dock", ShipTaskKinds.Dock, $"Dock at {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, 4f, 0f),
|
|
CreateSubTask("sub-deliver-unload", ShipTaskKinds.UnloadCargo, $"Unload at {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, 0f, ship.Definition.GetTotalCargoCapacity()),
|
|
CreateSubTask("sub-deliver-undock", ShipTaskKinds.Undock, $"Undock from {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, MathF.Max(4f, 12f), 0f),
|
|
];
|
|
}
|
|
|
|
private IReadOnlyList<ShipSubTaskRuntime> BuildLocalMiningSubTasks(ShipRuntime ship, ResourceNodeRuntime node)
|
|
{
|
|
var deposit = SelectMiningDeposit(node, ship.Id);
|
|
var extractionPosition = GetResourceHoldPosition(deposit?.Position ?? Vector3.Zero, ship.Id, 20f);
|
|
return
|
|
[
|
|
CreateSubTask("sub-mine-travel", ShipTaskKinds.Travel, $"Travel to {node.ItemId} field", node.SystemId, Vector3.Zero, node.AnchorId, 8f, 0f, targetAnchorId: node.AnchorId),
|
|
CreateSubTask("sub-mine", ShipTaskKinds.MineNode, $"Mine {node.ItemId}", node.SystemId, extractionPosition, node.Id, 8f, ship.Definition.GetTotalCargoCapacity(), itemId: node.ItemId, targetAnchorId: node.AnchorId, targetResourceNodeId: node.Id, targetResourceDepositId: deposit?.Id),
|
|
];
|
|
}
|
|
|
|
private IReadOnlyList<ShipSubTaskRuntime> BuildLocalMiningDeliverySubTasks(ShipRuntime ship, StationRuntime buyer, string itemId)
|
|
{
|
|
var amount = MathF.Max(1f, GetInventoryAmount(ship.Inventory, itemId));
|
|
return
|
|
[
|
|
CreateSubTask("sub-deliver-travel", ShipTaskKinds.Travel, $"Travel to {buyer.Label}", buyer.SystemId, buyer.Position, buyer.Id, MathF.Max(buyer.Radius + 12f, 12f), 0f),
|
|
CreateSubTask("sub-deliver-dock", ShipTaskKinds.Dock, $"Dock at {buyer.Label}", buyer.SystemId, buyer.Position, buyer.Id, 4f, 0f),
|
|
CreateSubTask("sub-deliver-unload", ShipTaskKinds.UnloadCargo, $"Unload {itemId} at {buyer.Label}", buyer.SystemId, buyer.Position, buyer.Id, 0f, amount, itemId: itemId),
|
|
CreateSubTask("sub-deliver-undock", ShipTaskKinds.Undock, $"Undock from {buyer.Label}", buyer.SystemId, buyer.Position, buyer.Id, MathF.Max(4f, 12f), 0f),
|
|
];
|
|
}
|
|
|
|
private IReadOnlyList<ShipSubTaskRuntime> BuildSalvageSubTasks(ShipRuntime ship, WreckRuntime wreck, StationRuntime homeStation, Vector3 approach)
|
|
{
|
|
return
|
|
[
|
|
CreateSubTask("sub-salvage-travel", ShipTaskKinds.Travel, $"Travel to wreck {wreck.Id}", wreck.SystemId, approach, wreck.Id, 8f, 0f),
|
|
CreateSubTask("sub-salvage-work", ShipTaskKinds.SalvageWreck, $"Salvage {wreck.ItemId}", wreck.SystemId, approach, wreck.Id, 8f, ship.Definition.GetTotalCargoCapacity(), itemId: wreck.ItemId),
|
|
CreateSubTask("sub-salvage-deliver-travel", ShipTaskKinds.Travel, $"Travel to {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, MathF.Max(homeStation.Radius + 12f, 12f), 0f),
|
|
CreateSubTask("sub-salvage-deliver-dock", ShipTaskKinds.Dock, $"Dock at {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, 4f, 0f),
|
|
CreateSubTask("sub-salvage-deliver-unload", ShipTaskKinds.UnloadCargo, $"Unload salvage at {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, 0f, ship.Definition.GetTotalCargoCapacity(), itemId: wreck.ItemId),
|
|
CreateSubTask("sub-salvage-deliver-undock", ShipTaskKinds.Undock, $"Undock from {homeStation.Label}", homeStation.SystemId, homeStation.Position, homeStation.Id, 12f, 0f),
|
|
];
|
|
}
|
|
|
|
private static ShipSubTaskRuntime CreateSubTask(
|
|
string id,
|
|
string kind,
|
|
string summary,
|
|
string targetSystemId,
|
|
Vector3 targetPosition,
|
|
string? targetEntityId,
|
|
float threshold,
|
|
float amount,
|
|
string? itemId = null,
|
|
string? moduleId = null,
|
|
string? targetAnchorId = null,
|
|
string? targetResourceNodeId = null,
|
|
string? targetResourceDepositId = null) =>
|
|
new()
|
|
{
|
|
Id = id,
|
|
Kind = kind,
|
|
Summary = summary,
|
|
TargetSystemId = targetSystemId,
|
|
TargetPosition = targetPosition,
|
|
TargetEntityId = targetEntityId,
|
|
TargetAnchorId = targetAnchorId,
|
|
TargetResourceNodeId = targetResourceNodeId,
|
|
TargetResourceDepositId = targetResourceDepositId,
|
|
ItemId = itemId,
|
|
ModuleId = moduleId,
|
|
Threshold = threshold,
|
|
Amount = amount,
|
|
};
|
|
}
|