Powerline.io Data Logger

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

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