108 lines
5.2 KiB
C#
108 lines
5.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 EditMode tests for the server-only <see cref="PlayerRespawnSystem"/> — the authoritative
|
|
/// death→respawn timer. A bare world is seeded with NetworkTime + PlayerSpawner singletons and a player
|
|
/// (Health, RespawnState, RespawnInvuln, LocalTransform, GhostOwner, EffectiveCharacterStats, PlayerTag).
|
|
/// Pins: a newly-dead player schedules a respawn tick; a due tick refills health + repositions + grants
|
|
/// invuln + clears the schedule; an alive player clears any stale pending schedule.
|
|
/// </summary>
|
|
public class PlayerRespawnSystemTests
|
|
{
|
|
static (World world, SimulationSystemGroup group) MakeWorld(string name, uint serverTick)
|
|
{
|
|
var world = new World(name);
|
|
var group = world.GetOrCreateSystemManaged<SimulationSystemGroup>();
|
|
group.AddSystemToUpdateList(world.GetOrCreateSystem<PlayerRespawnSystem>());
|
|
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) });
|
|
var sp = em.CreateEntity(typeof(PlayerSpawner));
|
|
em.SetComponentData(sp, new PlayerSpawner { SpawnPoint = new float3(0, 1, 0), SpawnRingRadius = 10f, RingSlots = 4 });
|
|
return (world, group);
|
|
}
|
|
|
|
static Entity MakePlayer(EntityManager em, float health, float maxHealth, uint respawnTick,
|
|
int delayTicks, int invulnTicks, float3 pos, int networkId)
|
|
{
|
|
var e = em.CreateEntity();
|
|
em.AddComponentData(e, new Health { Current = health, Max = maxHealth });
|
|
em.AddComponentData(e, new RespawnState { RespawnTick = respawnTick, DelayTicks = delayTicks, InvulnTicks = invulnTicks });
|
|
em.AddComponentData(e, new RespawnInvuln { UntilTick = 0 });
|
|
em.AddComponentData(e, LocalTransform.FromPosition(pos));
|
|
em.AddComponentData(e, new GhostOwner { NetworkId = networkId });
|
|
em.AddComponentData(e, new EffectiveCharacterStats { MaxHealth = maxHealth });
|
|
em.AddComponent<PlayerTag>(e);
|
|
return e;
|
|
}
|
|
|
|
[Test]
|
|
public void Newly_Dead_Player_Schedules_Respawn_Tick()
|
|
{
|
|
var (world, group) = MakeWorld("RespawnSchedule", serverTick: 200);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
var player = MakePlayer(em, health: 0f, maxHealth: 100f, respawnTick: 0,
|
|
delayTicks: 60, invulnTicks: 120, pos: new float3(5, 1, 5), networkId: 1);
|
|
|
|
group.Update();
|
|
|
|
Assert.AreEqual(RespawnMath.RespawnTick(200, 60), em.GetComponentData<RespawnState>(player).RespawnTick,
|
|
"A newly-dead player schedules its respawn tick (now + delay).");
|
|
Assert.AreEqual(0f, em.GetComponentData<Health>(player).Current, 1e-4f, "Still down until the tick is due.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Due_Respawn_Restores_Health_Repositions_And_Grants_Invuln()
|
|
{
|
|
var (world, group) = MakeWorld("RespawnRecover", serverTick: 200);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
var player = MakePlayer(em, health: 0f, maxHealth: 100f, respawnTick: 160,
|
|
delayTicks: 60, invulnTicks: 120, pos: new float3(999, 1, 999), networkId: 1);
|
|
|
|
group.Update();
|
|
|
|
Assert.AreEqual(100f, em.GetComponentData<Health>(player).Current, 1e-4f, "Health refills to the effective max.");
|
|
Assert.AreEqual(320u, em.GetComponentData<RespawnInvuln>(player).UntilTick,
|
|
"Post-respawn invulnerability is granted until now + InvulnTicks (200 + 120).");
|
|
Assert.AreEqual(0u, em.GetComponentData<RespawnState>(player).RespawnTick, "The respawn schedule is cleared on recovery.");
|
|
Assert.Less(em.GetComponentData<LocalTransform>(player).Position.x, 100f,
|
|
"The player is teleported from its death spot back to the base spawn ring.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Alive_Player_Clears_Stale_Pending_Respawn()
|
|
{
|
|
var (world, group) = MakeWorld("RespawnAliveClear", serverTick: 200);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
var player = MakePlayer(em, health: 50f, maxHealth: 100f, respawnTick: 999,
|
|
delayTicks: 60, invulnTicks: 120, pos: new float3(5, 1, 5), networkId: 1);
|
|
|
|
group.Update();
|
|
|
|
Assert.AreEqual(0u, em.GetComponentData<RespawnState>(player).RespawnTick,
|
|
"An alive player clears any stale pending respawn schedule.");
|
|
Assert.AreEqual(50f, em.GetComponentData<Health>(player).Current, 1e-4f, "Alive health is untouched.");
|
|
}
|
|
}
|
|
}
|
|
}
|