Files
Project-M/Assets/_Project/Tests/EditMode/DashTrailDamageSystemTests.cs
T
2026-07-13 18:30:41 -07:00

95 lines
4.2 KiB
C#

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 tests for <see cref="DashTrailDamageSystem"/> (Phase 1.7 Blade Dash). A dashing player with the
/// boon damages a nearby enemy ONCE per dash (StartTick-keyed dedup survives a re-tick); a fresh dash hits again;
/// no boon → no damage.
/// </summary>
public class DashTrailDamageSystemTests
{
static (World world, SimulationSystemGroup group, EntityManager em) MakeWorld(uint tick)
{
var world = new World("DashTrailTest");
var group = world.GetOrCreateSystemManaged<SimulationSystemGroup>();
group.AddSystemToUpdateList(world.GetOrCreateSystem<DashTrailDamageSystem>());
group.SortSystems();
world.SetTime(new TimeData(elapsedTime: 0f, deltaTime: 1f / 60f));
var em = world.EntityManager;
em.SetComponentData(em.CreateEntity(typeof(NetworkTime)), new NetworkTime { ServerTick = new NetworkTick(tick) });
return (world, group, em);
}
static void SetTick(EntityManager em, uint tick)
{
using var q = em.CreateEntityQuery(typeof(NetworkTime));
em.SetComponentData(q.GetSingletonEntity(), new NetworkTime { ServerTick = new NetworkTick(tick) });
}
static Entity MakeDasher(EntityManager em, byte flags, uint startTick, uint iframeUntil)
{
var e = em.CreateEntity(typeof(PlayerTag), typeof(GhostOwner), typeof(BoonEffects), typeof(DashTrailState));
em.AddComponentData(e, LocalTransform.FromPosition(new float3(0f, 0f, 0f)));
em.AddComponentData(e, new DashState { Dir = new float2(1f, 0f), StartTick = startTick, IFrameUntilTick = iframeUntil, RecoverUntilTick = iframeUntil + 9 });
em.AddComponent<Simulate>(e); // enabled by default
em.SetComponentData(e, new GhostOwner { NetworkId = 1 });
em.SetComponentData(e, new BoonEffects { Flags = flags });
return e;
}
static Entity MakeEnemy(EntityManager em, float3 pos)
{
var e = em.CreateEntity(typeof(EnemyTag));
em.AddComponentData(e, LocalTransform.FromPosition(pos));
em.AddComponentData(e, new HitRadius { Value = 0.5f });
em.AddComponentData(e, new Health { Current = 60f, Max = 60f });
em.AddBuffer<DamageEvent>(e);
return e;
}
[Test]
public void BladeDash_DamagesNearbyEnemy_OncePerDash_ReHitsOnNextDash()
{
var (world, group, em) = MakeWorld(100);
using (world)
{
var player = MakeDasher(em, BoonFlag.DashTrail, startTick: 100, iframeUntil: 112);
var enemy = MakeEnemy(em, new float3(1f, 0f, 0f)); // within 1.6 + 0.5
group.Update(); // tick 100, dashing
Assert.AreEqual(1, em.GetBuffer<DamageEvent>(enemy).Length, "enemy in the dash path takes one hit");
group.Update(); // same tick + same StartTick -> dedup, no second hit
Assert.AreEqual(1, em.GetBuffer<DamageEvent>(enemy).Length, "no re-hit within the same dash");
// A fresh dash (new StartTick) resets the dedup set -> the enemy can be hit again.
SetTick(em, 130);
em.SetComponentData(player, new DashState { Dir = new float2(1f, 0f), StartTick = 130, IFrameUntilTick = 142, RecoverUntilTick = 151 });
group.Update();
Assert.AreEqual(2, em.GetBuffer<DamageEvent>(enemy).Length, "a fresh dash hits the enemy again");
}
}
[Test]
public void NoBoon_NoDamage()
{
var (world, group, em) = MakeWorld(100);
using (world)
{
MakeDasher(em, flags: 0, startTick: 100, iframeUntil: 112); // no DashTrail flag
var enemy = MakeEnemy(em, new float3(1f, 0f, 0f));
group.Update();
Assert.AreEqual(0, em.GetBuffer<DamageEvent>(enemy).Length, "no Blade Dash boon -> no trail damage");
}
}
}
}