using ProjectM.Simulation;
using Unity.Burst;
using Unity.Entities;
using Unity.NetCode;
namespace ProjectM.Server
{
///
/// Server-only choice-of-3 boon dealer: once per (int-equality latch on
/// , attached beside the catalog singleton), when the run FSM enters RoomReward it
/// draws each EXPEDITION player's 3 distinct, rarity-weighted, class-filtered options via
/// — deterministically seeded from Hash(RunSeed, room, NetworkId) — and writes
/// the player's owner-only replicated (Pending=1). A base-region player (dead-respawned,
/// late joiner) gets NO offer and never holds the gate (RunDirector counts only Pending!=0). BoonApplySystem
/// (Step 10) consumes picks; the Returning-edge strip zeroes stragglers.
///
/// Ordering: [UpdateAfter(RunDirectorSystem)] — on the RoomReward ENTRY tick this runs after the
/// transition, so offers exist BEFORE RunDirector's exit gate first evaluates (next tick). No CyclePhase edge.
///
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(RunDirectorSystem))]
public partial struct BoonOfferSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate();
state.RequireForUpdate();
state.RequireForUpdate();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var catalogEntity = SystemAPI.GetSingletonEntity();
// One-shot: attach this system's latch beside the catalog singleton (the RoomFieldState idiom).
if (!SystemAPI.HasComponent(catalogEntity))
{
state.EntityManager.AddComponentData(catalogEntity, new BoonOfferState());
return; // structural change — clean re-read next tick
}
var dirEntity = SystemAPI.GetSingletonEntity();
var info = SystemAPI.GetComponent(dirEntity);
if (info.Lifecycle != RunLifecycle.RoomReward)
return;
var run = SystemAPI.GetComponent(dirEntity);
var offered = SystemAPI.GetComponent(catalogEntity);
if (offered.OfferedRoomEpoch == run.RoomEpoch)
return; // this room's offers are already dealt
var catalog = SystemAPI.GetComponent(catalogEntity);
if (!catalog.Value.IsCreated)
return;
ref var pool = ref catalog.Value.Value;
foreach (var (offer, owner, region, cls, fx) in
SystemAPI.Query, RefRO, RefRO, RefRO, RefRO>()
.WithAll())
{
if (region.ValueRO.Region != RegionId.Expedition)
continue; // home-bound players (dead-respawned, joiners) are dealt nothing
// Deterministic per-player draw: reconnect-stable per session, replay-reproducible per (seed, room, player, owned-effects-at-draw).
uint offerSeed = RunMapMath.Hash(run.RunSeed, (uint)info.CurrentRoom, (uint)owner.ValueRO.NetworkId) | 1u;
BoonMath.PickBoons(offerSeed, cls.ValueRO.ClassId, fx.ValueRO, ref pool, out byte o0, out byte o1, out byte o2);
offer.ValueRW = new BoonOffer { Pending = 1, Option0 = o0, Option1 = o1, Option2 = o2 };
}
offered.OfferedRoomEpoch = run.RoomEpoch;
SystemAPI.SetComponent(catalogEntity, offered);
}
}
}