Rebuild station towing without teleportation
This commit is contained in:
parent
27554981d1
commit
e3c81ff2ec
8 changed files with 124 additions and 39 deletions
|
|
@ -24,6 +24,38 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
AgentMinesAsteroidThroughEquippedBehavior();
|
||||
CargoCapacityComesFromHullAndCatalogVolumes();
|
||||
AgentSurveysBodyIntoCorporationKnowledge();
|
||||
AgentTowsStationIntoPersistentOrbit();
|
||||
}
|
||||
|
||||
private static void AgentTowsStationIntoPersistentOrbit()
|
||||
{
|
||||
var tug = Entity(0, 0, "workers");
|
||||
var station = Entity(1, 0, "workers");
|
||||
station.Kind = "station";
|
||||
tug.AgentTaskCapabilities = [AetheriaRuntimeAgentTaskTypes.Tow];
|
||||
var zone = new AetheriaRuntimeZoneSnapshotCommit
|
||||
{
|
||||
ZoneIndex = 0, Entities = [tug, station],
|
||||
Orbits = [new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = "parent-orbit", FixedPositionX = 20 }],
|
||||
Bodies = [new AetheriaRuntimeBodySnapshotCommit { BodyKey = "parent-body", Kind = "planet", OrbitKey = "parent-orbit" }]
|
||||
};
|
||||
var run = new AetheriaRuntimeRunCheckpointCommit
|
||||
{
|
||||
CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [zone],
|
||||
AgentTasks = [new AetheriaRuntimeAgentTaskCommit { TaskId = "tow-1", CorporationKey = "workers", TaskType = AetheriaRuntimeAgentTaskTypes.Tow, ZoneIndex = 0, Status = AetheriaRuntimeAgentTaskStatuses.Queued, TargetEntityIndex = 1, OrbitParentKey = "parent-body", OrbitDistance = 20, CompletionRadius = 5 }]
|
||||
};
|
||||
AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-tow-attach-smoke.cc"), run,
|
||||
new AetheriaRuntimeDaemonTickOptions { FrameId = 1, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 1, BuildPublications = false });
|
||||
Require(run.Zones[0].Entities[0].ChildEntityIndices.Contains(1), "tow pickup must attach station to tug parentage");
|
||||
RequireEqual("", run.Zones[0].Entities[1].OrbitKey, "attached station must no longer own an orbit");
|
||||
|
||||
AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-tow-detach-smoke.cc"), run,
|
||||
new AetheriaRuntimeDaemonTickOptions { FrameId = 2, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 2, BuildPublications = false });
|
||||
Require(!run.Zones[0].Entities[0].ChildEntityIndices.Contains(1), "tow delivery must detach station parentage");
|
||||
Require(!string.IsNullOrWhiteSpace(run.Zones[0].Entities[1].OrbitKey), "delivered station must own a persistent orbit");
|
||||
Require(run.Zones[0].Orbits.Any(orbit => orbit.OrbitKey == run.Zones[0].Entities[1].OrbitKey && orbit.ParentOrbitKey == "parent-orbit" && Math.Abs(orbit.Distance - 20) < 0.001),
|
||||
"delivered orbit must preserve requested parent and radius");
|
||||
RequireEqual(AetheriaRuntimeAgentTaskStatuses.Completed, run.AgentTasks[0].Status, "tow task must complete only after detach applies");
|
||||
}
|
||||
|
||||
private static void AgentSurveysBodyIntoCorporationKnowledge()
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
[Key(19)] public string Phase { get; set; } = "";
|
||||
[Key(20)] public IReadOnlyList<string> TargetBodyKeys { get; set; } = Array.Empty<string>();
|
||||
[Key(21)] public int CircuitIndex { get; set; }
|
||||
[Key(22)] public string OrbitParentKey { get; set; } = "";
|
||||
[Key(23)] public double OrbitDistance { get; set; }
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
|
|
@ -74,6 +76,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
[Key(11)] public string ItemKey { get; set; } = "";
|
||||
[Key(12)] public int Quantity { get; set; }
|
||||
[Key(13)] public IReadOnlyList<string> TargetBodyKeys { get; set; } = Array.Empty<string>();
|
||||
[Key(14)] public string OrbitParentKey { get; set; } = "";
|
||||
[Key(15)] public double OrbitDistance { get; set; }
|
||||
}
|
||||
|
||||
public static class AetheriaRuntimeAgentScheduler
|
||||
|
|
@ -137,6 +141,15 @@ namespace GameCult.Aetheria.State.Verse
|
|||
if (agent != null)
|
||||
Complete(task, agent, frameId);
|
||||
}
|
||||
foreach (var task in (run?.AgentTasks ?? Array.Empty<AetheriaRuntimeAgentTaskCommit>())
|
||||
.Where(task => task != null && string.Equals(task.TaskType, AetheriaRuntimeAgentTaskTypes.Tow, StringComparison.Ordinal)))
|
||||
{
|
||||
var detach = CommandId(task, frameId, "detach");
|
||||
var agent = FindEntity(run, task.ZoneIndex, task.AssignedEntityIndex);
|
||||
if (agent == null) continue;
|
||||
if (applied.Contains(detach)) Complete(task, agent, frameId);
|
||||
else if (rejected.Contains(detach)) Fail(task, agent);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssignQueuedTasks(AetheriaRuntimeRunCheckpointCommit run, long frameId)
|
||||
|
|
@ -210,6 +223,11 @@ namespace GameCult.Aetheria.State.Verse
|
|||
commands.AddRange(PlanPatrol(zone!, task, agent, frameId));
|
||||
continue;
|
||||
}
|
||||
if (string.Equals(task.TaskType, AetheriaRuntimeAgentTaskTypes.Tow, StringComparison.Ordinal))
|
||||
{
|
||||
commands.AddRange(PlanTow(run, zone!, task, agent, frameId));
|
||||
continue;
|
||||
}
|
||||
|
||||
var target = task.TargetEntityIndex < 0
|
||||
? null
|
||||
|
|
@ -455,6 +473,41 @@ namespace GameCult.Aetheria.State.Verse
|
|||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AetheriaRuntimeDaemonCommandDocument> PlanTow(
|
||||
AetheriaRuntimeRunCheckpointCommit run,
|
||||
AetheriaRuntimeZoneSnapshotCommit zone,
|
||||
AetheriaRuntimeAgentTaskCommit task,
|
||||
AetheriaRuntimeEntitySnapshotCommit agent,
|
||||
long frameId)
|
||||
{
|
||||
var station = zone.Entities.FirstOrDefault(value => value != null && value.EntityIndex == task.TargetEntityIndex);
|
||||
var parentBody = (zone.Bodies ?? Array.Empty<AetheriaRuntimeBodySnapshotCommit>())
|
||||
.FirstOrDefault(value => value != null && string.Equals(value.BodyKey, task.OrbitParentKey, StringComparison.Ordinal));
|
||||
var parent = AetheriaRuntimeDaemonRenderQueries.QueryBodyPoses(zone)
|
||||
.FirstOrDefault(value => string.Equals(value.BodyKey, task.OrbitParentKey, StringComparison.Ordinal));
|
||||
if (station == null || parentBody == null || string.IsNullOrWhiteSpace(parent.BodyKey) || string.IsNullOrWhiteSpace(parentBody.OrbitKey)) { Fail(task, agent); return Array.Empty<AetheriaRuntimeDaemonCommandDocument>(); }
|
||||
var attached = (agent.ChildEntityIndices ?? Array.Empty<int>()).Contains(station.EntityIndex);
|
||||
if (!attached)
|
||||
{
|
||||
var dx = station.PositionX - agent.PositionX; var dz = station.PositionZ - agent.PositionZ;
|
||||
if (Math.Sqrt(dx * dx + dz * dz) > Math.Max(0.01, task.CompletionRadius))
|
||||
return new[] { Movement(task, agent, frameId, dx, dz, 1) };
|
||||
task.Phase = "pickup";
|
||||
return new[] { Movement(task, agent, frameId, 0, 0, 0), Command(task, agent, frameId, AetheriaRuntimeDaemonCommandKinds.TowToStation, "attach", command => { command.TargetEntityKey = EntityKey(run, task.ZoneIndex, station.EntityIndex); command.TextValue = "attach"; }) };
|
||||
}
|
||||
task.Phase = "delivery";
|
||||
var radialX = agent.PositionX - parent.CenterX; var radialZ = agent.PositionZ - parent.CenterZ;
|
||||
var length = Math.Sqrt(radialX * radialX + radialZ * radialZ);
|
||||
if (length < 0.001) { radialX = 1; radialZ = 0; length = 1; }
|
||||
var destinationX = parent.CenterX + radialX / length * task.OrbitDistance;
|
||||
var destinationZ = parent.CenterZ + radialZ / length * task.OrbitDistance;
|
||||
var dxDelivery = destinationX - agent.PositionX; var dzDelivery = destinationZ - agent.PositionZ;
|
||||
if (Math.Sqrt(dxDelivery * dxDelivery + dzDelivery * dzDelivery) > Math.Max(0.01, task.CompletionRadius))
|
||||
return new[] { Movement(task, agent, frameId, dxDelivery, dzDelivery, 1) };
|
||||
task.Phase = "detach";
|
||||
return new[] { Movement(task, agent, frameId, 0, 0, 0), Command(task, agent, frameId, AetheriaRuntimeDaemonCommandKinds.TowToStation, "detach", command => { command.TargetEntityKey = EntityKey(run, task.ZoneIndex, station.EntityIndex); command.TextValue = "detach"; command.SubjectKey = parentBody.OrbitKey; command.ScalarValue = task.OrbitDistance; command.PositionX = destinationX; command.PositionZ = destinationZ; }) };
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AetheriaRuntimeDaemonCommandDocument> PlanPatrol(
|
||||
AetheriaRuntimeZoneSnapshotCommit zone,
|
||||
AetheriaRuntimeAgentTaskCommit task,
|
||||
|
|
|
|||
|
|
@ -190,6 +190,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
("phase", task.Phase),
|
||||
("targetBodyKeys", string.Join(",", task.TargetBodyKeys ?? Array.Empty<string>())),
|
||||
("circuitIndex", task.CircuitIndex.ToString(CultureInfo.InvariantCulture)),
|
||||
("orbitParentKey", task.OrbitParentKey),
|
||||
("orbitDistance", task.OrbitDistance.ToString("0.###", CultureInfo.InvariantCulture)),
|
||||
("targetPosition", string.Join(",", new[]
|
||||
{
|
||||
task.TargetPositionX.ToString("0.###", CultureInfo.InvariantCulture),
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ namespace GameCult.Aetheria.State.Verse
|
|||
new List<AetheriaRuntimeDaemonDockingIntent>();
|
||||
public List<AetheriaRuntimeDaemonWormholeIntent> Wormholes { get; } =
|
||||
new List<AetheriaRuntimeDaemonWormholeIntent>();
|
||||
public List<AetheriaRuntimeDaemonTowIntent> Towing { get; } =
|
||||
new List<AetheriaRuntimeDaemonTowIntent>();
|
||||
public bool SensorPingRequested { get; set; }
|
||||
|
||||
public bool HasAny =>
|
||||
|
|
@ -30,8 +28,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
Behaviors.Count > 0 ||
|
||||
Consumables.Count > 0 ||
|
||||
Docking.Count > 0 ||
|
||||
Wormholes.Count > 0 ||
|
||||
Towing.Count > 0;
|
||||
Wormholes.Count > 0;
|
||||
}
|
||||
|
||||
public sealed class AetheriaRuntimeDaemonMovementIntent
|
||||
|
|
@ -82,12 +79,4 @@ namespace GameCult.Aetheria.State.Verse
|
|||
public double PositionY { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AetheriaRuntimeDaemonTowIntent
|
||||
{
|
||||
public string ActorEntityKey { get; set; } = "";
|
||||
public string StationEntityKey { get; set; } = "";
|
||||
public int TargetZoneIndex { get; set; } = -1;
|
||||
public double PositionX { get; set; }
|
||||
public double PositionY { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
case AetheriaRuntimeDaemonCommandKinds.EnterWormhole:
|
||||
return ApplyEnterWormholeIntent(run, command, context.Intents);
|
||||
case AetheriaRuntimeDaemonCommandKinds.TowToStation:
|
||||
return ApplyTowToStationIntent(run, command, context.Intents);
|
||||
return ApplyTowToStation(run, command, context.DockingDistance);
|
||||
case AetheriaRuntimeDaemonCommandKinds.IssueAgentTask:
|
||||
return ApplyIssueAgentTask(run, command);
|
||||
case AetheriaRuntimeDaemonCommandKinds.CancelAgentTask:
|
||||
|
|
@ -215,6 +215,9 @@ namespace GameCult.Aetheria.State.Verse
|
|||
{
|
||||
return false;
|
||||
}
|
||||
if (string.Equals(taskType, AetheriaRuntimeAgentTaskTypes.Tow, StringComparison.Ordinal) &&
|
||||
(request.TargetEntityIndex < 0 || string.IsNullOrWhiteSpace(request.OrbitParentKey) || request.OrbitDistance <= 0))
|
||||
return false;
|
||||
|
||||
run.AgentTasks = (run.AgentTasks ?? Array.Empty<AetheriaRuntimeAgentTaskCommit>())
|
||||
.Concat(new[]
|
||||
|
|
@ -238,6 +241,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
? "pickup"
|
||||
: "",
|
||||
TargetBodyKeys = request.TargetBodyKeys ?? Array.Empty<string>(),
|
||||
OrbitParentKey = request.OrbitParentKey ?? "",
|
||||
OrbitDistance = request.OrbitDistance,
|
||||
Status = AetheriaRuntimeAgentTaskStatuses.Queued
|
||||
}
|
||||
})
|
||||
|
|
@ -1498,38 +1503,29 @@ namespace GameCult.Aetheria.State.Verse
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool ApplyTowToStationIntent(
|
||||
private static bool ApplyTowToStation(
|
||||
AetheriaRuntimeRunCheckpointCommit run,
|
||||
AetheriaRuntimeDaemonCommandDocument command,
|
||||
AetheriaRuntimeDaemonIntentState intents)
|
||||
double attachmentDistance)
|
||||
{
|
||||
var actor = ResolveActorEntityKey(run, command);
|
||||
if (string.IsNullOrWhiteSpace(actor) ||
|
||||
string.IsNullOrWhiteSpace(command.TargetEntityKey) ||
|
||||
!TryResolveEntity(
|
||||
run,
|
||||
command.TargetEntityKey,
|
||||
out var stationZoneIndex,
|
||||
out _,
|
||||
out var station))
|
||||
{
|
||||
if (!TryResolveEntity(run, actor, out var actorZone, out _, out var towing) ||
|
||||
!TryResolveEntity(run, command.TargetEntityKey, out var stationZone, out var stationIndex, out var station) || actorZone != stationZone)
|
||||
return false;
|
||||
var children = (towing.ChildEntityIndices ?? Array.Empty<int>()).ToList();
|
||||
if (string.Equals(command.TextValue, "attach", StringComparison.Ordinal))
|
||||
{
|
||||
if (children.Contains(stationIndex) || Math.Pow(station.PositionX - towing.PositionX, 2) + Math.Pow(station.PositionZ - towing.PositionZ, 2) > attachmentDistance * attachmentDistance)
|
||||
return false;
|
||||
children.Add(stationIndex); towing.ChildEntityIndices = children; station.OrbitKey = ""; station.PositionX = towing.PositionX; station.PositionZ = towing.PositionZ; return true;
|
||||
}
|
||||
|
||||
var destinationX = station.PositionX;
|
||||
var destinationY = station.PositionZ;
|
||||
if (!MoveEntityToZone(run, actor, stationZoneIndex, destinationX, destinationY, out var movedEntityKey))
|
||||
if (!string.Equals(command.TextValue, "detach", StringComparison.Ordinal) || !children.Remove(stationIndex) || string.IsNullOrWhiteSpace(command.SubjectKey) || command.ScalarValue <= 0)
|
||||
return false;
|
||||
|
||||
intents.Towing.Add(new AetheriaRuntimeDaemonTowIntent
|
||||
{
|
||||
ActorEntityKey = movedEntityKey,
|
||||
StationEntityKey = command.TargetEntityKey ?? "",
|
||||
TargetZoneIndex = stationZoneIndex,
|
||||
PositionX = destinationX,
|
||||
PositionY = destinationY
|
||||
});
|
||||
return true;
|
||||
towing.ChildEntityIndices = children; station.PositionX = command.PositionX; station.PositionZ = command.PositionZ;
|
||||
var zone = run.Zones.First(value => value.ZoneIndex == actorZone);
|
||||
var orbitKey = $"tow:{command.CommandId}";
|
||||
zone.Orbits = (zone.Orbits ?? Array.Empty<AetheriaRuntimeOrbitSnapshotCommit>()).Concat(new[] { new AetheriaRuntimeOrbitSnapshotCommit { OrbitKey = orbitKey, ParentOrbitKey = command.SubjectKey, Distance = command.ScalarValue, FixedPositionX = command.PositionX, FixedPositionY = command.PositionZ } }).ToArray();
|
||||
station.OrbitKey = orbitKey; return true;
|
||||
}
|
||||
|
||||
private static bool MoveEntityToZone(
|
||||
|
|
|
|||
|
|
@ -515,7 +515,9 @@ public sealed class AetheriaRuntimeDaemonOperationsClient
|
|||
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(key => key.Trim())
|
||||
.Where(key => !string.IsNullOrWhiteSpace(key))
|
||||
.ToArray()
|
||||
.ToArray(),
|
||||
OrbitParentKey = ReadPayloadString(request, "orbitParentKey", ""),
|
||||
OrbitDistance = ReadPayloadDouble(request, "orbitDistance", 0)
|
||||
}),
|
||||
AetheriaRuntimeDaemonCommandKinds.CancelAgentTask => client.CancelAgentTask(
|
||||
frame,
|
||||
|
|
|
|||
|
|
@ -178,6 +178,14 @@ namespace GameCult.Aetheria.State.Verse
|
|||
if (Math.Abs(entity.VelocityX) + Math.Abs(entity.VelocityY) > 0.01)
|
||||
Face(entity, entity.VelocityX, entity.VelocityY);
|
||||
}
|
||||
foreach (var parent in entities)
|
||||
foreach (var childIndex in parent.ChildEntityIndices ?? Array.Empty<int>())
|
||||
{
|
||||
var child = entities.FirstOrDefault(value => value.EntityIndex == childIndex);
|
||||
if (child == null) continue;
|
||||
child.PositionX = parent.PositionX; child.PositionZ = parent.PositionZ;
|
||||
child.VelocityX = parent.VelocityX; child.VelocityY = parent.VelocityY;
|
||||
}
|
||||
}
|
||||
|
||||
private static void StepCombat(
|
||||
|
|
|
|||
|
|
@ -922,6 +922,9 @@ namespace GameCult.Aetheria.State.Verse
|
|||
|
||||
[Key(39)]
|
||||
public string AssignedAgentTaskId { get; set; } = "";
|
||||
|
||||
[Key(40)]
|
||||
public string OrbitKey { get; set; } = "";
|
||||
}
|
||||
|
||||
[MessagePackObject]
|
||||
|
|
|
|||
Loading…
Reference in a new issue