using System; using System.Collections.Generic; using System.Linq; #nullable enable namespace GameCult.Aetheria.State.Verse { public sealed class AetheriaRuntimeDaemonOperationResult { public AetheriaRuntimeDaemonOperationResult( AetheriaRuntimeRunCheckpointCommit run, IReadOnlyList appliedCommandIds, IReadOnlyList rejectedCommandIds, AetheriaRuntimeDaemonIntentState? intents = null) { Run = run ?? new AetheriaRuntimeRunCheckpointCommit(); AppliedCommandIds = appliedCommandIds ?? Array.Empty(); RejectedCommandIds = rejectedCommandIds ?? Array.Empty(); Intents = intents ?? new AetheriaRuntimeDaemonIntentState(); } public AetheriaRuntimeRunCheckpointCommit Run { get; } public IReadOnlyList AppliedCommandIds { get; } public IReadOnlyList RejectedCommandIds { get; } public AetheriaRuntimeDaemonIntentState Intents { get; } } public sealed class AetheriaRuntimeDaemonOperationContext { public const double DefaultDockingDistance = 25.0; public const double DefaultWormholeExitRadius = 50.0; public IReadOnlyList LoadoutTemplates { get; set; } = Array.Empty(); public AetheriaRuntimeDaemonIntentState Intents { get; set; } = new AetheriaRuntimeDaemonIntentState(); public double DockingDistance { get; set; } = AetheriaRuntimeDaemonOperationContext.DefaultDockingDistance; public double WormholeExitRadius { get; set; } = AetheriaRuntimeDaemonOperationContext.DefaultWormholeExitRadius; } public static class AetheriaRuntimeDaemonOperations { public static AetheriaRuntimeDaemonOperationResult Execute( AetheriaRuntimeRunCheckpointCommit run, IEnumerable commands) { return Execute(run, commands, new AetheriaRuntimeDaemonOperationContext()); } public static AetheriaRuntimeDaemonOperationResult Execute( AetheriaRuntimeRunCheckpointCommit run, IEnumerable commands, AetheriaRuntimeDaemonOperationContext context) { run ??= new AetheriaRuntimeRunCheckpointCommit(); context ??= new AetheriaRuntimeDaemonOperationContext(); var applied = new List(); var rejected = new List(); foreach (var command in commands ?? Enumerable.Empty()) { if (command == null || string.IsNullOrWhiteSpace(command.CommandId)) continue; if (ApplyOne(run, command, context)) applied.Add(command.CommandId); else rejected.Add(command.CommandId); } return new AetheriaRuntimeDaemonOperationResult(run, applied, rejected, context.Intents); } private static bool ApplyOne( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonOperationContext context) { switch (command.Kind) { case AetheriaRuntimeDaemonCommandKinds.SetTarget: return ApplySetTarget(run, command); case AetheriaRuntimeDaemonCommandKinds.ClearTarget: return ApplyClearTarget(run, command); case AetheriaRuntimeDaemonCommandKinds.TargetNearest: return ApplyTargetCycle(run, command, TargetCycleMode.Nearest); case AetheriaRuntimeDaemonCommandKinds.TargetNext: return ApplyTargetCycle(run, command, TargetCycleMode.Next); case AetheriaRuntimeDaemonCommandKinds.TargetPrevious: return ApplyTargetCycle(run, command, TargetCycleMode.Previous); case AetheriaRuntimeDaemonCommandKinds.TargetReticle: return ApplyTargetReticle(run, command); case AetheriaRuntimeDaemonCommandKinds.SetLookDirection: return ApplySetLookDirection(run, command); case AetheriaRuntimeDaemonCommandKinds.SetTractorPower: if (!IsNormalizedScalar(command.ScalarValue)) return false; return ApplyCurrentEntity(run, command, entity => entity.TractorPower = command.ScalarValue); case AetheriaRuntimeDaemonCommandKinds.SetHeatsinksEnabled: return ApplyCurrentEntity(run, command, entity => entity.HeatsinksEnabled = command.ScalarValue > 0.5); case AetheriaRuntimeDaemonCommandKinds.SetOverrideShutdown: return ApplyTargetOrCurrentEntity(run, command, entity => entity.OverrideShutdown = command.ScalarValue > 0.5); case AetheriaRuntimeDaemonCommandKinds.SetShutdownPerformance: if (command.ScalarValue < 0.0 || command.ScalarValue > 1.0) return false; return ApplyTargetEntity(run, command.TargetEntityKey, entity => entity.ShutdownPerformance = command.ScalarValue); case AetheriaRuntimeDaemonCommandKinds.SetItemEnabled: return ApplyCurrentEquipmentItem(run, command, item => item.Enabled = command.ScalarValue > 0.5); case AetheriaRuntimeDaemonCommandKinds.ToggleShieldEnabled: return ApplyToggleEquipmentBehaviorItem(run, command, "Shield", item => item.Enabled = !item.Enabled); case AetheriaRuntimeDaemonCommandKinds.SetItemOverrideShutdown: return ApplyTargetEquipmentItem(run, command, item => item.OverrideShutdown = command.ScalarValue > 0.5); case AetheriaRuntimeDaemonCommandKinds.SetWeaponGroupMembership: return ApplySetWeaponGroupMembership(run, command); case AetheriaRuntimeDaemonCommandKinds.SetThermotoggleTargetTemperature: return ApplySetThermotoggleTargetTemperature(run, command); case AetheriaRuntimeDaemonCommandKinds.SetEntityName: return ApplyTargetEntity(run, command.TargetEntityKey, entity => entity.Name = command.TextValue ?? ""); case AetheriaRuntimeDaemonCommandKinds.DestroyEntity: return ApplyDestroyEntity(run, command); case AetheriaRuntimeDaemonCommandKinds.SetDockedCurrentShip: return ApplySetDockedCurrentShip(run, command.TargetEntityKey); case AetheriaRuntimeDaemonCommandKinds.TransferCargoItem: return ApplyTransferCargoItem(run, command); case AetheriaRuntimeDaemonCommandKinds.EquipItem: return ApplyEquipItem(run, command); case AetheriaRuntimeDaemonCommandKinds.StoreItem: return ApplyStoreItem(run, command); case AetheriaRuntimeDaemonCommandKinds.PickUpLoot: return ApplyPickUpLoot(run, command); case AetheriaRuntimeDaemonCommandKinds.ToggleHullConductivity: return ApplyToggleHullConductivity(run, command); case AetheriaRuntimeDaemonCommandKinds.TradePurchase: return ApplyTradePurchase(run, command); case AetheriaRuntimeDaemonCommandKinds.RestoreLoadout: return ApplyRestoreLoadout(run, command, context); case AetheriaRuntimeDaemonCommandKinds.SetMoveVector: return ApplySetMoveVector(run, command, context.Intents); case AetheriaRuntimeDaemonCommandKinds.FireWeaponGroup: return ApplyFireWeaponGroup(run, command, context.Intents); case AetheriaRuntimeDaemonCommandKinds.SetWeaponGroupActive: return ApplySetWeaponGroupActive(run, command, context.Intents); case AetheriaRuntimeDaemonCommandKinds.SetBehaviorActive: return ApplySetBehaviorActive(run, command, context.Intents); case AetheriaRuntimeDaemonCommandKinds.ActivateConsumable: return ApplyActivateConsumable(run, command, context.Intents); case AetheriaRuntimeDaemonCommandKinds.SensorPing: return ApplySensorPing(run, command, context.Intents); case AetheriaRuntimeDaemonCommandKinds.Dock: return ApplyDockIntent(run, command, context.Intents); case AetheriaRuntimeDaemonCommandKinds.DockNearest: return ApplyDockNearestIntent(run, command, context.Intents, context.DockingDistance); case AetheriaRuntimeDaemonCommandKinds.Undock: return ApplyUndockIntent(run, command, context.Intents); case AetheriaRuntimeDaemonCommandKinds.Interact: return ApplyInteractIntent( run, command, context.Intents, context.DockingDistance, context.WormholeExitRadius); case AetheriaRuntimeDaemonCommandKinds.EnterWormhole: return ApplyEnterWormholeIntent(run, command, context.Intents); case AetheriaRuntimeDaemonCommandKinds.TowToStation: return ApplyTowToStationIntent(run, command, context.Intents); default: return false; } } private static bool ApplySetTarget( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { var actorKey = string.IsNullOrWhiteSpace(command.ActorEntityKey) ? run.CurrentEntityKey : command.ActorEntityKey; if (!TryResolveEntity(run, actorKey, out var actorZone, out var actorIndex, out var actor) || !TryResolveEntity(run, command.TargetEntityKey, out var targetZone, out var targetIndex, out _) || actorZone != targetZone || actorIndex == targetIndex) { return false; } if (!(actor.Contacts ?? Array.Empty()) .Any(contact => contact != null && contact.TargetEntityIndex == targetIndex && contact.Visible)) { return false; } actor.TargetEntityIndex = targetIndex; return true; } private enum TargetCycleMode { Nearest, Next, Previous } private static bool ApplyTargetCycle( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, TargetCycleMode mode) { var actorKey = string.IsNullOrWhiteSpace(command.ActorEntityKey) ? run.CurrentEntityKey : command.ActorEntityKey; if (!TryResolveEntity(run, actorKey, out var actorZone, out var actorIndex, out var actor)) return false; var zone = (run.Zones ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == actorZone); if (zone == null) return false; var orderedTargets = VisibleHostileTargets(zone, actor, actorIndex) .OrderBy(target => target.DistanceSq) .ThenBy(target => target.EntityIndex) .ToArray(); if (orderedTargets.Length == 0) return false; if (mode == TargetCycleMode.Nearest) { actor.TargetEntityIndex = orderedTargets[0].EntityIndex; return true; } var currentIndex = Array.FindIndex( orderedTargets, target => target.EntityIndex == actor.TargetEntityIndex); if (currentIndex < 0) { actor.TargetEntityIndex = orderedTargets[0].EntityIndex; return true; } var nextIndex = mode == TargetCycleMode.Next ? (currentIndex + 1 + orderedTargets.Length) % orderedTargets.Length : (currentIndex - 1 + orderedTargets.Length) % orderedTargets.Length; actor.TargetEntityIndex = orderedTargets[nextIndex].EntityIndex; return true; } private static bool ApplyTargetReticle( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { var actorKey = string.IsNullOrWhiteSpace(command.ActorEntityKey) ? run.CurrentEntityKey : command.ActorEntityKey; if (!TryResolveEntity(run, actorKey, out var actorZone, out var actorIndex, out var actor)) return false; var zone = (run.Zones ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == actorZone); if (zone == null) return false; var lookLength = Math.Sqrt( (command.DirectionX * command.DirectionX) + (command.DirectionY * command.DirectionY) + (command.PositionZ * command.PositionZ)); if (lookLength <= double.Epsilon) return false; var lookX = command.DirectionX / lookLength; var lookY = command.DirectionY / lookLength; var lookZ = command.PositionZ / lookLength; var scoredTargets = VisibleHostileTargets(zone, actor, actorIndex) .Select(candidate => { var distance = Math.Sqrt(candidate.DistanceSq); var dot = distance <= double.Epsilon ? double.NegativeInfinity : ((candidate.DeltaX / distance) * lookX) + ((candidate.DeltaY / distance) * lookY) + ((candidate.DeltaZ / distance) * lookZ); return (candidate.EntityIndex, Dot: dot); }) .Where(candidate => candidate.Dot > double.NegativeInfinity) .OrderByDescending(candidate => candidate.Dot) .ThenBy(candidate => candidate.EntityIndex) .ToArray(); if (scoredTargets.Length == 0) return false; var target = scoredTargets[0]; actor.TargetEntityIndex = actor.TargetEntityIndex == target.EntityIndex ? -1 : target.EntityIndex; return true; } private static IEnumerable<(int EntityIndex, double DistanceSq, double DeltaX, double DeltaY, double DeltaZ)> VisibleHostileTargets( AetheriaRuntimeZoneSnapshotCommit zone, AetheriaRuntimeEntitySnapshotCommit actor, int actorIndex) { var entities = zone.Entities ?? Array.Empty(); foreach (var contact in actor.Contacts ?? Array.Empty()) { if (contact == null || !contact.Visible || !contact.Hostile || contact.TargetEntityIndex == actorIndex || contact.TargetEntityIndex < 0 || contact.TargetEntityIndex >= entities.Count) { continue; } var target = entities[contact.TargetEntityIndex]; if (target == null) continue; var deltaX = target.PositionX - actor.PositionX; var deltaY = target.PositionY - actor.PositionY; var deltaZ = target.PositionZ - actor.PositionZ; yield return ( contact.TargetEntityIndex, (deltaX * deltaX) + (deltaY * deltaY) + (deltaZ * deltaZ), deltaX, deltaY, deltaZ); } } private static bool ApplyClearTarget( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { return ApplyCurrentEntity(run, command, entity => entity.TargetEntityIndex = -1); } private static bool ApplySetLookDirection( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { return ApplyCurrentEntity(run, command, entity => { entity.DirectionX = command.DirectionX; entity.DirectionY = command.PositionZ; }); } private static bool ApplySetCurrentEntity( AetheriaRuntimeRunCheckpointCommit run, string entityKey) { if (!TryResolveEntity(run, entityKey, out var zoneIndex, out var entityIndex, out _)) return false; run.CurrentZoneIndex = zoneIndex; run.CurrentEntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(run.RunId, zoneIndex, entityIndex); return true; } private static bool ApplySetDockedCurrentShip( AetheriaRuntimeRunCheckpointCommit run, string entityKey) { if (!TryResolveEntity(run, entityKey, out var zoneIndex, out var entityIndex, out _)) return false; var zone = (run.Zones ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex); if (zone == null) return false; var isDocked = (zone.Entities ?? Array.Empty()) .Any(entity => entity != null && entity.EntityIndex != entityIndex && (entity.DockingBayAssignments ?? Array.Empty()).Contains(entityIndex)); if (!isDocked) return false; run.CurrentZoneIndex = zoneIndex; run.CurrentEntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(run.RunId, zoneIndex, entityIndex); return true; } private static bool ApplyTargetOrCurrentEntity( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, Action mutate) { return string.IsNullOrWhiteSpace(command.TargetEntityKey) ? ApplyCurrentEntity(run, command, mutate) : ApplyTargetEntity(run, command.TargetEntityKey, mutate); } private static bool ApplyCurrentEntity( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, Action mutate) { var entityKey = string.IsNullOrWhiteSpace(command.ActorEntityKey) ? run.CurrentEntityKey : command.ActorEntityKey; return ApplyTargetEntity(run, entityKey, mutate); } private static bool ApplyCurrentEquipmentItem( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, Action mutate) { var entityKey = string.IsNullOrWhiteSpace(command.TargetEntityKey) ? command.ActorEntityKey : command.TargetEntityKey; if (string.IsNullOrWhiteSpace(entityKey)) entityKey = run.CurrentEntityKey; return ApplyTargetEquipmentItem(run, entityKey, command.EquipmentIndex, mutate); } private static bool ApplyTargetEquipmentItem( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, Action mutate) { return ApplyTargetEquipmentItem(run, command.TargetEntityKey, command.EquipmentIndex, mutate); } private static bool ApplyTargetEquipmentItem( AetheriaRuntimeRunCheckpointCommit run, string entityKey, int equipmentIndex, Action mutate) { if (!TryResolveEntity(run, entityKey, out _, out _, out var entity)) return false; var equipment = entity.Equipment ?? Array.Empty(); if (equipmentIndex < 0 || equipmentIndex >= equipment.Count) return false; var item = equipment[equipmentIndex]?.Item; if (item == null) return false; mutate(item); return true; } private static bool ApplySetWeaponGroupMembership( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { if (!TryResolveEntity(run, command.TargetEntityKey, out _, out _, out var entity) || command.EquipmentIndex < 0 || command.WeaponGroup < 0) { return false; } var equipment = entity.Equipment ?? Array.Empty(); if (command.EquipmentIndex >= equipment.Count || equipment[command.EquipmentIndex]?.Item == null) { return false; } var sourceGroups = entity.WeaponGroups ?? Array.Empty>(); var groupCount = Math.Max(sourceGroups.Count, command.WeaponGroup + 1); var groups = new IReadOnlyList[groupCount]; for (var i = 0; i < groupCount; i++) { groups[i] = i < sourceGroups.Count ? (sourceGroups[i] ?? Array.Empty()).ToArray() : Array.Empty(); } var members = groups[command.WeaponGroup] .Where(index => index != command.EquipmentIndex) .ToList(); if (command.ScalarValue > 0.5) members.Add(command.EquipmentIndex); groups[command.WeaponGroup] = members .Distinct() .OrderBy(index => index) .ToArray(); entity.WeaponGroups = groups; return true; } private static bool ApplySetThermotoggleTargetTemperature( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { if (!TryResolveEntity(run, command.TargetEntityKey, out _, out _, out var entity)) return false; var behavior = (entity.BehaviorStates ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && string.Equals(candidate.OwnerKind, "equipment", StringComparison.Ordinal) && candidate.OwnerIndex == command.EquipmentIndex && candidate.BehaviorIndex == command.BehaviorIndex); if (behavior == null) return false; behavior.ThermotoggleTargetTemperature = command.ScalarValue; return true; } private static bool ApplyToggleEquipmentBehaviorItem( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, string behaviorKind, Action mutate) { var entityKey = string.IsNullOrWhiteSpace(command.TargetEntityKey) ? command.ActorEntityKey : command.TargetEntityKey; if (string.IsNullOrWhiteSpace(entityKey)) entityKey = run.CurrentEntityKey; if (!TryResolveEntity(run, entityKey, out _, out _, out var entity)) return false; var behavior = (entity.BehaviorStates ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && string.Equals(candidate.OwnerKind, "equipment", StringComparison.Ordinal) && string.Equals(candidate.BehaviorKind, behaviorKind, StringComparison.Ordinal)); if (behavior == null || behavior.OwnerIndex < 0) return false; var equipment = entity.Equipment ?? Array.Empty(); if (behavior.OwnerIndex >= equipment.Count) return false; var item = equipment[behavior.OwnerIndex]?.Item; if (item == null) return false; mutate(item); return true; } private static bool ApplyTransferCargoItem( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { var transfer = command.CargoTransfer ?? new AetheriaRuntimeCargoTransferCommand(); if (!TryResolveCargoBay( run, transfer.OriginEntityKey, transfer.OriginCargoIndex, out var originEntity, out var originCargoIndex, out _) || !TryResolveCargoBay( run, transfer.DestinationEntityKey, transfer.DestinationCargoIndex, out var destinationEntity, out var destinationCargoIndex, out _)) { return false; } if (!transfer.HasDestinationPosition && string.Equals(transfer.OriginEntityKey ?? "", transfer.DestinationEntityKey ?? "", StringComparison.Ordinal) && transfer.OriginCargoIndex == transfer.DestinationCargoIndex) { return false; } var sourceX = transfer.SourceX; var sourceY = transfer.SourceY; if (!TryRemoveCargoItem( originEntity, originCargoIndex, command.TextValue, sourceX, sourceY, out var slot)) { return false; } slot.X = transfer.HasDestinationPosition ? transfer.DestinationX : slot.X; slot.Y = transfer.HasDestinationPosition ? transfer.DestinationY : slot.Y; AddCargoItem(destinationEntity, destinationCargoIndex, slot); return true; } private static bool ApplyEquipItem( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { var transfer = command.EquipmentTransfer ?? new AetheriaRuntimeEquipmentTransferCommand(); var sourceKind = transfer.SourceKind ?? ""; var originEntityKey = transfer.OriginEntityKey ?? ""; var originIndex = transfer.OriginIndex; var sourceX = transfer.SourceX; var sourceY = transfer.SourceY; AetheriaRuntimeLoadoutItemSlotCommit slot; if (string.Equals(sourceKind, "equipment", StringComparison.Ordinal)) { if (!TryRemoveEquipmentItem(run, originEntityKey, originIndex, command.TextValue, out slot)) return false; } else { if (!TryResolveCargoBay(run, originEntityKey, originIndex, out var originEntity, out var originCargoIndex, out _) || !TryRemoveCargoItem(originEntity, originCargoIndex, command.TextValue, sourceX, sourceY, out slot)) { return false; } } if (!TryResolveEntity(run, transfer.DestinationEntityKey, out _, out _, out var destinationEntity)) return false; slot.X = transfer.HasDestinationPosition ? transfer.DestinationX : slot.X; slot.Y = transfer.HasDestinationPosition ? transfer.DestinationY : slot.Y; AddEquipmentItem(destinationEntity, slot); return true; } private static bool ApplyStoreItem( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { var store = command.StoreItem ?? new AetheriaRuntimeStoreItemCommand(); if (!TryRemoveEquipmentItem( run, store.OriginEntityKey, store.SourceEquipmentIndex, command.TextValue, out var slot) || !TryResolveCargoBay( run, store.DestinationEntityKey, store.DestinationCargoIndex, out var destinationEntity, out var destinationCargoIndex, out _)) { return false; } slot.X = store.HasDestinationPosition ? store.DestinationX : slot.X; slot.Y = store.HasDestinationPosition ? store.DestinationY : slot.Y; AddCargoItem(destinationEntity, destinationCargoIndex, slot); return true; } private static bool ApplyPickUpLoot( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { if (!TryResolveEntity(run, command.TargetEntityKey, out var zoneIndex, out _, out var entity)) return false; var zone = (run.Zones ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex); if (zone == null) return false; var pickups = (zone.DroppedPickups ?? Array.Empty()).ToList(); var pickupIndex = pickups.FindIndex(pickup => IsPickupMatch(pickup, command)); if (pickupIndex < 0) return false; var pickup = pickups[pickupIndex]; pickups.RemoveAt(pickupIndex); zone.DroppedPickups = pickups.ToArray(); AddCargoItem(entity, 0, new AetheriaRuntimeLoadoutItemSlotCommit { Item = pickup.Item ?? new AetheriaRuntimeLoadoutItemCommit { ItemKey = command.LootPickup.ItemKey ?? "", Quantity = command.LootPickup.Quantity } }); return true; } private static bool ApplyToggleHullConductivity( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { if (!TryResolveEntity(run, command.TargetEntityKey, out _, out _, out var entity)) return false; var x = (int)command.PositionX; var y = (int)command.PositionY; var axis = (int)command.ScalarValue; if (axis < 0 || axis > 1) return false; var gridName = axis == 0 ? "hull_conductivity_x" : "hull_conductivity_y"; var grids = (entity.StatGrids ?? Array.Empty()).ToArray(); var grid = grids.FirstOrDefault(candidate => candidate != null && string.Equals(candidate.Name ?? "", gridName, StringComparison.Ordinal)); if (grid == null || x < 0 || y < 0 || x >= grid.Width || y >= grid.Height) { return false; } var values = (grid.Values ?? Array.Empty()).ToArray(); var index = y * grid.Width + x; if (index < 0 || index >= values.Length) return false; values[index] = values[index] > 0.5 ? 0.0 : 1.0; grid.Values = values; entity.StatGrids = grids; return true; } private static bool ApplyTradePurchase( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { var purchase = command.TradePurchase ?? new AetheriaRuntimeTradePurchaseCommand(); var totalPrice = purchase.TotalPrice; if (totalPrice < 0 || run.Credits < totalPrice) return false; if (purchase.CreatesDockedShip) { if (!ApplyCreateDockedShipPurchase(run, purchase, out var purchasedShipKey)) return false; run.CurrentEntityKey = purchasedShipKey; run.Credits -= totalPrice; return true; } if (!TryResolveCargoBay( run, purchase.StationEntityKey, purchase.StationCargoIndex, out var stationEntity, out var stationCargoIndex, out _) || !TryResolveCargoBay( run, purchase.TargetEntityKey, purchase.TargetCargoIndex, out var targetEntity, out var targetCargoIndex, out _)) { return false; } var itemKey = purchase.ItemKey ?? ""; var quantity = Math.Max(1, purchase.Quantity); if (!TryRemoveCargoItemQuantity( stationEntity, stationCargoIndex, itemKey, purchase.SourceX, purchase.SourceY, quantity, out var purchasedSlot)) { return false; } purchasedSlot.X = 0; purchasedSlot.Y = 0; AddCargoItem(targetEntity, targetCargoIndex, purchasedSlot); run.Credits -= totalPrice; return true; } private static bool ApplyCreateDockedShipPurchase( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeTradePurchaseCommand purchase, out string purchasedShipKey) { purchasedShipKey = ""; var itemKey = purchase.ItemKey ?? ""; if (string.IsNullOrWhiteSpace(itemKey) || !TryResolveEntity(run, purchase.TargetEntityKey, out var zoneIndex, out var parentIndex, out var parent)) { return false; } var zones = (run.Zones ?? Array.Empty()).ToArray(); var zone = zones.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex); if (zone == null) return false; var entities = (zone.Entities ?? Array.Empty()).ToList(); var entityIndex = entities.Count; var ship = new AetheriaRuntimeEntitySnapshotCommit { EntityIndex = entityIndex, Name = string.IsNullOrWhiteSpace(itemKey) ? "Purchased Ship" : itemKey, Kind = "ship", HullItemKey = itemKey, DirectionX = parent.DirectionX, DirectionY = parent.DirectionY, PositionX = parent.PositionX, PositionY = parent.PositionY, PositionZ = parent.PositionZ, IsActive = true, TargetEntityIndex = -1, ShutdownPerformance = 0.25 }; entities.Add(ship); zone.Entities = entities.ToArray(); run.Zones = zones; var childIndices = (parent.ChildEntityIndices ?? Array.Empty()).ToList(); if (!childIndices.Contains(entityIndex)) childIndices.Add(entityIndex); parent.ChildEntityIndices = childIndices.ToArray(); var assignments = (parent.DockingBayAssignments ?? Array.Empty()).ToList(); var assigned = false; for (var index = 0; index < assignments.Count; index++) { if (assignments[index] >= 0) continue; assignments[index] = entityIndex; assigned = true; break; } if (!assigned) assignments.Add(entityIndex); parent.DockingBayAssignments = assignments.ToArray(); purchasedShipKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(run.RunId, zoneIndex, entityIndex); return true; } private static bool ApplyRestoreLoadout( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonOperationContext context) { var restore = command.LoadoutRestore ?? new AetheriaRuntimeLoadoutRestoreCommand(); var price = restore.Price; if (price < 0 || run.Credits < price) return false; var templateName = restore.TemplateName ?? ""; if (string.IsNullOrWhiteSpace(command.TargetEntityKey) || string.IsNullOrWhiteSpace(templateName)) { return false; } var template = (context.LoadoutTemplates ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && string.Equals(candidate.Name ?? "", templateName, StringComparison.Ordinal)); if (template == null) return false; if (!AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(command.TargetEntityKey, out var zoneIndex, out _)) return false; var newEntityKey = run.AppendLoadoutTemplateToZone( zoneIndex, command.TargetEntityKey, template); if (string.IsNullOrWhiteSpace(newEntityKey)) return false; run.CurrentEntityKey = newEntityKey; run.Credits -= price; return true; } private static bool ApplySetMoveVector( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents) { var actor = ResolveActorEntityKey(run, command); if (!TryResolveEntity(run, actor, out _, out _, out _) || !IsNormalizedScalar(command.ScalarValue) || !IsFinite(command.DirectionX) || !IsFinite(command.DirectionY)) { return false; } intents.Movement = new AetheriaRuntimeDaemonMovementIntent { ActorEntityKey = actor, DirectionX = command.DirectionX, DirectionY = command.DirectionY, Magnitude = command.ScalarValue }; return true; } private static bool ApplyFireWeaponGroup( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents) { var actor = ResolveActorEntityKey(run, command); if (!TryResolveEntity(run, actor, out _, out _, out var entity) || !HasWeaponGroup(entity, command.WeaponGroup)) { return false; } intents.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent { ActorEntityKey = actor, WeaponGroup = command.WeaponGroup, Fire = true, Active = true }); return true; } private static bool ApplySetWeaponGroupActive( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents) { var actor = ResolveActorEntityKey(run, command); if (!TryResolveEntity(run, actor, out _, out _, out var entity) || !HasWeaponGroup(entity, command.WeaponGroup)) { return false; } intents.WeaponGroups.Add(new AetheriaRuntimeDaemonWeaponGroupIntent { ActorEntityKey = actor, WeaponGroup = command.WeaponGroup, Active = command.ScalarValue > 0.5 }); return true; } private static bool ApplySetBehaviorActive( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents) { var actor = ResolveActorEntityKey(run, command); if (string.IsNullOrWhiteSpace(actor) || command.EquipmentIndex < 0 || command.BehaviorIndex < 0 || !TryResolveEntity(run, actor, out _, out _, out var entity) || !HasEquipmentBehavior(entity, command.EquipmentIndex, command.BehaviorIndex)) { return false; } intents.Behaviors.Add(new AetheriaRuntimeDaemonBehaviorIntent { ActorEntityKey = actor, EquipmentIndex = command.EquipmentIndex, BehaviorIndex = command.BehaviorIndex, Active = command.ScalarValue > 0.5 }); return true; } private static bool HasWeaponGroup(AetheriaRuntimeEntitySnapshotCommit entity, int weaponGroup) { var weaponGroups = entity.WeaponGroups ?? Array.Empty>(); if (weaponGroup >= 0 && weaponGroup < weaponGroups.Count) return true; return weaponGroup == 0 && (entity.WeaponStates ?? Array.Empty()) .Any(state => state != null && string.Equals(state.BehaviorKind, "ProjectileWeapon", StringComparison.Ordinal)); } private static bool HasEquipmentBehavior( AetheriaRuntimeEntitySnapshotCommit entity, int equipmentIndex, int behaviorIndex) { return (entity.BehaviorStates ?? Array.Empty()) .Any(candidate => candidate != null && string.Equals(candidate.OwnerKind, "equipment", StringComparison.Ordinal) && candidate.OwnerIndex == equipmentIndex && candidate.BehaviorIndex == behaviorIndex); } private static bool IsNormalizedScalar(double value) { return IsFinite(value) && value >= 0.0 && value <= 1.0; } private static bool IsFinite(double value) { return !double.IsNaN(value) && !double.IsInfinity(value); } private static bool ApplyActivateConsumable( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents) { var actor = ResolveActorEntityKey(run, command); if (string.IsNullOrWhiteSpace(actor) || string.IsNullOrWhiteSpace(command.TextValue)) return false; intents.Consumables.Add(new AetheriaRuntimeDaemonConsumableIntent { ActorEntityKey = actor, ItemKey = command.TextValue ?? "" }); return true; } private static bool ApplySensorPing( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents) { if (string.IsNullOrWhiteSpace(ResolveActorEntityKey(run, command))) return false; intents.SensorPingRequested = true; return true; } private static bool ApplyDockIntent( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents) { var actor = ResolveActorEntityKey(run, command); if (string.IsNullOrWhiteSpace(actor) || string.IsNullOrWhiteSpace(command.TargetEntityKey)) return false; if (!ApplyDockState(run, actor, command.TargetEntityKey)) return false; intents.Docking.Add(new AetheriaRuntimeDaemonDockingIntent { ActorEntityKey = actor, TargetEntityKey = command.TargetEntityKey ?? "", Dock = true }); return true; } private static bool ApplyDockNearestIntent( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents, double defaultDockingDistance) { var actorKey = ResolveActorEntityKey(run, command); if (string.IsNullOrWhiteSpace(actorKey) || !TryFindNearestDockTarget( run, actorKey, ResolveInteractionDistance(command.ScalarValue, defaultDockingDistance), out var targetKey)) { return false; } command.TargetEntityKey = targetKey; return ApplyDockIntent(run, command, intents); } private static bool ApplyUndockIntent( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents) { var actor = ResolveActorEntityKey(run, command); if (string.IsNullOrWhiteSpace(actor)) return false; if (!ApplyUndockState(run, actor)) return false; intents.Docking.Add(new AetheriaRuntimeDaemonDockingIntent { ActorEntityKey = actor, Undock = true }); return true; } private static bool ApplyInteractIntent( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents, double defaultDockingDistance, double defaultWormholeExitRadius) { var actorKey = ResolveActorEntityKey(run, command); if (string.IsNullOrWhiteSpace(actorKey) || !TryResolveEntity(run, actorKey, out var zoneIndex, out var actorIndex, out _)) { return false; } if (IsChildReferencedInZone(run, zoneIndex, actorIndex)) return ApplyUndockIntent(run, command, intents); if (TryFindNearestWormholeTarget( run, actorKey, ResolveInteractionDistance(command.PositionX, defaultWormholeExitRadius), out var targetZoneIndex, out var entryX, out var entryY)) { command.TargetZoneIndex = targetZoneIndex; command.PositionX = entryX; command.PositionY = entryY; return ApplyEnterWormholeIntent(run, command, intents); } return ApplyDockNearestIntent(run, command, intents, defaultDockingDistance); } private static double ResolveInteractionDistance(double commandDistance, double defaultDistance) { if (IsFinite(commandDistance) && commandDistance > 0.0) return commandDistance; return IsFinite(defaultDistance) && defaultDistance > 0.0 ? defaultDistance : double.PositiveInfinity; } private static bool TryFindNearestDockTarget( AetheriaRuntimeRunCheckpointCommit run, string actorEntityKey, double maxDistance, out string targetEntityKey) { targetEntityKey = ""; if (!TryResolveEntity(run, actorEntityKey, out var zoneIndex, out var actorIndex, out var actor)) return false; var zone = (run.Zones ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex); if (zone == null) return false; var maxDistanceSq = maxDistance > 0.0 ? maxDistance * maxDistance : double.PositiveInfinity; var closestDistanceSq = double.PositiveInfinity; var closestIndex = -1; var entities = zone.Entities ?? Array.Empty(); for (var index = 0; index < entities.Count; index++) { var candidate = entities[index]; if (candidate == null || index == actorIndex) continue; var deltaX = candidate.PositionX - actor.PositionX; var deltaY = candidate.PositionY - actor.PositionY; var distanceSq = (deltaX * deltaX) + (deltaY * deltaY); if (distanceSq >= maxDistanceSq || distanceSq >= closestDistanceSq) continue; closestDistanceSq = distanceSq; closestIndex = index; } if (closestIndex < 0) return false; targetEntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(run.RunId, zoneIndex, closestIndex); return true; } private static bool TryFindNearestWormholeTarget( AetheriaRuntimeRunCheckpointCommit run, string actorEntityKey, double maxDistance, out int targetZoneIndex, out double entryX, out double entryY) { targetZoneIndex = -1; entryX = 0.0; entryY = 0.0; if (!TryResolveEntity(run, actorEntityKey, out var zoneIndex, out _, out var actor)) return false; var zone = (run.Zones ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex); if (zone == null) return false; var maxDistanceSq = maxDistance > 0.0 ? maxDistance * maxDistance : double.PositiveInfinity; var closestDistanceSq = double.PositiveInfinity; foreach (var adjacentZoneIndex in zone.AdjacentZoneIndices ?? Array.Empty()) { var candidate = (run.Zones ?? Array.Empty()) .FirstOrDefault(candidateZone => candidateZone != null && candidateZone.ZoneIndex == adjacentZoneIndex); if (candidate == null) continue; var deltaX = candidate.PositionX - actor.PositionX; var deltaY = candidate.PositionY - actor.PositionZ; var distanceSq = (deltaX * deltaX) + (deltaY * deltaY); if (distanceSq >= maxDistanceSq || distanceSq >= closestDistanceSq) continue; closestDistanceSq = distanceSq; targetZoneIndex = adjacentZoneIndex; entryX = candidate.PositionX; entryY = candidate.PositionY; } return targetZoneIndex >= 0; } private static bool ApplyDockState( AetheriaRuntimeRunCheckpointCommit run, string actorEntityKey, string targetEntityKey) { if (!TryResolveEntity(run, actorEntityKey, out var actorZoneIndex, out var actorIndex, out _) || !TryResolveEntity(run, targetEntityKey, out var targetZoneIndex, out var targetIndex, out var target)) { return false; } if (actorZoneIndex != targetZoneIndex || actorIndex == targetIndex) return false; if (IsChildReferencedInZone(run, actorZoneIndex, actorIndex)) return false; RemoveChildReferenceFromZone(run, actorZoneIndex, actorIndex); var childIndices = (target.ChildEntityIndices ?? Array.Empty()).ToList(); if (!childIndices.Contains(actorIndex)) childIndices.Add(actorIndex); target.ChildEntityIndices = childIndices.ToArray(); var assignments = (target.DockingBayAssignments ?? Array.Empty()).ToList(); var assigned = false; for (var index = 0; index < assignments.Count; index++) { if (assignments[index] >= 0) continue; assignments[index] = actorIndex; assigned = true; break; } if (!assigned) assignments.Add(actorIndex); target.DockingBayAssignments = assignments.ToArray(); return true; } private static bool ApplyUndockState( AetheriaRuntimeRunCheckpointCommit run, string actorEntityKey) { if (!TryResolveEntity(run, actorEntityKey, out var zoneIndex, out var actorIndex, out _)) return false; return RemoveChildReferenceFromZone(run, zoneIndex, actorIndex); } private static bool IsChildReferencedInZone( AetheriaRuntimeRunCheckpointCommit run, int zoneIndex, int childEntityIndex) { var zone = (run.Zones ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex); if (zone == null) return false; foreach (var entity in zone.Entities ?? Array.Empty()) { if (entity == null) continue; if ((entity.ChildEntityIndices ?? Array.Empty()).Contains(childEntityIndex)) return true; if ((entity.DockingBayAssignments ?? Array.Empty()).Contains(childEntityIndex)) return true; } return false; } private static bool RemoveChildReferenceFromZone( AetheriaRuntimeRunCheckpointCommit run, int zoneIndex, int childEntityIndex) { var zone = (run.Zones ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex); if (zone == null) return false; var removed = false; foreach (var entity in zone.Entities ?? Array.Empty()) { if (entity == null) continue; var childIndices = (entity.ChildEntityIndices ?? Array.Empty()).ToList(); if (childIndices.RemoveAll(index => index == childEntityIndex) > 0) { entity.ChildEntityIndices = childIndices.ToArray(); removed = true; } var assignments = (entity.DockingBayAssignments ?? Array.Empty()).ToArray(); for (var index = 0; index < assignments.Length; index++) { if (assignments[index] != childEntityIndex) continue; assignments[index] = -1; removed = true; } entity.DockingBayAssignments = assignments; } return removed; } private static bool ApplyEnterWormholeIntent( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents) { var actor = ResolveActorEntityKey(run, command); if (string.IsNullOrWhiteSpace(actor) || command.TargetZoneIndex < 0) return false; if (!MoveEntityToZone(run, actor, command.TargetZoneIndex, command.PositionX, command.PositionY, out var movedEntityKey)) return false; intents.Wormholes.Add(new AetheriaRuntimeDaemonWormholeIntent { ActorEntityKey = movedEntityKey, TargetZoneIndex = command.TargetZoneIndex, PositionX = command.PositionX, PositionY = command.PositionY }); return true; } private static bool ApplyTowToStationIntent( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command, AetheriaRuntimeDaemonIntentState intents) { var actor = ResolveActorEntityKey(run, command); if (string.IsNullOrWhiteSpace(actor) || string.IsNullOrWhiteSpace(command.TargetEntityKey) || !TryResolveEntity( run, command.TargetEntityKey, out var stationZoneIndex, out _, out var station)) { return false; } var destinationX = station.PositionX; var destinationY = station.PositionZ; if (!MoveEntityToZone(run, actor, stationZoneIndex, destinationX, destinationY, out var movedEntityKey)) return false; intents.Towing.Add(new AetheriaRuntimeDaemonTowIntent { ActorEntityKey = movedEntityKey, StationEntityKey = command.TargetEntityKey ?? "", TargetZoneIndex = stationZoneIndex, PositionX = destinationX, PositionY = destinationY }); return true; } private static bool MoveEntityToZone( AetheriaRuntimeRunCheckpointCommit run, string entityKey, int targetZoneIndex, double positionX, double positionY, out string movedEntityKey) { movedEntityKey = ""; if (!AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(entityKey, out var sourceZoneIndex, out var sourceEntityIndex)) return false; var zones = (run.Zones ?? Array.Empty()).ToList(); var sourceZone = zones.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == sourceZoneIndex); if (sourceZone == null) return false; var sourceEntities = (sourceZone.Entities ?? Array.Empty()).ToList(); if (sourceEntityIndex < 0 || sourceEntityIndex >= sourceEntities.Count) return false; var movedEntity = sourceEntities[sourceEntityIndex]; if (movedEntity == null) return false; if (IsChildReferencedInZone(run, sourceZoneIndex, sourceEntityIndex)) return false; var movedCurrentEntity = AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(run.CurrentEntityKey, out var currentZoneIndex, out var currentEntityIndex) && currentZoneIndex == sourceZoneIndex && currentEntityIndex == sourceEntityIndex; RemoveChildReferenceFromZone(run, sourceZoneIndex, sourceEntityIndex); sourceEntities.RemoveAt(sourceEntityIndex); sourceZone.Entities = sourceEntities.ToArray(); ReindexZoneAfterEntityRemoval(sourceZone, sourceEntityIndex); var targetZone = zones.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == targetZoneIndex); if (targetZone == null) { targetZone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = targetZoneIndex, Entities = Array.Empty() }; zones.Add(targetZone); run.Zones = zones.ToArray(); } var targetEntities = (targetZone.Entities ?? Array.Empty()).ToList(); var targetEntityIndex = targetEntities.Count; movedEntity.EntityIndex = targetEntityIndex; movedEntity.TargetEntityIndex = -1; movedEntity.PositionX = positionX; movedEntity.PositionZ = positionY; targetEntities.Add(movedEntity); targetZone.Entities = targetEntities.ToArray(); movedEntityKey = AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(run.RunId, targetZoneIndex, targetEntityIndex); if (movedCurrentEntity) { run.CurrentZoneIndex = targetZoneIndex; run.CurrentEntityKey = movedEntityKey; } else { ReindexCurrentEntityKeyAfterRemoval(run, sourceZoneIndex, sourceEntityIndex); } var discovered = (run.DiscoveredZoneIndices ?? Array.Empty()).ToList(); if (!discovered.Contains(targetZoneIndex)) { discovered.Add(targetZoneIndex); run.DiscoveredZoneIndices = discovered.ToArray(); } return true; } private static bool ApplyDestroyEntity( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { if (!AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(command.TargetEntityKey, out var zoneIndex, out var entityIndex)) return false; var zones = (run.Zones ?? Array.Empty()).ToList(); var zone = zones.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex); if (zone == null) return false; var entities = (zone.Entities ?? Array.Empty()).ToList(); if (entityIndex < 0 || entityIndex >= entities.Count) return false; entities.RemoveAt(entityIndex); zone.Entities = entities.ToArray(); ReindexZoneAfterEntityRemoval(zone, entityIndex); ReindexCurrentEntityKeyAfterRemoval(run, zoneIndex, entityIndex); return true; } private static void ReindexZoneAfterEntityRemoval(AetheriaRuntimeZoneSnapshotCommit zone, int removedEntityIndex) { var entities = zone.Entities ?? Array.Empty(); for (var index = 0; index < entities.Count; index++) { var entity = entities[index]; if (entity == null) continue; entity.EntityIndex = index; entity.TargetEntityIndex = ReindexEntityReference(entity.TargetEntityIndex, removedEntityIndex); entity.ChildEntityIndices = (entity.ChildEntityIndices ?? Array.Empty()) .Select(childIndex => ReindexEntityReference(childIndex, removedEntityIndex)) .Where(childIndex => childIndex >= 0) .ToArray(); entity.DockingBayAssignments = (entity.DockingBayAssignments ?? Array.Empty()) .Select(childIndex => ReindexEntityReference(childIndex, removedEntityIndex)) .ToArray(); entity.Contacts = (entity.Contacts ?? Array.Empty()) .Select(contact => { if (contact != null) contact.TargetEntityIndex = ReindexEntityReference(contact.TargetEntityIndex, removedEntityIndex); return contact; }) .Where(contact => contact != null && contact.TargetEntityIndex >= 0) .ToArray(); } } private static void ReindexCurrentEntityKeyAfterRemoval( AetheriaRuntimeRunCheckpointCommit run, int zoneIndex, int removedEntityIndex) { if (!AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(run.CurrentEntityKey, out var currentZoneIndex, out var currentEntityIndex) || currentZoneIndex != zoneIndex) { return; } var reindexedCurrent = ReindexEntityReference(currentEntityIndex, removedEntityIndex); run.CurrentEntityKey = reindexedCurrent < 0 ? "" : AetheriaRuntimeRunCheckpointCommit.EntityRecordKey(run.RunId, zoneIndex, reindexedCurrent); } private static bool ApplyTargetEntity( AetheriaRuntimeRunCheckpointCommit run, string entityKey, Action mutate) { if (!TryResolveEntity(run, entityKey, out _, out _, out var entity)) return false; mutate(entity); return true; } private static bool TryResolveEntity( AetheriaRuntimeRunCheckpointCommit run, string entityKey, out int zoneIndex, out int entityIndex, out AetheriaRuntimeEntitySnapshotCommit entity) { entity = null!; zoneIndex = -1; entityIndex = -1; if (!AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey(entityKey, out zoneIndex, out entityIndex)) return false; var parsedZoneIndex = zoneIndex; var zone = (run.Zones ?? Array.Empty()) .FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == parsedZoneIndex); var entities = zone?.Entities ?? Array.Empty(); if (entityIndex < 0 || entityIndex >= entities.Count) return false; entity = entities[entityIndex]; return entity != null; } private static int ReindexEntityReference(int referencedEntityIndex, int removedEntityIndex) { if (referencedEntityIndex < 0) return referencedEntityIndex; if (referencedEntityIndex == removedEntityIndex) return -1; return referencedEntityIndex > removedEntityIndex ? referencedEntityIndex - 1 : referencedEntityIndex; } private static string ResolveActorEntityKey( AetheriaRuntimeRunCheckpointCommit run, AetheriaRuntimeDaemonCommandDocument command) { return string.IsNullOrWhiteSpace(command.ActorEntityKey) ? run.CurrentEntityKey ?? "" : command.ActorEntityKey ?? ""; } private static bool TryResolveCargoBay( AetheriaRuntimeRunCheckpointCommit run, string entityKey, int cargoIndex, out AetheriaRuntimeEntitySnapshotCommit entity, out int resolvedCargoIndex, out AetheriaRuntimeCargoBayLoadoutCommit cargo) { entity = null!; cargo = null!; resolvedCargoIndex = -1; if (!TryResolveEntity(run, entityKey, out _, out _, out entity)) return false; var cargoContents = entity.CargoContents ?? Array.Empty(); if (cargoIndex < 0 || cargoIndex >= cargoContents.Count) return false; cargo = cargoContents[cargoIndex]; if (cargo == null) return false; resolvedCargoIndex = cargoIndex; return true; } private static bool TryRemoveCargoItem( AetheriaRuntimeEntitySnapshotCommit entity, int cargoIndex, string itemKey, int x, int y, out AetheriaRuntimeLoadoutItemSlotCommit slot) { slot = null!; var cargoContents = (entity.CargoContents ?? Array.Empty()).ToArray(); if (cargoIndex < 0 || cargoIndex >= cargoContents.Length || cargoContents[cargoIndex] == null) return false; var items = (cargoContents[cargoIndex].Items ?? Array.Empty()).ToList(); var itemIndex = items.FindIndex(candidate => IsCargoSlotMatch(candidate, itemKey, x, y)); if (itemIndex < 0) return false; slot = items[itemIndex]; items.RemoveAt(itemIndex); cargoContents[cargoIndex] = new AetheriaRuntimeCargoBayLoadoutCommit { Items = items.ToArray() }; entity.CargoContents = cargoContents; return true; } private static bool TryRemoveCargoItemQuantity( AetheriaRuntimeEntitySnapshotCommit entity, int cargoIndex, string itemKey, int x, int y, int quantity, out AetheriaRuntimeLoadoutItemSlotCommit slot) { slot = null!; var cargoContents = (entity.CargoContents ?? Array.Empty()).ToArray(); if (cargoIndex < 0 || cargoIndex >= cargoContents.Length || cargoContents[cargoIndex] == null) return false; var items = (cargoContents[cargoIndex].Items ?? Array.Empty()).ToList(); var itemIndex = items.FindIndex(candidate => IsCargoSlotMatch(candidate, itemKey, x, y)); if (itemIndex < 0) return false; var source = items[itemIndex]; var sourceQuantity = Math.Max(1, source.Item?.Quantity ?? 1); if (quantity <= 0 || quantity > sourceQuantity) return false; slot = CloneSlot(source); slot.Item.Quantity = quantity; if (quantity == sourceQuantity) { items.RemoveAt(itemIndex); } else { source.Item.Quantity = sourceQuantity - quantity; } cargoContents[cargoIndex] = new AetheriaRuntimeCargoBayLoadoutCommit { Items = items.ToArray() }; entity.CargoContents = cargoContents; return true; } private static void AddCargoItem( AetheriaRuntimeEntitySnapshotCommit entity, int cargoIndex, AetheriaRuntimeLoadoutItemSlotCommit slot) { var cargoContents = EnsureCargoContents(entity, cargoIndex + 1); var cargo = cargoContents[cargoIndex] ?? new AetheriaRuntimeCargoBayLoadoutCommit(); cargoContents[cargoIndex] = new AetheriaRuntimeCargoBayLoadoutCommit { Items = (cargo.Items ?? Array.Empty()) .Concat(new[] { slot }) .ToArray() }; entity.CargoContents = cargoContents; } private static AetheriaRuntimeLoadoutItemSlotCommit CloneSlot(AetheriaRuntimeLoadoutItemSlotCommit slot) { var item = slot?.Item; return new AetheriaRuntimeLoadoutItemSlotCommit { X = slot?.X ?? 0, Y = slot?.Y ?? 0, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = item?.ItemKey ?? "", Quality = item?.Quality ?? 1.0, Durability = item?.Durability ?? 1.0, Quantity = item?.Quantity ?? 1, Enabled = item?.Enabled ?? true, OverrideShutdown = item?.OverrideShutdown ?? false, Temperature = item?.Temperature ?? 0 } }; } private static bool TryRemoveEquipmentItem( AetheriaRuntimeRunCheckpointCommit run, string entityKey, int equipmentIndex, string itemKey, out AetheriaRuntimeLoadoutItemSlotCommit slot) { slot = null!; if (!TryResolveEntity(run, entityKey, out _, out _, out var entity)) return false; var equipment = (entity.Equipment ?? Array.Empty()).ToList(); if (equipmentIndex < 0 || equipmentIndex >= equipment.Count) return false; slot = equipment[equipmentIndex]; if (!IsItemMatch(slot?.Item, itemKey)) return false; equipment.RemoveAt(equipmentIndex); entity.Equipment = equipment.ToArray(); entity.WeaponGroups = (entity.WeaponGroups ?? Array.Empty>()) .Select(group => (IReadOnlyList)(group ?? Array.Empty()) .Where(index => index != equipmentIndex) .Select(index => index > equipmentIndex ? index - 1 : index) .ToArray()) .ToArray(); return true; } private static void AddEquipmentItem( AetheriaRuntimeEntitySnapshotCommit entity, AetheriaRuntimeLoadoutItemSlotCommit slot) { entity.Equipment = (entity.Equipment ?? Array.Empty()) .Concat(new[] { slot }) .ToArray(); } private static AetheriaRuntimeCargoBayLoadoutCommit[] EnsureCargoContents( AetheriaRuntimeEntitySnapshotCommit entity, int count) { var source = entity.CargoContents ?? Array.Empty(); var cargoContents = new AetheriaRuntimeCargoBayLoadoutCommit[Math.Max(source.Count, count)]; for (var i = 0; i < cargoContents.Length; i++) { cargoContents[i] = i < source.Count ? source[i] ?? new AetheriaRuntimeCargoBayLoadoutCommit() : new AetheriaRuntimeCargoBayLoadoutCommit(); } return cargoContents; } private static bool IsCargoSlotMatch( AetheriaRuntimeLoadoutItemSlotCommit slot, string itemKey, int x, int y) { if (slot == null || !IsItemMatch(slot.Item, itemKey)) return false; return x == int.MinValue || y == int.MinValue || (slot.X == x && slot.Y == y); } private static bool IsItemMatch(AetheriaRuntimeLoadoutItemCommit item, string itemKey) { return item != null && (string.IsNullOrWhiteSpace(itemKey) || string.Equals(item.ItemKey ?? "", itemKey ?? "", StringComparison.Ordinal)); } private static bool IsPickupMatch( AetheriaRuntimeDroppedPickupCommit pickup, AetheriaRuntimeDaemonCommandDocument command) { var pickupCommand = command.LootPickup ?? new AetheriaRuntimeLootPickupCommand(); if (pickup == null || !IsItemMatch(pickup.Item, pickupCommand.ItemKey)) return false; var expectedX = pickupCommand.PositionX; var expectedY = pickupCommand.PositionY; var expectedZ = pickupCommand.PositionZ; return Math.Abs(pickup.PositionX - expectedX) < 0.001 && Math.Abs(pickup.PositionY - expectedY) < 0.001 && Math.Abs(pickup.PositionZ - expectedZ) < 0.001; } } }