Refine ship orders and viewer controls
This commit is contained in:
@@ -12,8 +12,7 @@ public sealed class ShipRuntime
|
||||
public Vector3 Velocity { get; set; } = Vector3.Zero;
|
||||
public ShipState State { get; set; } = ShipState.Idle;
|
||||
public required DefaultBehaviorRuntime DefaultBehavior { get; set; }
|
||||
public List<ShipOrderRuntime> OrderQueue { get; } = [];
|
||||
public ShipPlanRuntime? ActivePlan { get; set; }
|
||||
public ShipOrderQueue OrderQueue { get; } = new();
|
||||
public required ShipSkillProfileRuntime Skills { get; set; }
|
||||
public bool NeedsReplan { get; set; } = true;
|
||||
public float ReplanCooldownSeconds { get; set; }
|
||||
@@ -30,10 +29,190 @@ public sealed class ShipRuntime
|
||||
public float Health { get; set; }
|
||||
public HashSet<string> KnownStationIds { get; } = new(StringComparer.Ordinal);
|
||||
public List<string> History { get; } = [];
|
||||
public string? ActiveOrderId { get; set; }
|
||||
public int ActiveSubTaskIndex { get; set; }
|
||||
public List<ShipSubTaskRuntime> ActiveSubTasks { get; } = [];
|
||||
public string LastSignature { get; set; } = string.Empty;
|
||||
public string LastDeltaSignature { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class ShipOrderQueue : IReadOnlyList<ShipOrderRuntime>
|
||||
{
|
||||
public const int MaxOrders = 8;
|
||||
|
||||
private readonly List<ShipOrderRuntime> _orders = [];
|
||||
|
||||
public int Count => _orders.Count;
|
||||
|
||||
public ShipOrderRuntime this[int index] => _orders[index];
|
||||
|
||||
public IEnumerator<ShipOrderRuntime> GetEnumerator() => _orders.GetEnumerator();
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
public void Enqueue(ShipOrderRuntime order)
|
||||
{
|
||||
if (_orders.Count >= MaxOrders)
|
||||
{
|
||||
throw new InvalidOperationException("Order queue is full.");
|
||||
}
|
||||
|
||||
_orders.Add(order);
|
||||
}
|
||||
|
||||
public void EnqueuePlayerOrder(ShipOrderRuntime order)
|
||||
{
|
||||
if (order.SourceKind != ShipOrderSourceKind.Player)
|
||||
{
|
||||
throw new InvalidOperationException("Player segment only accepts player orders.");
|
||||
}
|
||||
|
||||
EnsureCapacityForNewOrder(order.Id);
|
||||
_orders.Insert(GetManagedInsertionIndex(), order);
|
||||
}
|
||||
|
||||
public void EnqueueManagedOrder(ShipOrderRuntime order)
|
||||
{
|
||||
EnsureCapacityForNewOrder(order.Id);
|
||||
_orders.Add(order);
|
||||
}
|
||||
|
||||
public void AddOrReplaceManagedOrder(ShipOrderRuntime order)
|
||||
=> AddOrReplaceManagedOrder(order, insertAtFront: false);
|
||||
|
||||
public void AddOrReplaceManagedOrderAtFront(ShipOrderRuntime order)
|
||||
=> AddOrReplaceManagedOrder(order, insertAtFront: true);
|
||||
|
||||
private void AddOrReplaceManagedOrder(ShipOrderRuntime order, bool insertAtFront)
|
||||
{
|
||||
var existingIndex = _orders.FindIndex(candidate => string.Equals(candidate.Id, order.Id, StringComparison.Ordinal));
|
||||
if (existingIndex >= 0)
|
||||
{
|
||||
_orders[existingIndex] = order;
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureCapacityForNewOrder(order.Id);
|
||||
if (insertAtFront)
|
||||
{
|
||||
_orders.Insert(GetManagedInsertionIndex(), order);
|
||||
return;
|
||||
}
|
||||
|
||||
_orders.Add(order);
|
||||
}
|
||||
|
||||
public bool Remove(ShipOrderRuntime order) => RemoveById(order.Id);
|
||||
|
||||
public bool RemoveById(string orderId) => _orders.RemoveAll(order => string.Equals(order.Id, orderId, StringComparison.Ordinal)) > 0;
|
||||
|
||||
public int RemoveWhere(Predicate<ShipOrderRuntime> predicate) => _orders.RemoveAll(predicate);
|
||||
|
||||
public ShipOrderRuntime? FindById(string orderId) => _orders.FirstOrDefault(order => string.Equals(order.Id, orderId, StringComparison.Ordinal));
|
||||
|
||||
public ShipOrderRuntime? FindLeadingOrderForSource(ShipOrderSourceKind sourceKind) =>
|
||||
_orders.FirstOrDefault(order => order.SourceKind == sourceKind);
|
||||
|
||||
public string? GetLeadingOrderLabelForSource(ShipOrderSourceKind sourceKind) =>
|
||||
FindLeadingOrderForSource(sourceKind) is { } order
|
||||
? order.Label ?? order.Kind
|
||||
: null;
|
||||
|
||||
public bool HasOrdersFromSource(ShipOrderSourceKind sourceKind) => _orders.Any(order => order.SourceKind == sourceKind);
|
||||
|
||||
public ShipOrderRuntime? GetCurrentOrder() =>
|
||||
_orders.FirstOrDefault(order => order.Status is OrderStatus.Queued or OrderStatus.Active);
|
||||
|
||||
public bool TryMovePlayerOrder(string orderId, int targetIndex)
|
||||
{
|
||||
var currentIndex = _orders.FindIndex(order => string.Equals(order.Id, orderId, StringComparison.Ordinal));
|
||||
if (currentIndex < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var order = _orders[currentIndex];
|
||||
if (order.SourceKind != ShipOrderSourceKind.Player)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var playerOrderIds = _orders
|
||||
.Select((candidate, index) => (candidate, index))
|
||||
.Where(entry => entry.candidate.SourceKind == ShipOrderSourceKind.Player)
|
||||
.Select(entry => entry.index)
|
||||
.ToList();
|
||||
if (playerOrderIds.Count <= 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var clampedPlayerIndex = Math.Clamp(targetIndex, 0, playerOrderIds.Count - 1);
|
||||
var destinationIndex = playerOrderIds[clampedPlayerIndex];
|
||||
if (currentIndex == destinationIndex)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_orders.RemoveAt(currentIndex);
|
||||
if (currentIndex < destinationIndex)
|
||||
{
|
||||
destinationIndex -= 1;
|
||||
}
|
||||
|
||||
_orders.Insert(destinationIndex, order);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryCompleteOrder(string orderId) => TryTransitionOrder(orderId, OrderStatus.Completed);
|
||||
|
||||
public bool TryFailOrder(string orderId, string? failureReason = null)
|
||||
{
|
||||
var order = FindById(orderId);
|
||||
if (order is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
order.FailureReason = failureReason ?? order.FailureReason;
|
||||
if (order.SourceKind == ShipOrderSourceKind.Player)
|
||||
{
|
||||
order.Status = OrderStatus.Failed;
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryTransitionOrder(orderId, OrderStatus.Failed);
|
||||
}
|
||||
|
||||
public bool TryTransitionOrder(string orderId, OrderStatus terminalStatus)
|
||||
{
|
||||
var order = FindById(orderId);
|
||||
if (order is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
order.Status = terminalStatus;
|
||||
return RemoveById(orderId);
|
||||
}
|
||||
|
||||
private int GetManagedInsertionIndex() =>
|
||||
_orders.TakeWhile(order => order.SourceKind == ShipOrderSourceKind.Player).Count();
|
||||
|
||||
private void EnsureCapacityForNewOrder(string orderId)
|
||||
{
|
||||
if (FindById(orderId) is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_orders.Count >= MaxOrders)
|
||||
{
|
||||
throw new InvalidOperationException("Order queue is full.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ShipSkillProfileRuntime
|
||||
{
|
||||
public int Navigation { get; set; }
|
||||
@@ -111,33 +290,6 @@ public sealed class ShipOrderTemplateRuntime
|
||||
public bool KnownStationsOnly { get; set; }
|
||||
}
|
||||
|
||||
public sealed class ShipPlanRuntime
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required AiPlanSourceKind SourceKind { get; init; }
|
||||
public required string SourceId { get; init; }
|
||||
public required string Kind { get; init; }
|
||||
public required string Summary { get; set; }
|
||||
public AiPlanStatus Status { get; set; } = AiPlanStatus.Planned;
|
||||
public int CurrentStepIndex { get; set; }
|
||||
public DateTimeOffset CreatedAtUtc { get; init; } = DateTimeOffset.UtcNow;
|
||||
public DateTimeOffset UpdatedAtUtc { get; set; } = DateTimeOffset.UtcNow;
|
||||
public string? InterruptReason { get; set; }
|
||||
public string? FailureReason { get; set; }
|
||||
public List<ShipPlanStepRuntime> Steps { get; } = [];
|
||||
}
|
||||
|
||||
public sealed class ShipPlanStepRuntime
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string Kind { get; init; }
|
||||
public required string Summary { get; set; }
|
||||
public AiPlanStepStatus Status { get; set; } = AiPlanStepStatus.Planned;
|
||||
public int CurrentSubTaskIndex { get; set; }
|
||||
public string? BlockingReason { get; set; }
|
||||
public List<ShipSubTaskRuntime> SubTasks { get; } = [];
|
||||
}
|
||||
|
||||
public sealed class ShipSubTaskRuntime
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
|
||||
Reference in New Issue
Block a user