using ProjectM.Simulation;
using Unity.Collections;
using Unity.Entities;
using Unity.NetCode;
namespace ProjectM.Server
{
///
/// Server receiver for — the player picks their class at base. Honored ONLY in
/// Staging (class = a between-runs choice; mid-run it would desync the fight). Resolves sender → player (the
/// MetaSpend/ReadyToggle idiom), then applies the FULL in-place swap via (class seeds +
/// permanent-meta re-sync) and writes AbilityRef / PlayerClass / AbilityCooldown + .
/// Plain server group, before RunDirectorSystem (the receiver convention); requests are ALWAYS destroyed. NOT
/// Burst-compiled (a cross-assembly blob+buffer helper on a low-frequency RPC — Burst safety over micro-perf).
///
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(RunDirectorSystem))]
public partial struct ClassSelectReceiveSystem : ISystem
{
public void OnCreate(ref SystemState state)
{
var b = new EntityQueryBuilder(Allocator.Temp).WithAll();
state.RequireForUpdate(state.GetEntityQuery(b));
state.RequireForUpdate();
}
public void OnUpdate(ref SystemState state)
{
bool accept = SystemAPI.GetSingleton().Lifecycle == RunLifecycle.Staging;
var playerByConn = new NativeHashMap(8, Allocator.Temp);
foreach (var (owner, e) in
SystemAPI.Query>().WithAll().WithEntityAccess())
playerByConn[owner.ValueRO.NetworkId] = e;
// Meta re-sync inputs (on the director/ledger ghost). dir stays Null if the catalog is absent (guarded).
Entity dir = Entity.Null;
bool haveMeta = SystemAPI.TryGetSingleton(out var metaCat)
&& SystemAPI.TryGetSingletonEntity(out dir) && SystemAPI.HasBuffer(dir);
bool haveDb = SystemAPI.TryGetSingleton(out var abilityDb);
var ecb = new EntityCommandBuffer(Allocator.Temp);
foreach (var (receive, req, reqEntity) in
SystemAPI.Query, RefRO>().WithEntityAccess())
{
ecb.DestroyEntity(reqEntity); // ALWAYS consumed
if (!accept) continue;
var conn = receive.ValueRO.SourceConnection;
if (!SystemAPI.HasComponent(conn)
|| !playerByConn.TryGetValue(SystemAPI.GetComponent(conn).Value, out var player))
continue;
if (!SystemAPI.HasComponent(player)) continue;
var mods = SystemAPI.GetBuffer(player);
var metaRecord = haveMeta ? SystemAPI.GetBuffer(dir) : default;
ClassSwapUtil.Apply(req.ValueRO.ClassId, mods, haveMeta, metaCat, metaRecord,
out byte newClass, out byte newAbilityId);
SystemAPI.SetComponent(player, new AbilityRef { Id = newAbilityId });
if (SystemAPI.HasComponent(player))
SystemAPI.SetComponent(player, new PlayerClass { ClassId = newClass });
if (SystemAPI.HasComponent(player))
SystemAPI.SetComponent(player, new AbilityCooldown { NextFireTick = 0 }); // swapped ability fires now
if (haveDb && SystemAPI.HasComponent(player) && SystemAPI.HasComponent(player))
{
byte charId = SystemAPI.GetComponent(player).Id;
if (abilityDb.Value.Value.TryGetCharacter(charId, out var baseChar))
{
var hp = SystemAPI.GetComponent(player);
ClassSwapUtil.HealClamp(ref hp, baseChar.MaxHealth, mods);
SystemAPI.SetComponent(player, hp);
}
}
}
ecb.Playback(state.EntityManager);
ecb.Dispose();
playerByConn.Dispose();
}
}
}