// ==UserScript== // @name Powerline.io Data Logger // @namespace http://tampermonkey.net/ // @version 1.1 // @description Log game data from Powerline.io with name logging, average score, all of your kill screen stats + unseen stats, option for recording only your top runs, and records all the data into a JSON for in-depth analysis // @author ᴀʏʟɪᴠᴀ ⋆。°·☁ // @match https://powerline.io/* // @grant none // @license MIT // @downloadURL none // ==/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:
ENTER - Hide panel
\` (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: None`; // Default name input const nameInput = document.createElement('div'); nameInput.style.cssText = ` margin: 10px 0; padding: 10px 0; border-bottom: 1px solid #00FFFF; `; nameInput.innerHTML = ` `; // Score Limit Controls const scoreLimitDiv = document.createElement('div'); scoreLimitDiv.style.cssText = ` margin: 10px 0; padding: 10px 0; border-bottom: 1px solid #00FFFF; `; scoreLimitDiv.innerHTML = ` `; 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 = `
Games logged: ${data.statistics.totalGames}
Avg time: ${data.statistics.averageTimeAlive}
Avg score: ${data.statistics.averageScore}
Total kills: ${data.statistics.totalKills}
Best streak: ${data.statistics.bestKillStreak}
Best score: ${data.statistics.bestScore}
Names used: ${namesUsed}
Most used: ${mostUsedName}
Default Name: ${data.metadata.playerName || 'None'}
Current Name: ${currentGameName || 'None'}
`; 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'); })();