OpenFrontIO Auto-Join Lobby

Auto-join lobbies based on game mode preferences (FFA, Team with all team configurations, player filters). Tested and 100% functional against OpenFront v0.26.16

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         OpenFrontIO Auto-Join Lobby
// @namespace    http://tampermonkey.net/
// @version      1.4.7
// @description  Auto-join lobbies based on game mode preferences (FFA, Team with all team configurations, player filters). Tested and 100% functional against OpenFront v0.26.16
// @author       DeLoVaN
// @homepageURL  https://github.com/DeLoWaN/openfront-autojoin-lobby
// @match        https://openfront.io/*
// @match        https://*.openfront.io/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // Tested and 100% functional against OpenFront v0.26.16

    // Configuration
    const CONFIG = {
        pollInterval: 1000, // ms - how often to check for matching lobbies
        rootURL: 'https://openfront.io/' // Root URL for lobby detection
    };

    // State
    let autoJoinEnabled = true; // Always start with ON (search active by default)
    let criteriaList = []; // List of criteria (can contain multiple modes)
    let monitoringInterval = null;
    let timerInterval = null;
    let gameInfoInterval = null; // Interval for updating current game info display
    let joinedLobbies = new Set(); // To avoid double joins
    let searchStartTime = null; // Timestamp when auto-join search started
    let gameFoundTime = null; // Timestamp when game was found (for fixed timer display)
    let isJoining = false; // Prevent concurrent join attempts
    let soundEnabled = true; // Sound notification enabled by default
    let recentlyLeftLobbyID = null; // Track lobby ID that was just left to prevent auto-rejoin
    let joinMode = 'notify'; // 'autojoin' or 'notify' - default to 'notify'
    let notifiedLobbies = new Set(); // Track lobbies we've notified about
    let lastNotifiedGameID = null; // Track the last game we notified about (for timer reset)
    let notificationTimeout = null; // Timeout for auto-dismissing notification
    let gameFoundAudio = null; // Preloaded audio for game found notification
    let gameStartAudio = null; // Preloaded audio for game start bell

    // Load settings from storage
    function loadSettings() {
        const saved = GM_getValue('autoJoinSettings', null);
        if (saved) {
            // Always keep search enabled
            autoJoinEnabled = true;
            criteriaList = saved.criteria || [];
            soundEnabled = saved.soundEnabled !== undefined ? saved.soundEnabled : true;
            joinMode = saved.joinMode || 'notify'; // Default to 'notify'
        }
    }

    // Save settings to storage
    function saveSettings() {
        GM_setValue('autoJoinSettings', {
            enabled: autoJoinEnabled,
            criteria: criteriaList,
            soundEnabled: soundEnabled,
            joinMode: joinMode
        });
    }

    // Update search timer display
    function updateSearchTimer() {
        const timerElement = document.getElementById('search-timer');
        if (!timerElement) return;

        if (!autoJoinEnabled || searchStartTime === null) {
            timerElement.style.display = 'none';
            gameFoundTime = null;
            return;
        }

        // If game was found, show fixed time
        if (gameFoundTime !== null) {
            const elapsed = Math.floor((gameFoundTime - searchStartTime) / 1000); // seconds
            const minutes = Math.floor(elapsed / 60);
            const seconds = elapsed % 60;
            timerElement.textContent = `Game found! (${minutes}m ${seconds}s)`;
            timerElement.style.display = 'inline';
            return;
        }

        // Otherwise show live timer
        const elapsed = Math.floor((Date.now() - searchStartTime) / 1000); // seconds
        const minutes = Math.floor(elapsed / 60);
        const seconds = elapsed % 60;

        timerElement.textContent = `Searching: ${minutes}m ${seconds}s`;
        timerElement.style.display = 'inline';
    }

    // Update current game info display (players per team for team games)
    function updateCurrentGameInfo() {
        const gameInfoElement = document.getElementById('current-game-info');
        if (!gameInfoElement) return;

        // Only show on lobby page
        if (!isOnLobbyPage()) {
            gameInfoElement.style.display = 'none';
            return;
        }

        // Always show the element to avoid resizing, but update content based on game type
        gameInfoElement.style.display = 'block';

        // Get current displayed lobby
        const publicLobby = document.querySelector('public-lobby');
        if (!publicLobby || !publicLobby.lobbies || !Array.isArray(publicLobby.lobbies) || publicLobby.lobbies.length === 0) {
            gameInfoElement.textContent = 'Current game: No game';
            gameInfoElement.classList.add('not-applicable');
            return;
        }

        const currentLobby = publicLobby.lobbies[0];
        if (!currentLobby || !currentLobby.gameConfig) {
            gameInfoElement.textContent = 'Current game: No game';
            gameInfoElement.classList.add('not-applicable');
            return;
        }

        const config = currentLobby.gameConfig;
        
        // Check if it's a team game
        if (config.gameMode !== 'Team') {
            gameInfoElement.textContent = 'Current game: Not a team game';
            gameInfoElement.classList.add('not-applicable');
            return;
        }

        // Get game capacity
        const gameCapacity = currentLobby.maxClients || config.maxClients || config.maxPlayers || null;
        if (gameCapacity === null) {
            gameInfoElement.textContent = 'Current game: Capacity unknown';
            gameInfoElement.classList.add('not-applicable');
            return;
        }

        // Get team configuration
        const playerTeams = config.playerTeams;
        
        // Display the info for team games
        // For Duos/Trios/Quads, show the mode name directly
        if (playerTeams === 'Duos') {
            gameInfoElement.textContent = 'Current game: Duos';
            gameInfoElement.classList.remove('not-applicable');
        } else if (playerTeams === 'Trios') {
            gameInfoElement.textContent = 'Current game: Trios';
            gameInfoElement.classList.remove('not-applicable');
        } else if (playerTeams === 'Quads') {
            gameInfoElement.textContent = 'Current game: Quads';
            gameInfoElement.classList.remove('not-applicable');
        } else if (typeof playerTeams === 'number') {
            // For modes with number of teams, show players per team
            const playersPerTeam = getPlayersPerTeam(playerTeams, gameCapacity);
            if (playersPerTeam === null) {
                gameInfoElement.textContent = 'Current game: Team configuration unknown';
                gameInfoElement.classList.add('not-applicable');
                return;
            }
            gameInfoElement.textContent = `Current game: ${playersPerTeam} players per team (${playerTeams} teams)`;
            gameInfoElement.classList.remove('not-applicable');
        } else {
            gameInfoElement.textContent = 'Current game: Team configuration unknown';
            gameInfoElement.classList.add('not-applicable');
        }
    }

    // Helper function to get players per team from playerTeams
    // When playerTeams is a string like 'Quads', 'Trios', 'Duos', it represents players per team directly
    // When playerTeams is a number, it represents the number of teams, so we calculate players per team
    function getPlayersPerTeam(playerTeams, gameCapacity) {
        // String values represent players per team directly
        if (playerTeams === 'Duos') return 2;
        if (playerTeams === 'Trios') return 3;
        if (playerTeams === 'Quads') return 4;
        // Number values represent the number of teams
        if (typeof playerTeams === 'number' && playerTeams > 0) {
            return Math.floor(gameCapacity / playerTeams);
        }
        return null;
    }

    // Check if lobby matches criteria
    function matchesCriteria(lobby, criteriaList) {
        if (!lobby || !lobby.gameConfig || !criteriaList || criteriaList.length === 0) {
            return false;
        }

        const config = lobby.gameConfig;
        // Get game capacity (max players the game can hold)
        // Try common property names for capacity
        const gameCapacity = lobby.maxClients || config.maxClients || config.maxPlayers || null;

        // Check if lobby matches at least one criterion
        for (const criteria of criteriaList) {
            let matches = false;

            if (criteria.gameMode === 'FFA') {
                if (config.gameMode !== 'Free For All') {
                    continue; // Move to next criterion
                }
                matches = true;
            } else if (criteria.gameMode === 'Team') {
                if (config.gameMode !== 'Team') {
                    continue;
                }

                // If a team count is specified, check it
                if (criteria.teamCount !== null && criteria.teamCount !== undefined) {
                    const playerTeams = config.playerTeams;

                    // Handle special modes
                    // Note: Duos/Trios/Quads are game modes (players per team), NOT team counts
                    // They should only match their exact string values, not numeric team counts
                    if (criteria.teamCount === 'Duos') {
                        if (playerTeams !== 'Duos') {
                            continue;
                        }
                    } else if (criteria.teamCount === 'Trios') {
                        if (playerTeams !== 'Trios') {
                            continue;
                        }
                    } else if (criteria.teamCount === 'Quads') {
                        if (playerTeams !== 'Quads') {
                            continue;
                        }
                    } else if (typeof criteria.teamCount === 'number') {
                        if (playerTeams !== criteria.teamCount) {
                            continue;
                        }
                    }
                }

                matches = true;
            }

            // If mode matches, check player filters
            if (matches) {
                if (criteria.gameMode === 'FFA') {
                    // For FFA, check based on game capacity (total players)
                    if (gameCapacity === null) {
                        // No capacity info available, skip capacity filtering
                        return true;
                    }

                    // Check minPlayers (minimum game capacity)
                    if (criteria.minPlayers !== null && criteria.minPlayers !== undefined) {
                        if (gameCapacity < criteria.minPlayers) {
                            continue; // Game capacity is less than minimum required
                        }
                    }

                    // Check maxPlayers (maximum game capacity)
                    if (criteria.maxPlayers !== null && criteria.maxPlayers !== undefined) {
                        if (gameCapacity > criteria.maxPlayers) {
                            continue; // Game capacity exceeds maximum allowed
                        }
                    }
                } else if (criteria.gameMode === 'Team') {
                    // For Team games, check based on players per team
                    // BUT: Duos/Trios/Quads always have 2/3/4 players per team by definition,
                    // so we skip the players per team filter for these modes
                    const playerTeams = config.playerTeams;
                    const isFixedPlayersPerTeam = playerTeams === 'Duos' || playerTeams === 'Trios' || playerTeams === 'Quads';
                    
                    // Only apply players per team filter for modes with number of teams (2/3/4/5/6/7 teams)
                    if (!isFixedPlayersPerTeam) {
                        if (gameCapacity === null) {
                            // No capacity info available, skip capacity filtering
                            return true;
                        }

                        // Get players per team
                        const playersPerTeam = getPlayersPerTeam(playerTeams, gameCapacity);
                        
                        if (playersPerTeam === null) {
                            // Cannot determine players per team, skip capacity filtering
                            return true;
                        }

                        // Check minPlayers (minimum players per team)
                        if (criteria.minPlayers !== null && criteria.minPlayers !== undefined) {
                            if (playersPerTeam < criteria.minPlayers) {
                                continue; // Players per team is less than minimum required
                            }
                        }

                        // Check maxPlayers (maximum players per team)
                        if (criteria.maxPlayers !== null && criteria.maxPlayers !== undefined) {
                            if (playersPerTeam > criteria.maxPlayers) {
                                continue; // Players per team exceeds maximum allowed
                            }
                        }
                    }
                    // For Duos/Trios/Quads, skip players per team filtering (always 2/3/4 by definition)
                }

                // All criteria satisfied
                return true;
            }
        }

        // No criterion matches
        return false;
    }

    // Helper function to check if we're on the lobby page (allowing hash/path variations)
    function isOnLobbyPage() {
        try {
            // Parse the configured root URL
            const configUrl = new URL(CONFIG.rootURL);
            const configOrigin = configUrl.origin;
            const configHostname = configUrl.hostname;

            // Get current location base
            const currentOrigin = window.location.origin;
            const currentHostname = window.location.hostname;

            // Check if origins/hostnames match
            if (currentOrigin !== configOrigin && currentHostname !== configHostname) {
                return false;
            }

            // Get current pathname and hash (normalize)
            const currentPath = window.location.pathname;
            const currentHash = window.location.hash;

            // Normalize path: remove trailing slashes and check if it's root or lobby-related
            const normalizedPath = currentPath.replace(/\/+$/, '') || '/';
            const isRootPath = normalizedPath === '/';

            // Check if path is a lobby-related path suffix (e.g., /public-lobby)
            const isLobbyPath = normalizedPath === '/public-lobby' || normalizedPath.startsWith('/public-lobby/');

            // Allow hash fragments like #/public-lobby or empty hash
            const isLobbyHash = !currentHash || currentHash === '#' || currentHash === '#/public-lobby' || currentHash.startsWith('#/public-lobby/');

            // Consider it the lobby page if:
            // 1. Origin/hostname matches AND
            // 2. Either:
            //    - Path is root/empty AND hash is empty or lobby-related, OR
            //    - Path is a lobby path (path-based routing, hash doesn't matter)
            return (isRootPath && isLobbyHash) || isLobbyPath;
        } catch (error) {
            // Fallback to strict comparison if URL parsing fails
            console.warn('[Auto-Join] Error checking lobby page, using fallback:', error);
            return location.href === CONFIG.rootURL;
        }
    }

    // Check lobbies and auto-join if match found
    async function checkLobbies() {
        try {
            // Always update current game info display
            updateCurrentGameInfo();

            // Prevent concurrent join attempts
            if (isJoining) {
                return;
            }

            // Check if we have criteria
            if (!criteriaList || criteriaList.length === 0) {
                console.warn('[Auto-Join] No criteria configured. Please select at least one game mode.');
                return;
            }

            // Check we're not already in a game
            if (!isOnLobbyPage()) {
                return;
            }

            // Get lobbies from the public-lobby component
            const publicLobby = document.querySelector('public-lobby');
            if (!publicLobby || !publicLobby.lobbies || !Array.isArray(publicLobby.lobbies)) {
                return; // Component not ready yet
            }
            const lobbies = publicLobby.lobbies;

            // In notify mode, check if we need to reset the timer when a new game appears
            if (joinMode === 'notify' && gameFoundTime !== null && lastNotifiedGameID !== null) {
                // Check if the currently displayed lobby is different from the one we notified about
                const currentLobby = lobbies.length > 0 ? lobbies[0] : null;
                const currentLobbyID = currentLobby ? currentLobby.gameID : null;
                
                // If current lobby is different from the one we notified about, reset timer and continue searching
                if (currentLobbyID !== lastNotifiedGameID) {
                    gameFoundTime = null;
                    lastNotifiedGameID = null;
                    // Restart timer if it was stopped
                    if (!timerInterval) {
                        timerInterval = setInterval(() => {
                            updateSearchTimer();
                        }, 1000);
                    }
                    updateSearchTimer();
                    updateUI({ status: null });
                }
            }

            // Search for a lobby matching criteria
            for (const lobby of lobbies) {

                if (matchesCriteria(lobby, criteriaList)) {
                    // Skip if this is the lobby we just left (permanently block auto-rejoin)
                    if (recentlyLeftLobbyID === lobby.gameID) {
                        continue;
                    }
                    
                    if (joinMode === 'notify') {
                        // Notify mode: show notification instead of joining
                        if (!notifiedLobbies.has(lobby.gameID)) {
                            showGameFoundNotification(lobby);
                            playGameFoundSound(); // Chime when match is found
                            notifiedLobbies.add(lobby.gameID);
                            // Mark that game was found and stop timer updates
                            gameFoundTime = Date.now();
                            lastNotifiedGameID = lobby.gameID;
                            stopTimer();
                            updateSearchTimer();
                            // Update UI status
                            updateUI({ status: 'found', gameID: lobby.gameID });
                            return; // Only notify about one lobby at a time
                        }
                    } else {
                        // Auto-join mode: existing behavior
                        // Check we haven't already joined this lobby
                        if (!joinedLobbies.has(lobby.gameID)) {
                            joinLobby(lobby);
                            joinedLobbies.add(lobby.gameID);
                            // Clear recently left lobby ID since we're joining a different one
                            recentlyLeftLobbyID = null;
                            return; // Only join one lobby at a time
                        }
                    }
                }
            }
        } catch (error) {
            console.error('[Auto-Join] Error checking lobbies:', error);
        }
    }


    // Preload audio files for better performance
    function preloadSounds() {
        try {
            gameFoundAudio = new Audio('https://github.com/DeLoWaN/openfront-autojoin-lobby/raw/refs/heads/main/notification_sounds/new-notification-014-363678.mp3');
            gameFoundAudio.volume = 0.5;
            gameFoundAudio.preload = 'auto';
            
            gameStartAudio = new Audio('https://github.com/DeLoWaN/openfront-autojoin-lobby/raw/refs/heads/main/notification_sounds/opening-bell-421471.mp3');
            gameStartAudio.volume = 0.5;
            gameStartAudio.preload = 'auto';
            
        } catch (error) {
            console.warn('[Auto-Join] Could not preload audio files:', error);
        }
    }

    // Play sound notification (chime) when a game is found
    function playGameFoundSound() {
        if (!soundEnabled || !gameFoundAudio) return;

        try {
            gameFoundAudio.currentTime = 0; // Reset to start
            gameFoundAudio.play().catch(error => {
                console.warn('[Auto-Join] Could not play game found sound:', error);
            });
        } catch (error) {
            console.warn('[Auto-Join] Could not play game found sound:', error);
        }
    }

    // Play boxing ring bell sound when game starts/joins
    function playGameStartSound() {
        if (!soundEnabled || !gameStartAudio) return;

        try {
            gameStartAudio.currentTime = 0; // Reset to start
            gameStartAudio.play().catch(error => {
                console.warn('[Auto-Join] Could not play game start sound:', error);
            });
        } catch (error) {
            console.warn('[Auto-Join] Could not play game start sound:', error);
        }
    }

    // Get formatted game details text for notification
    function getGameDetailsText(lobby) {
        if (!lobby || !lobby.gameConfig) {
            return 'Game Found!';
        }

        const config = lobby.gameConfig;
        const gameCapacity = lobby.maxClients || config.maxClients || config.maxPlayers || null;

        if (config.gameMode === 'Free For All') {
            if (gameCapacity !== null) {
                return `Game Found! FFA - ${gameCapacity} players`;
            }
            return 'Game Found! FFA';
        } else if (config.gameMode === 'Team') {
            const playerTeams = config.playerTeams;
            
            let teamCountText = '';
            if (playerTeams === 'Duos') {
                teamCountText = 'Duos';
            } else if (playerTeams === 'Trios') {
                teamCountText = 'Trios';
            } else if (playerTeams === 'Quads') {
                teamCountText = 'Quads';
            } else if (typeof playerTeams === 'number') {
                teamCountText = `${playerTeams} teams`;
            } else {
                teamCountText = 'Team';
            }

            // For Duos/Trios/Quads, just show the mode name (always 2/3/4 players per team)
            if (playerTeams === 'Duos' || playerTeams === 'Trios' || playerTeams === 'Quads') {
                return `Game Found! Team (${teamCountText})`;
            }
            
            // For modes with number of teams, show players per team
            if (typeof playerTeams === 'number' && gameCapacity !== null) {
                const playersPerTeam = getPlayersPerTeam(playerTeams, gameCapacity);
                if (playersPerTeam !== null) {
                    return `Game Found! Team (${teamCountText}) - ${playersPerTeam} players per team`;
                }
            }
            
            return `Game Found! Team (${teamCountText})`;
        }

        return 'Game Found!';
    }

    // Dismiss notification
    function dismissNotification(targetElement = null) {
        const elements = targetElement
            ? [targetElement]
            : Array.from(document.querySelectorAll('.game-found-notification'));

        if (elements.length === 0) {
            if (notificationTimeout) {
                clearTimeout(notificationTimeout);
                notificationTimeout = null;
            }
            return;
        }

        elements.forEach(element => {
            if (!element.classList.contains('notification-dismissing')) {
                element.classList.add('notification-dismissing');
            }
            setTimeout(() => element.remove(), 300);
        });

        if (notificationTimeout) {
            clearTimeout(notificationTimeout);
            notificationTimeout = null;
        }
    }

    // Show game found notification
    function showGameFoundNotification(lobby) {
        // If notification already exists, dismiss it first (show only latest)
        const existingNotification = document.getElementById('game-found-notification');
        if (existingNotification) {
            dismissNotification(existingNotification);
            // Wait a bit for the dismiss animation
            setTimeout(() => {
                createNewNotification(lobby);
            }, 50);
        } else {
            createNewNotification(lobby);
        }
    }

    // Create new notification element
    function createNewNotification(lobby) {
        const notification = document.createElement('div');
        notification.id = 'game-found-notification';
        notification.className = 'game-found-notification';
        
        const message = getGameDetailsText(lobby);
        notification.textContent = message;
        
        // Click to dismiss
        notification.addEventListener('click', () => dismissNotification(notification));
        
        // Add to body
        document.body.appendChild(notification);
        
        // Trigger animation
        setTimeout(() => {
            notification.classList.add('notification-visible');
        }, 10);
        
        // Auto-dismiss after 10 seconds
        notificationTimeout = setTimeout(() => {
            dismissNotification(notification);
        }, 10000);
    }

    // Join a lobby - only join if we can click the button (for visual feedback)
    function joinLobby(lobby) {
        // Prevent concurrent joins
        if (isJoining) {
            return;
        }

        // Try to get the public lobby component and check if the lobby matches
        const publicLobby = document.querySelector('public-lobby');
        const lobbyButton = publicLobby?.querySelector('button');

        // Only join if we can click the button (lobby must be displayed and button enabled)
        if (!publicLobby || !lobbyButton || lobbyButton.disabled) {
            // Remove from joinedLobbies so we can retry later
            joinedLobbies.delete(lobby.gameID);
            return;
        }

        // Get current displayed lobby (public-lobby shows lobbies[0])
        const currentLobby = publicLobby.lobbies?.[0];
        if (!currentLobby || currentLobby.gameID !== lobby.gameID) {
            // Remove from joinedLobbies so we can retry when it becomes visible
            joinedLobbies.delete(lobby.gameID);
            return;
        }

        // All conditions met - proceed with join
        isJoining = true;

        // Mark that game was found and stop timer updates
        gameFoundTime = Date.now();
        stopTimer();
        updateSearchTimer();

        // Play sound notification (chime when match is found)
        playGameFoundSound();

        // Click the button - component will handle join with visual feedback (green highlight)
        lobbyButton.click();

        // Update UI to indicate we joined
        updateUI({ status: 'joined', gameID: lobby.gameID });

        // Reset joining flag after a delay (allows time for join to process)
        setTimeout(() => {
            isJoining = false;
        }, 2000);
    }

    // Try to auto-start search if conditions are met
    function tryAutoStartSearch() {
        // Only auto-start if:
        // 1. We're in lobby
        // 2. Search is enabled
        // 3. We have valid criteria
        // 4. We're not already monitoring
        if (!isOnLobbyPage()) {
            return;
        }

        if (!autoJoinEnabled) {
            return;
        }

        // Build criteria from UI
        criteriaList = buildCriteriaFromUI();

        if (!criteriaList || criteriaList.length === 0) {
            return;
        }

        // Start monitoring if not already running
        if (!monitoringInterval) {
            startMonitoring();
            saveSettings();
            updateUI();
        }
    }

    // Start monitoring lobbies
    function startMonitoring() {
        if (monitoringInterval) return; // Already monitoring

        searchStartTime = Date.now();
        gameFoundTime = null; // Reset game found time
        lastNotifiedGameID = null; // Reset last notified game ID
        // Clear notified lobbies when starting new search
        notifiedLobbies.clear();
        updateSearchTimer();

        // Update timer display every second
        timerInterval = setInterval(() => {
            updateSearchTimer();
        }, 1000);

        // Start checking lobbies
        monitoringInterval = setInterval(checkLobbies, CONFIG.pollInterval);
    }

    // Stop monitoring lobbies
    function stopMonitoring() {
        if (monitoringInterval) {
            clearInterval(monitoringInterval);
            monitoringInterval = null;
        }
        stopTimer();
        searchStartTime = null;
        gameFoundTime = null;
        lastNotifiedGameID = null;
        updateSearchTimer();
        // Dismiss any active notification
        dismissNotification();
        // Clear notified lobbies
        notifiedLobbies.clear();
        // Reset status to inactive when stopping
        updateUI({ status: null });
    }

    // Stop timer interval
    function stopTimer() {
        if (timerInterval) {
            clearInterval(timerInterval);
            timerInterval = null;
        }
    }

    // Start game info update interval
    function startGameInfoUpdates() {
        if (gameInfoInterval) return; // Already running
        
        // Update immediately
        updateCurrentGameInfo();
        
        // Update every second
        gameInfoInterval = setInterval(() => {
            updateCurrentGameInfo();
        }, 1000);
    }

    // Stop game info update interval
    function stopGameInfoUpdates() {
        if (gameInfoInterval) {
            clearInterval(gameInfoInterval);
            gameInfoInterval = null;
        }
        // Hide the display
        const gameInfoElement = document.getElementById('current-game-info');
        if (gameInfoElement) {
            gameInfoElement.style.display = 'none';
        }
    }

    // Update slider visual fill and sync with hidden inputs
    function updateSliderRange(minSliderId, maxSliderId, minInputId, maxInputId, fillId, minValueId, maxValueId) {
        const minSlider = document.getElementById(minSliderId);
        const maxSlider = document.getElementById(maxSliderId);
        const minInput = document.getElementById(minInputId);
        const maxInput = document.getElementById(maxInputId);
        const fill = document.getElementById(fillId);
        const minValueDisplay = document.getElementById(minValueId);
        const maxValueDisplay = document.getElementById(maxValueId);

        if (!minSlider || !maxSlider || !fill) return;

        const minVal = parseInt(minSlider.value, 10);
        const maxVal = parseInt(maxSlider.value, 10);
        const min = parseInt(minSlider.min, 10);
        const max = parseInt(minSlider.max, 10);

        // Ensure min <= max
        if (minVal > maxVal) {
            if (document.activeElement === minSlider) {
                maxSlider.value = minVal;
                if (maxInput) maxInput.value = minVal === max ? '' : minVal;
            } else {
                minSlider.value = maxVal;
                if (minInput) minInput.value = maxVal === min ? '' : maxVal;
            }
            return updateSliderRange(minSliderId, maxSliderId, minInputId, maxInputId, fillId, minValueId, maxValueId);
        }

        // Calculate fill position and width
        const minPercent = ((minVal - min) / (max - min)) * 100;
        const maxPercent = ((maxVal - min) / (max - min)) * 100;

        fill.style.left = minPercent + '%';
        fill.style.width = (maxPercent - minPercent) + '%';

        // Update hidden inputs
        if (minInput) {
            minInput.value = minVal === min ? '' : minVal;
        }
        if (maxInput) {
            maxInput.value = maxVal === max ? '' : maxVal;
        }

        // Update displayed values
        if (minValueDisplay) {
            minValueDisplay.textContent = minVal === min ? 'Any' : minVal;
        }
        if (maxValueDisplay) {
            maxValueDisplay.textContent = maxVal === max ? 'Any' : maxVal;
        }
    }

    // Initialize slider from hidden input values
    function initializeSlider(minSliderId, maxSliderId, minInputId, maxInputId, fillId, minValueId, maxValueId) {
        const minSlider = document.getElementById(minSliderId);
        const maxSlider = document.getElementById(maxSliderId);
        const minInput = document.getElementById(minInputId);
        const maxInput = document.getElementById(maxInputId);

        if (!minSlider || !maxSlider) return;

        const min = parseInt(minSlider.min, 10);
        const max = parseInt(minSlider.max, 10);

        // Get values from hidden inputs or use defaults
        let minVal = min;
        let maxVal = max;

        if (minInput && minInput.value) {
            minVal = Math.max(min, Math.min(max, parseInt(minInput.value, 10)));
        }
        if (maxInput && maxInput.value) {
            maxVal = Math.max(min, Math.min(max, parseInt(maxInput.value, 10)));
        }

        // Ensure min <= max
        if (minVal > maxVal) {
            minVal = maxVal;
        }

        minSlider.value = minVal;
        maxSlider.value = maxVal;

        updateSliderRange(minSliderId, maxSliderId, minInputId, maxInputId, fillId, minValueId, maxValueId);
    }

    // Get number value from input
    function getNumberValue(id) {
        const input = document.getElementById(id);
        if (!input || !input.value) return null;
        const value = parseInt(input.value, 10);
        return isNaN(value) ? null : value;
    }

    // Helper function to update mode label styles
    function updateModeLabels(isAutoJoin) {
        const labelLeft = document.querySelector('.mode-label-left');
        const labelRight = document.querySelector('.mode-label-right');
        if (labelLeft && labelRight) {
            if (isAutoJoin) {
                labelLeft.style.opacity = '0.6';
                labelLeft.style.color = 'rgba(255, 255, 255, 0.7)';
                labelRight.style.opacity = '1';
                labelRight.style.color = 'rgba(255, 255, 255, 0.95)';
            } else {
                labelLeft.style.opacity = '1';
                labelLeft.style.color = 'rgba(255, 255, 255, 0.95)';
                labelRight.style.opacity = '0.6';
                labelRight.style.color = 'rgba(255, 255, 255, 0.7)';
            }
        }
    }

    // Get all selected team counts from UI (returns array)
    function getAllTeamCountValues() {
        const selectedCounts = [];
        const teamCountIds = [
            { id: 'autojoin-team-duos', value: 'Duos' },
            { id: 'autojoin-team-trios', value: 'Trios' },
            { id: 'autojoin-team-quads', value: 'Quads' },
            { id: 'autojoin-team-2', value: 2 },
            { id: 'autojoin-team-3', value: 3 },
            { id: 'autojoin-team-4', value: 4 },
            { id: 'autojoin-team-5', value: 5 },
            { id: 'autojoin-team-6', value: 6 },
            { id: 'autojoin-team-7', value: 7 }
        ];

        teamCountIds.forEach(({ id, value }) => {
            const checkbox = document.getElementById(id);
            if (checkbox && checkbox.checked) {
                selectedCounts.push(value);
            }
        });

        return selectedCounts.length > 0 ? selectedCounts : null;
    }

    // Helper function for select/deselect all team counts
    function setAllTeamCounts(checked) {
        const checkboxes = [
            'autojoin-team-2', 'autojoin-team-3', 'autojoin-team-4', 'autojoin-team-5',
            'autojoin-team-6', 'autojoin-team-7', 'autojoin-team-duos', 'autojoin-team-trios',
            'autojoin-team-quads'
        ];
        checkboxes.forEach(id => {
            const checkbox = document.getElementById(id);
            if (checkbox) checkbox.checked = checked;
        });
    }

    // Build criteria list from UI
    function buildCriteriaFromUI() {
        const criteriaList = [];

        // Check FFA
        const ffaChecked = document.getElementById('autojoin-ffa').checked;
        if (ffaChecked) {
            const ffaCriteria = {
                gameMode: 'FFA',
                minPlayers: getNumberValue('autojoin-ffa-min') || null,
                maxPlayers: getNumberValue('autojoin-ffa-max') || null
            };
            criteriaList.push(ffaCriteria);
        }

        // Check Team
        const teamChecked = document.getElementById('autojoin-team').checked;
        if (teamChecked) {
            const selectedTeamCounts = getAllTeamCountValues();
            const minPlayers = getNumberValue('autojoin-team-min') || null;
            const maxPlayers = getNumberValue('autojoin-team-max') || null;

            if (selectedTeamCounts === null) {
                // No specific team counts selected, create one criteria that accepts all Team modes
                const teamCriteria = {
                    gameMode: 'Team',
                    teamCount: null,
                    minPlayers: minPlayers,
                    maxPlayers: maxPlayers
                };
                criteriaList.push(teamCriteria);
            } else {
                // Create a separate criteria for each selected team count
                for (const teamCount of selectedTeamCounts) {
                    // Duos/Trios/Quads are game modes with fixed players per team (2/3/4)
                    // Player per team filters do NOT apply to these modes
                    const isFixedPlayersPerTeam = teamCount === 'Duos' || teamCount === 'Trios' || teamCount === 'Quads';
                    
                    const teamCriteria = {
                        gameMode: 'Team',
                        teamCount: teamCount,
                        // Only include player filters for modes with variable team counts (2/3/4/5/6/7 teams)
                        minPlayers: isFixedPlayersPerTeam ? null : minPlayers,
                        maxPlayers: isFixedPlayersPerTeam ? null : maxPlayers
                    };
                    criteriaList.push(teamCriteria);
                }
            }
        }

        return criteriaList;
    }

    // Update UI based on state
    function updateUI(options = {}) {
        const statusIndicator = document.querySelector('#autojoin-status .status-indicator');
        const statusText = document.querySelector('#autojoin-status .status-text');

        if (statusIndicator) {
            // Always show as active (search is always on)
            statusIndicator.classList.add('active');
        }

        if (statusText) {
            // Show "Joined" if explicitly set
            if (options.status === 'joined') {
                statusText.textContent = 'Joined';
            } else {
                // In notify mode, always show "Searching" (timer will show "Game found!" when applicable)
                statusText.textContent = joinMode === 'notify' ? 'Searching' : 'Active';
            }
        }

        updateSearchTimer();
    }

    // Create UI
    function createUI() {
        // Check if UI already exists
        if (document.getElementById('openfront-autojoin-panel')) {
            return;
        }

        const panel = document.createElement('div');
        panel.id = 'openfront-autojoin-panel';
        panel.className = 'autojoin-panel';
        panel.innerHTML = `
            <div class="autojoin-header" id="autojoin-header-drag">
                <h3>Auto-Join Lobby</h3>
            </div>

            <div class="autojoin-content">
                <!-- Mode Selector -->
                <div class="join-mode-selector">
                    <span class="mode-label-left">Notify Only</span>
                    <label class="mode-toggle-switch">
                        <input type="checkbox" id="join-mode-toggle">
                        <span class="mode-toggle-slider"></span>
                    </label>
                    <span class="mode-label-right">Auto-Join</span>
                </div>

                <!-- FFA Section -->
                <div class="autojoin-mode-section">
                    <label class="mode-checkbox-label">
                        <input type="checkbox" id="autojoin-ffa" name="gameMode" value="FFA">
                        <span>FFA</span>
                    </label>

                    <div class="autojoin-mode-config" id="ffa-config" style="display: none;">
                        <div class="player-filter-info">
                            <small>Filter by max players:</small>
                        </div>
                        <div class="capacity-range-wrapper">
                            <div class="capacity-range-visual">
                                <div class="capacity-track">
                                    <div class="capacity-range-fill" id="ffa-range-fill"></div>
                                    <input type="range" id="autojoin-ffa-min-slider" min="1" max="100" value="1" class="capacity-slider capacity-slider-min">
                                    <input type="range" id="autojoin-ffa-max-slider" min="1" max="100" value="100" class="capacity-slider capacity-slider-max">
                                </div>
                                <div class="capacity-labels">
                                    <div class="capacity-label-group">
                                        <label for="autojoin-ffa-min-slider">Min:</label>
                                        <span class="capacity-value" id="ffa-min-value">Any</span>
                                    </div>
                                    <div class="capacity-label-group">
                                        <label for="autojoin-ffa-max-slider">Max:</label>
                                        <span class="capacity-value" id="ffa-max-value">Any</span>
                                    </div>
                                </div>
                            </div>
                            <div class="capacity-inputs-hidden">
                                <input type="number" id="autojoin-ffa-min" min="1" max="100" style="display: none;">
                                <input type="number" id="autojoin-ffa-max" min="1" max="100" style="display: none;">
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Team Section -->
                <div class="autojoin-mode-section">
                    <label class="mode-checkbox-label">
                        <input type="checkbox" id="autojoin-team" name="gameMode" value="Team">
                        <span>Team</span>
                    </label>

                    <div class="autojoin-mode-config" id="team-config" style="display: none;">
                        <!-- Fixed Team Modes (Duos/Trios/Quads) - No player filters -->
                        <div class="team-mode-panel fixed-modes-panel">
                            <div class="panel-header">
                                <label style="display: block; margin-bottom: 6px; font-size: 0.9em; font-weight: 600;">Fixed Team Modes (Duos/Trios/Quads):</label>
                                <div style="display: flex; gap: 4px; margin-bottom: 6px;">
                                    <button type="button" id="autojoin-fixed-select-all" class="select-all-btn">Select All</button>
                                    <button type="button" id="autojoin-fixed-deselect-all" class="select-all-btn">Deselect All</button>
                                </div>
                            </div>
                            <div class="team-count-options">
                                <label><input type="checkbox" id="autojoin-team-duos" value="Duos"> Duos</label>
                                <label><input type="checkbox" id="autojoin-team-trios" value="Trios"> Trios</label>
                                <label><input type="checkbox" id="autojoin-team-quads" value="Quads"> Quads</label>
                            </div>
                        </div>

                        <!-- Variable Team Counts (2-7 teams) - With player filters -->
                        <div class="team-mode-panel variable-modes-panel">
                            <div class="panel-header">
                                <label style="display: block; margin-bottom: 6px; font-size: 0.9em; font-weight: 600;">Variable Team Counts (2-7 teams):</label>
                                <div style="display: flex; gap: 4px; margin-bottom: 6px;">
                                    <button type="button" id="autojoin-variable-select-all" class="select-all-btn">Select All</button>
                                    <button type="button" id="autojoin-variable-deselect-all" class="select-all-btn">Deselect All</button>
                                </div>
                            </div>
                            <div class="team-count-options">
                                <label><input type="checkbox" id="autojoin-team-2" value="2"> 2 teams</label>
                                <label><input type="checkbox" id="autojoin-team-3" value="3"> 3 teams</label>
                                <label><input type="checkbox" id="autojoin-team-4" value="4"> 4 teams</label>
                                <label><input type="checkbox" id="autojoin-team-5" value="5"> 5 teams</label>
                                <label><input type="checkbox" id="autojoin-team-6" value="6"> 6 teams</label>
                                <label><input type="checkbox" id="autojoin-team-7" value="7"> 7 teams</label>
                            </div>
                            <div class="player-filter-info">
                                <small>Filter by players per team:</small>
                            </div>
                            <div class="capacity-range-wrapper">
                                <div class="capacity-range-visual">
                                    <div class="capacity-track">
                                        <div class="capacity-range-fill" id="team-range-fill"></div>
                                        <input type="range" id="autojoin-team-min-slider" min="0" max="50" value="0" class="capacity-slider capacity-slider-min">
                                        <input type="range" id="autojoin-team-max-slider" min="0" max="50" value="50" class="capacity-slider capacity-slider-max">
                                    </div>
                                    <div class="capacity-labels">
                                        <div class="capacity-label-group">
                                            <label for="autojoin-team-min-slider">Min:</label>
                                            <span class="capacity-value" id="team-min-value">Any</span>
                                        </div>
                                        <div class="capacity-label-group">
                                            <label for="autojoin-team-max-slider">Max:</label>
                                            <span class="capacity-value" id="team-max-value">Any</span>
                                        </div>
                                    </div>
                                </div>
                                <div class="capacity-inputs-hidden">
                                    <input type="number" id="autojoin-team-min" min="0" max="50" style="display: none;">
                                    <input type="number" id="autojoin-team-max" min="0" max="50" style="display: none;">
                                </div>
                            </div>
                        </div>
                        <div class="current-game-info" id="current-game-info" style="display: none;"></div>
                    </div>
                </div>

                <div class="autojoin-status" id="autojoin-status">
                    <span class="status-label">Status:</span>
                    <span class="status-indicator"></span>
                    <span class="status-text">Searching</span>
                    <span class="search-timer" id="search-timer" style="display: none;"></span>
                </div>

                <div class="autojoin-settings">
                    <label class="sound-toggle-label">
                        <input type="checkbox" id="autojoin-sound-toggle">
                        <span>🔔 Sound</span>
                    </label>
                </div>
            </div>
        `;

        document.body.appendChild(panel);

        // Make panel draggable
        makePanelDraggable(panel);

        // Setup event listeners
        setupEventListeners();

        // Load saved settings into UI
        loadUIFromSettings();

        // Initialize sliders (in case no saved settings)
        initializeSlider('autojoin-ffa-min-slider', 'autojoin-ffa-max-slider', 'autojoin-ffa-min', 'autojoin-ffa-max', 'ffa-range-fill', 'ffa-min-value', 'ffa-max-value');
        initializeSlider('autojoin-team-min-slider', 'autojoin-team-max-slider', 'autojoin-team-min', 'autojoin-team-max', 'team-range-fill', 'team-min-value', 'team-max-value');

        // Update UI
        updateUI();

        // Auto-start search if criteria exist and we're in lobby
        setTimeout(() => {
            tryAutoStartSearch();
        }, 500); // Small delay to ensure page is ready
    }

    // Make panel draggable
    function makePanelDraggable(panel) {
        const header = document.getElementById('autojoin-header-drag');
        if (!header) return;

        let isDragging = false;
        let currentX;
        let currentY;
        let initialX;
        let initialY;
        let xOffset = 0;
        let yOffset = 0;

        // Load saved position
        const savedPos = GM_getValue('autoJoinPanelPosition', null);
        if (savedPos) {
            panel.style.left = savedPos.x + 'px';
            panel.style.top = savedPos.y + 'px';
            xOffset = savedPos.x;
            yOffset = savedPos.y;
        }

        header.addEventListener('mousedown', dragStart);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', dragEnd);

        function dragStart(e) {

            initialX = e.clientX - xOffset;
            initialY = e.clientY - yOffset;

            if (e.target === header || header.contains(e.target)) {
                isDragging = true;
                header.style.cursor = 'grabbing';
            }
        }

        function drag(e) {
            if (isDragging) {
                e.preventDefault();
                currentX = e.clientX - initialX;
                currentY = e.clientY - initialY;

                xOffset = currentX;
                yOffset = currentY;

                panel.style.left = currentX + 'px';
                panel.style.top = currentY + 'px';
                panel.style.right = 'auto';
            }
        }

        function dragEnd(e) {
            initialX = currentX;
            initialY = currentY;
            isDragging = false;
            header.style.cursor = 'grab';

            // Save position
            GM_setValue('autoJoinPanelPosition', { x: xOffset, y: yOffset });
        }
    }

    // Setup event listeners
    function setupEventListeners() {

        // FFA checkbox - show/hide config
        const ffaCheckbox = document.getElementById('autojoin-ffa');
        const ffaConfig = document.getElementById('ffa-config');
        if (ffaCheckbox && ffaConfig) {
            ffaCheckbox.addEventListener('change', () => {
                ffaConfig.style.display = ffaCheckbox.checked ? 'block' : 'none';
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                // Auto-start search if criteria are valid and we're in lobby
                tryAutoStartSearch();
            });
        }

        // Team checkbox - show/hide config
        const teamCheckbox = document.getElementById('autojoin-team');
        const teamConfig = document.getElementById('team-config');
        if (teamCheckbox && teamConfig) {
            teamCheckbox.addEventListener('change', () => {
                teamConfig.style.display = teamCheckbox.checked ? 'block' : 'none';
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                // Auto-start search if criteria are valid and we're in lobby
                tryAutoStartSearch();
            });
        }

        // Select All / Deselect All buttons for fixed modes (Duos/Trios/Quads)
        const fixedSelectAllBtn = document.getElementById('autojoin-fixed-select-all');
        const fixedDeselectAllBtn = document.getElementById('autojoin-fixed-deselect-all');
        if (fixedSelectAllBtn) {
            fixedSelectAllBtn.addEventListener('click', () => {
                const checkboxes = ['autojoin-team-duos', 'autojoin-team-trios', 'autojoin-team-quads'];
                checkboxes.forEach(id => {
                    const checkbox = document.getElementById(id);
                    if (checkbox) checkbox.checked = true;
                });
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                tryAutoStartSearch();
            });
        }
        if (fixedDeselectAllBtn) {
            fixedDeselectAllBtn.addEventListener('click', () => {
                const checkboxes = ['autojoin-team-duos', 'autojoin-team-trios', 'autojoin-team-quads'];
                checkboxes.forEach(id => {
                    const checkbox = document.getElementById(id);
                    if (checkbox) checkbox.checked = false;
                });
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                tryAutoStartSearch();
            });
        }

        // Select All / Deselect All buttons for variable modes (2-7 teams)
        const variableSelectAllBtn = document.getElementById('autojoin-variable-select-all');
        const variableDeselectAllBtn = document.getElementById('autojoin-variable-deselect-all');
        if (variableSelectAllBtn) {
            variableSelectAllBtn.addEventListener('click', () => {
                const checkboxes = ['autojoin-team-2', 'autojoin-team-3', 'autojoin-team-4', 'autojoin-team-5', 'autojoin-team-6', 'autojoin-team-7'];
                checkboxes.forEach(id => {
                    const checkbox = document.getElementById(id);
                    if (checkbox) checkbox.checked = true;
                });
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                tryAutoStartSearch();
            });
        }
        if (variableDeselectAllBtn) {
            variableDeselectAllBtn.addEventListener('click', () => {
                const checkboxes = ['autojoin-team-2', 'autojoin-team-3', 'autojoin-team-4', 'autojoin-team-5', 'autojoin-team-6', 'autojoin-team-7'];
                checkboxes.forEach(id => {
                    const checkbox = document.getElementById(id);
                    if (checkbox) checkbox.checked = false;
                });
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                tryAutoStartSearch();
            });
        }

        // Listen to all team count checkbox changes
        const teamCountCheckboxes = [
            'autojoin-team-2', 'autojoin-team-3', 'autojoin-team-4', 'autojoin-team-5',
            'autojoin-team-6', 'autojoin-team-7', 'autojoin-team-duos', 'autojoin-team-trios',
            'autojoin-team-quads'
        ];
        teamCountCheckboxes.forEach(id => {
            const checkbox = document.getElementById(id);
            if (checkbox) {
                checkbox.addEventListener('change', () => {
                    criteriaList = buildCriteriaFromUI();
                    saveSettings();
                    tryAutoStartSearch();
                });
            }
        });

        // Setup slider event listeners
        const sliderPairs = [
            {
                minSlider: 'autojoin-ffa-min-slider',
                maxSlider: 'autojoin-ffa-max-slider',
                minInput: 'autojoin-ffa-min',
                maxInput: 'autojoin-ffa-max',
                fill: 'ffa-range-fill',
                minValue: 'ffa-min-value',
                maxValue: 'ffa-max-value'
            },
            {
                minSlider: 'autojoin-team-min-slider',
                maxSlider: 'autojoin-team-max-slider',
                minInput: 'autojoin-team-min',
                maxInput: 'autojoin-team-max',
                fill: 'team-range-fill',
                minValue: 'team-min-value',
                maxValue: 'team-max-value'
            }
        ];

        sliderPairs.forEach(pair => {
            const minSlider = document.getElementById(pair.minSlider);
            const maxSlider = document.getElementById(pair.maxSlider);

            if (minSlider) {
                minSlider.addEventListener('input', () => {
                    updateSliderRange(pair.minSlider, pair.maxSlider, pair.minInput, pair.maxInput, pair.fill, pair.minValue, pair.maxValue);
                    criteriaList = buildCriteriaFromUI();
                    saveSettings();
                    tryAutoStartSearch();
                });
            }

            if (maxSlider) {
                maxSlider.addEventListener('input', () => {
                    updateSliderRange(pair.minSlider, pair.maxSlider, pair.minInput, pair.maxInput, pair.fill, pair.minValue, pair.maxValue);
                    criteriaList = buildCriteriaFromUI();
                    saveSettings();
                    tryAutoStartSearch();
                });
            }
        });

        // Sound toggle
        const soundToggle = document.getElementById('autojoin-sound-toggle');
        if (soundToggle) {
            soundToggle.checked = soundEnabled;
            soundToggle.addEventListener('change', () => {
                soundEnabled = soundToggle.checked;
                saveSettings();
            });
        }

        // Mode selector toggle
        const modeToggle = document.getElementById('join-mode-toggle');
        if (modeToggle) {
            modeToggle.addEventListener('change', (e) => {
                joinMode = e.target.checked ? 'autojoin' : 'notify';
                updateModeLabels(e.target.checked);
                saveSettings();
                // Restart search if already running to apply mode change
                if (autoJoinEnabled && monitoringInterval) {
                    stopMonitoring();
                    startMonitoring();
                }
                // Update UI to reflect mode change
                updateUI();
            });
        }
        

        // Listen for leave-lobby event - restart search when user manually leaves
        document.addEventListener('leave-lobby', () => {
            if (autoJoinEnabled) {
                // Get the lobby ID that was just left
                // Try to get it from the current lobby display first
                let leftLobbyID = null;
                const publicLobby = document.querySelector('public-lobby');
                if (publicLobby && publicLobby.lobbies && publicLobby.lobbies.length > 0) {
                    const currentLobby = publicLobby.lobbies[0];
                    if (currentLobby && currentLobby.gameID) {
                        leftLobbyID = currentLobby.gameID;
                    }
                }
                // If not found in display, check joinedLobbies before clearing
                if (!leftLobbyID && joinedLobbies.size > 0) {
                    // Get the first (and likely only) lobby ID from the set
                    leftLobbyID = Array.from(joinedLobbies)[0];
                }
                if (leftLobbyID) {
                    recentlyLeftLobbyID = leftLobbyID;
                }
                // Clear joined lobbies to allow rejoining other lobbies
                joinedLobbies.clear();
                // Clear notified lobbies
                notifiedLobbies.clear();
                // Reset game found time
                gameFoundTime = null;
                lastNotifiedGameID = null;
                // Restart monitoring (this will reset the search timer)
                stopMonitoring();
                startMonitoring();
                saveSettings();
                updateUI();
            }
        });
    }

    // Load settings into UI
    function loadUIFromSettings() {
        // Load join mode
        const modeToggle = document.getElementById('join-mode-toggle');
        if (modeToggle) {
            modeToggle.checked = joinMode === 'autojoin';
            updateModeLabels(joinMode === 'autojoin');
        }

        // Load criteria into UI
        let teamCheckboxChecked = false;
        let teamMinPlayers = null;
        let teamMaxPlayers = null;

        for (const criteria of criteriaList) {
            if (criteria.gameMode === 'FFA') {
                const ffaCheckbox = document.getElementById('autojoin-ffa');
                if (ffaCheckbox) {
                    ffaCheckbox.checked = true;
                    const ffaConfig = document.getElementById('ffa-config');
                    if (ffaConfig) ffaConfig.style.display = 'block';

                    if (criteria.minPlayers) {
                        const minInput = document.getElementById('autojoin-ffa-min');
                        if (minInput) minInput.value = criteria.minPlayers;
                    }
                    if (criteria.maxPlayers) {
                        const maxInput = document.getElementById('autojoin-ffa-max');
                        if (maxInput) maxInput.value = criteria.maxPlayers;
                    }
                    // Initialize FFA slider from loaded values
                    initializeSlider('autojoin-ffa-min-slider', 'autojoin-ffa-max-slider', 'autojoin-ffa-min', 'autojoin-ffa-max', 'ffa-range-fill', 'ffa-min-value', 'ffa-max-value');
                }
            } else if (criteria.gameMode === 'Team') {
                // Mark team checkbox as checked (only once)
                if (!teamCheckboxChecked) {
                    const teamCheckbox = document.getElementById('autojoin-team');
                    if (teamCheckbox) {
                        teamCheckbox.checked = true;
                        teamCheckboxChecked = true;
                        const teamConfig = document.getElementById('team-config');
                        if (teamConfig) teamConfig.style.display = 'block';
                    }
                }

                // Store min/max players (they should be the same for all Team criteria)
                if (criteria.minPlayers !== null && criteria.minPlayers !== undefined) {
                    teamMinPlayers = criteria.minPlayers;
                }
                if (criteria.maxPlayers !== null && criteria.maxPlayers !== undefined) {
                    teamMaxPlayers = criteria.maxPlayers;
                }

                // Set team count checkbox (can have multiple)
                if (criteria.teamCount !== null && criteria.teamCount !== undefined) {
                    if (criteria.teamCount === 'Duos') {
                        const checkbox = document.getElementById('autojoin-team-duos');
                        if (checkbox) checkbox.checked = true;
                    } else if (criteria.teamCount === 'Trios') {
                        const checkbox = document.getElementById('autojoin-team-trios');
                        if (checkbox) checkbox.checked = true;
                    } else if (criteria.teamCount === 'Quads') {
                        const checkbox = document.getElementById('autojoin-team-quads');
                        if (checkbox) checkbox.checked = true;
                    } else if (typeof criteria.teamCount === 'number') {
                        const checkbox = document.getElementById(`autojoin-team-${criteria.teamCount}`);
                        if (checkbox) checkbox.checked = true;
                    }
                }
            }
        }

        // Set min/max players for Team (use the last values found, they should be consistent)
        if (teamCheckboxChecked) {
            if (teamMinPlayers !== null) {
                const minInput = document.getElementById('autojoin-team-min');
                if (minInput) minInput.value = teamMinPlayers;
            }
            if (teamMaxPlayers !== null) {
                const maxInput = document.getElementById('autojoin-team-max');
                if (maxInput) maxInput.value = teamMaxPlayers;
            }
            // Initialize Team slider from loaded values
            initializeSlider('autojoin-team-min-slider', 'autojoin-team-max-slider', 'autojoin-team-min', 'autojoin-team-max', 'team-range-fill', 'team-min-value', 'team-max-value');
        }
    }

    // Add CSS styles
    GM_addStyle(`
        .autojoin-panel {
            position: fixed;
            top: 20px;
            right: 20px;
            width: 460px;
            max-height: 90vh;
            overflow-y: auto;
            background: rgba(25, 25, 30, 0.85);
            border: 1px solid rgba(59, 130, 246, 0.4);
            border-radius: 8px;
            padding: 0;
            z-index: 10000;
            color: rgba(255, 255, 255, 0.9);
            font-family: sans-serif;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
            transition: opacity 0.3s ease, transform 0.3s ease;
        }

        .autojoin-panel.hidden {
            display: none;
        }

        .autojoin-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
            border-bottom: 1px solid rgba(59, 130, 246, 0.3);
            padding: 8px 12px 6px 12px;
            cursor: grab;
            user-select: none;
        }

        .autojoin-header:active {
            cursor: grabbing;
        }

        .autojoin-content {
            padding: 0 12px 10px 12px;
        }

        .autojoin-header h3 {
            margin: 0;
            font-size: 1.05em;
            color: rgba(255, 255, 255, 0.95);
        }


        .autojoin-mode-section {
            margin-bottom: 8px;
            padding: 6px;
            background: rgba(59, 130, 246, 0.08);
            border-radius: 4px;
        }

        .mode-checkbox-label {
            display: flex;
            align-items: center;
            gap: 6px;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 6px;
            font-size: 0.95em;
            color: rgba(255, 255, 255, 0.95);
        }

        .mode-checkbox-label input[type="checkbox"] {
            width: 18px;
            height: 18px;
            cursor: pointer;
        }

        .autojoin-mode-config {
            margin-left: 26px;
            margin-top: 6px;
            padding: 6px;
            background: rgba(0, 0, 0, 0.2);
            border-radius: 4px;
        }


        .player-filter-info {
            margin-bottom: 6px;
            padding: 3px 0;
        }

        .player-filter-info small {
            color: rgba(255, 255, 255, 0.75);
            font-size: 0.9em;
        }

        .capacity-range-wrapper {
            margin-top: 6px;
        }

        .capacity-range-visual {
            position: relative;
            padding: 12px 0 6px 0;
        }

        .capacity-track {
            position: relative;
            height: 6px;
            background: rgba(59, 130, 246, 0.2);
            border-radius: 3px;
            margin-bottom: 8px;
        }

        .capacity-range-fill {
            position: absolute;
            height: 100%;
            background: rgba(59, 130, 246, 0.5);
            border-radius: 3px;
            pointer-events: none;
            opacity: 0.7;
            transition: left 0.1s ease, width 0.1s ease;
        }

        .capacity-slider {
            position: absolute;
            width: 100%;
            height: 6px;
            top: 0;
            left: 0;
            background: transparent;
            outline: none;
            -webkit-appearance: none;
            pointer-events: none;
            margin: 0;
        }

        .capacity-slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 18px;
            height: 18px;
            border-radius: 50%;
            background: rgba(59, 130, 246, 0.8);
            cursor: pointer;
            pointer-events: all;
            border: 2px solid rgba(255, 255, 255, 0.9);
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
            transition: transform 0.1s ease;
        }

        .capacity-slider::-webkit-slider-thumb:hover {
            transform: scale(1.1);
            background: rgba(96, 165, 250, 0.9);
        }

        .capacity-slider::-moz-range-thumb {
            width: 18px;
            height: 18px;
            border-radius: 50%;
            background: rgba(59, 130, 246, 0.8);
            cursor: pointer;
            pointer-events: all;
            border: 2px solid rgba(255, 255, 255, 0.9);
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
            transition: transform 0.1s ease;
        }

        .capacity-slider::-moz-range-thumb:hover {
            transform: scale(1.1);
            background: rgba(96, 165, 250, 0.9);
        }

        .capacity-slider-min {
            z-index: 2;
        }

        .capacity-slider-max {
            z-index: 1;
        }

        .capacity-labels {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-top: 6px;
        }

        .capacity-label-group {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 3px;
        }

        .capacity-label-group label {
            font-size: 0.9em;
            color: rgba(255, 255, 255, 0.8);
            font-weight: normal;
            margin: 0;
        }

        .capacity-value {
            font-size: 0.9em;
            color: rgba(255, 255, 255, 0.95);
            font-weight: 500;
            min-width: 40px;
            text-align: center;
        }

        .capacity-inputs-hidden {
            display: none;
        }


        .team-mode-panel {
            margin-bottom: 12px;
            padding: 8px;
            background: rgba(0, 0, 0, 0.15);
            border-radius: 4px;
            border: 1px solid rgba(59, 130, 246, 0.2);
        }

        .team-mode-panel:last-child {
            margin-bottom: 0;
        }

        .fixed-modes-panel {
            border-color: rgba(147, 197, 253, 0.3);
        }

        .variable-modes-panel {
            border-color: rgba(16, 185, 129, 0.3);
        }

        .panel-header {
            margin-bottom: 6px;
        }

        .team-count-options {
            display: flex;
            flex-direction: column;
            gap: 3px;
            margin-top: 3px;
        }

        .team-count-options label {
            display: flex;
            align-items: center;
            gap: 4px;
            cursor: pointer;
            font-size: 0.9em;
            color: rgba(255, 255, 255, 0.9);
        }

        .autojoin-status {
            margin: 8px 0;
            padding: 6px;
            background: rgba(59, 130, 246, 0.15);
            border-radius: 4px;
            display: flex;
            align-items: center;
            gap: 8px;
            flex-wrap: wrap;
        }

        .status-label {
            font-weight: 600;
            color: rgba(255, 255, 255, 0.9);
            font-size: 0.9em;
        }

        .status-indicator {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background: rgba(239, 68, 68, 0.8);
        }

        .status-indicator.active {
            background: rgba(16, 185, 129, 0.8);
            animation: pulse 1s infinite;
        }


        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }

        .search-timer {
            margin-left: auto;
            font-size: 0.9em;
            color: rgba(147, 197, 253, 0.9);
            font-weight: 500;
        }

        .select-all-btn {
            background: rgba(59, 130, 246, 0.2);
            color: rgba(255, 255, 255, 0.9);
            border: 1px solid rgba(59, 130, 246, 0.4);
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.85em;
            padding: 3px 8px;
            flex: 1;
        }

        .select-all-btn:hover {
            background: rgba(59, 130, 246, 0.35);
        }

        .autojoin-settings {
            margin: 8px 0 0 0;
            padding: 6px;
            background: rgba(59, 130, 246, 0.08);
            border-radius: 4px;
        }

        .sound-toggle-label {
            display: flex;
            align-items: center;
            gap: 6px;
            cursor: pointer;
            font-size: 0.9em;
            color: rgba(255, 255, 255, 0.9);
        }

        .sound-toggle-label input[type="checkbox"] {
            width: 18px;
            height: 18px;
            cursor: pointer;
        }

        /* Remove any extra spacing/margins that might cause gray bar */
        .autojoin-status {
            margin-bottom: 8px;
        }

        .current-game-info {
            margin: 6px 0;
            padding: 5px 8px;
            background: rgba(59, 130, 246, 0.12);
            border-radius: 4px;
            font-size: 0.9em;
            color: rgba(147, 197, 253, 0.9);
            text-align: center;
            border: 1px solid rgba(59, 130, 246, 0.25);
        }

        .current-game-info.not-applicable {
            background: rgba(100, 100, 100, 0.1);
            color: rgba(255, 255, 255, 0.5);
            border-color: rgba(100, 100, 100, 0.2);
            font-style: italic;
        }

        .join-mode-selector {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 8px;
            padding: 6px;
            background: rgba(59, 130, 246, 0.08);
            border-radius: 4px;
            justify-content: center;
        }

        .mode-label-left,
        .mode-label-right {
            font-size: 0.9em;
            color: rgba(255, 255, 255, 0.9);
            font-weight: 500;
            user-select: none;
            transition: opacity 0.3s, color 0.3s;
        }

        .mode-label-left {
            opacity: 1;
            color: rgba(255, 255, 255, 0.95);
        }

        .mode-label-right {
            opacity: 0.6;
            color: rgba(255, 255, 255, 0.7);
        }

        .mode-toggle-switch {
            position: relative;
            display: inline-block;
            width: 50px;
            height: 26px;
            cursor: pointer;
        }

        .mode-toggle-switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }

        .mode-toggle-slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgba(59, 130, 246, 0.3);
            transition: 0.3s;
            border-radius: 26px;
        }

        .mode-toggle-slider:before {
            position: absolute;
            content: "";
            height: 20px;
            width: 20px;
            left: 3px;
            bottom: 3px;
            background-color: white;
            transition: 0.3s;
            border-radius: 50%;
        }

        .mode-toggle-switch input:checked + .mode-toggle-slider {
            background-color: rgba(16, 185, 129, 0.6);
        }

        .mode-toggle-switch input:checked + .mode-toggle-slider:before {
            transform: translateX(24px);
        }

        .mode-toggle-switch:hover .mode-toggle-slider {
            box-shadow: 0 0 8px rgba(59, 130, 246, 0.5);
        }

        .game-found-notification {
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%) translateY(-100px);
            background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
            border: 3px solid #60a5fa;
            border-radius: 8px;
            padding: 20px 30px;
            z-index: 20000;
            color: white;
            font-family: sans-serif;
            font-size: 1.1em;
            font-weight: 600;
            text-align: center;
            cursor: pointer;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5), 0 0 20px rgba(59, 130, 246, 0.5);
            transition: transform 0.3s ease, opacity 0.3s ease;
            opacity: 0;
            min-width: 300px;
            max-width: 500px;
            animation: notificationPulse 2s infinite;
        }

        .game-found-notification.notification-visible {
            transform: translateX(-50%) translateY(0);
            opacity: 1;
        }

        .game-found-notification.notification-dismissing {
            transform: translateX(-50%) translateY(-100px);
            opacity: 0;
        }

        @keyframes notificationPulse {
            0%, 100% {
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5), 0 0 0 0 rgba(59, 130, 246, 0.7);
            }
            50% {
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5), 0 0 0 8px rgba(59, 130, 246, 0);
            }
        }

        .game-found-notification:hover {
            background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
            border-color: #93c5fd;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5), 0 0 25px rgba(59, 130, 246, 0.7);
        }
    `);

    // Update panel visibility based on game state (URL-based detection)
    function updatePanelVisibility() {
        const panel = document.getElementById('openfront-autojoin-panel');
        if (!panel) return;

        const isLobby = isOnLobbyPage();
        const wasInGame = panel.dataset.wasInGame === 'true';

        if (!isLobby) {
            // In game → hide panel
            panel.classList.add('hidden');
            stopGameInfoUpdates(); // Stop updating game info when not in lobby
            // Dismiss notification when game starts
            dismissNotification();
            // If we just entered a game, disable auto-join only if we joined via auto-join mode
            if (!wasInGame) {
                // Play boxing ring bell sound when game starts
                playGameStartSound();
                // Only disable search if we joined via auto-join mode
                if (autoJoinEnabled && joinMode === 'autojoin') {
                    autoJoinEnabled = false;
                    stopMonitoring();
                    saveSettings();
                    updateUI();
                } else if (autoJoinEnabled && joinMode === 'notify') {
                    // In notify mode, keep search active (user manually joined)
                }
            }
            panel.dataset.wasInGame = 'true';
        } else {
            // In lobby → show panel and clear joined lobbies
            panel.classList.remove('hidden');
            startGameInfoUpdates(); // Start updating game info when in lobby
            joinedLobbies.clear(); // Clear to allow rejoining same lobby if needed

            // If we just returned to lobby, auto-start search in notify mode
            if (wasInGame) {
                // Always enable search when returning to lobby
                autoJoinEnabled = true;
                // Set to notify mode by default when returning to lobby
                joinMode = 'notify';
                // Update toggle and labels
                const modeToggle = document.getElementById('join-mode-toggle');
                if (modeToggle) {
                    modeToggle.checked = false;
                    updateModeLabels(false);
                }
                // Save settings and auto-start search if criteria exist
                saveSettings();
                updateUI();
                tryAutoStartSearch();
            }
            // Clear notified lobbies when returning to lobby
            notifiedLobbies.clear();
            lastNotifiedGameID = null;
            gameFoundTime = null;
            panel.dataset.wasInGame = 'false';
        }
    }

    // Observe URL changes to update panel visibility (event-based approach)
    function observeURL() {
        // Use popstate for browser navigation (back/forward buttons)
        window.addEventListener('popstate', updatePanelVisibility);

        // Use hashchange for hash changes
        window.addEventListener('hashchange', updatePanelVisibility);

        // Use pushstate/replacestate interception for programmatic navigation
        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;

        history.pushState = function(...args) {
            originalPushState.apply(history, args);
            setTimeout(updatePanelVisibility, 0);
        };

        history.replaceState = function(...args) {
            originalReplaceState.apply(history, args);
            setTimeout(updatePanelVisibility, 0);
        };

        // Initial check
        updatePanelVisibility();
    }

    // Initialize
    function init() {
        loadSettings();
        createUI();

        // Preload audio files
        preloadSounds();

        // Monitor URL changes to show/hide panel (event-based, more efficient)
        observeURL();
    }

    // Start when DOM is ready (immediate check like lobby_player_list.js)
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        // Initialize immediately (no delay needed with URL-based detection)
        init();
    }

})();