using ProjectM.Simulation; using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using Unity.Transforms; namespace ProjectM.Server { /// /// Server-authoritative upgrade pickup grant. When a player overlaps an /// (planar XZ distance within the pickup's ), appends the pickup's modifier to /// the player's replicated buffer (which replicates to the predicting /// owner, so StatRecomputeSystem folds identical effective stats on both worlds) and destroys the /// pickup. Runs in the default (NOT the prediction loop) since the /// grant is a non-predicted server event. The buffer append + pickup destroy are batched through an /// played back immediately — so a plain-world EditMode test needs no /// separate ECB system. /// [BurstCompile] [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)] public partial struct UpgradePickupSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate(); } [BurstCompile] public void OnUpdate(ref SystemState state) { // Snapshot modifiable players (carrying the modifier buffer + a transform) once this tick. var playerEntities = new NativeList(Allocator.Temp); var playerPositions = new NativeList(Allocator.Temp); foreach (var (xform, e) in SystemAPI.Query>() .WithAll() .WithEntityAccess()) { playerEntities.Add(e); playerPositions.Add(xform.ValueRO.Position); } var ecb = new EntityCommandBuffer(Allocator.Temp); foreach (var (xform, radius, pickup, pickupEntity) in SystemAPI.Query, RefRO, RefRO>() .WithEntityAccess()) { float2 pp = new float2(xform.ValueRO.Position.x, xform.ValueRO.Position.z); float r = radius.ValueRO.Value; for (int i = 0; i < playerEntities.Length; i++) { float2 cp = new float2(playerPositions[i].x, playerPositions[i].z); if (math.distancesq(pp, cp) > r * r) continue; ecb.AppendToBuffer(playerEntities[i], new StatModifier { Target = pickup.ValueRO.Target, Op = pickup.ValueRO.Op, Value = pickup.ValueRO.Value, SourceId = pickup.ValueRO.SourceId, }); ecb.DestroyEntity(pickupEntity); break; // granted to the first overlapping player, then despawns } } ecb.Playback(state.EntityManager); ecb.Dispose(); playerEntities.Dispose(); playerPositions.Dispose(); } } }