Move loot bodies and pickup contacts into Ymir
This commit is contained in:
parent
def4a5b906
commit
3a38f050de
7 changed files with 142 additions and 43 deletions
|
|
@ -15,6 +15,7 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
MissingWorldPhysicsOwnerCannotAdvanceShips();
|
||||
TractorRampsAndPullsThroughYmirWithoutTeleportingCargo();
|
||||
PickupIsCapacityCheckedExactlyOnceAndExpires();
|
||||
PickupShieldContactCollectsOrBounces();
|
||||
ThermalCellsUseFossilConductionAndRadiation();
|
||||
MultipleActorsUseTheSameMovementLever();
|
||||
AgentClaimsAndCompletesExploreTaskThroughCommands();
|
||||
|
|
@ -802,17 +803,16 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
private static void TractorRampsAndPullsThroughYmirWithoutTeleportingCargo()
|
||||
{
|
||||
var ship = Entity(0, 0, "player");
|
||||
var wreck = Entity(1, 60, "wreck");
|
||||
wreck.CargoContents = [Cargo(("salvage", 1, 0, 0))];
|
||||
ship.TargetEntityIndex = 1;
|
||||
var run = new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship, wreck] }] };
|
||||
ship.DirectionX = 1; ship.DirectionY = 0;
|
||||
var pickup = new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 3, PositionX = 60, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = "salvage", Quantity = 1 }, LifetimeSeconds = 30 };
|
||||
var run = new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship], DroppedPickups = [pickup] }] };
|
||||
var command = AetheriaRuntimeDaemonCommandDocument.Create(AetheriaRuntimeDaemonCommandKinds.SetTractorPower, "pilot", "tractor-smoke", 0, "zone.0.entity.0");
|
||||
command.CommandId = "tractor-on"; command.ScalarValue = 1;
|
||||
AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-tractor-smoke.cc"), run,
|
||||
new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 0.25, SimulationTimeSeconds = 0.25, ObservedCommands = [command], ProjectilePhysics = AetheriaRuntimeProjectilePhysicsUnavailable.Instance, BuildPublications = false });
|
||||
RequireNear(0.5, ship.TractorPower, 0.000001, "tractor power must use the fossil two-per-second ramp");
|
||||
Require(wreck.VelocityX < 0 && wreck.PositionX < 60, "Ymir must pull the targeted body toward the tractor source");
|
||||
RequireEqual(1, CargoQuantity(wreck, "salvage"), "tractor force must not teleport target cargo");
|
||||
Require(pickup.VelocityX < 0 && pickup.PositionX < 60, "Ymir must pull a pickup inside the forward tractor volume toward the ship");
|
||||
RequireEqual(1, pickup.Item.Quantity, "tractor force must not consume the pickup item");
|
||||
RequireEqual(0, CargoQuantity(ship, "salvage"), "scooping must remain a separate capacity-checked transaction");
|
||||
}
|
||||
|
||||
|
|
@ -844,6 +844,30 @@ internal sealed class AetheriaDaemonYmirSmokeChecks
|
|||
RequireEqual(0, zone.DroppedPickups.Count, "pickup must expire after the fossil thirty-second lifetime");
|
||||
}
|
||||
|
||||
private static void PickupShieldContactCollectsOrBounces()
|
||||
{
|
||||
var hull = CatalogItem("contact-hull"); hull.HullCapacity = PerformanceStat(1);
|
||||
var salvage = CatalogItem("contact-salvage"); salvage.Volume = 1;
|
||||
var catalog = new AetheriaRuntimeCatalogSnapshot([hull, salvage], [], []);
|
||||
AetheriaRuntimeRunCheckpointCommit Scenario(bool full)
|
||||
{
|
||||
var ship = Entity(0, 0, "player"); ship.HullItemKey = hull.ItemKey;
|
||||
ship.CargoContents = [full ? Cargo((salvage.ItemKey, 1, 0, 0)) : Cargo()];
|
||||
return new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship], DroppedPickups = [new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 10, PositionX = 20, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, LifetimeSeconds = 30 }] }] };
|
||||
}
|
||||
var open = Scenario(false);
|
||||
AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-pickup-contact-open.cc"), open,
|
||||
new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 0.1, Catalog = catalog, ProjectilePhysics = AetheriaRuntimeProjectilePhysicsUnavailable.Instance, BuildPublications = false });
|
||||
RequireEqual(0, open.Zones[0].DroppedPickups.Count, "shield contact with capacity must collect pickup automatically");
|
||||
RequireEqual(1, CargoQuantity(open.Zones[0].Entities[0], salvage.ItemKey), "contact collection must commit cargo once");
|
||||
|
||||
var full = Scenario(true);
|
||||
AetheriaRuntimeDaemonTickRunner.Tick(Path.Combine(Path.GetTempPath(), "aetheria-pickup-contact-full.cc"), full,
|
||||
new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new AetheriaYmirWorldPhysics(), FrameId = 1, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 0.1, Catalog = catalog, ProjectilePhysics = AetheriaRuntimeProjectilePhysicsUnavailable.Instance, BuildPublications = false });
|
||||
RequireEqual(1, full.Zones[0].DroppedPickups.Count, "full hold must leave contacted pickup alive");
|
||||
Require(full.Zones[0].DroppedPickups[0].VelocityX > 20, "failed pickup must receive the fossil outward kick");
|
||||
}
|
||||
|
||||
private static (AetheriaRuntimeRunCheckpointCommit Run, AetheriaRuntimeZoneSnapshotCommit Zone, AetheriaRuntimeEntitySnapshotCommit Target) Scenario()
|
||||
{
|
||||
var source = Entity(1, -100, "player");
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ public sealed class AetheriaYmirWorldPhysics : IAetheriaRuntimeWorldPhysics
|
|||
public const double TractorTraction = 25;
|
||||
public const double TractorDistance = 75;
|
||||
private const string Prefix = "aetheria.daemon.entity.";
|
||||
private const string PickupPrefix = "aetheria.daemon.pickup.";
|
||||
private readonly YmirSimulator _simulator;
|
||||
public AetheriaYmirWorldPhysics(YmirSimulator? simulator = null) => _simulator = simulator ?? new YmirSimulator();
|
||||
public string AuthorityId => "ymir.core";
|
||||
|
|
@ -19,31 +20,44 @@ public sealed class AetheriaYmirWorldPhysics : IAetheriaRuntimeWorldPhysics
|
|||
if (deltaSeconds <= 0) throw new ArgumentOutOfRangeException(nameof(deltaSeconds));
|
||||
var attached = (entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>()).SelectMany(entity => entity.ChildEntityIndices ?? Array.Empty<int>()).ToHashSet();
|
||||
var active = (entities ?? Array.Empty<AetheriaRuntimeEntitySnapshotCommit>()).Where(entity => entity.IsActive && !attached.Contains(entity.EntityIndex)).ToArray();
|
||||
var velocity = active.ToDictionary(entity => entity.EntityIndex, entity => new Vec2((float)entity.VelocityX, (float)entity.VelocityY));
|
||||
foreach (var actor in active.Where(entity => entity.TractorPower > 0 && entity.TargetEntityIndex >= 0))
|
||||
var entityVelocity = active.ToDictionary(entity => entity.EntityIndex, entity => new Vec2((float)entity.VelocityX, (float)entity.VelocityY));
|
||||
var pickups = (zone.DroppedPickups ?? Array.Empty<AetheriaRuntimeDroppedPickupCommit>()).Where(pickup => pickup != null && pickup.AgeSeconds < pickup.LifetimeSeconds).ToArray();
|
||||
var pickupVelocity = pickups.ToDictionary(pickup => pickup.PickupIndex, pickup => new Vec2((float)pickup.VelocityX, (float)pickup.VelocityZ));
|
||||
foreach (var actor in active.Where(entity => entity.TractorPower > 0))
|
||||
{
|
||||
var target = active.FirstOrDefault(entity => entity.EntityIndex == actor.TargetEntityIndex);
|
||||
if (target == null) continue;
|
||||
var dx = actor.PositionX - target.PositionX; var dz = actor.PositionZ - target.PositionZ;
|
||||
var distance = Math.Sqrt(dx * dx + dz * dz);
|
||||
if (distance <= 0.001 || distance > TractorDistance + TractorRadius) continue;
|
||||
var impulse = TractorTraction * actor.TractorPower * deltaSeconds;
|
||||
var current = velocity[target.EntityIndex];
|
||||
velocity[target.EntityIndex] = new Vec2(current.X + (float)(dx / distance * impulse), current.Y + (float)(dz / distance * impulse));
|
||||
var forwardLength = Math.Sqrt(actor.DirectionX * actor.DirectionX + actor.DirectionY * actor.DirectionY);
|
||||
var fx = forwardLength < 0.001 ? 0 : actor.DirectionX / forwardLength;
|
||||
var fz = forwardLength < 0.001 ? 1 : actor.DirectionY / forwardLength;
|
||||
foreach (var pickup in pickups)
|
||||
{
|
||||
var dx = pickup.PositionX - actor.PositionX; var dz = pickup.PositionZ - actor.PositionZ;
|
||||
var along = dx * fx + dz * fz;
|
||||
var lateral = Math.Abs(dx * fz - dz * fx);
|
||||
if (along < 0 || along > TractorDistance || lateral > TractorRadius) continue;
|
||||
var distance = Math.Sqrt(dx * dx + dz * dz);
|
||||
if (distance <= 0.001) continue;
|
||||
var impulse = TractorTraction * actor.TractorPower * deltaSeconds;
|
||||
var current = pickupVelocity[pickup.PickupIndex];
|
||||
pickupVelocity[pickup.PickupIndex] = new Vec2(current.X - (float)(dx / distance * impulse), current.Y - (float)(dz / distance * impulse));
|
||||
}
|
||||
}
|
||||
var bodies = active
|
||||
.Select(entity => new PhysicsBody(Prefix + entity.EntityIndex, new Vec2((float)entity.PositionX, (float)entity.PositionZ), velocity[entity.EntityIndex],
|
||||
.Select(entity => new PhysicsBody(Prefix + entity.EntityIndex, new Vec2((float)entity.PositionX, (float)entity.PositionZ), entityVelocity[entity.EntityIndex],
|
||||
string.Equals(entity.Kind, "station", StringComparison.OrdinalIgnoreCase) ? 48 : 20, 1,
|
||||
IsStatic: string.Equals(entity.Kind, "station", StringComparison.OrdinalIgnoreCase), Restitution: 0.2f,
|
||||
Direction: new Vec2((float)entity.DirectionX, (float)entity.DirectionY))).ToArray();
|
||||
Direction: new Vec2((float)entity.DirectionX, (float)entity.DirectionY)))
|
||||
.Concat(pickups.Select(pickup => new PhysicsBody(PickupPrefix + pickup.PickupIndex, new Vec2((float)pickup.PositionX, (float)pickup.PositionZ), pickupVelocity[pickup.PickupIndex], 5, 1, Restitution: 0.2f)))
|
||||
.ToArray();
|
||||
var fields = AetheriaRuntimeDaemonRenderQueries.QueryBodyPoses(zone)
|
||||
.Select(pose => (Pose: pose, Body: (zone.Bodies ?? Array.Empty<AetheriaRuntimeBodySnapshotCommit>()).FirstOrDefault(body => body != null && body.BodyKey == pose.BodyKey)))
|
||||
.Where(pair => pair.Body != null && pair.Body.GravityInfluenceRadius > 0 && pair.Body.GravityWellDepth != 0)
|
||||
.Select(pair => new RadialField("aetheria.daemon.gravity." + pair.Pose.BodyKey, new Vec2((float)pair.Pose.CenterX, (float)pair.Pose.CenterZ), (float)pair.Body!.GravityWellDepth, (float)pair.Body.GravityInfluenceRadius)).ToArray();
|
||||
var result = _simulator.Step(new SimulationStepRequest((float)deltaSeconds, new YmirWorld((float)zone.SimulationTimeSeconds, bodies, fields)));
|
||||
int Index(string id) => int.TryParse(id.AsSpan(Prefix.Length), out var value) ? value : -1;
|
||||
int Index(string id) => id.StartsWith(Prefix, StringComparison.Ordinal) && int.TryParse(id.AsSpan(Prefix.Length), out var value) ? value : -1;
|
||||
int PickupIndex(string id) => id.StartsWith(PickupPrefix, StringComparison.Ordinal) && int.TryParse(id.AsSpan(PickupPrefix.Length), out var value) ? value : -1;
|
||||
return new AetheriaRuntimeWorldStep(
|
||||
result.World.Bodies.Select(body => new AetheriaRuntimeWorldBodyStep { EntityIndex = Index(body.Id), PositionX = body.Position.X, PositionZ = body.Position.Y, VelocityX = body.Velocity.X, VelocityY = body.Velocity.Y, DirectionX = body.Direction?.X ?? 0, DirectionY = body.Direction?.Y ?? 1 }).ToArray(),
|
||||
result.Contacts.Select(contact => new AetheriaRuntimeWorldContact { EntityAIndex = Index(contact.BodyA), EntityBIndex = Index(contact.BodyB), PointX = contact.Point.X, PointZ = contact.Point.Y, NormalX = contact.Normal.X, NormalZ = contact.Normal.Y, RelativeSpeed = contact.RelativeSpeed }).ToArray());
|
||||
result.World.Bodies.Where(body => body.Id.StartsWith(Prefix, StringComparison.Ordinal)).Select(body => new AetheriaRuntimeWorldBodyStep { EntityIndex = Index(body.Id), PositionX = body.Position.X, PositionZ = body.Position.Y, VelocityX = body.Velocity.X, VelocityY = body.Velocity.Y, DirectionX = body.Direction?.X ?? 0, DirectionY = body.Direction?.Y ?? 1 }).ToArray(),
|
||||
result.World.Bodies.Where(body => body.Id.StartsWith(PickupPrefix, StringComparison.Ordinal)).Select(body => new AetheriaRuntimeWorldPickupStep { PickupIndex = PickupIndex(body.Id), PositionX = body.Position.X, PositionZ = body.Position.Y, VelocityX = body.Velocity.X, VelocityZ = body.Velocity.Y }).ToArray(),
|
||||
result.Contacts.Select(contact => new AetheriaRuntimeWorldContact { EntityAIndex = Index(contact.BodyA), EntityBIndex = Index(contact.BodyB), PickupIndex = Math.Max(PickupIndex(contact.BodyA), PickupIndex(contact.BodyB)), PointX = contact.Point.X, PointZ = contact.Point.Y, NormalX = contact.Normal.X, NormalZ = contact.Normal.Y, RelativeSpeed = contact.RelativeSpeed }).ToArray());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -803,23 +803,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
return false;
|
||||
|
||||
var pickup = pickups[pickupIndex];
|
||||
var quantity = Math.Max(1, pickup.Item?.Quantity ?? command.LootPickup.Quantity);
|
||||
if (pickup.AgeSeconds >= pickup.LifetimeSeconds ||
|
||||
Math.Pow(pickup.PositionX - entity.PositionX, 2) + Math.Pow(pickup.PositionZ - entity.PositionZ, 2) > 25 * 25 ||
|
||||
quantity > AetheriaRuntimeCargoCapacityQueries.UnitsThatFit(entity, catalog, pickup.Item?.ItemKey ?? command.LootPickup.ItemKey))
|
||||
return false;
|
||||
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;
|
||||
return AetheriaRuntimePickupTransactions.TryCollect(zone, entity, pickup.PickupIndex, catalog);
|
||||
}
|
||||
|
||||
private static bool ApplyToggleHullConductivity(
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ namespace GameCult.Aetheria.State.Verse
|
|||
StepTractorPower(entities, deltaSeconds);
|
||||
StepRaiderAi(entities);
|
||||
StepTargetPursuit(entities, settings);
|
||||
StepWorldPhysics(zone, entities, deltaSeconds, worldPhysics);
|
||||
var worldStep = StepWorldPhysics(zone, entities, deltaSeconds, worldPhysics);
|
||||
ResolvePickupContacts(zone, entities, worldStep, catalog);
|
||||
StepCombat(run, zone, entities, intents, deltaSeconds, settings, projectilePhysics);
|
||||
AetheriaRuntimeMiningSimulation.Step(run, zone, entities, intents, catalog, frameId, simulationTimeSeconds, deltaSeconds);
|
||||
AetheriaRuntimeSurveySimulation.Step(run, zone, entities, intents, catalog, frameId, simulationTimeSeconds, deltaSeconds);
|
||||
|
|
@ -158,7 +159,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
}
|
||||
}
|
||||
|
||||
private static void StepWorldPhysics(
|
||||
private static AetheriaRuntimeWorldStep StepWorldPhysics(
|
||||
AetheriaRuntimeZoneSnapshotCommit zone,
|
||||
IReadOnlyList<AetheriaRuntimeEntitySnapshotCommit> entities,
|
||||
double deltaSeconds,
|
||||
|
|
@ -173,6 +174,14 @@ namespace GameCult.Aetheria.State.Verse
|
|||
entity.VelocityX = body.VelocityX; entity.VelocityY = body.VelocityY;
|
||||
entity.DirectionX = body.DirectionX; entity.DirectionY = body.DirectionY;
|
||||
}
|
||||
var pickups = (zone.DroppedPickups ?? Array.Empty<AetheriaRuntimeDroppedPickupCommit>())
|
||||
.Where(pickup => pickup != null).ToDictionary(pickup => pickup.PickupIndex);
|
||||
foreach (var body in result.Pickups)
|
||||
{
|
||||
if (!pickups.TryGetValue(body.PickupIndex, out var pickup)) continue;
|
||||
pickup.PositionX = body.PositionX; pickup.PositionZ = body.PositionZ;
|
||||
pickup.VelocityX = body.VelocityX; pickup.VelocityZ = body.VelocityZ;
|
||||
}
|
||||
foreach (var parent in entities)
|
||||
foreach (var childIndex in parent.ChildEntityIndices ?? Array.Empty<int>())
|
||||
{
|
||||
|
|
@ -181,6 +190,30 @@ namespace GameCult.Aetheria.State.Verse
|
|||
child.PositionX = parent.PositionX; child.PositionZ = parent.PositionZ;
|
||||
child.VelocityX = parent.VelocityX; child.VelocityY = parent.VelocityY;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ResolvePickupContacts(
|
||||
AetheriaRuntimeZoneSnapshotCommit zone,
|
||||
IReadOnlyList<AetheriaRuntimeEntitySnapshotCommit> entities,
|
||||
AetheriaRuntimeWorldStep worldStep,
|
||||
AetheriaRuntimeCatalogSnapshot? catalog)
|
||||
{
|
||||
foreach (var contact in worldStep.Contacts.Where(contact => contact.PickupIndex >= 0))
|
||||
{
|
||||
var entityIndex = Math.Max(contact.EntityAIndex, contact.EntityBIndex);
|
||||
var entity = entities.FirstOrDefault(value => value.EntityIndex == entityIndex && value.IsActive);
|
||||
var pickup = (zone.DroppedPickups ?? Array.Empty<AetheriaRuntimeDroppedPickupCommit>())
|
||||
.FirstOrDefault(value => value != null && value.PickupIndex == contact.PickupIndex);
|
||||
if (entity == null || pickup == null) continue;
|
||||
if (AetheriaRuntimePickupTransactions.TryCollect(zone, entity, pickup.PickupIndex, catalog, requireRange: false))
|
||||
continue;
|
||||
var dx = pickup.PositionX - entity.PositionX; var dz = pickup.PositionZ - entity.PositionZ;
|
||||
var length = Math.Sqrt(dx * dx + dz * dz);
|
||||
if (length < 0.001) { dx = 1; dz = 0; length = 1; }
|
||||
pickup.VelocityX += dx / length * 25;
|
||||
pickup.VelocityZ += dz / length * 25;
|
||||
}
|
||||
}
|
||||
|
||||
private static void StepCombat(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace GameCult.Aetheria.State.Verse
|
||||
{
|
||||
public static class AetheriaRuntimePickupTransactions
|
||||
{
|
||||
public static bool TryCollect(AetheriaRuntimeZoneSnapshotCommit zone, AetheriaRuntimeEntitySnapshotCommit entity,
|
||||
int pickupIndex, AetheriaRuntimeCatalogSnapshot? catalog, bool requireRange = true)
|
||||
{
|
||||
var pickups = (zone.DroppedPickups ?? Array.Empty<AetheriaRuntimeDroppedPickupCommit>()).ToList();
|
||||
var index = pickups.FindIndex(pickup => pickup != null && pickup.PickupIndex == pickupIndex);
|
||||
if (index < 0) return false;
|
||||
var pickup = pickups[index];
|
||||
var quantity = Math.Max(1, pickup.Item?.Quantity ?? 1);
|
||||
if (pickup.AgeSeconds >= pickup.LifetimeSeconds ||
|
||||
(requireRange && Math.Pow(pickup.PositionX - entity.PositionX, 2) + Math.Pow(pickup.PositionZ - entity.PositionZ, 2) > 25 * 25) ||
|
||||
quantity > AetheriaRuntimeCargoCapacityQueries.UnitsThatFit(entity, catalog, pickup.Item?.ItemKey ?? ""))
|
||||
return false;
|
||||
var bays = (entity.CargoContents ?? Array.Empty<AetheriaRuntimeCargoBayLoadoutCommit>()).ToList();
|
||||
if (bays.Count == 0) bays.Add(new AetheriaRuntimeCargoBayLoadoutCommit());
|
||||
var slots = (bays[0].Items ?? Array.Empty<AetheriaRuntimeLoadoutItemSlotCommit>()).ToList();
|
||||
var existing = slots.FirstOrDefault(slot => string.Equals(slot?.Item?.ItemKey, pickup.Item.ItemKey, StringComparison.Ordinal));
|
||||
if (existing != null) existing.Item.Quantity += quantity;
|
||||
else slots.Add(new AetheriaRuntimeLoadoutItemSlotCommit { Item = pickup.Item });
|
||||
bays[0].Items = slots; entity.CargoContents = bays;
|
||||
pickups.RemoveAt(index); zone.DroppedPickups = pickups;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,12 +13,22 @@ namespace GameCult.Aetheria.State.Verse
|
|||
|
||||
public sealed class AetheriaRuntimeWorldStep
|
||||
{
|
||||
public AetheriaRuntimeWorldStep(IReadOnlyList<AetheriaRuntimeWorldBodyStep> bodies, IReadOnlyList<AetheriaRuntimeWorldContact> contacts)
|
||||
{ Bodies = bodies ?? Array.Empty<AetheriaRuntimeWorldBodyStep>(); Contacts = contacts ?? Array.Empty<AetheriaRuntimeWorldContact>(); }
|
||||
public AetheriaRuntimeWorldStep(IReadOnlyList<AetheriaRuntimeWorldBodyStep> bodies, IReadOnlyList<AetheriaRuntimeWorldPickupStep> pickups, IReadOnlyList<AetheriaRuntimeWorldContact> contacts)
|
||||
{ Bodies = bodies ?? Array.Empty<AetheriaRuntimeWorldBodyStep>(); Pickups = pickups ?? Array.Empty<AetheriaRuntimeWorldPickupStep>(); Contacts = contacts ?? Array.Empty<AetheriaRuntimeWorldContact>(); }
|
||||
public IReadOnlyList<AetheriaRuntimeWorldBodyStep> Bodies { get; }
|
||||
public IReadOnlyList<AetheriaRuntimeWorldPickupStep> Pickups { get; }
|
||||
public IReadOnlyList<AetheriaRuntimeWorldContact> Contacts { get; }
|
||||
}
|
||||
|
||||
public sealed class AetheriaRuntimeWorldPickupStep
|
||||
{
|
||||
public int PickupIndex { get; set; } = -1;
|
||||
public double PositionX { get; set; }
|
||||
public double PositionZ { get; set; }
|
||||
public double VelocityX { get; set; }
|
||||
public double VelocityZ { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AetheriaRuntimeWorldBodyStep
|
||||
{
|
||||
public int EntityIndex { get; set; } = -1;
|
||||
|
|
@ -34,6 +44,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
{
|
||||
public int EntityAIndex { get; set; } = -1;
|
||||
public int EntityBIndex { get; set; } = -1;
|
||||
public int PickupIndex { get; set; } = -1;
|
||||
public double PointX { get; set; }
|
||||
public double PointZ { get; set; }
|
||||
public double NormalX { get; set; }
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ validation proves contract shape, not wiring, exclusivity, lowering, or parity.
|
|||
| Thermal/energy | cell conduction and capacitor/reactor/radiator network | daemon | ship/equipment status and warnings | schematic/post effects | cell conduction/radiation wired; energy network and medical thresholds remain reference | capacitor/reactor/radiator ordering, shared energy and medical timelines |
|
||||
| Weapons | ServerShared behaviors + Unity effects | daemon + Ymir where spatial | action capability, weapon state, attack events | effects/audio/HUD | one synthetic weapon wired; original engine reference-only | each weapon family |
|
||||
| Destruction/drops | entity death and pickup paths | daemon | death/drop events and world entities | effects and pickups | fragments wired; chronology and original drop transaction absent | exactly-once drops and cleanup |
|
||||
| Tractor/scoop | analog tractor and physical timed pickups | daemon + Ymir | tractor power, target, pickup entity and receipt | beam/interaction feedback | fossil 2/s power ramp and authored radius/traction/distance move targets through Ymir; pickup uses stable identity, range/capacity validation, exactly-once removal, and 30s daemon lifetime; instant cargo teleport deleted | put dropped pickup trajectories and failed-capacity bounce into the Ymir world; prove runtime feedback |
|
||||
| Tractor/scoop | analog tractor and physical timed pickups | daemon + Ymir | tractor power, pickup body and receipt | beam/interaction feedback | fossil 2/s power ramp and authored forward radius/traction/distance feed pickup bodies into the shared Ymir world; shield contact collects exactly once when capacity permits or applies the fossil 25-unit rejection kick; 30s daemon lifetime; instant cargo teleport deleted | prove generic runtime beam, contact, success and rejection feedback |
|
||||
| Dock/undock | wormhole-first interact; first eligible bay; component prerequisites | daemon + Ymir placement | dock commands/context | camera/UI transition | daemon path wired/projected; Ymir placement and parity unproven | priority, eligibility, placement and reconnect |
|
||||
| Inventory/equipment | inventory panels + ServerShared | daemon | inventory/equipment surfaces | generic UI | owner-exists; wired/projected in fragments; not proven | all transfer directions and rejection |
|
||||
| Trade | trade UI/economy | daemon | stock, quote, purchase commands | generic UI | purchase owner wired/projected; not proven | atomic purchase and capacity |
|
||||
|
|
|
|||
Loading…
Reference in a new issue