false, 'error' => 'Missing room id']); exit; } $sessionPlayer = get_session_player($roomId); $playerToken = $sessionPlayer['token'] ?? null; session_write_close(); header('Content-Type: text/event-stream'); header('Cache-Control: no-cache, no-store, must-revalidate'); header('Pragma: no-cache'); header('Expires: 0'); header('X-Accel-Buffering: no'); @ini_set('zlib.output_compression', '0'); @ini_set('output_buffering', '0'); while (ob_get_level() > 0) { @ob_end_flush(); } ob_implicit_flush(true); set_time_limit(0); ignore_user_abort(true); $startedAt = time(); $lastHash = ''; while (!connection_aborted()) { $room = get_room($roomId); if (!$room) { echo "event: room_closed\n"; echo 'data: {"success":false,"error":"Room not found"}' . "\n\n"; flush(); break; } $state = $room['state'] ?? []; $hasDynamicObjects = !empty($state['bombs']) || !empty($state['explosions']); if ($room['status'] === 'playing' || $hasDynamicObjects) { $room = update_room_tick($room); save_room_state((int) $room['id'], $room['state'], (string) $room['status']); } $payload = [ 'success' => true, 'room' => [ 'id' => (int) $room['id'], 'name' => $room['name'], 'status' => $room['status'], 'max_players' => (int) $room['max_players'], ], 'state' => $room['state'], 'player_token' => $playerToken, 'server_time' => now_ms(), ]; $json = json_encode($payload, JSON_UNESCAPED_UNICODE); if ($json === false) { break; } $stateForHash = $payload['state']; unset($stateForHash['last_tick']); $hash = sha1(json_encode([ 'room' => $payload['room'], 'state' => $stateForHash, 'player_token' => $payload['player_token'], ], JSON_UNESCAPED_UNICODE) ?: $json); if ($hash !== $lastHash) { $lastHash = $hash; echo "event: state\n"; echo 'data: ' . $json . "\n\n"; flush(); } else { echo "event: ping\n"; echo 'data: {"ts":' . now_ms() . "}\n\n"; flush(); } usleep(($room['status'] === 'playing') ? 120000 : 350000); if ((time() - $startedAt) >= 25) { break; } }