From 7e80803a88e543cc2e9a5477dff158e1310b30b2 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 21 Jun 2026 11:51:37 +0100 Subject: [PATCH] Move interaction priority into daemon operations --- Aetheria.State.Verify/Program.cs | 13 +++- Assets/Scripts/Gameplay/ActionGameManager.cs | 34 +++----- .../Gameplay/AetheriaDaemonOperations.cs | 5 ++ .../Tests/DaemonRuntimeDocumentTests.cs | 73 ++++++++++++++++++ .../Runtime/AetheriaRuntimeDaemonDocuments.cs | 1 + .../AetheriaRuntimeDaemonOperationClient.cs | 12 +++ .../AetheriaRuntimeDaemonOperations.cs | 77 +++++++++++++++++++ 7 files changed, 188 insertions(+), 27 deletions(-) diff --git a/Aetheria.State.Verify/Program.cs b/Aetheria.State.Verify/Program.cs index 2720bbafe..4a011a2bb 100644 --- a/Aetheria.State.Verify/Program.cs +++ b/Aetheria.State.Verify/Program.cs @@ -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 diff --git a/Assets/Scripts/Gameplay/ActionGameManager.cs b/Assets/Scripts/Gameplay/ActionGameManager.cs index 0bd256531..02f41616c 100644 --- a/Assets/Scripts/Gameplay/ActionGameManager.cs +++ b/Assets/Scripts/Gameplay/ActionGameManager.cs @@ -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; } } diff --git a/Assets/Scripts/Gameplay/AetheriaDaemonOperations.cs b/Assets/Scripts/Gameplay/AetheriaDaemonOperations.cs index 2c190296b..44f9200d0 100644 --- a/Assets/Scripts/Gameplay/AetheriaDaemonOperations.cs +++ b/Assets/Scripts/Gameplay/AetheriaDaemonOperations.cs @@ -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)); diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs index 0c8b91999..e9a81c389 100644 --- a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs @@ -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() { diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs index e0e2e835f..3357942cf 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonDocuments.cs @@ -84,6 +84,7 @@ namespace GameCult.Aetheria.State.Verse Dock, DockNearest, Undock, + Interact, SetDockedCurrentShip, TowToStation, EnterWormhole, diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationClient.cs index e58ae64db..60c1237a5 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperationClient.cs @@ -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) diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperations.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperations.cs index 4cc5920e1..826d125ec 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperations.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonOperations.cs @@ -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()) + .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,