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)");
}
}
}
}