using ProjectM.Simulation; using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.NetCode; namespace ProjectM.Server { /// /// Server receiver for — a participant interacting the room-exit portal during /// RoomExplore. Honored ONLY when RunInfo.Lifecycle==RoomExplore and the sender is an EXPEDITION player /// (region gate, the RouteSelect idiom). Sets the server-only latch IN-PLACE; it does /// NOT write RunInfo or tear the room down — RunDirectorSystem (the sole FSM/teardown owner) consumes the latch and /// advances. Plain server group, before RunDirectorSystem; requests ALWAYS destroyed; NO CyclePhase edge. /// [BurstCompile] [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)] [UpdateInGroup(typeof(SimulationSystemGroup))] [UpdateBefore(typeof(RunDirectorSystem))] public partial struct PortalInteractReceiveSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { var b = new EntityQueryBuilder(Allocator.Temp).WithAll(); state.RequireForUpdate(state.GetEntityQuery(b)); state.RequireForUpdate(); state.RequireForUpdate(); } [BurstCompile] public void OnUpdate(ref SystemState state) { var dirEntity = SystemAPI.GetSingletonEntity(); bool gateOpen = SystemAPI.GetComponent(dirEntity).Lifecycle == RunLifecycle.RoomExplore; // Sender region lookup (N3 idiom): a base-bound joiner cannot pull the party out of the room. var regionByConn = new NativeHashMap(8, Allocator.Temp); foreach (var (owner, region) in SystemAPI.Query, RefRO>().WithAll()) regionByConn[owner.ValueRO.NetworkId] = region.ValueRO.Region; bool interacted = SystemAPI.GetComponent(dirEntity).HasInteract != 0; var ecb = new EntityCommandBuffer(Allocator.Temp); foreach (var (receive, requestEntity) in SystemAPI.Query>().WithAll().WithEntityAccess()) { var conn = receive.ValueRO.SourceConnection; bool valid = gateOpen && !interacted && SystemAPI.HasComponent(conn) && regionByConn.TryGetValue(SystemAPI.GetComponent(conn).Value, out byte senderRegion) && senderRegion == RegionId.Expedition; if (valid) { SystemAPI.SetComponent(dirEntity, new PortalCommand { HasInteract = 1 }); interacted = true; } ecb.DestroyEntity(requestEntity); } ecb.Playback(state.EntityManager); ecb.Dispose(); regionByConn.Dispose(); } } }