// ==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
// @downloadURL https://update.greasyfork.icu/scripts/526152/Powerlineio%20Data%20Logger.user.js
// @updateURL https://update.greasyfork.icu/scripts/526152/Powerlineio%20Data%20Logger.meta.js
// ==/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 = `