Ranking And Evolution for Drawaria

Añade rangos visuales dinámicos, barra de XP al dibujar y notificaciones de nivel, para un video más inmersivo de Drawaria.online.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Ranking And Evolution for Drawaria
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Añade rangos visuales dinámicos, barra de XP al dibujar y notificaciones de nivel, para un video más inmersivo de Drawaria.online.
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @grant        GM_addStyle
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuración de Idioma ---
    const translations = {
        es: {
            legend: "⚜️ Leyenda",
            master: "🔥 Maestro",
            expert: "⭐ Experto",
            apprentice: "🎭 Aprendiz",
            player: "👤 Jugador",
            xp_bar_label: "XP: {current} / {next} (Nivel {level})",
            level_up_message: "¡Subida de Nivel! Nivel {level}"
        },
        en: {
            legend: "⚜️ Legend",
            master: "🔥 Master",
            expert: "⭐ Expert",
            apprentice: "🎭 Apprentice",
            player: "👤 Player",
            xp_bar_label: "XP: {current} / {next} (Level {level})",
            level_up_message: "Level Up! Level {level}"
        },
        ru: {
            legend: "⚜️ Легенда",
            master: "🔥 Мастер",
            expert: "⭐ Эксперт",
            apprentice: "🎭 Ученик",
            player: "👤 Игрок",
            xp_bar_label: "Опыт: {current} / {next} (Уровень {level})",
            level_up_message: "Повышение уровня! Уровень {level}"
        }
    };

    const browserLang = (navigator.language || navigator.userLanguage).split('-')[0];
    const lang = translations[browserLang] ? browserLang : 'en'; // Default to English if language not found
    const T = translations[lang];

    // --- 1. ESTRUCTURA DE RANGOS BASE ---
    const RANGOS = [
        { nombre: T.legend,   minPuntos: 6000, color: "#FFD700" }, // Dorado
        { nombre: T.master,    minPuntos: 3000, color: "#E51A4C" }, // Rojo intenso
        { nombre: T.expert,    minPuntos: 1500, color: "#1E90FF" }, // Azul brillante
        { nombre: T.apprentice, color: "#32CD32" }, // Verde
        { nombre: T.player,     minPuntos: 0,    color: "#AAAAAA" }  // Gris
    ].map(r => {
        // Asignar un minPuntos por defecto si no existe (para que el bucle funcione)
        if (r.minPuntos === undefined) r.minPuntos = 0;
        return r;
    });
    // Asegurarnos que el rango más bajo tenga minPuntos: 0
    if (RANGOS[RANGOS.length - 1].minPuntos !== 0) {
        RANGOS.push({ nombre: T.player, minPuntos: 0, color: "#AAAAAA" });
    }

    /**
     * Determina el rango base de un jugador según su puntuación real.
     * @param {number} puntos - La puntuación del jugador.
     * @returns {object} - El objeto del rango correspondiente.
     */
    function obtenerRangoPorPuntosReales(puntos) {
        for (const rango of RANGOS) {
            if (puntos >= rango.minPuntos) {
                return rango;
            }
        }
        return RANGOS[RANGOS.length - 1]; // Jugador
    }

    /**
     * Asigna un rango aleatorio para otros jugadores (para la ilusión del video).
     * @returns {object} - Un objeto de rango aleatorio.
     */
    function obtenerRangoAleatorio() {
        const randomIndex = Math.floor(Math.random() * RANGOS.length);
        return RANGOS[randomIndex];
    }

    /**
     * Actualiza los rangos visuales de todos los jugadores en la UI.
     */
    function actualizarRangosEnUI() {
        const players = document.querySelectorAll('.playerlist-row');
        const selfPlayerNameInput = document.querySelector('#playername');
        // Si el input del nombre del jugador no existe aún, salimos.
        if (!selfPlayerNameInput) return;
        const selfPlayerName = selfPlayerNameInput.value.trim();

        players.forEach(playerRow => {
            // Evita re-procesar si ya tiene un rango asignado en esta carga
            if (playerRow.dataset.rangoAsignado === 'true') {
                return;
            }

            const nameElement = playerRow.querySelector('.playerlist-name');
            if (!nameElement) return;

            const playerName = nameElement.textContent.trim();
            let rangoParaMostrar;

            if (playerName === selfPlayerName) {
                // Para el propio jugador: usa la puntuación real del juego
                const accountScore = parseInt(playerRow.dataset.account_score, 10);
                const roundScore = parseInt(playerRow.dataset.roundscore, 10);
                // Prioriza accountScore, luego roundScore, si ambos son NaN, usa 0.
                const puntuacionReal = !isNaN(accountScore) ? accountScore : (isNaN(roundScore) ? 0 : roundScore);
                rangoParaMostrar = obtenerRangoPorPuntosReales(puntuacionReal);
            } else {
                // Para otros jugadores: asigna un rango aleatorio para la sesión
                // Y lo mantiene fijo.
                if (!playerRow.dataset.randomRankAssigned) {
                    rangoParaMostrar = obtenerRangoAleatorio();
                    // Guarda el rango aleatorio asignado para mantenerlo consistente durante la sesión
                    playerRow.dataset.randomRankAssigned = JSON.stringify(rangoParaMostrar);
                } else {
                    // Si ya se le asignó un rango aleatorio, lo reutiliza
                    rangoParaMostrar = JSON.parse(playerRow.dataset.randomRankAssigned);
                }
            }

            let rankSpan = playerRow.querySelector('.custom-rank-span');
            if (!rankSpan) {
                rankSpan = document.createElement('span');
                rankSpan.className = 'custom-rank-span';
                rankSpan.style.marginRight = '8px';
                rankSpan.style.fontWeight = 'bold';
                // Inserta el span antes del nombre del jugador
                nameElement.prepend(rankSpan);
            }

            rankSpan.textContent = rangoParaMostrar.nombre;
            rankSpan.style.color = rangoParaMostrar.color;
            // Marca la fila como procesada para evitar duplicación y re-procesamiento innecesario
            playerRow.dataset.rangoAsignado = 'true';
        });
    }

    // --- 2. SISTEMA DE XP AL DIBUJAR ---
    let userXP = 0;
    let userSimulatedLevel = 1;
    let xpToNextSimulatedLevel = 100; // XP inicial para el nivel 2
    const XP_MULTIPLIER_PER_LEVEL = 1.1; // Aumenta el XP necesario un 10% por nivel
    const XP_PER_DRAW_SEGMENT = 1; // Cuánta XP se gana por segmento de dibujo

    let isDrawing = false;
    let lastX = 0;
    let lastY = 0;
    let drawingXP = 0; // Acumulador de XP en la sesión de dibujo actual para guardado frecuente

    // Cargar XP, nivel y XP para el siguiente nivel desde localStorage
    function loadXPProgress() {
        try {
            const savedXP = localStorage.getItem('drawaria_userXP');
            const savedLevel = localStorage.getItem('drawaria_userLevel');
            const savedXPToNext = localStorage.getItem('drawaria_xpToNext');

            userXP = savedXP !== null ? parseFloat(savedXP) : 0;
            userSimulatedLevel = savedLevel !== null ? parseInt(savedLevel, 10) : 1;
            xpToNextSimulatedLevel = savedXPToNext !== null ? parseFloat(savedXPToNext) : 100;

            // Asegurarse de que los valores sean válidos
            if (isNaN(userXP)) userXP = 0;
            if (isNaN(userSimulatedLevel) || userSimulatedLevel < 1) userSimulatedLevel = 1;
            if (isNaN(xpToNextSimulatedLevel) || xpToNextSimulatedLevel < 100) xpToNextSimulatedLevel = 100;

            console.log(`[DrawariaXP] Progreso cargado: XP=${userXP}, Nivel=${userSimulatedLevel}, Siguiente=${xpToNextSimulatedLevel}`);
        } catch (e) {
            console.error("[DrawariaXP] Error al cargar progreso de XP:", e);
            // Resetear a valores por defecto en caso de error
            userXP = 0;
            userSimulatedLevel = 1;
            xpToNextSimulatedLevel = 100;
        }
    }

    // Guardar XP, nivel y XP para el siguiente nivel en localStorage
    function saveXPProgress() {
        try {
            localStorage.setItem('drawaria_userXP', userXP);
            localStorage.setItem('drawaria_userLevel', userSimulatedLevel);
            localStorage.setItem('drawaria_xpToNext', xpToNextSimulatedLevel);
            // console.log(`[DrawariaXP] Progreso guardado: XP=${userXP}, Nivel=${userSimulatedLevel}, Siguiente=${xpToNextSimulatedLevel}`);
        } catch (e) {
            console.error("[DrawariaXP] Error al guardar progreso de XP:", e);
        }
    }

    // Crear y actualizar la barra de XP
    let xpBarElement;
    let xpFillElement;
    let xpTextElement;

    function createXPBar() {
        xpBarElement = document.createElement('div');
        xpBarElement.id = 'custom-xp-bar';
        xpBarElement.innerHTML = `
            <div id="xp-fill"></div>
            <span id="xp-text"></span>
        `;
        document.body.appendChild(xpBarElement);

        GM_addStyle(`
            #custom-xp-bar {
                position: fixed;
                bottom: 10px;
                left: 50%;
                transform: translateX(-50%);
                width: 300px;
                height: 25px;
                background-color: rgba(0, 0, 0, 0.7);
                border-radius: 5px;
                overflow: hidden;
                z-index: 9999;
                display: flex;
                align-items: center;
                justify-content: center;
                box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
            }
            #xp-fill {
                height: 100%;
                width: 0%;
                background-color: #4CAF50;
                transition: width 0.2s ease-out;
                position: absolute;
                left: 0;
            }
            #xp-text {
                position: relative;
                color: white;
                font-size: 14px;
                font-weight: bold;
                text-shadow: 1px 1px 2px black;
                z-index: 1;
            }
        `);

        xpFillElement = xpBarElement.querySelector('#xp-fill');
        xpTextElement = xpBarElement.querySelector('#xp-text');
        updateXPBar();
    }

    function updateXPBar() {
        if (!xpFillElement || !xpTextElement) return;

        let percentage = 0;
        if (xpToNextSimulatedLevel > 0) {
            percentage = (userXP / xpToNextSimulatedLevel) * 100;
        }
        if (percentage > 100) percentage = 100; // Cap at 100%

        xpFillElement.style.width = `${percentage}%`;
        xpTextElement.textContent = T.xp_bar_label
            .replace('{current}', Math.floor(userXP))
            .replace('{next}', Math.ceil(xpToNextSimulatedLevel))
            .replace('{level}', userSimulatedLevel);
    }

    // Notificación de "Subida de Nivel"
    function displayLevelUpBadge() {
        const badge = document.createElement('div');
        badge.id = 'level-up-badge';
        badge.textContent = T.level_up_message.replace('{level}', userSimulatedLevel);
        document.body.appendChild(badge);

        GM_addStyle(`
            #level-up-badge {
                position: fixed;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                padding: 20px 40px;
                background-color: #FF5722; /* Naranja */
                color: white;
                font-size: 28px;
                font-weight: bold;
                border-radius: 10px;
                box-shadow: 0 0 20px rgba(0, 0, 0, 0.8);
                z-index: 10000;
                animation: fadeInOut 3s forwards;
                text-align: center;
                text-shadow: 2px 2px 4px black;
            }
            @keyframes fadeInOut {
                0% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
                10% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
                90% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
                100% { opacity: 0; transform: translate(-50%, -50%) scale(1.2); }
            }
        `);

        setTimeout(() => {
            if (badge && badge.parentNode) {
                badge.parentNode.removeChild(badge);
            }
        }, 3000);
    }

    // Detección de dibujo para ganar XP
    function setupDrawingXP() {
        const canvas = document.getElementById('canvas');
        if (!canvas) {
            console.log("[DrawariaXP] Canvas no encontrado. Reintentando...");
            // Espera un poco más antes de reintentar si el canvas aún no está presente
            setTimeout(setupDrawingXP, 1000);
            return;
        }
        console.log("[DrawariaXP] Canvas encontrado, configurando detección de dibujo.");

        canvas.addEventListener('mousedown', (e) => {
            if (e.button === 0) { // Clic izquierdo
                isDrawing = true;
                lastX = e.clientX;
                lastY = e.clientY;
                drawingXP = 0; // Reiniciar XP de dibujo para esta nueva "sesión"
            }
        });

        canvas.addEventListener('mousemove', (e) => {
            if (isDrawing) {
                const dx = e.clientX - lastX;
                const dy = e.clientY - lastY;
                const distanceSquared = dx * dx + dy * dy;

                // Consideramos un segmento de dibujo si se ha movido al menos 10 píxeles
                if (distanceSquared > 100) { // 10^2 = 100
                    userXP += XP_PER_DRAW_SEGMENT;
                    drawingXP += XP_PER_DRAW_SEGMENT; // Acumula XP para el guardado frecuente
                    lastX = e.clientX;
                    lastY = e.clientY;
                    updateXPBar();

                    // Procesar subida de nivel
                    if (userXP >= xpToNextSimulatedLevel) {
                        displayLevelUpBadge();
                        userSimulatedLevel++;
                        // Opcional: Restar la XP necesaria para el nivel anterior si quieres que la XP "sobrante" cuente.
                        // userXP -= xpToNextSimulatedLevel;
                        // Si prefieres resetear XP al 0 al subir de nivel:
                        userXP = 0; // Resetea la XP al cero

                        xpToNextSimulatedLevel = Math.floor(xpToNextSimulatedLevel * XP_MULTIPLIER_PER_LEVEL);
                        // Asegurarse de que la próxima meta de XP sea al menos un poco mayor
                        if (xpToNextSimulatedLevel <= 100) xpToNextSimulatedLevel = 100;

                        updateXPBar(); // Actualiza la barra para reflejar el nuevo nivel
                    }

                    // Guardar progreso cada 10 XP acumuladas en esta sesión de dibujo
                    if (drawingXP >= 10) {
                        saveXPProgress();
                        drawingXP = 0; // Reiniciar contador de guardado
                    }
                }
            }
        });

        canvas.addEventListener('mouseup', () => {
            if (isDrawing) {
                isDrawing = false;
                saveXPProgress(); // Guardar al soltar el botón del ratón
            }
        });

        // Manejo para cuando el usuario sale de la página o la pierde
        window.addEventListener('beforeunload', saveXPProgress);
        window.addEventListener('blur', () => {
            if (isDrawing) {
                isDrawing = false;
                saveXPProgress();
            }
        });
    }


    // --- 3. OBSERVADOR DE CAMBIOS EN LA UI ---
    // Vigila la lista de jugadores para detectar nuevas incorporaciones.
    const playerListContainer = document.getElementById('playerlist');

    if (playerListContainer) {
        const observer = new MutationObserver((mutationsList) => {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    // Cuando se añaden nodos a la lista de jugadores...
                    mutation.addedNodes.forEach(node => {
                        // Asegurarse de que es un elemento de fila de jugador y resetear su flag de procesamiento
                        if (node.nodeType === Node.ELEMENT_NODE && node.classList && node.classList.contains('playerlist-row')) {
                            node.dataset.rangoAsignado = 'false';
                        }
                    });
                    // Re-actualizar todos los rangos después de cualquier cambio en la lista
                    actualizarRangosEnUI();
                    break; // Salir del bucle de mutaciones una vez procesado
                }
            }
        });

        // Observar adiciones y eliminaciones de nodos hijos dentro del contenedor de la lista de jugadores
        observer.observe(playerListContainer, { childList: true });
    } else {
        console.log("[DrawariaRank] Contenedor de lista de jugadores no encontrado. Reintentando en 2 segundos.");
        setTimeout(() => {
            if (!playerListContainer) {
                console.error("[DrawariaRank] Contenedor de lista de jugadores no encontrado. El script de rangos no funcionará.");
            }
        }, 2000);
    }

    // --- Inicialización ---
    // Esperar a que el contenido principal de Drawaria esté cargado
    // El evento 'load' puede ser un poco tardío si el script se inyecta pronto.
    // Usaremos un setTimeout o un MutationObserver más específico si es necesario.
    // Por ahora, un setTimeout al 'load' es una buena aproximación.
    window.addEventListener('load', () => {
        // Usamos un pequeño retraso para asegurar que los elementos del juego estén disponibles
        // y para no interferir con la carga inicial del juego.
        setTimeout(() => {
            loadXPProgress();
            createXPBar();
            setupDrawingXP();
            actualizarRangosEnUI(); // Actualizar rangos al cargar la página
            updateXPBar(); // Asegura que la barra de XP se muestre correctamente al inicio

            // Además, podemos observar cambios en los atributos de los jugadores que puedan afectar sus puntuaciones
            // o la lista misma, pero MutationObserver en childList ya cubre la adición/eliminación.
            // Si las puntuaciones se actualizan dinámicamente sin recargar la fila, sería necesario
            // observar atributos o usar una técnica diferente. Por ahora, asumimos que la fila se actualiza.

        }, 2000); // Un retraso de 2 segundos para dar tiempo a que el juego cargue sus elementos.
    });

})();