Map Updates

This commit is contained in:
2026-06-04 21:49:03 -07:00
parent 16b01bec38
commit 15bc1022ee
43 changed files with 4054 additions and 62 deletions
@@ -12,10 +12,13 @@ namespace ProjectM.Server
/// counts players whose server-only <see cref="RegionTag"/> is the Expedition region, and on the
/// empty-&gt;occupied edge (a new sortie) bumps <see cref="CycleRuntime.ExpeditionEpoch"/> and scatters
/// <see cref="ResourceFieldSpawner.Count"/> resource-node ghosts (seeded by the epoch) around the expedition
/// origin, each RegionTag{Expedition}; on the occupied-&gt;empty edge (the LAST player left) it destroys every
/// node. So the field lives as long as anyone is out there, not on a global timer. Plain server
/// SimulationSystemGroup. Server-authoritative; clients despawn nodes via GhostDespawnSystem. Per-epoch
/// reproducible (the seed is the monotonic int epoch, compared by equality — never tick math, never 0).
/// origin — PLUS, if a <see cref="ClutterFieldSpawner"/> singleton is present,
/// <see cref="ClutterFieldSpawner.Count"/> Blight-clutter ghosts (seeded DISTINCTLY so clutter and nodes don't
/// co-locate, Variant round-robined), each RegionTag{Expedition}; on the occupied-&gt;empty edge (the LAST
/// player left) it destroys every node AND every clutter piece. So the field lives as long as anyone is out
/// there, not on a global timer. Plain server SimulationSystemGroup. Server-authoritative; clients despawn
/// ghosts via GhostDespawnSystem. Per-epoch reproducible (the seed is the monotonic int epoch, compared by
/// equality — never tick math, never 0).
/// </summary>
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
@@ -80,14 +83,42 @@ namespace ProjectM.Server
rn.ResourceId = (byte)(ResourceId.Aether + (byte)(i % 3));
ecb.SetComponent(node, rn);
}
// Blight clutter (OPTIONAL singleton): scatter alongside the nodes with a DISTINCT seed so the
// two fields don't co-locate. Round-robin Variant for client visual variety.
if (SystemAPI.TryGetSingleton<ClutterFieldSpawner>(out var clutterSpawner)
&& clutterSpawner.Prefab != Entity.Null)
{
var clutterXform = SystemAPI.GetComponent<LocalTransform>(clutterSpawner.Prefab);
var prefabClutter = SystemAPI.GetComponent<BlightClutter>(clutterSpawner.Prefab);
var crng = new Random((uint)math.max(1, runtime.ExpeditionEpoch * 2 + 1));
int ccount = math.max(1, clutterSpawner.Count);
for (int i = 0; i < ccount; i++)
{
var piece = ecb.Instantiate(clutterSpawner.Prefab);
float ang = crng.NextFloat(0f, math.PI * 2f);
float rad = clutterSpawner.Radius * math.sqrt(crng.NextFloat(0f, 1f));
var xform = clutterXform;
xform.Position = origin + new float3(math.cos(ang) * rad, 0f, math.sin(ang) * rad);
ecb.SetComponent(piece, xform);
var bc = prefabClutter;
bc.Variant = (byte)(i % 3);
ecb.SetComponent(piece, bc);
}
}
runtime.LastSpawnedEpoch = runtime.ExpeditionEpoch;
}
// DESTROY: the last player left the expedition — clear the whole field.
// DESTROY: the last player left the expedition — clear the whole field (nodes + clutter).
if (wasOccupied && !occupied)
{
foreach (var (rn, e) in SystemAPI.Query<RefRO<ResourceNode>>().WithEntityAccess())
ecb.DestroyEntity(e);
foreach (var (bc, e) in SystemAPI.Query<RefRO<BlightClutter>>().WithEntityAccess())
ecb.DestroyEntity(e);
}
runtime.PrevExpeditionOccupied = (byte)(occupied ? 1 : 0);