Further Tests & Progress
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
using NUnit.Framework;
|
||||
using ProjectM.Server;
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Core;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
using Unity.Transforms;
|
||||
|
||||
namespace ProjectM.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Plain-Entities EditMode tests for the server-only <see cref="TurretFireSystem"/> (hitscan defense turret).
|
||||
/// A bare world is seeded with a <c>NetworkTime</c> singleton, a turret (PlacedStructure + Turret + RegionTag
|
||||
/// + LocalTransform) and Husks (Health + EnemyTag + RegionTag + LocalTransform + a DamageEvent buffer). The
|
||||
/// system snapshots living Husks, fires at the nearest in-range one in its OWN region on the
|
||||
/// <c>PlacedStructure.NextTick</c> cooldown, and appends a <c>DamageEvent{SourceNetworkId=-1}</c>. These tests
|
||||
/// pin range filtering, region gating, and the wrap-safe cooldown.
|
||||
/// </summary>
|
||||
public class TurretFireSystemTests
|
||||
{
|
||||
static (World world, SimulationSystemGroup group) MakeWorld(string name, uint serverTick)
|
||||
{
|
||||
var world = new World(name);
|
||||
var group = world.GetOrCreateSystemManaged<SimulationSystemGroup>();
|
||||
group.AddSystemToUpdateList(world.GetOrCreateSystem<TurretFireSystem>());
|
||||
group.SortSystems();
|
||||
world.SetTime(new TimeData(elapsedTime: 0f, deltaTime: 1f / 60f));
|
||||
SetServerTick(world, serverTick);
|
||||
return (world, group);
|
||||
}
|
||||
|
||||
static void SetServerTick(World world, uint tick)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
using var q = em.CreateEntityQuery(typeof(NetworkTime));
|
||||
Entity e = q.IsEmpty ? em.CreateEntity(typeof(NetworkTime)) : q.GetSingletonEntity();
|
||||
em.SetComponentData(e, new NetworkTime { ServerTick = new NetworkTick(tick) });
|
||||
}
|
||||
|
||||
static Entity MakeTurret(EntityManager em, float3 pos, byte region, float range, int cooldown, float damage)
|
||||
{
|
||||
var e = em.CreateEntity();
|
||||
em.AddComponentData(e, LocalTransform.FromPosition(pos));
|
||||
em.AddComponentData(e, new RegionTag { Region = region });
|
||||
em.AddComponentData(e, new PlacedStructure { Type = StructureType.Turret, NextTick = 0 });
|
||||
em.AddComponentData(e, new Turret { Range = range, CooldownTicks = cooldown, Damage = damage });
|
||||
return e;
|
||||
}
|
||||
|
||||
static Entity MakeHusk(EntityManager em, float3 pos, byte region, float health)
|
||||
{
|
||||
var e = em.CreateEntity();
|
||||
em.AddComponentData(e, LocalTransform.FromPosition(pos));
|
||||
em.AddComponentData(e, new RegionTag { Region = region });
|
||||
em.AddComponentData(e, new Health { Current = health, Max = health });
|
||||
em.AddComponent<EnemyTag>(e);
|
||||
em.AddBuffer<DamageEvent>(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Turret_Fires_At_InRange_Husk_In_Its_Region()
|
||||
{
|
||||
var (world, group) = MakeWorld("TurretFireWorld", serverTick: 200);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
var turret = MakeTurret(em, new float3(5, 1, 5), RegionId.Base, range: 20f, cooldown: 30, damage: 10f);
|
||||
var husk = MakeHusk(em, new float3(10, 1, 10), RegionId.Base, health: 50f);
|
||||
|
||||
group.Update();
|
||||
|
||||
var dmg = em.GetBuffer<DamageEvent>(husk);
|
||||
Assert.AreEqual(1, dmg.Length, "An in-range, same-region Husk takes exactly one turret hit.");
|
||||
Assert.AreEqual(10f, dmg[0].Amount, 1e-4f, "DamageEvent carries the turret's damage.");
|
||||
Assert.AreEqual(-1, dmg[0].SourceNetworkId, "Turret damage uses the -1 (world) source id.");
|
||||
Assert.AreNotEqual(0u, em.GetComponentData<PlacedStructure>(turret).NextTick,
|
||||
"The cooldown tick is stamped after firing.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Turret_Ignores_Husk_Out_Of_Range()
|
||||
{
|
||||
var (world, group) = MakeWorld("TurretRangeWorld", serverTick: 200);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
MakeTurret(em, new float3(0, 1, 0), RegionId.Base, range: 5f, cooldown: 30, damage: 10f);
|
||||
var husk = MakeHusk(em, new float3(100, 1, 0), RegionId.Base, health: 50f);
|
||||
|
||||
group.Update();
|
||||
|
||||
Assert.AreEqual(0, em.GetBuffer<DamageEvent>(husk).Length, "A Husk beyond Range takes no hit.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Turret_Ignores_Husk_In_A_Different_Region()
|
||||
{
|
||||
var (world, group) = MakeWorld("TurretRegionWorld", serverTick: 200);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
MakeTurret(em, new float3(0, 1, 0), RegionId.Base, range: 50f, cooldown: 30, damage: 10f);
|
||||
var husk = MakeHusk(em, new float3(2, 1, 2), RegionId.Expedition, health: 50f);
|
||||
|
||||
group.Update();
|
||||
|
||||
Assert.AreEqual(0, em.GetBuffer<DamageEvent>(husk).Length,
|
||||
"A Husk in a different region is never targeted (region gating).");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Turret_Respects_Cooldown_Then_Fires_Again()
|
||||
{
|
||||
var (world, group) = MakeWorld("TurretCooldownWorld", serverTick: 200);
|
||||
using (world)
|
||||
{
|
||||
var em = world.EntityManager;
|
||||
MakeTurret(em, new float3(0, 1, 0), RegionId.Base, range: 50f, cooldown: 30, damage: 10f);
|
||||
var husk = MakeHusk(em, new float3(3, 1, 0), RegionId.Base, health: 5000f);
|
||||
|
||||
group.Update(); // fires at tick 200, NextTick -> 230
|
||||
Assert.AreEqual(1, em.GetBuffer<DamageEvent>(husk).Length, "Fires on the first ready tick.");
|
||||
|
||||
SetServerTick(world, 210);
|
||||
group.Update(); // 210 < 230 -> still cooling down
|
||||
Assert.AreEqual(1, em.GetBuffer<DamageEvent>(husk).Length, "No second shot while cooling down.");
|
||||
|
||||
SetServerTick(world, 240);
|
||||
group.Update(); // 240 >= 230 -> ready again
|
||||
Assert.AreEqual(2, em.GetBuffer<DamageEvent>(husk).Length, "Fires again once the cooldown elapses.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user