139 lines
6.1 KiB
C#
139 lines
6.1 KiB
C#
using NUnit.Framework;
|
|
using ProjectM.Server;
|
|
using ProjectM.Simulation;
|
|
using Unity.Core;
|
|
using Unity.Entities;
|
|
using Unity.NetCode;
|
|
|
|
namespace ProjectM.Tests
|
|
{
|
|
/// <summary>
|
|
/// Plain-Entities EditMode tests for the server-only <see cref="CyclePhaseSystem"/> — the macro-loop director
|
|
/// (Expedition → Defend → Build → next cycle). A bare world is seeded with a NetworkTime singleton and a cycle
|
|
/// entity carrying CycleState + CycleRuntime (and optionally WaveState / GoalProgress). All timing is wrap-safe
|
|
/// NetworkTick math; these tests pin each phase transition and the per-cycle goal-charge increment.
|
|
/// </summary>
|
|
public class CyclePhaseSystemTests
|
|
{
|
|
static (World world, SimulationSystemGroup group) MakeWorld(string name, uint serverTick)
|
|
{
|
|
var world = new World(name);
|
|
var group = world.GetOrCreateSystemManaged<SimulationSystemGroup>();
|
|
group.AddSystemToUpdateList(world.GetOrCreateSystem<CyclePhaseSystem>());
|
|
group.SortSystems();
|
|
world.SetTime(new TimeData(elapsedTime: 0f, deltaTime: 1f / 60f));
|
|
var em = world.EntityManager;
|
|
var nt = em.CreateEntity(typeof(NetworkTime));
|
|
em.SetComponentData(nt, new NetworkTime { ServerTick = new NetworkTick(serverTick) });
|
|
return (world, group);
|
|
}
|
|
|
|
static Entity MakeCycle(EntityManager em, byte phase, uint phaseEndTick, int cycleNumber, int defendStartWave)
|
|
{
|
|
var e = em.CreateEntity(typeof(CycleState), typeof(CycleRuntime));
|
|
em.SetComponentData(e, new CycleState { Phase = phase, PhaseEndTick = phaseEndTick, CycleNumber = cycleNumber });
|
|
em.SetComponentData(e, new CycleRuntime { DefendStartWave = defendStartWave });
|
|
return e;
|
|
}
|
|
|
|
static void MakeWaveState(EntityManager em, int waveNumber, int remainingToSpawn)
|
|
{
|
|
var e = em.CreateEntity(typeof(WaveState));
|
|
em.SetComponentData(e, new WaveState { WaveNumber = waveNumber, RemainingToSpawn = remainingToSpawn });
|
|
}
|
|
|
|
[Test]
|
|
public void Expedition_Enters_Defend_When_Timer_Due_Capturing_StartWave()
|
|
{
|
|
var (world, group) = MakeWorld("CycleExpToDefend", serverTick: 200);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
var cycle = MakeCycle(em, CyclePhase.Expedition, phaseEndTick: 100, cycleNumber: 1, defendStartWave: 0);
|
|
MakeWaveState(em, waveNumber: 5, remainingToSpawn: 0);
|
|
|
|
group.Update();
|
|
|
|
var cs = em.GetComponentData<CycleState>(cycle);
|
|
Assert.AreEqual(CyclePhase.Defend, cs.Phase, "An expired Expedition timer enters Defend.");
|
|
Assert.AreEqual(0u, cs.PhaseEndTick, "Defend is wave-driven, so PhaseEndTick is cleared.");
|
|
Assert.AreEqual(5, em.GetComponentData<CycleRuntime>(cycle).DefendStartWave,
|
|
"DefendStartWave captures the current WaveState.WaveNumber.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Expedition_Holds_While_Timer_Pending()
|
|
{
|
|
var (world, group) = MakeWorld("CycleExpHold", serverTick: 200);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
var cycle = MakeCycle(em, CyclePhase.Expedition, phaseEndTick: 5000, cycleNumber: 1, defendStartWave: 0);
|
|
|
|
group.Update();
|
|
|
|
Assert.AreEqual(CyclePhase.Expedition, em.GetComponentData<CycleState>(cycle).Phase,
|
|
"Expedition holds until its timer is due.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Defend_Enters_Build_When_Wave_Cleared()
|
|
{
|
|
var (world, group) = MakeWorld("CycleDefendToBuild", serverTick: 200);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
var cycle = MakeCycle(em, CyclePhase.Defend, phaseEndTick: 0, cycleNumber: 1, defendStartWave: 1);
|
|
// Wave advanced past the captured start, fully spawned, and no Husks alive (none created).
|
|
MakeWaveState(em, waveNumber: 2, remainingToSpawn: 0);
|
|
|
|
group.Update();
|
|
|
|
var cs = em.GetComponentData<CycleState>(cycle);
|
|
Assert.AreEqual(CyclePhase.Build, cs.Phase, "A cleared Defend wave enters Build.");
|
|
Assert.AreNotEqual(0u, cs.PhaseEndTick, "Build is timed, so a PhaseEndTick is stamped.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Build_Enters_Expedition_Incrementing_Cycle_And_Goal()
|
|
{
|
|
var (world, group) = MakeWorld("CycleBuildToExp", serverTick: 200);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
var cycle = MakeCycle(em, CyclePhase.Build, phaseEndTick: 100, cycleNumber: 1, defendStartWave: 0);
|
|
em.AddComponentData(cycle, new GoalProgress { Charge = 0, Target = 10 });
|
|
|
|
group.Update();
|
|
|
|
var cs = em.GetComponentData<CycleState>(cycle);
|
|
Assert.AreEqual(CyclePhase.Expedition, cs.Phase, "An expired Build timer starts the next Expedition.");
|
|
Assert.AreEqual(2, cs.CycleNumber, "CycleNumber increments on the new cycle.");
|
|
Assert.AreEqual(1, em.GetComponentData<GoalProgress>(cycle).Charge,
|
|
"One goal charge accrues per completed cycle (single writer).");
|
|
}
|
|
}
|
|
|
|
|
|
[Test]
|
|
public void WaveNumber_Is_Synced_From_WaveState_For_The_Hud()
|
|
{
|
|
var (world, group) = MakeWorld("CycleWaveSync", serverTick: 200);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
var cycle = MakeCycle(em, CyclePhase.Defend, phaseEndTick: 0, cycleNumber: 1, defendStartWave: 1);
|
|
MakeWaveState(em, waveNumber: 4, remainingToSpawn: 2);
|
|
|
|
group.Update();
|
|
|
|
Assert.AreEqual(4, em.GetComponentData<CycleState>(cycle).WaveNumber,
|
|
"CycleState.WaveNumber mirrors the server-only WaveState.WaveNumber so the replicated-state-only HUD can show it.");
|
|
}
|
|
}
|
|
}
|
|
}
|