129 lines
5.4 KiB
C#
129 lines
5.4 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 tests for <see cref="KillRewardSystem"/> (Phase 1.7 on-kill boons). Siphon heals the credited
|
|
/// killer (clamped to their effective max, once per corpse via the Dying.Rewarded latch); Frenzy upserts a single
|
|
/// cooldown-reduction row; an unresolved killer (KillerNetId < 0) grants nothing but is still latched.
|
|
/// </summary>
|
|
public class KillRewardSystemTests
|
|
{
|
|
const uint T0 = 5000;
|
|
|
|
static (World world, SimulationSystemGroup group, EntityManager em) MakeWorld()
|
|
{
|
|
var world = new World("KillRewardTest");
|
|
var group = world.GetOrCreateSystemManaged<SimulationSystemGroup>();
|
|
group.AddSystemToUpdateList(world.GetOrCreateSystem<KillRewardSystem>());
|
|
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(T0) });
|
|
return (world, group, em);
|
|
}
|
|
|
|
static Entity MakeKiller(EntityManager em, int netId, byte flags, float hp, float maxHp)
|
|
{
|
|
var e = em.CreateEntity(typeof(PlayerTag), typeof(GhostOwner), typeof(BoonEffects),
|
|
typeof(Health), typeof(EffectiveCharacterStats));
|
|
em.AddBuffer<StatModifier>(e);
|
|
em.AddBuffer<TimedModifier>(e);
|
|
em.SetComponentData(e, new GhostOwner { NetworkId = netId });
|
|
em.SetComponentData(e, new BoonEffects { Flags = flags });
|
|
em.SetComponentData(e, new Health { Current = hp, Max = maxHp });
|
|
em.SetComponentData(e, new EffectiveCharacterStats { MaxHealth = maxHp });
|
|
return e;
|
|
}
|
|
|
|
static Entity MakeCorpse(EntityManager em, int killerNetId)
|
|
{
|
|
var e = em.CreateEntity(typeof(EnemyTag), typeof(Dying));
|
|
em.SetComponentData(e, new Dying { UntilTick = T0 + 50, KillerNetId = killerNetId, Rewarded = 0 });
|
|
return e;
|
|
}
|
|
|
|
static int FrenzyRows(EntityManager em, Entity player)
|
|
{
|
|
var mods = em.GetBuffer<StatModifier>(player);
|
|
int n = 0;
|
|
for (int i = 0; i < mods.Length; i++) if (mods[i].SourceId == Tuning.FrenzySourceId) n++;
|
|
return n;
|
|
}
|
|
|
|
[Test]
|
|
public void Siphon_HealsKiller_ClampedToMax_OncePerCorpse()
|
|
{
|
|
var (world, group, em) = MakeWorld();
|
|
using (world)
|
|
{
|
|
var killer = MakeKiller(em, 1, BoonFlag.Siphon, hp: 50f, maxHp: 130f);
|
|
var corpse = MakeCorpse(em, killerNetId: 1);
|
|
|
|
group.Update();
|
|
Assert.Greater(em.GetComponentData<Health>(killer).Current, 50f, "Siphon healed the killer");
|
|
Assert.AreEqual(1, em.GetComponentData<Dying>(corpse).Rewarded, "corpse latched as rewarded");
|
|
|
|
float afterFirst = em.GetComponentData<Health>(killer).Current;
|
|
group.Update(); // second tick: Rewarded==1 -> no double-heal
|
|
Assert.AreEqual(afterFirst, em.GetComponentData<Health>(killer).Current, 1e-4f, "no double-heal on a re-tick");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Siphon_DoesNotOverheal_AboveEffectiveMax()
|
|
{
|
|
var (world, group, em) = MakeWorld();
|
|
using (world)
|
|
{
|
|
var killer = MakeKiller(em, 1, BoonFlag.Siphon, hp: 128f, maxHp: 130f);
|
|
MakeCorpse(em, killerNetId: 1);
|
|
group.Update();
|
|
Assert.AreEqual(130f, em.GetComponentData<Health>(killer).Current, 1e-4f, "heal clamps to the effective max");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Frenzy_UpsertsSingleCooldownRow()
|
|
{
|
|
var (world, group, em) = MakeWorld();
|
|
using (world)
|
|
{
|
|
var killer = MakeKiller(em, 1, BoonFlag.Frenzy, hp: 100f, maxHp: 130f);
|
|
MakeCorpse(em, killerNetId: 1);
|
|
|
|
group.Update();
|
|
Assert.AreEqual(1, FrenzyRows(em, killer), "one Frenzy StatModifier row");
|
|
|
|
// A second corpse (new kill) re-stamps rather than stacking.
|
|
var c2 = em.CreateEntity(typeof(EnemyTag), typeof(Dying));
|
|
em.SetComponentData(c2, new Dying { UntilTick = T0 + 60, KillerNetId = 1, Rewarded = 0 });
|
|
group.Update();
|
|
Assert.AreEqual(1, FrenzyRows(em, killer), "Frenzy refreshes, never stacks");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void UnresolvedKiller_GrantsNothing_ButLatches()
|
|
{
|
|
var (world, group, em) = MakeWorld();
|
|
using (world)
|
|
{
|
|
var killer = MakeKiller(em, 1, BoonFlag.Siphon | BoonFlag.Frenzy, hp: 50f, maxHp: 130f);
|
|
var corpse = MakeCorpse(em, killerNetId: -1); // environment/AoE kill — no credit
|
|
|
|
group.Update();
|
|
Assert.AreEqual(50f, em.GetComponentData<Health>(killer).Current, 1e-4f, "no heal for an uncredited kill");
|
|
Assert.AreEqual(0, FrenzyRows(em, killer), "no Frenzy for an uncredited kill");
|
|
Assert.AreEqual(1, em.GetComponentData<Dying>(corpse).Rewarded, "still latched so it is not reprocessed");
|
|
}
|
|
}
|
|
}
|
|
}
|