using NUnit.Framework; using ProjectM.Server; using ProjectM.Simulation; using Unity.Core; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; namespace ProjectM.Tests { /// /// Plain-Entities EditMode tests for the once-per-epoch zone-clear reward folded into /// (DR-040 BLOCKER 4). A returning player banks flat Ore to the shared ledger /// IFF this epoch's expedition wave was actually cleared and not yet rewarded — and never twice for the same /// epoch (the co-op same-tick / gate-re-entry de-dup). /// public class ExpeditionGateRewardTests { static (World world, SimulationSystemGroup group, Entity cycle) MakeWorld(string name, int epoch, byte clearedThisEpoch, int lastRewardedEpoch) { var world = new World(name); var group = world.GetOrCreateSystemManaged(); group.AddSystemToUpdateList(world.GetOrCreateSystem()); group.SortSystems(); world.SetTime(new TimeData(elapsedTime: 0f, deltaTime: 1f / 60f)); var em = world.EntityManager; // CycleDirector-like entity: cycle state/runtime + the shared resource ledger + threat state. var cyc = em.CreateEntity(typeof(CycleState), typeof(CycleRuntime), typeof(ResourceLedger), typeof(ThreatState)); em.SetComponentData(cyc, new CycleState { Phase = CyclePhase.Calm }); em.SetComponentData(cyc, new CycleRuntime { ExpeditionEpoch = epoch, ClearedThisEpoch = clearedThisEpoch, LastRewardedEpoch = lastRewardedEpoch, }); em.AddBuffer(cyc); // Zone-enemy director singleton (only RewardOre matters to the reward fold). var dir = em.CreateEntity(typeof(ZoneEnemyDirector)); em.SetComponentData(dir, new ZoneEnemyDirector { RewardOre = 25 }); // A gate Expedition->Base sitting at the expedition origin. var gate = em.CreateEntity(typeof(ExpeditionGate), typeof(LocalTransform)); em.SetComponentData(gate, new ExpeditionGate { FromRegion = RegionId.Expedition, ToRegion = RegionId.Base, Radius = 3f, ArrivalPos = new float3(0, 1, 0), }); em.SetComponentData(gate, LocalTransform.FromPosition(new float3(1000, 1, 0))); return (world, group, cyc); } static Entity MakeExpeditionPlayerAtGate(EntityManager em) { var e = em.CreateEntity(); em.AddComponentData(e, new RegionTag { Region = RegionId.Expedition }); em.AddComponentData(e, LocalTransform.FromPosition(new float3(1000, 1, 0))); em.AddComponent(e); return e; } static int OreInLedger(EntityManager em, Entity cyc) { var buf = em.GetBuffer(cyc); for (int i = 0; i < buf.Length; i++) if (buf[i].ItemId == (ushort)ResourceId.Ore) return buf[i].Count; return 0; } [Test] public void Cleared_Return_Banks_Ore_Once() { var (world, group, cyc) = MakeWorld("GateRewardOnce", epoch: 1, clearedThisEpoch: 1, lastRewardedEpoch: 0); using (world) { var em = world.EntityManager; var player = MakeExpeditionPlayerAtGate(em); group.Update(); // player walks the gate back to base -> reward Assert.AreEqual(25, OreInLedger(em, cyc), "a cleared return banks RewardOre to the shared ledger"); Assert.AreEqual(1, em.GetComponentData(cyc).LastRewardedEpoch, "the epoch is marked rewarded"); // Force a second same-epoch return (the player is back in the expedition at the gate). em.SetComponentData(player, new RegionTag { Region = RegionId.Expedition }); em.SetComponentData(player, LocalTransform.FromPosition(new float3(1000, 1, 0))); group.Update(); // returns again, but the epoch was already rewarded Assert.AreEqual(25, OreInLedger(em, cyc), "the same epoch never pays twice (co-op / re-entry de-dup)"); } } [Test] public void Uncleared_Return_Banks_Nothing() { var (world, group, cyc) = MakeWorld("GateRewardUncleared", epoch: 1, clearedThisEpoch: 0, lastRewardedEpoch: 0); using (world) { var em = world.EntityManager; MakeExpeditionPlayerAtGate(em); group.Update(); Assert.AreEqual(0, OreInLedger(em, cyc), "returning without clearing the wave banks nothing (no farming)"); } } } }