Move target cycling into daemon operations
This commit is contained in:
parent
c6f8b95317
commit
1f47ea387b
7 changed files with 282 additions and 17 deletions
|
|
@ -7686,6 +7686,10 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root)
|
|||
var requiredTargetAuthoritySymbols = new[]
|
||||
{
|
||||
"ApplySetTarget(run, command)",
|
||||
"ApplyTargetCycle(run, command, TargetCycleMode.Nearest)",
|
||||
"ApplyTargetCycle(run, command, TargetCycleMode.Next)",
|
||||
"ApplyTargetCycle(run, command, TargetCycleMode.Previous)",
|
||||
"VisibleHostileTargets(zone, actor, actorIndex)",
|
||||
"actor.Contacts ?? Array.Empty<AetheriaRuntimeEntityContactCommit>()",
|
||||
"contact.TargetEntityIndex == targetIndex && contact.Visible"
|
||||
};
|
||||
|
|
@ -7699,6 +7703,42 @@ static void RequireUnityObserverDoesNotTickLocalSimulation(string root)
|
|||
string.Join(", ", missingTargetAuthoritySymbols));
|
||||
}
|
||||
|
||||
var requiredUnityTargetRequestSymbols = new[]
|
||||
{
|
||||
"RequestTargetNearest()",
|
||||
"RequestTargetNext()",
|
||||
"RequestTargetPrevious()",
|
||||
"observer.Operations.TargetNearest()",
|
||||
"observer.Operations.TargetNext()",
|
||||
"observer.Operations.TargetPrevious()"
|
||||
};
|
||||
var unityTargetRequestSources = actionGameManager + "\n" + daemonOperations;
|
||||
var missingUnityTargetRequestSymbols = requiredUnityTargetRequestSymbols
|
||||
.Where(symbol => !unityTargetRequestSources.Contains(symbol, StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
if (missingUnityTargetRequestSymbols.Length > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Unity target cycling no longer lowers through typed daemon requests: " +
|
||||
string.Join(", ", missingUnityTargetRequestSymbols));
|
||||
}
|
||||
|
||||
var forbiddenUnityTargetCycleSymbols = new[]
|
||||
{
|
||||
".MaxBy(x => CultMath.math.length(x.CultPosition - CurrentEntity.CultPosition))",
|
||||
".OrderBy(x => CultMath.math.length(x.CultPosition - CurrentEntity.CultPosition))",
|
||||
"Array.IndexOf(targets"
|
||||
};
|
||||
var unityTargetCycleHits = forbiddenUnityTargetCycleSymbols
|
||||
.Where(symbol => actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
if (unityTargetCycleHits.Length > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Unity target cycling still orders or selects targets from renderer-local entity state instead of daemon contacts: " +
|
||||
string.Join(", ", unityTargetCycleHits));
|
||||
}
|
||||
|
||||
var requiredIntentAuthoritySymbols = new[]
|
||||
{
|
||||
"!HasWeaponGroup(entity, command.WeaponGroup)",
|
||||
|
|
|
|||
|
|
@ -2067,31 +2067,17 @@ public class ActionGameManager : MonoBehaviour
|
|||
|
||||
Input.Player.TargetNearest.performed += context =>
|
||||
{
|
||||
if(CurrentEntity.VisibleEnemies.Any())
|
||||
{
|
||||
RequestTargetSelection(CurrentEntity.VisibleEnemies.Where(x => x != CurrentEntity)
|
||||
.MaxBy(x => CultMath.math.length(x.CultPosition - CurrentEntity.CultPosition)));
|
||||
}
|
||||
RequestTargetNearest();
|
||||
};
|
||||
|
||||
Input.Player.TargetNext.performed += context =>
|
||||
{
|
||||
if (!CurrentEntity.VisibleEnemies.Any()) return;
|
||||
var targets = CurrentEntity.VisibleEnemies.Where(x => x != CurrentEntity)
|
||||
.OrderBy(x => CultMath.math.length(x.CultPosition - CurrentEntity.CultPosition))
|
||||
.ToArray();
|
||||
var currentTargetIndex = Array.IndexOf(targets, CurrentEntity.Target.Value);
|
||||
RequestTargetSelection(targets[(currentTargetIndex + 1) % targets.Length]);
|
||||
RequestTargetNext();
|
||||
};
|
||||
|
||||
Input.Player.TargetPrevious.performed += context =>
|
||||
{
|
||||
if (!CurrentEntity.VisibleEnemies.Any()) return;
|
||||
var targets = CurrentEntity.VisibleEnemies.Where(x => x != CurrentEntity)
|
||||
.OrderBy(x => CultMath.math.length(x.CultPosition - CurrentEntity.CultPosition))
|
||||
.ToArray();
|
||||
var currentTargetIndex = Array.IndexOf(targets, CurrentEntity.Target.Value);
|
||||
RequestTargetSelection(targets[(currentTargetIndex + targets.Length - 1) % targets.Length]);
|
||||
RequestTargetPrevious();
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
|
@ -3743,6 +3729,21 @@ public class ActionGameManager : MonoBehaviour
|
|||
TryRequestDaemonTargetSelection(target);
|
||||
}
|
||||
|
||||
private void RequestTargetNearest()
|
||||
{
|
||||
TryRequestDaemonTargetCycle(AetheriaRuntimeDaemonCommandKinds.TargetNearest);
|
||||
}
|
||||
|
||||
private void RequestTargetNext()
|
||||
{
|
||||
TryRequestDaemonTargetCycle(AetheriaRuntimeDaemonCommandKinds.TargetNext);
|
||||
}
|
||||
|
||||
private void RequestTargetPrevious()
|
||||
{
|
||||
TryRequestDaemonTargetCycle(AetheriaRuntimeDaemonCommandKinds.TargetPrevious);
|
||||
}
|
||||
|
||||
private bool TryRequestDaemonTargetSelection(Entity target)
|
||||
{
|
||||
var observer = ResolveDaemonObserver();
|
||||
|
|
@ -3775,6 +3776,38 @@ public class ActionGameManager : MonoBehaviour
|
|||
}
|
||||
}
|
||||
|
||||
private bool TryRequestDaemonTargetCycle(AetheriaRuntimeDaemonCommandKinds command)
|
||||
{
|
||||
var observer = ResolveDaemonObserver();
|
||||
if (observer == null || !observer.HasAuthoritativeState)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (command)
|
||||
{
|
||||
case AetheriaRuntimeDaemonCommandKinds.TargetNearest:
|
||||
observer.Operations.TargetNearest();
|
||||
return true;
|
||||
case AetheriaRuntimeDaemonCommandKinds.TargetNext:
|
||||
observer.Operations.TargetNext();
|
||||
return true;
|
||||
case AetheriaRuntimeDaemonCommandKinds.TargetPrevious:
|
||||
observer.Operations.TargetPrevious();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"Failed to send Aetheria daemon target cycle operation; operation not submitted: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void RequestOverrideShutdown(bool enabled)
|
||||
{
|
||||
TryRequestDaemonOverrideShutdown(enabled);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,21 @@ public sealed class AetheriaDaemonOperations
|
|||
return Send((client, observed) => client.ClearTarget(observed));
|
||||
}
|
||||
|
||||
public AetheriaRuntimeDaemonCommandEnvelope TargetNearest()
|
||||
{
|
||||
return Send((client, observed) => client.TargetNearest(observed));
|
||||
}
|
||||
|
||||
public AetheriaRuntimeDaemonCommandEnvelope TargetNext()
|
||||
{
|
||||
return Send((client, observed) => client.TargetNext(observed));
|
||||
}
|
||||
|
||||
public AetheriaRuntimeDaemonCommandEnvelope TargetPrevious()
|
||||
{
|
||||
return Send((client, observed) => client.TargetPrevious(observed));
|
||||
}
|
||||
|
||||
public AetheriaRuntimeDaemonCommandEnvelope SetMoveVector(
|
||||
double directionX,
|
||||
double directionY,
|
||||
|
|
|
|||
|
|
@ -1071,6 +1071,79 @@ public class DaemonRuntimeDocumentTests
|
|||
Assert.AreEqual(-1, run.Zones[0].Entities[0].TargetEntityIndex);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DaemonOperationsCyclesTargetsFromAuthoritativeContacts()
|
||||
{
|
||||
var run = RunWithTwoEntities();
|
||||
var zone = run.Zones[0];
|
||||
zone.Entities[0].PositionX = 0;
|
||||
zone.Entities[0].PositionY = 0;
|
||||
zone.Entities[1].PositionX = 5;
|
||||
zone.Entities[1].PositionY = 0;
|
||||
zone.Entities = zone.Entities
|
||||
.Concat(new[]
|
||||
{
|
||||
new AetheriaRuntimeEntitySnapshotCommit
|
||||
{
|
||||
EntityIndex = 2,
|
||||
Name = "Near Hostile",
|
||||
PositionX = 2,
|
||||
PositionY = 0
|
||||
}
|
||||
})
|
||||
.ToArray();
|
||||
zone.Entities[0].Contacts = new[]
|
||||
{
|
||||
new AetheriaRuntimeEntityContactCommit
|
||||
{
|
||||
TargetEntityIndex = 1,
|
||||
Visible = true,
|
||||
Hostile = true
|
||||
},
|
||||
new AetheriaRuntimeEntityContactCommit
|
||||
{
|
||||
TargetEntityIndex = 2,
|
||||
Visible = true,
|
||||
Hostile = true
|
||||
}
|
||||
};
|
||||
|
||||
var nearest = AetheriaRuntimeDaemonCommandDocument.Create(
|
||||
AetheriaRuntimeDaemonCommandKinds.TargetNearest,
|
||||
"codex",
|
||||
"session-target",
|
||||
15,
|
||||
"zone.0.entity.0");
|
||||
var next = AetheriaRuntimeDaemonCommandDocument.Create(
|
||||
AetheriaRuntimeDaemonCommandKinds.TargetNext,
|
||||
"codex",
|
||||
"session-target",
|
||||
16,
|
||||
"zone.0.entity.0");
|
||||
var previous = AetheriaRuntimeDaemonCommandDocument.Create(
|
||||
AetheriaRuntimeDaemonCommandKinds.TargetPrevious,
|
||||
"codex",
|
||||
"session-target",
|
||||
17,
|
||||
"zone.0.entity.0");
|
||||
|
||||
var previousFromNoneResult = AetheriaRuntimeDaemonOperations.Execute(run, new[] { previous });
|
||||
Assert.AreEqual(1, previousFromNoneResult.AppliedCommandIds.Count);
|
||||
Assert.AreEqual(2, zone.Entities[0].TargetEntityIndex);
|
||||
|
||||
var nearestResult = AetheriaRuntimeDaemonOperations.Execute(run, new[] { nearest });
|
||||
Assert.AreEqual(1, nearestResult.AppliedCommandIds.Count);
|
||||
Assert.AreEqual(2, zone.Entities[0].TargetEntityIndex);
|
||||
|
||||
var nextResult = AetheriaRuntimeDaemonOperations.Execute(run, new[] { next });
|
||||
Assert.AreEqual(1, nextResult.AppliedCommandIds.Count);
|
||||
Assert.AreEqual(1, zone.Entities[0].TargetEntityIndex);
|
||||
|
||||
var previousResult = AetheriaRuntimeDaemonOperations.Execute(run, new[] { previous });
|
||||
Assert.AreEqual(1, previousResult.AppliedCommandIds.Count);
|
||||
Assert.AreEqual(2, zone.Entities[0].TargetEntityIndex);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DaemonOperationsMutatesHotEntityControlsInDaemonState()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ namespace GameCult.Aetheria.State.Verse
|
|||
Ping,
|
||||
SetTarget,
|
||||
ClearTarget,
|
||||
TargetNearest,
|
||||
TargetNext,
|
||||
TargetPrevious,
|
||||
SetMoveVector,
|
||||
SetLookDirection,
|
||||
SetTractorPower,
|
||||
|
|
|
|||
|
|
@ -117,6 +117,21 @@ namespace GameCult.Aetheria.State.Verse
|
|||
return Send(AetheriaRuntimeDaemonCommandKinds.ClearTarget, observed);
|
||||
}
|
||||
|
||||
public AetheriaRuntimeDaemonCommandEnvelope TargetNearest(AetheriaRuntimeObservedDaemonState? observed)
|
||||
{
|
||||
return Send(AetheriaRuntimeDaemonCommandKinds.TargetNearest, observed);
|
||||
}
|
||||
|
||||
public AetheriaRuntimeDaemonCommandEnvelope TargetNext(AetheriaRuntimeObservedDaemonState? observed)
|
||||
{
|
||||
return Send(AetheriaRuntimeDaemonCommandKinds.TargetNext, observed);
|
||||
}
|
||||
|
||||
public AetheriaRuntimeDaemonCommandEnvelope TargetPrevious(AetheriaRuntimeObservedDaemonState? observed)
|
||||
{
|
||||
return Send(AetheriaRuntimeDaemonCommandKinds.TargetPrevious, observed);
|
||||
}
|
||||
|
||||
public AetheriaRuntimeDaemonCommandEnvelope SetMoveVector(
|
||||
AetheriaRuntimeObservedDaemonState? observed,
|
||||
double directionX,
|
||||
|
|
|
|||
|
|
@ -79,6 +79,12 @@ namespace GameCult.Aetheria.State.Verse
|
|||
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.SetLookDirection:
|
||||
return ApplySetLookDirection(run, command);
|
||||
case AetheriaRuntimeDaemonCommandKinds.SetTractorPower:
|
||||
|
|
@ -189,6 +195,86 @@ namespace GameCult.Aetheria.State.Verse
|
|||
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 IEnumerable<(int EntityIndex, double DistanceSq)> 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;
|
||||
yield return (contact.TargetEntityIndex, (deltaX * deltaX) + (deltaY * deltaY));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ApplyClearTarget(
|
||||
AetheriaRuntimeRunCheckpointCommit run,
|
||||
AetheriaRuntimeDaemonCommandDocument command)
|
||||
|
|
|
|||
Loading…
Reference in a new issue