using NUnit.Framework;
using ProjectM.Simulation;
using Unity.Collections;
using Unity.Entities;
namespace ProjectM.Tests
{
///
/// Plain-Entities test for : 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.
///
public class StatRecomputeSystemTests
{
const byte AbilityPrimary = (byte)AbilityId.Primary;
const byte CharDefault = (byte)CharacterId.Default;
static BlobAssetReference BuildDb()
{
using var b = new BlobBuilder(Allocator.Temp);
ref var root = ref b.ConstructRoot();
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(Allocator.Persistent);
}
static (World world, Entity player) MakeWorld(out BlobAssetReference blob)
{
var world = new World("StatRecomputeTestWorld");
var group = world.GetOrCreateSystemManaged();
group.AddSystemToUpdateList(world.GetOrCreateSystem());
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(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().Update();
var ea = world.EntityManager.GetComponentData(player);
var ec = world.EntityManager.GetComponentData(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().Update();
var ea = world.EntityManager.GetComponentData(player);
var ec = world.EntityManager.GetComponentData(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();
group.Update();
var first = world.EntityManager.GetComponentData(player).Damage;
for (int i = 0; i < 5; i++) group.Update();
var last = world.EntityManager.GetComponentData(player).Damage;
Assert.AreEqual(30f, first, 1e-3f);
Assert.AreEqual(first, last, 1e-4f);
}
finally { world.Dispose(); blob.Dispose(); }
}
}
}