Move interaction priority into daemon operations

This commit is contained in:
Meta Vens 2026-06-21 11:51:37 +01:00
commit 7e80803a88
7 changed files with 188 additions and 27 deletions

View file

@ -7533,12 +7533,15 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root)
{
"public void RequestDock()",
"public void RequestUndock()",
"private void RequestInteract()",
"public void RequestTowToStation()",
"TryRequestDaemonDock()",
"TryRequestDaemonUndock()",
"TryRequestDaemonInteract()",
"TryRequestDaemonTowToStation()",
"observer.Operations.DockNearest(Settings.GameplaySettings.DockingDistance)",
"observer.Operations.Undock()",
"observer.Operations.Interact(",
"observer.Operations.TowToStation(",
"TowingStation.CultPositionXZ.x",
"TowingStation.CultPositionXZ.y"
@ -7563,7 +7566,9 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root)
"Missing cockpit component",
"Missing thruster component",
"Missing reactor component",
"Must empty docking bay"
"Must empty docking bay",
"ZoneRenderer.WormholeInstances.Keys",
"RequestEnterWormhole(wormhole);"
};
var unityDockingAuthorityHits = forbiddenUnityDockingAuthoritySymbols
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
@ -7617,9 +7622,13 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root)
var requiredDaemonDockNearestSymbols = new[]
{
"AetheriaRuntimeDaemonCommandKinds.DockNearest",
"AetheriaRuntimeDaemonCommandKinds.Interact",
"public AetheriaRuntimeDaemonCommandEnvelope DockNearest(",
"public AetheriaRuntimeDaemonCommandEnvelope Interact(",
"ApplyDockNearestIntent(run, command, context.Intents)",
"TryFindNearestDockTarget(run, actorKey, command.ScalarValue, out var targetKey)"
"ApplyInteractIntent(run, command, context.Intents)",
"TryFindNearestDockTarget(run, actorKey, command.ScalarValue, out var targetKey)",
"TryFindNearestWormholeTarget("
};
var daemonDockNearestSources = daemonDocuments + "\n" + daemonOperationClient + "\n" + daemonOperationsSource;
var missingDaemonDockNearestSymbols = requiredDaemonDockNearestSymbols

View file

@ -2000,17 +2000,7 @@ public class ActionGameManager : MonoBehaviour
Dialog.Show();
Dialog.MoveToCursor();
}
else if (CurrentEntity.Parent == null)
{
foreach (var wormhole in ZoneRenderer.WormholeInstances.Keys)
{
if (!(CultMath.math.length(wormhole.Position - CurrentEntity.CultPositionXZ) < Settings.GameplaySettings.WormholeExitRadius)) continue;
RequestEnterWormhole(wormhole);
return;
}
RequestDock();
}
else RequestUndock();
else RequestInteract();
};
Input.Global.MainMenu.performed += context =>
@ -2165,35 +2155,29 @@ public class ActionGameManager : MonoBehaviour
foreach (var a in _actionBarActions) a.Disable();
}
private void RequestEnterWormhole(Wormhole wormhole)
private void RequestInteract()
{
TryRequestDaemonEnterWormhole(wormhole);
TryRequestDaemonInteract();
}
private bool TryRequestDaemonEnterWormhole(Wormhole wormhole)
private bool TryRequestDaemonInteract()
{
var observer = ResolveDaemonObserver();
if (observer == null ||
!observer.HasAuthoritativeState ||
wormhole == null)
{
return false;
}
var targetZoneIndex = ZoneIndex(wormhole.Target);
if (targetZoneIndex < 0)
if (observer == null || !observer.HasAuthoritativeState)
{
return false;
}
try
{
observer.Operations.EnterWormhole(targetZoneIndex, wormhole.Position.x, wormhole.Position.y);
observer.Operations.Interact(
Settings.GameplaySettings.DockingDistance,
Settings.GameplaySettings.WormholeExitRadius);
return true;
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to send Aetheria daemon wormhole operation; operation not submitted: {ex.Message}");
Debug.LogWarning($"Failed to send Aetheria daemon interact operation; operation not submitted: {ex.Message}");
return false;
}
}

View file

@ -210,6 +210,11 @@ public sealed class AetheriaDaemonOperations
return Send((client, observed) => client.Undock(observed));
}
public AetheriaRuntimeDaemonCommandEnvelope Interact(double dockDistance, double wormholeDistance)
{
return Send((client, observed) => client.Interact(observed, dockDistance, wormholeDistance));
}
public AetheriaRuntimeDaemonCommandEnvelope SetDockedCurrentShip(string targetEntityKey)
{
return Send((client, observed) => client.SetDockedCurrentShip(observed, targetEntityKey));

View file

@ -2003,6 +2003,79 @@ public class DaemonRuntimeDocumentTests
CollectionAssert.Contains(run.Zones[0].Entities[1].DockingBayAssignments, 0);
}
[Test]
public void DaemonOperationsInteractPrioritizesUndockWormholeThenDock()
{
var undockRun = RunWithTwoEntities();
undockRun.Zones[0].Entities[1].ChildEntityIndices = new[] { 0 };
undockRun.Zones[0].Entities[1].DockingBayAssignments = new[] { 0, -1 };
var undock = AetheriaRuntimeDaemonCommandDocument.Create(
AetheriaRuntimeDaemonCommandKinds.Interact,
"codex",
"session-interact",
46,
"zone.0.entity.0");
undock.ScalarValue = 10;
undock.PositionX = 10;
var undockResult = AetheriaRuntimeDaemonOperations.Execute(undockRun, new[] { undock });
Assert.AreEqual(1, undockResult.AppliedCommandIds.Count);
Assert.AreEqual(1, undockResult.Intents.Docking.Count);
Assert.IsTrue(undockResult.Intents.Docking[0].Undock);
CollectionAssert.DoesNotContain(undockRun.Zones[0].Entities[1].DockingBayAssignments, 0);
var wormholeRun = RunWithTwoEntities();
wormholeRun.Zones[0].AdjacentZoneIndices = new[] { 2 };
wormholeRun.Zones[0].Entities[0].PositionX = 0;
wormholeRun.Zones[0].Entities[0].PositionZ = 0;
wormholeRun.Zones = wormholeRun.Zones
.Concat(new[]
{
new AetheriaRuntimeZoneSnapshotCommit
{
ZoneIndex = 2,
PositionX = 3,
PositionY = 4
}
})
.ToArray();
var wormhole = AetheriaRuntimeDaemonCommandDocument.Create(
AetheriaRuntimeDaemonCommandKinds.Interact,
"codex",
"session-interact",
47,
"zone.0.entity.0");
wormhole.ScalarValue = 10;
wormhole.PositionX = 10;
var wormholeResult = AetheriaRuntimeDaemonOperations.Execute(wormholeRun, new[] { wormhole });
Assert.AreEqual(1, wormholeResult.AppliedCommandIds.Count);
Assert.AreEqual(1, wormholeResult.Intents.Wormholes.Count);
Assert.AreEqual(2, wormholeRun.CurrentZoneIndex);
Assert.AreEqual(3, FindZone(wormholeRun, 2).Entities[0].PositionX, 0.0001);
Assert.AreEqual(4, FindZone(wormholeRun, 2).Entities[0].PositionZ, 0.0001);
var dockRun = RunWithTwoEntities();
dockRun.Zones[0].Entities[0].PositionX = 0;
dockRun.Zones[0].Entities[0].PositionY = 0;
dockRun.Zones[0].Entities[1].PositionX = 3;
dockRun.Zones[0].Entities[1].PositionY = 4;
var dock = AetheriaRuntimeDaemonCommandDocument.Create(
AetheriaRuntimeDaemonCommandKinds.Interact,
"codex",
"session-interact",
48,
"zone.0.entity.0");
dock.ScalarValue = 10;
dock.PositionX = 1;
var dockResult = AetheriaRuntimeDaemonOperations.Execute(dockRun, new[] { dock });
Assert.AreEqual(1, dockResult.AppliedCommandIds.Count);
Assert.AreEqual(1, dockResult.Intents.Docking.Count);
Assert.IsTrue(dockResult.Intents.Docking[0].Dock);
CollectionAssert.Contains(dockRun.Zones[0].Entities[1].ChildEntityIndices, 0);
}
[Test]
public void DaemonOperationsTowsEntityToStationInDaemonState()
{

View file

@ -84,6 +84,7 @@ namespace GameCult.Aetheria.State.Verse
Dock,
DockNearest,
Undock,
Interact,
SetDockedCurrentShip,
TowToStation,
EnterWormhole,

View file

@ -407,6 +407,18 @@ namespace GameCult.Aetheria.State.Verse
return Send(AetheriaRuntimeDaemonCommandKinds.Undock, observed);
}
public AetheriaRuntimeDaemonCommandEnvelope Interact(
AetheriaRuntimeObservedDaemonState? observed,
double dockDistance,
double wormholeDistance)
{
return Send(AetheriaRuntimeDaemonCommandKinds.Interact, observed, command =>
{
command.ScalarValue = dockDistance;
command.PositionX = wormholeDistance;
});
}
public AetheriaRuntimeDaemonCommandEnvelope SetDockedCurrentShip(
AetheriaRuntimeObservedDaemonState? observed,
string targetEntityKey)

View file

@ -163,6 +163,8 @@ namespace GameCult.Aetheria.State.Verse
return ApplyDockNearestIntent(run, command, context.Intents);
case AetheriaRuntimeDaemonCommandKinds.Undock:
return ApplyUndockIntent(run, command, context.Intents);
case AetheriaRuntimeDaemonCommandKinds.Interact:
return ApplyInteractIntent(run, command, context.Intents);
case AetheriaRuntimeDaemonCommandKinds.EnterWormhole:
return ApplyEnterWormholeIntent(run, command, context.Intents);
case AetheriaRuntimeDaemonCommandKinds.TowToStation:
@ -1095,6 +1097,38 @@ namespace GameCult.Aetheria.State.Verse
return true;
}
private static bool ApplyInteractIntent(
AetheriaRuntimeRunCheckpointCommit run,
AetheriaRuntimeDaemonCommandDocument command,
AetheriaRuntimeDaemonIntentState intents)
{
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,
command.PositionX,
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);
}
private static bool TryFindNearestDockTarget(
AetheriaRuntimeRunCheckpointCommit run,
string actorEntityKey,
@ -1137,6 +1171,49 @@ namespace GameCult.Aetheria.State.Verse
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,