Powerline.io Data Logger

Track and analyze your Powerline.io gameplay with real time statistics, name performance, and exportable data for deep insights

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Powerline.io Data Logger
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Track and analyze your Powerline.io gameplay with real time statistics, name performance, and exportable data for deep insights
// @author       ᴀʏʟɪᴠᴀ  ⋆。°·☁
// @match        https://powerline.io/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Default script name constant
    const defaultScriptName = "Powerline.io Data Logger";

    // Initialize storage if it doesn't exist
    if (!localStorage.getItem('powerlineData')) {
        localStorage.setItem('powerlineData', JSON.stringify({
            metadata: {
                playerName: '',
                startDate: new Date().toISOString(),
                lastUpdated: new Date().toISOString()
            },
            playerNames: {},
            games: [],
            statistics: {
                totalGames: 0,
                averageTimeAlive: '0s',
                averageScore: 0,
                totalKills: 0,
                bestKillStreak: 0,
                bestScore: 0,
                deathTypes: {}
            }
        }));
    }

    let lastGameTime = null;
    let uiVisible = true;
    let currentGameName = null;

    // Constants for kill reasons
    const KILL_REASONS = {
        LEFT_SCREEN: 'LEFT_SCREEN',
        KILLED: 'KILLED',
        BOUNDARY: 'BOUNDARY',
        SUICIDE: 'SUICIDE',
        UNKNOWN: 'UNKNOWN'
    };

    // Monitors the player's name from the input field and in-game UI
    function monitorPlayerName() {
        const nickInput = document.getElementById('nick');
        if (nickInput && nickInput.value) {
            const name = nickInput.value.trim();
            if (name && name !== currentGameName) {
                updateCurrentName(name);
            }
        }
        const nameElements = document.querySelectorAll('.name');
        nameElements.forEach(elem => {
            const name = elem.textContent.trim();
            if (name && document.getElementById('nick') && name === document.getElementById('nick').value.trim()) {
                updateCurrentName(name);
            }
        });
    }

    // Updates the current game name and logs its usage in localStorage
    function updateCurrentName(newName) {
        if (!newName || newName === currentGameName || newName.length < 1 || newName.length > 15) return;

        currentGameName = newName;
        console.log("Current game name updated:", currentGameName);

        let data = JSON.parse(localStorage.getItem('powerlineData'));
        if (!data.playerNames[newName]) {
            data.playerNames[newName] = 1;
        } else {
            data.playerNames[newName]++;
        }
        data.metadata.lastUpdated = new Date().toISOString();
        localStorage.setItem('powerlineData', JSON.stringify(data));
        updateCounter();
    }

    // Returns the effective player name based on current game name or default
    function getEffectivePlayerName() {
        const data = JSON.parse(localStorage.getItem('powerlineData'));
        return currentGameName || data.metadata.playerName || 'Unknown';
    }

    // Listen for play button clicks and Enter key events in the nickname field
    document.addEventListener('DOMContentLoaded', function() {
        const playButton = document.querySelector('button[onclick*="clickPlay"]');
        if (playButton) {
            playButton.addEventListener('click', function() {
                const nickInput = document.getElementById('nick');
                if (nickInput && nickInput.value) {
                    updateCurrentName(nickInput.value.trim());
                }
            });
        }
        const nickInput = document.getElementById('nick');
        if (nickInput) {
            nickInput.addEventListener('keydown', function(event) {
                if (event.keyCode === 13) { // Enter key
                    updateCurrentName(this.value.trim());
                }
            });
        }
    });

    // Saves game data when changes are detected in gameplay elements
    function saveGameData() {
        const currentTime = document.getElementById('stat-time')?.textContent.trim();
        if (!currentTime || currentTime === lastGameTime || currentTime === '0s') {
            return;
        }
        lastGameTime = currentTime;

        const deathTitle = document.getElementById('stat-title')?.textContent.trim() || '';

        let killReason = KILL_REASONS.UNKNOWN;
        if (deathTitle.includes('COLLIDED')) {
            killReason = KILL_REASONS.BOUNDARY;
        } else if (deathTitle.includes('KILLED BY')) {
            killReason = KILL_REASONS.KILLED;
        } else if (deathTitle.includes('LEFT SCREEN')) {
            killReason = KILL_REASONS.LEFT_SCREEN;
        } else if (deathTitle.includes('SUICIDE')) {
            killReason = KILL_REASONS.SUICIDE;
        }

        let data = JSON.parse(localStorage.getItem('powerlineData'));
        monitorPlayerName();

        const currentKillStreak = parseInt(document.getElementById('stat-bks')?.textContent.trim() || '0');
        const currentScore = parseInt(document.getElementById('stat-blength')?.textContent.trim() || '0');
        const bestKillStreak = Math.max(currentKillStreak, data.statistics.bestKillStreak || 0);
        const bestScore = Math.max(currentScore, data.statistics.bestScore || 0);

        const gameData = {
            timestamp: new Date().toISOString(),
            timeAlive: currentTime,
            topPosition: document.getElementById('stat-top')?.textContent.trim() || '0',
            score: document.getElementById('stat-length')?.textContent.trim() || '0',
            currentKills: document.getElementById('stat-ks')?.textContent.trim() || '0',
            bestKillStreak: bestKillStreak.toString(),
            bestScore: bestScore.toString(),
            deathType: killReason,
            killedBy: deathTitle.includes('KILLED BY') ? deathTitle.split('KILLED BY')[1].trim() : 'none',
            playerName: currentGameName || 'Unknown',  // The actual in-game name used
            defaultName: data.metadata.playerName  // The player's default/main name
        };

        data.games.push(gameData);

        const scoreLimitInput = document.getElementById('score-limit-input');
        const infinityToggle = document.getElementById('infinity-toggle');
        const scoreLimit = parseInt(scoreLimitInput?.value || '0');
        if (!infinityToggle.checked && scoreLimit > 0 && data.games.length > scoreLimit) {
            data.games.sort((a, b) => parseInt(b.score) - parseInt(a.score));
            data.games = data.games.slice(0, scoreLimit);
        }

        data.metadata.lastUpdated = new Date().toISOString();
        data.statistics.bestKillStreak = bestKillStreak;
        data.statistics.bestScore = bestScore;
        data = updateStatistics(data);

        localStorage.setItem('powerlineData', JSON.stringify(data, null, 4));
        updateCounter();
    }

    // Update statistics based on logged games
    function updateStatistics(data) {
        data.statistics.totalGames = data.games.length;
        let totalTime = 0;
        let totalScore = 0;
        let totalKills = 0;

        data.games.forEach(game => {
            let time = parseFloat(game.timeAlive);
            if (!isNaN(time)) totalTime += time;

            let score = parseInt(game.score);
            if (!isNaN(score)) totalScore += score;

            let kills = parseInt(game.currentKills);
            if (!isNaN(kills)) totalKills += kills;
        });

        data.statistics.averageTimeAlive = data.games.length ? (totalTime / data.games.length).toFixed(1) + 's' : '0s';
        data.statistics.averageScore = data.games.length ? Math.round(totalScore / data.games.length) : 0;
        data.statistics.totalKills = totalKills;

        data.statistics.deathTypes = {};
        data.games.forEach(game => {
            if (!data.statistics.deathTypes[game.deathType]) {
                data.statistics.deathTypes[game.deathType] = 1;
            } else {
                data.statistics.deathTypes[game.deathType]++;
            }
        });

        return data;
    }

    // Create control panel element with styling
    const controlPanel = document.createElement('div');
    controlPanel.style.cssText = `
        position: fixed;
        top: 10px;
        right: 10px;
        z-index: 9999;
        background: rgba(0, 0, 0, 0.8);
        padding: 15px;
        border-radius: 8px;
        color: #00FFFF;
        transition: opacity 0.3s ease;
        font-family: Arial, sans-serif;
        box-shadow: 0 0 10px rgba(0, 0, 255, 0.3);
    `;

    // Header displaying the script name
    const headerDisplay = document.createElement('div');
    headerDisplay.style.cssText = `
        font-size: 14px;
        font-weight: bold;
        margin-bottom: 10px;
        text-align: center;
    `;
    headerDisplay.textContent = defaultScriptName;

    // Info section for controls
    const controlsInfo = document.createElement('div');
    controlsInfo.style.cssText = `
        font-size: 11px;
        margin-bottom: 10px;
        padding-bottom: 10px;
        border-bottom: 1px solid #00FFFF;
        opacity: 0.8;
    `;
    controlsInfo.innerHTML = `
        Controls:<br>
        ENTER - Hide panel<br>
        \` (Backtick) - Toggle panel
    `;

    // Current name display
    const nameDisplay = document.createElement('div');
    nameDisplay.style.cssText = `
        margin: 10px 0;
        padding: 10px 0;
        border-bottom: 1px solid #00FFFF;
        font-size: 12px;
    `;
    nameDisplay.innerHTML = `Current Name: <span id="current-name-display">None</span>`;

    // Default name input
    const nameInput = document.createElement('div');
    nameInput.style.cssText = `
        margin: 10px 0;
        padding: 10px 0;
        border-bottom: 1px solid #00FFFF;
    `;
    nameInput.innerHTML = `
        <label style="display: block; margin-bottom: 5px; font-size: 12px;">Default Player Name:</label>
        <input type="text" id="player-name-input" style="
            background: rgba(0, 0, 0, 0.5);
            border: 1px solid #00FFFF;
            color: #00FFFF;
            padding: 5px;
            width: 100%;
            border-radius: 4px;
            font-size: 12px;
        ">
    `;

    // Score Limit Controls
    const scoreLimitDiv = document.createElement('div');
    scoreLimitDiv.style.cssText = `
        margin: 10px 0;
        padding: 10px 0;
        border-bottom: 1px solid #00FFFF;
    `;
    scoreLimitDiv.innerHTML = `
        <label style="display: block; margin-bottom: 5px; font-size: 12px;">Top Scores Limit:</label>
        <input type="number" id="score-limit-input" style="
            background: rgba(0, 0, 0, 0.5);
            border: 1px solid #00FFFF;
            color: #00FFFF;
            padding: 5px;
            width: 100%;
            border-radius: 4px;
            font-size: 12px;
        " placeholder="e.g., 100">
        <label style="font-size: 12px; display: block; margin-top: 5px;">
            <input type="checkbox" id="infinity-toggle" style="margin-right: 5px;">
            Unlimited Records
        </label>
    `;

    const buttonStyle = `
        background-color: #004444;
        color: #00FFFF;
        border: 1px solid #00FFFF;
        padding: 8px 15px;
        margin: 5px;
        border-radius: 5px;
        cursor: pointer;
        transition: all 0.2s ease;
        font-size: 14px;
    `;

    // Export Data button
    const exportButton = document.createElement('button');
    exportButton.textContent = 'Export Data';
    exportButton.style.cssText = buttonStyle;
    exportButton.onclick = function() {
        const data = localStorage.getItem('powerlineData');
        const blob = new Blob([data], {type: 'application/json'});
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        const date = new Date().toISOString().split('T')[0];
        a.download = `powerline-data-${date}.json`;
        a.click();
    };

    // Clear Data button
    const clearButton = document.createElement('button');
    clearButton.textContent = 'Clear Data';
    clearButton.style.cssText = buttonStyle;
    clearButton.onclick = function() {
        if (confirm('Are you sure you want to clear all logged data?')) {
            const defaultName = JSON.parse(localStorage.getItem('powerlineData')).metadata.playerName;
            localStorage.setItem('powerlineData', JSON.stringify({
                metadata: {
                    playerName: defaultName,  // Preserve the default name
                    startDate: new Date().toISOString(),
                    lastUpdated: new Date().toISOString()
                },
                playerNames: {},
                games: [],
                statistics: {
                    totalGames: 0,
                    averageTimeAlive: '0s',
                    averageScore: 0,
                    totalKills: 0,
                    bestKillStreak: 0,
                    bestScore: 0,
                    deathTypes: {}
                }
            }));
            updateCounter();
        }
    };

    // Statistics display
    const statsDisplay = document.createElement('div');
    statsDisplay.style.cssText = `
        margin-top: 10px;
        padding-top: 10px;
        border-top: 1px solid #00FFFF;
        font-size: 12px;
        text-align: left;
    `;

   function updateCounter() {
    const data = JSON.parse(localStorage.getItem('powerlineData'));
    const namesUsed = Object.keys(data.playerNames).length;
    const mostUsedName = Object.entries(data.playerNames)
        .sort(([,a], [,b]) => b - a)[0]?.[0] || 'None';

    statsDisplay.innerHTML = `
        <div>Games logged: ${data.statistics.totalGames}</div>
        <div>Avg time: ${data.statistics.averageTimeAlive}</div>
        <div>Avg score: ${data.statistics.averageScore}</div>
        <div>Total kills: ${data.statistics.totalKills}</div>
        <div>Best streak: ${data.statistics.bestKillStreak}</div>
        <div>Best score: ${data.statistics.bestScore}</div>
        <div>Names used: ${namesUsed}</div>
        <div>Most used: ${mostUsedName}</div>
        <div>Default Name: ${data.metadata.playerName || 'None'}</div>
        <div>Current Name: ${currentGameName || 'None'}</div>
    `;

    const nameDisplaySpan = document.getElementById('current-name-display');
    if (nameDisplaySpan) {
        nameDisplaySpan.textContent = currentGameName || 'None';
    }
}

// Assemble the control panel
controlPanel.appendChild(headerDisplay);
controlPanel.appendChild(controlsInfo);
controlPanel.appendChild(nameDisplay);
controlPanel.appendChild(nameInput);
controlPanel.appendChild(scoreLimitDiv);
controlPanel.appendChild(exportButton);
controlPanel.appendChild(clearButton);
controlPanel.appendChild(statsDisplay);
document.body.appendChild(controlPanel);

// Set up the default name input
const defaultNameInput = nameInput.querySelector('#player-name-input');
defaultNameInput.value = JSON.parse(localStorage.getItem('powerlineData')).metadata.playerName;
defaultNameInput.addEventListener('change', function(e) {
    const data = JSON.parse(localStorage.getItem('powerlineData'));
    data.metadata.playerName = e.target.value;
    localStorage.setItem('powerlineData', JSON.stringify(data));
});

// Toggle control panel visibility
function toggleUIVisibility(force = null) {
    uiVisible = force !== null ? force : !uiVisible;
    controlPanel.style.opacity = uiVisible ? '1' : '0';
    controlPanel.style.pointerEvents = uiVisible ? 'auto' : 'none';
}

// Keyboard event listeners to hide/toggle the panel
document.addEventListener('keydown', function(e) {
    if (e.key === 'Enter') {
        toggleUIVisibility(false);
    } else if (e.key === '`') {
        toggleUIVisibility();
    }
});

// MutationObserver
const timeObserver = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
        if (mutation.type === 'characterData' || mutation.type === 'childList') {
            saveGameData();
        }
    });
});

// Start observing the game time element
const startObserving = () => {
    const timeElement = document.getElementById('stat-time');
    if (timeElement) {
        timeObserver.observe(timeElement, {
            characterData: true,
            childList: true,
            subtree: true
        });
    }
};

// Initialize observation when the stat-time element is available
const checkInterval = setInterval(() => {
    if (document.getElementById('stat-time')) {
        startObserving();
        clearInterval(checkInterval);
    }
}, 1000);

// Additional observers for updating the player name from various UI elements
const setupNameObservers = () => {
    const leaderboard = document.getElementById('leaderboard');
    if (leaderboard) {
        new MutationObserver(monitorPlayerName).observe(leaderboard, {
            childList: true,
            subtree: true,
            characterData: true
        });
    }
    const scoreDisplay = document.querySelector('.stats');
    if (scoreDisplay) {
        new MutationObserver(monitorPlayerName).observe(scoreDisplay, {
            childList: true,
            subtree: true,
            characterData: true
        });
    }
    const deathScreen = document.getElementById('stat-title');
    if (deathScreen) {
        new MutationObserver(monitorPlayerName).observe(deathScreen, {
            childList: true,
            characterData: true
        });
    }
};

const setupInterval = setInterval(() => {
    if (document.getElementById('leaderboard') ||
        document.querySelector('.stats') ||
        document.getElementById('stat-title')) {
        setupNameObservers();
        clearInterval(setupInterval);
    }
}, 1000);

updateCounter();
console.log('Enhanced Powerline.io Data Logger initialized with improved name tracking');
})();