From 8e8c15a135b8a9476c2bc4a7fb8f5e29991c1dd6 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 17 Jul 2026 13:56:05 +0100 Subject: [PATCH] Own combat and loot through daemon facts --- Aetheria.State.Daemon.Smoke/Program.cs | 11 ++++++++++- .../AetheriaDaemonZoneGenerator.cs | 16 +++++++++------- .../Scripts/Tests/DaemonRuntimeDocumentTests.cs | 16 +++++++++++++++- .../Runtime/AetheriaRuntimeDaemonSimulation.cs | 14 ++++++++++---- 4 files changed, 44 insertions(+), 13 deletions(-) diff --git a/Aetheria.State.Daemon.Smoke/Program.cs b/Aetheria.State.Daemon.Smoke/Program.cs index 59a65eb26..b52f60dda 100644 --- a/Aetheria.State.Daemon.Smoke/Program.cs +++ b/Aetheria.State.Daemon.Smoke/Program.cs @@ -3570,6 +3570,10 @@ internal sealed class AetheriaDaemonYmirSmokeChecks source.DirectionX = 1; source.DirectionY = 0; source.TargetEntityIndex = 2; + source.Contacts = [new AetheriaRuntimeEntityContactCommit + { + TargetEntityIndex = 2, InfoGathered = 1, Visible = true, Hostile = true + }]; source.WeaponGroups = [new[] { 0 }]; source.Equipment = [ @@ -8200,7 +8204,10 @@ internal sealed class AetheriaDaemonYmirSmokeChecks var catalog = new AetheriaRuntimeCatalogSnapshot([hull, salvage, cargoBay], [], []); var ship = Entity(0, 0, "player"); ship.HullItemKey = hull.ItemKey; ship.CargoContents = [Cargo()]; ship.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } }]; - var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship], DroppedPickups = [new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 7, PositionX = AetheriaRuntimeTractorMechanics.CollectionDistance + 0.001, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, LifetimeSeconds = 30 }] }; + var ally = Entity(1, AetheriaRuntimeTractorMechanics.CollectionDistance, "player"); ally.HullItemKey = hull.ItemKey; + ally.CargoContents = [Cargo()]; + ally.CargoBays = [new AetheriaRuntimeLoadoutItemSlotCommit { Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = cargoBay.ItemKey, Quantity = 1 } }]; + var zone = new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, Entities = [ship, ally], DroppedPickups = [new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 7, PositionX = AetheriaRuntimeTractorMechanics.CollectionDistance + 0.001, Item = new AetheriaRuntimeLoadoutItemCommit { ItemKey = salvage.ItemKey, Quantity = 1 }, LifetimeSeconds = 30 }] }; var run = new AetheriaRuntimeRunCheckpointCommit { CurrentZoneIndex = 0, CurrentEntityKey = "zone.0.entity.0", Zones = [zone] }; var forbiddenCommand = AetheriaRuntimeDaemonCommandDocument.Create(AetheriaRuntimeDaemonCommandKinds.PickUpLoot, "pilot", "pickup-smoke", 0, "zone.0.entity.0"); forbiddenCommand.CommandId = "pickup-command-forbidden"; @@ -8219,6 +8226,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks new AetheriaRuntimeDaemonTickOptions { WorldPhysics = new ScriptedWorldPhysics(), FrameId = 2, FixedDeltaSeconds = 0.1, SimulationTimeSeconds = 0.2, Catalog = catalog, BuildPublications = false }); RequireEqual(0, zone.DroppedPickups.Count, "pickup at the 2D collection boundary must be consumed"); RequireEqual(1, CargoQuantity(ship, salvage.ItemKey), "one pickup identity must commit cargo exactly once"); + RequireEqual(0, CargoQuantity(ally, salvage.ItemKey), + "a closer allied simulation ship must not steal pickup ownership from the current controlled ship"); RequireEqual(1, run.GameEvents.Count(value => value.Kind == "pickup.collected" && value.PickupIndex == 7), "one consumed pickup identity must emit one collection event"); var collectionEvent = run.GameEvents.Single(value => value.Kind == "pickup.collected" && value.PickupIndex == 7); diff --git a/Aetheria.State.Daemon/AetheriaDaemonZoneGenerator.cs b/Aetheria.State.Daemon/AetheriaDaemonZoneGenerator.cs index 86804fa1e..01a287da1 100644 --- a/Aetheria.State.Daemon/AetheriaDaemonZoneGenerator.cs +++ b/Aetheria.State.Daemon/AetheriaDaemonZoneGenerator.cs @@ -41,7 +41,6 @@ internal static class AetheriaDaemonZoneGenerator var corporationKeys = (catalog.Corporations ?? Array.Empty()) .Select(value => value.CorporationKey) .Where(value => !string.IsNullOrWhiteSpace(value)) - .OrderBy(value => value, StringComparer.Ordinal) .ToArray(); if (corporationKeys.Length == 0) { @@ -250,7 +249,7 @@ internal static class AetheriaDaemonZoneGenerator var entities = new[] { - Entity(loadouts, availabilityFactions, "Anchor Station", "station", -50, -30, 0, 0, "player", 760, keys[6], [keys[1], keys[2], keys[3], keys[4], keys[6], keys[7], keys[11]]), + Entity(loadouts, availabilityFactions, "Anchor Station", "station", -50, -30, 0, 0, "player", 760, keys[6], [keys[1], keys[2], keys[3], keys[4], keys[6], keys[7], keys[11]], 2, 900), Entity(loadouts, availabilityFactions, "Vanguard One", "ship", -40, -30, 0, 0, "player", 540, keys[6], [keys[0], keys[2], keys[4], keys[6], keys[7]]), Entity(loadouts, availabilityFactions, "Wing Two", "ship", 145, 125, -5, 7, "player", 450, keys[6], [keys[0], keys[1], keys[6], keys[8]]), Entity(loadouts, availabilityFactions, "Torch Three", "ship", -235, 210, 8, -4, "player", 500, keys[7], [keys[0], keys[1], keys[7]]), @@ -386,17 +385,21 @@ internal static class AetheriaDaemonZoneGenerator string faction, double visibility, string target, - string[] contactKeys) + string[] contactKeys, + int securityLevel = 0, + double securityRadius = 0) { var availabilityFaction = availabilityFactions[faction]; var loadout = loadouts[availabilityFaction].Build(kind, availabilityFaction); var entity = AetheriaDaemonGeneratedEntityFactory.Create( name, kind, - faction, + availabilityFaction, loadout, x, - z); + z, + securityLevel: securityLevel, + securityRadius: securityRadius); entity.Direction = Vec2(vx == 0 && vy == 0 ? 0 : vx, vx == 0 && vy == 0 ? 1 : vy); entity.LookDirection = Vec2(vx == 0 && vy == 0 ? 0 : vx, vx == 0 && vy == 0 ? 1 : vy); entity.Velocity = Vec2(vx, vy); @@ -405,8 +408,7 @@ internal static class AetheriaDaemonZoneGenerator entity.Contacts = contactKeys.Select(key => new AetheriaEntityContactSnapshot { TargetEntityKey = key, - InfoGathered = 1, - Hostile = string.Equals(faction, "player", StringComparison.OrdinalIgnoreCase) && key.Contains(".entity.6.", StringComparison.Ordinal) + InfoGathered = 1 }).ToArray(); if (string.Equals(faction, "raider", StringComparison.OrdinalIgnoreCase)) entity.StatGrids[0].Values[0] = 85; diff --git a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs index 6dbfe1389..784abec2a 100644 --- a/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs +++ b/Assets/Scripts/Tests/DaemonRuntimeDocumentTests.cs @@ -2494,6 +2494,7 @@ public class DaemonRuntimeDocumentTests var pilot = new AetheriaRuntimeEntitySnapshotCommit { EntityIndex = 1, + EntityId = "global:aetheria.run_state.local-terminus.zone.0.entity.1.v1", Kind = "ship", FactionKey = "player", IsActive = true, @@ -2508,6 +2509,17 @@ public class DaemonRuntimeDocumentTests }, CargoContents = new[] { new AetheriaRuntimeCargoBayLoadoutCommit() } }; + var alliedShip = new AetheriaRuntimeEntitySnapshotCommit + { + EntityIndex = 2, + EntityId = "global:aetheria.run_state.local-terminus.zone.0.entity.2.v1", + Kind = "ship", + FactionKey = "player", + IsActive = true, + PositionX = 9, + CargoBays = pilot.CargoBays, + CargoContents = new[] { new AetheriaRuntimeCargoBayLoadoutCommit() } + }; var pickup = new AetheriaRuntimeDroppedPickupCommit { PickupIndex = 6, @@ -2525,7 +2537,7 @@ public class DaemonRuntimeDocumentTests new AetheriaRuntimeZoneSnapshotCommit { ZoneIndex = 0, - Entities = new[] { pilot }, + Entities = new[] { pilot, alliedShip }, DroppedPickups = new[] { pickup } } } @@ -2540,6 +2552,8 @@ public class DaemonRuntimeDocumentTests catalog); Assert.IsTrue(pilot.CargoContents.SelectMany(bay => bay.Items).Any(slot => slot.Item.ItemKey == "raider-salvage")); + Assert.IsFalse(alliedShip.CargoContents.SelectMany(bay => bay.Items).Any(slot => slot.Item.ItemKey == "raider-salvage"), + "an allied simulation ship must not steal cargo intended for the current controlled ship"); Assert.AreEqual(0, run.Zones[0].DroppedPickups.Count, "daemon XZ proximity must own collection without tractor power or a Ymir contact fact"); } diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulation.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulation.cs index a43ca43b9..47f1eb002 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulation.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSimulation.cs @@ -438,13 +438,18 @@ namespace GameCult.Aetheria.State.Verse AetheriaRuntimeCatalogSnapshot? catalog, long frameId) { + if (!AetheriaRuntimeRunCheckpointCommit.TryParseEntityKey( + run.CurrentEntityKey, out var currentZoneIndex, out var currentEntityIndex) || + currentZoneIndex != zone.ZoneIndex) + return; var attached = (entities ?? Array.Empty()) .Where(value => value != null) .SelectMany(value => value.ChildEntityIndices ?? Array.Empty()) .ToHashSet(); var ships = (entities ?? Array.Empty()) .Where(value => value != null && value.IsActive && value.WormholeTransition == null && - !attached.Contains(value.EntityIndex) && IsPickupCollector(value, catalog)) + !attached.Contains(value.EntityIndex) && IsPickupCollector(value, catalog) && + value.EntityIndex == currentEntityIndex) .Select(value => value!) .OrderBy(value => value.EntityIndex) .ToArray(); @@ -2694,9 +2699,10 @@ namespace GameCult.Aetheria.State.Verse AetheriaRuntimeEntitySnapshotCommit left, AetheriaRuntimeEntitySnapshotCommit right) { - return IsPlayerOwned(left) != IsPlayerOwned(right) && - (string.Equals(left.FactionKey, "raider", StringComparison.OrdinalIgnoreCase) || - string.Equals(right.FactionKey, "raider", StringComparison.OrdinalIgnoreCase)); + return (left.Contacts ?? Array.Empty()) + .Any(contact => contact != null && + contact.TargetEntityIndex == right.EntityIndex && + contact.Hostile); } private static double DefaultHull(