152 lines
6.2 KiB
C#
152 lines
6.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="BuildPlaceSystem"/> — the RPC structure-placement
|
|
/// handler. A bare world is seeded with StructureCatalog (+ a Turret entry referencing a Prefab-tagged prefab),
|
|
/// BaseAnchor, ResourceLedger (+ Ore), NetworkTime, and synthetic BuildPlaceRequest + ReceiveRpcCommandRequest
|
|
/// entities. The headline case is co-op atomicity: two same-tick requests for one cell must place EXACTLY one
|
|
/// structure and withdraw the cost ONCE (the in-place commit). Also pins cost/plot validation and request cleanup.
|
|
/// </summary>
|
|
public class BuildPlaceSystemTests
|
|
{
|
|
static (World world, SimulationSystemGroup group) MakeWorld(string name, int oreCount)
|
|
{
|
|
var world = new World(name);
|
|
var group = world.GetOrCreateSystemManaged<SimulationSystemGroup>();
|
|
group.AddSystemToUpdateList(world.GetOrCreateSystem<BuildPlaceSystem>());
|
|
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(300) });
|
|
|
|
var anchor = em.CreateEntity(typeof(BaseAnchor));
|
|
em.SetComponentData(anchor, new BaseAnchor
|
|
{
|
|
AnchorPos = new float3(5, 0, 5),
|
|
GridOrigin = new float3(0, 0, 0),
|
|
CellSize = 2f,
|
|
GridDims = new int2(5, 5),
|
|
});
|
|
|
|
// Turret prefab: LocalTransform (looked up for placement) + PlacedStructure (SetComponent target on the
|
|
// clone) + a real Prefab tag so it is excluded from the live-structure occupancy scan.
|
|
var prefab = em.CreateEntity(typeof(LocalTransform), typeof(PlacedStructure));
|
|
em.AddComponent<Prefab>(prefab);
|
|
|
|
var catalogE = em.CreateEntity(typeof(StructureCatalog));
|
|
var catalog = em.AddBuffer<StructureCatalogEntry>(catalogE);
|
|
catalog.Add(new StructureCatalogEntry
|
|
{
|
|
Type = StructureType.Turret, Prefab = prefab, CostResourceId = ResourceId.Ore, CostAmount = 10,
|
|
});
|
|
|
|
var ledgerE = em.CreateEntity(typeof(ResourceLedger));
|
|
var ledger = em.AddBuffer<StorageEntry>(ledgerE);
|
|
ledger.Add(new StorageEntry { ItemId = ResourceId.Ore, Count = oreCount });
|
|
|
|
return (world, group);
|
|
}
|
|
|
|
static void MakeBuildRequest(EntityManager em, byte type, int cellX, int cellZ)
|
|
{
|
|
var e = em.CreateEntity();
|
|
em.AddComponentData(e, new BuildPlaceRequest { StructureType = type, CellX = cellX, CellZ = cellZ });
|
|
em.AddComponentData(e, default(ReceiveRpcCommandRequest));
|
|
}
|
|
|
|
static int StructureCount(EntityManager em)
|
|
{
|
|
using var q = em.CreateEntityQuery(typeof(PlacedStructure));
|
|
return q.CalculateEntityCount();
|
|
}
|
|
|
|
static int OreCount(EntityManager em)
|
|
{
|
|
using var q = em.CreateEntityQuery(typeof(ResourceLedger));
|
|
var ledger = em.GetBuffer<StorageEntry>(q.GetSingletonEntity());
|
|
for (int i = 0; i < ledger.Length; i++)
|
|
if (ledger[i].ItemId == ResourceId.Ore) return ledger[i].Count;
|
|
return 0;
|
|
}
|
|
|
|
[Test]
|
|
public void Valid_Request_Places_Structure_Withdraws_Cost_And_Destroys_Request()
|
|
{
|
|
var (world, group) = MakeWorld("BuildValid", oreCount: 50);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
MakeBuildRequest(em, StructureType.Turret, cellX: 1, cellZ: 1);
|
|
|
|
group.Update();
|
|
|
|
Assert.AreEqual(1, StructureCount(em), "A valid request places exactly one structure.");
|
|
Assert.AreEqual(40, OreCount(em), "The build cost (10) is withdrawn from the ledger.");
|
|
using var reqQ = em.CreateEntityQuery(typeof(BuildPlaceRequest));
|
|
Assert.AreEqual(0, reqQ.CalculateEntityCount(), "The handled request is destroyed.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Two_Same_Cell_Requests_Place_Only_One_And_Withdraw_Once()
|
|
{
|
|
var (world, group) = MakeWorld("BuildAtomic", oreCount: 50);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
MakeBuildRequest(em, StructureType.Turret, cellX: 1, cellZ: 1);
|
|
MakeBuildRequest(em, StructureType.Turret, cellX: 1, cellZ: 1);
|
|
|
|
group.Update();
|
|
|
|
Assert.AreEqual(1, StructureCount(em),
|
|
"Two same-tick requests for one cell place exactly one structure (co-op atomicity).");
|
|
Assert.AreEqual(40, OreCount(em), "The cost is withdrawn exactly once, not twice.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Insufficient_Resources_Places_Nothing()
|
|
{
|
|
var (world, group) = MakeWorld("BuildPoor", oreCount: 5);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
MakeBuildRequest(em, StructureType.Turret, cellX: 1, cellZ: 1);
|
|
|
|
group.Update();
|
|
|
|
Assert.AreEqual(0, StructureCount(em), "A request that can't afford the cost places nothing.");
|
|
Assert.AreEqual(5, OreCount(em), "The ledger is untouched on an unaffordable request.");
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void Out_Of_Plot_Cell_Places_Nothing()
|
|
{
|
|
var (world, group) = MakeWorld("BuildOOB", oreCount: 50);
|
|
using (world)
|
|
{
|
|
var em = world.EntityManager;
|
|
MakeBuildRequest(em, StructureType.Turret, cellX: 99, cellZ: 99);
|
|
|
|
group.Update();
|
|
|
|
Assert.AreEqual(0, StructureCount(em), "An out-of-plot cell places nothing.");
|
|
Assert.AreEqual(50, OreCount(em), "No cost is withdrawn for an illegal placement.");
|
|
}
|
|
}
|
|
}
|
|
}
|