Files
2026-05-31 21:35:12 -07:00

79 lines
3.2 KiB
C#

using ProjectM.Simulation;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
namespace ProjectM.Server
{
/// <summary>
/// Server-authoritative upgrade pickup grant. When a player overlaps an <see cref="UpgradePickup"/>
/// (planar XZ distance within the pickup's <see cref="HitRadius"/>), appends the pickup's modifier to
/// the player's replicated <see cref="StatModifier"/> buffer (which replicates to the predicting
/// owner, so StatRecomputeSystem folds identical effective stats on both worlds) and destroys the
/// pickup. Runs in the default <see cref="SimulationSystemGroup"/> (NOT the prediction loop) since the
/// grant is a non-predicted server event. The buffer append + pickup destroy are batched through an
/// <see cref="EntityCommandBuffer"/> played back immediately — so a plain-world EditMode test needs no
/// separate ECB system.
/// </summary>
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
public partial struct UpgradePickupSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate<UpgradePickup>();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
// Snapshot modifiable players (carrying the modifier buffer + a transform) once this tick.
var playerEntities = new NativeList<Entity>(Allocator.Temp);
var playerPositions = new NativeList<float3>(Allocator.Temp);
foreach (var (xform, e) in
SystemAPI.Query<RefRO<LocalTransform>>()
.WithAll<PlayerTag, StatModifier>()
.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<LocalTransform>, RefRO<HitRadius>, RefRO<UpgradePickup>>()
.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();
}
}
}