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.

// ==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.
    });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址