118 lines
5.4 KiB
C#
118 lines
5.4 KiB
C#
using NUnit.Framework;
|
|
using ProjectM.Simulation;
|
|
using Unity.Collections;
|
|
using Unity.Entities;
|
|
|
|
namespace ProjectM.Tests
|
|
{
|
|
/// <summary>
|
|
/// Plain-Entities test for <see cref="StatRecomputeSystem"/>: builds an AbilityDatabase blob singleton
|
|
/// + a player-like entity (refs / modifier buffer / effective components), ticks the
|
|
/// SimulationSystemGroup, and asserts the effective stats equal the folded (base + modifiers) values
|
|
/// and stay stable across repeated ticks (the every-tick recompute is idempotent). Version-independent.
|
|
/// </summary>
|
|
public class StatRecomputeSystemTests
|
|
{
|
|
const byte AbilityPrimary = (byte)AbilityId.Primary;
|
|
const byte CharDefault = (byte)CharacterId.Default;
|
|
|
|
static BlobAssetReference<AbilityDatabaseBlob> BuildDb()
|
|
{
|
|
using var b = new BlobBuilder(Allocator.Temp);
|
|
ref var root = ref b.ConstructRoot<AbilityDatabaseBlob>();
|
|
var a = b.Allocate(ref root.Abilities, 1);
|
|
a[0] = new AbilityDefBlob
|
|
{
|
|
Id = AbilityPrimary, Damage = 20f, ProjectileSpeed = 25f, Range = 20f,
|
|
AutoTargetRange = 12f, AutoTargetConeRadians = 0.6f, CooldownTicks = 12, Name = "Primary"
|
|
};
|
|
var c = b.Allocate(ref root.Characters, 1);
|
|
c[0] = new CharacterStatsBlob
|
|
{
|
|
Id = CharDefault, MoveSpeed = 6f, TurnRateRadiansPerSec = 12.5f, MaxHealth = 100f, Name = "Default"
|
|
};
|
|
return b.CreateBlobAssetReference<AbilityDatabaseBlob>(Allocator.Persistent);
|
|
}
|
|
|
|
static (World world, Entity player) MakeWorld(out BlobAssetReference<AbilityDatabaseBlob> blob)
|
|
{
|
|
var world = new World("StatRecomputeTestWorld");
|
|
var group = world.GetOrCreateSystemManaged<SimulationSystemGroup>();
|
|
group.AddSystemToUpdateList(world.GetOrCreateSystem<StatRecomputeSystem>());
|
|
group.SortSystems();
|
|
|
|
var em = world.EntityManager;
|
|
blob = BuildDb();
|
|
var dbEntity = em.CreateEntity(typeof(AbilityDatabase));
|
|
em.SetComponentData(dbEntity, new AbilityDatabase { Value = blob });
|
|
|
|
var player = em.CreateEntity(
|
|
typeof(AbilityRef), typeof(CharacterStatsRef), typeof(StatModifier),
|
|
typeof(EffectiveAbilityStats), typeof(EffectiveCharacterStats), typeof(Simulate));
|
|
em.SetComponentData(player, new AbilityRef { Id = AbilityPrimary });
|
|
em.SetComponentData(player, new CharacterStatsRef { Id = CharDefault });
|
|
return (world, player);
|
|
}
|
|
|
|
static void AddMod(World world, Entity player, StatTarget target, ModOp op, float value)
|
|
{
|
|
var buf = world.EntityManager.GetBuffer<StatModifier>(player);
|
|
buf.Add(new StatModifier { Target = (byte)target, Op = (byte)op, Value = value });
|
|
}
|
|
|
|
[Test]
|
|
public void NoModifiers_Effective_Equals_Base()
|
|
{
|
|
var (world, player) = MakeWorld(out var blob);
|
|
try
|
|
{
|
|
world.GetExistingSystemManaged<SimulationSystemGroup>().Update();
|
|
var ea = world.EntityManager.GetComponentData<EffectiveAbilityStats>(player);
|
|
var ec = world.EntityManager.GetComponentData<EffectiveCharacterStats>(player);
|
|
Assert.AreEqual(20f, ea.Damage, 1e-3f);
|
|
Assert.AreEqual(25f, ea.ProjectileSpeed, 1e-3f);
|
|
Assert.AreEqual(12, ea.CooldownTicks);
|
|
Assert.AreEqual(6f, ec.MoveSpeed, 1e-3f);
|
|
Assert.AreEqual(100f, ec.MaxHealth, 1e-3f);
|
|
}
|
|
finally { world.Dispose(); blob.Dispose(); }
|
|
}
|
|
|
|
[Test]
|
|
public void Modifiers_Fold_Into_Effective()
|
|
{
|
|
var (world, player) = MakeWorld(out var blob);
|
|
try
|
|
{
|
|
AddMod(world, player, StatTarget.Damage, ModOp.Flat, 5f);
|
|
AddMod(world, player, StatTarget.Damage, ModOp.PercentAdd, 0.5f); // (20+5)*1.5 = 37.5
|
|
AddMod(world, player, StatTarget.MoveSpeed, ModOp.PercentAdd, 0.5f); // 6*1.5 = 9
|
|
world.GetExistingSystemManaged<SimulationSystemGroup>().Update();
|
|
var ea = world.EntityManager.GetComponentData<EffectiveAbilityStats>(player);
|
|
var ec = world.EntityManager.GetComponentData<EffectiveCharacterStats>(player);
|
|
Assert.AreEqual(37.5f, ea.Damage, 1e-3f);
|
|
Assert.AreEqual(9f, ec.MoveSpeed, 1e-3f);
|
|
}
|
|
finally { world.Dispose(); blob.Dispose(); }
|
|
}
|
|
|
|
[Test]
|
|
public void Recompute_Is_Idempotent_Across_Ticks()
|
|
{
|
|
var (world, player) = MakeWorld(out var blob);
|
|
try
|
|
{
|
|
AddMod(world, player, StatTarget.Damage, ModOp.Flat, 10f); // 20+10 = 30
|
|
var group = world.GetExistingSystemManaged<SimulationSystemGroup>();
|
|
group.Update();
|
|
var first = world.EntityManager.GetComponentData<EffectiveAbilityStats>(player).Damage;
|
|
for (int i = 0; i < 5; i++) group.Update();
|
|
var last = world.EntityManager.GetComponentData<EffectiveAbilityStats>(player).Damage;
|
|
Assert.AreEqual(30f, first, 1e-3f);
|
|
Assert.AreEqual(first, last, 1e-4f);
|
|
}
|
|
finally { world.Dispose(); blob.Dispose(); }
|
|
}
|
|
}
|
|
}
|