Refine ship orders and viewer controls

This commit is contained in:
2026-04-09 12:42:52 -04:00
parent 6c92ab50c8
commit 8503855a4c
64 changed files with 2939 additions and 2037 deletions

View File

@@ -6,27 +6,12 @@ namespace SpaceGame.Api.Ships.AI;
public sealed partial class ShipAiService
{
private static ShipOrderRuntime? GetTopOrder(ShipRuntime ship) =>
ship.OrderQueue
.Where(order => order.Status is OrderStatus.Queued or OrderStatus.Active)
.OrderByDescending(GetOrderSourcePriority)
.ThenByDescending(order => order.Priority)
.ThenBy(order => order.CreatedAtUtc)
.FirstOrDefault();
private static int GetOrderSourcePriority(ShipOrderRuntime order) => order.SourceKind switch
{
ShipOrderSourceKind.Player => 300,
ShipOrderSourceKind.Commander => 200,
ShipOrderSourceKind.Behavior => 100,
_ => 0,
};
private void SyncBehaviorOrders(SimulationWorld world, ShipRuntime ship)
{
var desiredOrder = BuildManagedBehaviorOrder(world, ship);
ship.OrderQueue.RemoveAll(order =>
ship.OrderQueue.RemoveWhere(order =>
order.SourceKind == ShipOrderSourceKind.Behavior
&& order.Id.StartsWith("behavior-", StringComparison.Ordinal)
&& (desiredOrder is null || !string.Equals(order.Id, desiredOrder.Id, StringComparison.Ordinal)));
if (desiredOrder is null)
@@ -34,10 +19,10 @@ public sealed partial class ShipAiService
return;
}
var existing = ship.OrderQueue.FirstOrDefault(order => string.Equals(order.Id, desiredOrder.Id, StringComparison.Ordinal));
var existing = ship.OrderQueue.FindById(desiredOrder.Id);
if (existing is null)
{
ship.OrderQueue.Add(desiredOrder);
ship.OrderQueue.AddOrReplaceManagedOrder(desiredOrder);
return;
}
@@ -46,8 +31,7 @@ public sealed partial class ShipAiService
return;
}
ship.OrderQueue.Remove(existing);
ship.OrderQueue.Add(desiredOrder);
ship.OrderQueue.AddOrReplaceManagedOrder(desiredOrder);
}
private ShipOrderRuntime? BuildManagedBehaviorOrder(SimulationWorld world, ShipRuntime ship)
@@ -76,7 +60,7 @@ public sealed partial class ShipAiService
};
}
if (string.Equals(behaviorKind, DockAndWait, StringComparison.Ordinal))
if (string.Equals(behaviorKind, DockAtStation, StringComparison.Ordinal))
{
var station = ResolveStation(world, assignment?.TargetEntityId ?? ship.DefaultBehavior.TargetEntityId ?? ship.DefaultBehavior.HomeStationId);
if (station is null)
@@ -88,38 +72,36 @@ public sealed partial class ShipAiService
ship.LastAccessFailureReason = null;
return new ShipOrderRuntime
{
Id = $"behavior-{ship.Id}-dock-and-wait",
Kind = ShipOrderKinds.DockAndWait,
Id = $"behavior-{ship.Id}-dock-at-station",
Kind = ShipOrderKinds.DockAtStation,
SourceKind = ShipOrderSourceKind.Behavior,
SourceId = behaviorKind,
Priority = 0,
InterruptCurrentPlan = false,
Label = $"Dock and wait at {station.Label}",
Label = $"Dock at {station.Label}",
TargetEntityId = station.Id,
TargetSystemId = station.SystemId,
DestinationStationId = station.Id,
WaitSeconds = MathF.Max(2f, ship.DefaultBehavior.WaitSeconds),
Radius = ship.DefaultBehavior.Radius,
MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
};
}
if (string.Equals(behaviorKind, FlyAndWait, StringComparison.Ordinal))
if (string.Equals(behaviorKind, Move, StringComparison.Ordinal))
{
ship.LastAccessFailureReason = null;
return new ShipOrderRuntime
{
Id = $"behavior-{ship.Id}-fly-and-wait",
Kind = ShipOrderKinds.FlyAndWait,
Id = $"behavior-{ship.Id}-move",
Kind = ShipOrderKinds.Move,
SourceKind = ShipOrderSourceKind.Behavior,
SourceId = behaviorKind,
Priority = 0,
InterruptCurrentPlan = false,
Label = "Fly and wait",
Label = "Fly to position",
TargetSystemId = systemId,
TargetPosition = ship.DefaultBehavior.TargetPosition ?? ship.Position,
WaitSeconds = MathF.Max(2f, ship.DefaultBehavior.WaitSeconds),
Radius = ship.DefaultBehavior.Radius,
MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
@@ -306,13 +288,12 @@ public sealed partial class ShipAiService
}
ship.LastAccessFailureReason = null;
return CreateManagedFlyAndWaitOrder(
return CreateManagedMoveOrder(
ship,
behaviorKind,
"Protect position",
targetSystemId,
targetPosition,
MathF.Max(2f, ship.DefaultBehavior.WaitSeconds),
MathF.Max(6f, ship.DefaultBehavior.Radius));
}
@@ -365,13 +346,12 @@ public sealed partial class ShipAiService
}
ship.LastAccessFailureReason = null;
return CreateManagedFlyAndWaitOrder(
return CreateManagedMoveOrder(
ship,
behaviorKind,
$"Guard {station.Label}",
station.SystemId,
GetFormationPosition(station.Position, ship.Id, MathF.Max(station.Radius + 18f, ship.DefaultBehavior.Radius)),
MathF.Max(2f, ship.DefaultBehavior.WaitSeconds),
MathF.Max(6f, ship.DefaultBehavior.Radius));
}
@@ -410,7 +390,7 @@ public sealed partial class ShipAiService
&& SelectKnownStationVisit(world, ship, homeStation) is { } visitStation)
{
ship.LastAccessFailureReason = null;
return CreateManagedDockAndWaitOrder(ship, behaviorKind, visitStation, MathF.Max(2f, ship.DefaultBehavior.WaitSeconds), $"Revisit {visitStation.Label}");
return CreateManagedDockAtStationOrder(ship, behaviorKind, visitStation, $"Revisit {visitStation.Label}");
}
ship.LastAccessFailureReason = "no-trade-route";
@@ -641,7 +621,7 @@ public sealed partial class ShipAiService
}
ship.LastAccessFailureReason = null;
return CreateManagedFlyAndWaitOrder(ship, sourceKind, "Patrol waypoint", targetSystemId, targetPosition, MathF.Max(2f, ship.DefaultBehavior.WaitSeconds), MathF.Max(6f, ship.DefaultBehavior.Radius), orderIdSuffix: "patrol-fly-and-wait");
return CreateManagedMoveOrder(ship, sourceKind, "Patrol waypoint", targetSystemId, targetPosition, MathF.Max(6f, ship.DefaultBehavior.Radius), orderIdSuffix: "patrol-move");
}
private static ShipOrderRuntime CreateManagedAttackOrder(
@@ -687,11 +667,11 @@ public sealed partial class ShipAiService
KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
};
private static ShipOrderRuntime CreateManagedDockAndWaitOrder(ShipRuntime ship, string behaviorKind, StationRuntime station, float waitSeconds, string label) =>
private static ShipOrderRuntime CreateManagedDockAtStationOrder(ShipRuntime ship, string behaviorKind, StationRuntime station, string label) =>
new()
{
Id = $"behavior-{ship.Id}-{behaviorKind}-dock-and-wait",
Kind = ShipOrderKinds.DockAndWait,
Id = $"behavior-{ship.Id}-{behaviorKind}-dock-at-station",
Kind = ShipOrderKinds.DockAtStation,
SourceKind = ShipOrderSourceKind.Behavior,
SourceId = behaviorKind,
Priority = 0,
@@ -700,25 +680,23 @@ public sealed partial class ShipAiService
TargetEntityId = station.Id,
TargetSystemId = station.SystemId,
DestinationStationId = station.Id,
WaitSeconds = waitSeconds,
Radius = ship.DefaultBehavior.Radius,
MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
};
private static ShipOrderRuntime CreateManagedFlyAndWaitOrder(
private static ShipOrderRuntime CreateManagedMoveOrder(
ShipRuntime ship,
string behaviorKind,
string label,
string targetSystemId,
Vector3 targetPosition,
float waitSeconds,
float radius,
string? orderIdSuffix = null) =>
new()
{
Id = $"behavior-{ship.Id}-{orderIdSuffix ?? behaviorKind}",
Kind = ShipOrderKinds.FlyAndWait,
Kind = ShipOrderKinds.Move,
SourceKind = ShipOrderSourceKind.Behavior,
SourceId = behaviorKind,
Priority = 0,
@@ -726,7 +704,6 @@ public sealed partial class ShipAiService
Label = label,
TargetSystemId = targetSystemId,
TargetPosition = targetPosition,
WaitSeconds = waitSeconds,
Radius = radius,
MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,