1871 lines
76 KiB
C#
1871 lines
76 KiB
C#
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<string> appliedCommandIds,
|
|
IReadOnlyList<string> rejectedCommandIds,
|
|
AetheriaRuntimeDaemonIntentState? intents = null)
|
|
{
|
|
Run = run ?? new AetheriaRuntimeRunCheckpointCommit();
|
|
AppliedCommandIds = appliedCommandIds ?? Array.Empty<string>();
|
|
RejectedCommandIds = rejectedCommandIds ?? Array.Empty<string>();
|
|
Intents = intents ?? new AetheriaRuntimeDaemonIntentState();
|
|
}
|
|
|
|
public AetheriaRuntimeRunCheckpointCommit Run { get; }
|
|
public IReadOnlyList<string> AppliedCommandIds { get; }
|
|
public IReadOnlyList<string> RejectedCommandIds { get; }
|
|
public AetheriaRuntimeDaemonIntentState Intents { get; }
|
|
}
|
|
|
|
public sealed class AetheriaRuntimeDaemonOperationContext
|
|
{
|
|
public const double DefaultDockingDistance = 25.0;
|
|
public const double DefaultWormholeExitRadius = 50.0;
|
|
|
|
public IReadOnlyList<AetheriaRuntimeLoadoutTemplateCommit> LoadoutTemplates { get; set; } =
|
|
Array.Empty<AetheriaRuntimeLoadoutTemplateCommit>();
|
|
|
|
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<AetheriaRuntimeDaemonCommandDocument> commands)
|
|
{
|
|
return Execute(run, commands, new AetheriaRuntimeDaemonOperationContext());
|
|
}
|
|
|
|
public static AetheriaRuntimeDaemonOperationResult Execute(
|
|
AetheriaRuntimeRunCheckpointCommit run,
|
|
IEnumerable<AetheriaRuntimeDaemonCommandDocument> commands,
|
|
AetheriaRuntimeDaemonOperationContext context)
|
|
{
|
|
run ??= new AetheriaRuntimeRunCheckpointCommit();
|
|
context ??= new AetheriaRuntimeDaemonOperationContext();
|
|
var applied = new List<string>();
|
|
var rejected = new List<string>();
|
|
|
|
foreach (var command in commands ?? Enumerable.Empty<AetheriaRuntimeDaemonCommandDocument>())
|
|
{
|
|
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<AetheriaRuntimeEntityContactCommit>())
|
|
.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<AetheriaRuntimeZoneSnapshotCommit>())
|
|
.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<AetheriaRuntimeZoneSnapshotCommit>())
|
|
.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<AetheriaRuntimeEntitySnapshotCommit>();
|
|
foreach (var contact in actor.Contacts ?? Array.Empty<AetheriaRuntimeEntityContactCommit>())
|
|
{
|
|
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<AetheriaRuntimeZoneSnapshotCommit>())
|
|
.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex);
|
|
if (zone == null)
|
|
return false;
|
|
|
|
var isDocked = (zone.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
|
|
.Any(entity =>
|
|
entity != null &&
|
|
entity.EntityIndex != entityIndex &&
|
|
(entity.DockingBayAssignments ?? Array.Empty<int>()).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<AetheriaRuntimeEntitySnapshotCommit> mutate)
|
|
{
|
|
return string.IsNullOrWhiteSpace(command.TargetEntityKey)
|
|
? ApplyCurrentEntity(run, command, mutate)
|
|
: ApplyTargetEntity(run, command.TargetEntityKey, mutate);
|
|
}
|
|
|
|
private static bool ApplyCurrentEntity(
|
|
AetheriaRuntimeRunCheckpointCommit run,
|
|
AetheriaRuntimeDaemonCommandDocument command,
|
|
Action<AetheriaRuntimeEntitySnapshotCommit> 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<AetheriaRuntimeLoadoutItemCommit> 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<AetheriaRuntimeLoadoutItemCommit> mutate)
|
|
{
|
|
return ApplyTargetEquipmentItem(run, command.TargetEntityKey, command.EquipmentIndex, mutate);
|
|
}
|
|
|
|
private static bool ApplyTargetEquipmentItem(
|
|
AetheriaRuntimeRunCheckpointCommit run,
|
|
string entityKey,
|
|
int equipmentIndex,
|
|
Action<AetheriaRuntimeLoadoutItemCommit> mutate)
|
|
{
|
|
if (!TryResolveEntity(run, entityKey, out _, out _, out var entity))
|
|
return false;
|
|
|
|
var equipment = entity.Equipment ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>();
|
|
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<AetheriaRuntimeLoadoutItemSlotCommit>();
|
|
if (command.EquipmentIndex >= equipment.Count ||
|
|
equipment[command.EquipmentIndex]?.Item == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var sourceGroups = entity.WeaponGroups ?? Array.Empty<IReadOnlyList<int>>();
|
|
var groupCount = Math.Max(sourceGroups.Count, command.WeaponGroup + 1);
|
|
var groups = new IReadOnlyList<int>[groupCount];
|
|
for (var i = 0; i < groupCount; i++)
|
|
{
|
|
groups[i] = i < sourceGroups.Count
|
|
? (sourceGroups[i] ?? Array.Empty<int>()).ToArray()
|
|
: Array.Empty<int>();
|
|
}
|
|
|
|
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<AetheriaRuntimeBehaviorStateCommit>())
|
|
.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<AetheriaRuntimeLoadoutItemCommit> 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<AetheriaRuntimeBehaviorStateCommit>())
|
|
.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<AetheriaRuntimeLoadoutItemSlotCommit>();
|
|
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<AetheriaRuntimeZoneSnapshotCommit>())
|
|
.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex);
|
|
if (zone == null)
|
|
return false;
|
|
|
|
var pickups = (zone.DroppedPickups ?? Array.Empty<AetheriaRuntimeDroppedPickupCommit>()).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<AetheriaRuntimeEntityStatGridCommit>()).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<double>()).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<AetheriaRuntimeZoneSnapshotCommit>()).ToArray();
|
|
var zone = zones.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex);
|
|
if (zone == null)
|
|
return false;
|
|
|
|
var entities = (zone.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>()).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<int>()).ToList();
|
|
if (!childIndices.Contains(entityIndex))
|
|
childIndices.Add(entityIndex);
|
|
parent.ChildEntityIndices = childIndices.ToArray();
|
|
|
|
var assignments = (parent.DockingBayAssignments ?? Array.Empty<int>()).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<AetheriaRuntimeLoadoutTemplateCommit>())
|
|
.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<IReadOnlyList<int>>();
|
|
if (weaponGroup >= 0 && weaponGroup < weaponGroups.Count)
|
|
return true;
|
|
return weaponGroup == 0 &&
|
|
(entity.WeaponStates ?? Array.Empty<AetheriaRuntimeWeaponStateCommit>())
|
|
.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<AetheriaRuntimeBehaviorStateCommit>())
|
|
.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<AetheriaRuntimeZoneSnapshotCommit>())
|
|
.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<AetheriaRuntimeEntitySnapshotCommit>();
|
|
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<AetheriaRuntimeZoneSnapshotCommit>())
|
|
.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<int>())
|
|
{
|
|
var candidate = (run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
|
|
.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<int>()).ToList();
|
|
if (!childIndices.Contains(actorIndex))
|
|
childIndices.Add(actorIndex);
|
|
target.ChildEntityIndices = childIndices.ToArray();
|
|
|
|
var assignments = (target.DockingBayAssignments ?? Array.Empty<int>()).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<AetheriaRuntimeZoneSnapshotCommit>())
|
|
.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex);
|
|
if (zone == null)
|
|
return false;
|
|
|
|
foreach (var entity in zone.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
|
|
{
|
|
if (entity == null)
|
|
continue;
|
|
|
|
if ((entity.ChildEntityIndices ?? Array.Empty<int>()).Contains(childEntityIndex))
|
|
return true;
|
|
|
|
if ((entity.DockingBayAssignments ?? Array.Empty<int>()).Contains(childEntityIndex))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool RemoveChildReferenceFromZone(
|
|
AetheriaRuntimeRunCheckpointCommit run,
|
|
int zoneIndex,
|
|
int childEntityIndex)
|
|
{
|
|
var zone = (run.Zones ?? Array.Empty<AetheriaRuntimeZoneSnapshotCommit>())
|
|
.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex);
|
|
if (zone == null)
|
|
return false;
|
|
|
|
var removed = false;
|
|
foreach (var entity in zone.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>())
|
|
{
|
|
if (entity == null)
|
|
continue;
|
|
|
|
var childIndices = (entity.ChildEntityIndices ?? Array.Empty<int>()).ToList();
|
|
if (childIndices.RemoveAll(index => index == childEntityIndex) > 0)
|
|
{
|
|
entity.ChildEntityIndices = childIndices.ToArray();
|
|
removed = true;
|
|
}
|
|
|
|
var assignments = (entity.DockingBayAssignments ?? Array.Empty<int>()).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<AetheriaRuntimeZoneSnapshotCommit>()).ToList();
|
|
var sourceZone = zones.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == sourceZoneIndex);
|
|
if (sourceZone == null)
|
|
return false;
|
|
|
|
var sourceEntities = (sourceZone.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>()).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<AetheriaRuntimeEntitySnapshotCommit>()
|
|
};
|
|
zones.Add(targetZone);
|
|
run.Zones = zones.ToArray();
|
|
}
|
|
|
|
var targetEntities = (targetZone.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>()).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<int>()).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<AetheriaRuntimeZoneSnapshotCommit>()).ToList();
|
|
var zone = zones.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == zoneIndex);
|
|
if (zone == null)
|
|
return false;
|
|
|
|
var entities = (zone.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>()).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<AetheriaRuntimeEntitySnapshotCommit>();
|
|
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<int>())
|
|
.Select(childIndex => ReindexEntityReference(childIndex, removedEntityIndex))
|
|
.Where(childIndex => childIndex >= 0)
|
|
.ToArray();
|
|
entity.DockingBayAssignments = (entity.DockingBayAssignments ?? Array.Empty<int>())
|
|
.Select(childIndex => ReindexEntityReference(childIndex, removedEntityIndex))
|
|
.ToArray();
|
|
entity.Contacts = (entity.Contacts ?? Array.Empty<AetheriaRuntimeEntityContactCommit>())
|
|
.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<AetheriaRuntimeEntitySnapshotCommit> 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<AetheriaRuntimeZoneSnapshotCommit>())
|
|
.FirstOrDefault(candidate => candidate != null && candidate.ZoneIndex == parsedZoneIndex);
|
|
var entities = zone?.Entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>();
|
|
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<AetheriaRuntimeCargoBayLoadoutCommit>();
|
|
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<AetheriaRuntimeCargoBayLoadoutCommit>()).ToArray();
|
|
if (cargoIndex < 0 || cargoIndex >= cargoContents.Length || cargoContents[cargoIndex] == null)
|
|
return false;
|
|
|
|
var items = (cargoContents[cargoIndex].Items ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>()).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<AetheriaRuntimeCargoBayLoadoutCommit>()).ToArray();
|
|
if (cargoIndex < 0 || cargoIndex >= cargoContents.Length || cargoContents[cargoIndex] == null)
|
|
return false;
|
|
|
|
var items = (cargoContents[cargoIndex].Items ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>()).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<AetheriaRuntimeLoadoutItemSlotCommit>())
|
|
.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<AetheriaRuntimeLoadoutItemSlotCommit>()).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<IReadOnlyList<int>>())
|
|
.Select(group => (IReadOnlyList<int>)(group ?? Array.Empty<int>())
|
|
.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<AetheriaRuntimeLoadoutItemSlotCommit>())
|
|
.Concat(new[] { slot })
|
|
.ToArray();
|
|
}
|
|
|
|
private static AetheriaRuntimeCargoBayLoadoutCommit[] EnsureCargoContents(
|
|
AetheriaRuntimeEntitySnapshotCommit entity,
|
|
int count)
|
|
{
|
|
var source = entity.CargoContents ?? Array.Empty<AetheriaRuntimeCargoBayLoadoutCommit>();
|
|
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;
|
|
}
|
|
|
|
}
|
|
}
|