Skip to content

Instantly share code, notes, and snippets.

@nabbynz
Last active April 17, 2024 05:51
Show Gist options
  • Save nabbynz/759b3002563ab97a3f6a2a0f76ee8797 to your computer and use it in GitHub Desktop.
Save nabbynz/759b3002563ab97a3f6a2a0f76ee8797 to your computer and use it in GitHub Desktop.
Shows a list of today's pubs on the TagPro Homepage
// ==UserScript==
// @name Today's TagPro Pubs
// @description Shows a list of today's pubs on the TagPro Homepage with graphs and shit.
// @version 0.1.0
// @match https://*.koalabeast.com/
// @updateURL https://gist.github.com/nabbynz/759b3002563ab97a3f6a2a0f76ee8797/raw/Today's TagPro Pubs.user.js
// @downloadURL https://gist.github.com/nabbynz/759b3002563ab97a3f6a2a0f76ee8797/raw/Today's TagPro Pubs.user.js
// @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_addStyle
// @author nabby
// @require https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js
// @require https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js
// ==/UserScript==
'use strict';
console.log('START: ' + GM_info.script.name + ' (v' + GM_info.script.version + ' by ' + GM_info.script.author + ')');
/* eslint-env jquery */
/* globals tagpro, moment, dayjs, io */
/* eslint-disable no-multi-spaces, no-useless-concat */
const REMOVE_FROM_CACHE_AFTER = 3 * 24 * 60 * 60 * 1000; // remove cached games after 3 days
const redTextColor = '#ffb5bd';
const blueTextColor = '#cfcfff';
const redBackgroundColor = '#a13636';
const blueBackgroundColor = '#194773';
let loggedInUserId = $('#profile-btn').length ? $('#profile-btn').attr('href').substring($('#profile-btn').attr('href').lastIndexOf('/') + 1) : '';
let lastGet = 0;
let cachedGames = GM_getValue('cachedGames', {});
let watchedGames = GM_getValue('watchedGames', {});
let removeOldCachedGames = function() {
let doSave = false;
for (let gameId in cachedGames) {
if (cachedGames[gameId].dateCached < Date.now() - REMOVE_FROM_CACHE_AFTER) {
delete cachedGames[gameId];
doSave = true;
}
}
for (let gameId in watchedGames) {
if (watchedGames[gameId].dateWatched < Date.now() - REMOVE_FROM_CACHE_AFTER) {
delete watchedGames[gameId];
doSave = true;
}
}
if (doSave) {
GM_setValue('cachedGames', cachedGames);
GM_setValue('watchedGames', watchedGames);
}
};
removeOldCachedGames();
let reformatServerName = function(server) {
let city = server.replace('Map Testing - ', '').replace(' - Test', '').trim();
let isMapTest = server.includes('Map Testing - ');
let isTest = !isMapTest && server.includes(' - Test');
if (server.indexOf(',') >= 0) {
city = server.slice(0, server.indexOf(','));
}
return city + ((isMapTest || isTest) ? ' (' + (isMapTest ? 'MT' : '') + (isTest ? 'T' : '') + ')' : '');
};
let getReplayKey = function(gameId, userId, extra, host) {
return btoa((gameId + (userId || "")).match(/\w{2}/g).map((gameId=>String.fromCharCode(parseInt(gameId, 16)))).join("")).replaceAll("+", "_");
};
let getReplayUrl = function(gameId, userId, extra, host) {
return (host || "") + "/game?replay=" + getReplayKey(gameId, userId) + (extra || "");
};
let formatDate = function(started, asTitle, asCalendar) {
let date;
if (asTitle) {
date = moment(started).format("YYYY/MM/DD hh:mma");
} else {
date = moment(started).calendar(null, {
sameDay: "[Today] hh:mma",
lastDay: "[Yesterday] hh:mma",
lastWeek: "dddd hh:mma",
sameElse: "YYYY/MM/DD hh:mma"
});
}
return asCalendar ? date.split(" ") : date;
};
let addChatLogContainer = function() {
$('#LPG_Container').append('<div id="LPG_ChatLog"><div id="LPG_ChatLog_Header"><div id="LPG_CapTimeline_Container"></div><span id="LPG_ChatLog_Close" title="Close">X</span></div><div id="LPG_ChatLog_Chat"></div></div>');
};
let jsonData = [];
let mapsData = { maps: {}, counts: { all: 0, weightTotal: 0 } };
let gameStatsCached = null;
let gameStatsLoadedDate = null;
let initGameStatsNeeded = true;
let showReplaysType = 'all';
let initGameStats = function() {
GM_addStyle('#LPG_GameStats_Container { position:fixed; display:flex; flex-flow:row wrap; width:1150px; justify-content:space-between; top:50%; left:50%; padding:10px; font-size:13px; transform:translate(-50%, -50%); text-align:center; color:#ccc; background:#0a0a0a; border:2px ridge #9566df; border-radius:6px; box-shadow:0px 0px 25px 10px black; z-index:999; }');
GM_addStyle('#LPG_GameStats_Close { position:absolute; width:17px; height:17px; top:3px; right:2px; padding:0 0 0 1px; color:red; border:1px solid darkred; font-size:12px; font-weight:bold; border-radius:10px; cursor:pointer; }');
GM_addStyle('#LPG_GameStats_Header { width:fit-content; background:#3f3f3f; height:40px; border-bottom:1px solid #444; }');
GM_addStyle('#LPG_GameStats_Table { width:fit-content; text-align:center; max-height:220px; overflow-y:scroll; display:block; }');
GM_addStyle('#LPG_ServerStats_Header { width:fit-content; margin:20px auto 0; background:#3f3f3f; height:40px; border-bottom:1px solid #444; }');
GM_addStyle('#LPG_ServerStats_Table { width:fit-content; margin:0px auto 20px; text-align:center; max-height:220px; overflow-y:scroll; display:block; }');
GM_addStyle('#LPG_GameStats_Table tr, #LPG_ServerStats_Table tr { background:#2a2a2a; }');
GM_addStyle('#LPG_GameStats_Table tr:nth-child(2n), #LPG_ServerStats_Table tr:nth-child(2n) { background:#212121; }');
GM_addStyle('#LPG_GameStats_Table tbody tr:hover, #LPG_ServerStats_Table tbody tr:hover { background:#482d4e !important; }');
GM_addStyle('#LPG_GameStats_Table tr td, #LPG_GameStats_Header tr th, #LPG_ServerStats_Table tr td, #LPG_ServerStats_Header tr th { padding:1px 5px; }');
GM_addStyle('#LPG_GameStats_Table tr td:nth-child(2), #LPG_GameStats_Header tr th:nth-child(2) { width:200px; max-width:200px; text-align:left; text-overflow:ellipsis; overflow:hidden; white-space:nowrap; }');
GM_addStyle('#LPG_ServerStats_Table tr td:nth-child(2), #LPG_ServerStats_Header tr th:nth-child(2) { width:200px; max-width:200px; text-align:left; text-overflow:ellipsis; overflow:hidden; white-space:nowrap; }');
GM_addStyle('#LPG_GameStats_Table tr td:nth-child(n+3), #LPG_GameStats_Header tr th:nth-child(n+3) { width:60px; max-width:60px; }');
GM_addStyle('#LPG_ServerStats_Table tr td:nth-child(n+3), #LPG_ServerStats_Header tr th:nth-child(n+3) { width:60px; max-width:60px; }');
GM_addStyle('#LPG_GameStats_Table::-webkit-scrollbar, #LPG_ServerStats_Table::-webkit-scrollbar { width:3px; }');
GM_addStyle('#LPG_GameStats_Table::-webkit-scrollbar-thumb, #LPG_ServerStats_Table::-webkit-scrollbar-thumb { background:#9566df; }');
GM_addStyle('#LPG_GameStats_Table::-webkit-scrollbar-track, #LPG_ServerStats_Table::-webkit-scrollbar-track { background:#555; }');
GM_addStyle('.LPG_Rotation_rotation { color:#59e183; }');
GM_addStyle('.LPG_Rotation_trial { color:#b760a8; }');
GM_addStyle('.LPG_Rotation_classic { color:#3384ff; }');
GM_addStyle('.LPG_Rotation_retired { color:#696969; }');
GM_addStyle('.LPG_Rotation_group { color:#a5509f; }');
GM_addStyle('.LPG_Rotation_racing { color:#7ca90f; }');
GM_addStyle('#LPG_Extra_Info { margin:20px auto; padding:10px 40px; border:1px outset #797979; border-radius:8px; height:fit-content; background:#222; }');
GM_addStyle('#LPG_Extra_Info_Table { width:250px; }');
GM_addStyle('#LPG_Extra_Info_Table td:nth-child(1) { width:50%; padding:0 6px; text-align:right; }');
GM_addStyle('#LPG_Extra_Info_Table td:nth-child(2) { width:50%; padding:0 6px; text-align:left; }');
GM_addStyle('#LPG_GameStats_Graph_Container { position:relative; margin:10px; padding:10px; background:#000; border:1px outset #888; border-radius:8px; }');
GM_addStyle('#LPG_GameStats_Graph_ToolTip { position:absolute; display:none; padding:10px 40px; font-size:12px; color:#aaa; background:#222; border:1px outset #777; border-radius:10px; box-shadow:3px 3px 6px black; white-space:nowrap; }');
GM_addStyle('.LPG_GameStats_Graph_LabelX { font-size:10px; font-weight:bold; fill:#ccc; }');
GM_addStyle('.LPG_GameStats_Graph_LabelY { font-size:10px; font-weight:bold; fill:#ccc; }');
GM_addStyle('.LPG_GameStats_Graph_MyTeam { stroke:#00ff00; stroke-width:2; opacity:0.4; }');
GM_addStyle('.LPG_GameStats_Graph_Duration_Red { stroke:rgb(255,121,121,0.65); stroke-width:2px; stroke-linecap:square; filter:drop-shadow(0px -1px 4px #e00) drop-shadow(0px -1px 2px #e00); }');
GM_addStyle('.LPG_GameStats_Graph_Duration_Blue { stroke:rgba(141,171,255,0.65); stroke-width:2px; stroke-linecap:square; filter:drop-shadow(0px 1px 4px #09f) drop-shadow(0px 1px 2px #09f); }');
GM_addStyle('.LPG_GameStats_Graph_Duration_Abandoned { stroke:#ffffff; stroke-width:1; opacity:0.4; }');
GM_addStyle('.LPG_GameStats_Graph_GameStart_Red { stroke:rgb(163 61 61); }');
GM_addStyle('.LPG_GameStats_Graph_GameStart_Blue { stroke:rgb(90 113 144); }');
GM_addStyle('.LPG_GameStats_Graph_GameStart_Abandoned { stroke:#ffffff; opacity:0.4; }');
GM_addStyle('.LPG_GameStats_Graph_Guide { stroke:#444; stroke-width:1; stroke-dasharray:4 6; opacity:1; }');
$('body').append('<div id="LPG_GameStats_Container"><span id="LPG_GameStats_Close" title="Close">X</span></div>');
$('#LPG_GameStats_Container').hide();
$('#LPG_GameStats_Container').on('click', '.LPG_GameStats_Sort', function() {
const target = this.dataset.target;
const index = this.cellIndex;
let sortasc = false;
if (!this.dataset.sortasc || this.dataset.sortasc === undefined || this.dataset.sortasc === 'false') {
this.dataset.sortasc = 'true';
sortasc = true;
} else {
this.dataset.sortasc = 'false';
sortasc = false;
}
$('#' + target + ' tbody tr').sort(function(a, b) {
if (index === 1 || target === 'LPG_GameStats_Table' && index === 7 || target === 'LPG_ServerStats_Table' && index === 8) {
if (sortasc) {
return a.children[index].dataset.sortby.localeCompare(b.children[index].dataset.sortby, 'en', {numeric: true});
} else {
return b.children[index].dataset.sortby.localeCompare(a.children[index].dataset.sortby, 'en', {numeric: true});
}
} else {
if (sortasc) {
return a.children[index].dataset.sortby - b.children[index].dataset.sortby;
} else {
return b.children[index].dataset.sortby - a.children[index].dataset.sortby;
}
}
}).appendTo( $('#' + target) );
$('#' + target).prev('table').find('.LPG_GameStats_Sort').css('text-decoration', 'none');
$(this).css('text-decoration', 'underline');
});
$('#LPG_GameStats_Close').on('click', function() {
$('#LPG_GameStats_Container').fadeOut(100);
});
$('#LPG_GameStats_Container').on('click', '.LPG_GameStats_Graph_Details', function() {
let gameId = this.dataset.gameid;
//let replayId = this.dataset.replayid;
$('#LPG_GameStats_Container').prepend( $('#LPG_ChatLog') );
if (cachedGames[gameId]) {
showChatLog(gameId, true);
} else {
if (!gameId || gameId.length !== 24) {
return;
}
getProcessAndShowReplayChat(gameId, true);
}
});
$('#LPG_GameStats_Container').on('mouseover', '.LPG_GameStats_Graph_Details', function(e) {
$('#LPG_GameStats_Graph_ToolTip').html('<div>' + e.target.dataset.name + '</div><div>' + e.target.dataset.date + '</div><div>Duration: ' + e.target.dataset.duration + '</div><div>' + (e.target.dataset.winner === '1' ? 'Red Won ' : e.target.dataset.winner === '2' ? 'Blue Won ' : e.target.dataset.winner === '-1' ? 'Abandoned: ' : '') + e.target.dataset.score + '</div>' + (e.target.dataset.myteamname ? '<div>My Team: ' + e.target.dataset.myteamname + '</div>' : ''));
$('#LPG_GameStats_Graph_ToolTip').css({ 'left':e.offsetX - 100, 'top':e.clientY - 215 }).show();
}).on('mouseleave', '.LPG_GameStats_Graph_Details', function() {
$('#LPG_GameStats_Graph_ToolTip').hide();
});
$('#LPG_GameStats_Container').on('click', '.LPG_GameStats_ShowMapGames', function() {
let wasSelected = this.dataset.selected;
$('.LPG_GameStats_ShowMapGames').css('color', 'inherit').attr('data-selected', '');
if (wasSelected) { // show all...
$('#LPG_GameStats_Graph_Container').find('.LPG_GameStats_Graph_Details').fadeIn(50);
$('#LPG_GameStats_Graph_Container').find('.LPG_GameStats_MyGame').fadeIn(50);
} else { // show matched...
$(this).css('color', '#a27ed3');
this.dataset.selected = 'selected';
$('#LPG_GameStats_Graph_Container').find('.LPG_GameStats_Graph_Details').hide();
$('#LPG_GameStats_Graph_Container').find('.LPG_GameStats_Graph_Details[data-map="' + this.dataset.sortby + '"]').fadeIn(50);
$('#LPG_GameStats_Graph_Container').find('.LPG_GameStats_MyGame').hide();
}
});
};
let showGamesStats = function() {
let allData = { maps: {}, servers: {}, serversGraph: [], serverGamesCount:{} };
$('#LPG_GameStats_Container').children().not('#LPG_GameStats_Close').remove();
$('#LPG_GameStats_Container').append('<div style="width:515px; font-size:12px;"><table id="LPG_GameStats_Header">' +
' <tr>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_GameStats_Table" title="Category" style="width:15px; max-width:15px;"></th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_GameStats_Table" title="Map Name">Map</th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_GameStats_Table" title="Total Number of Games" data-sortasc="false" style="text-decoration:underline;">Games</th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_GameStats_Table" title="Average Game Duration">Duration</th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_GameStats_Table" title="Caps Per Minute">CPM</th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_GameStats_Table" title="Caps % Difference">Caps</th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_GameStats_Table" title="Spawn Rate % Difference">Spawn</th>' +
' <th style="padding:0; width:3px;"></th>' +
' </tr>' +
'</table>' +
'<table id="LPG_GameStats_Table"></table></div>');
$('#LPG_GameStats_Container').append('<div id="LPG_Extra_Info"></div>');
$('#LPG_GameStats_Container').append('<div style="width:560px; font-size:12px;"><table id="LPG_ServerStats_Header">' +
' <tr>' +
' <th style="padding:0; width:0px;"></th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_ServerStats_Table" title="Average Game Duration">Server</th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_ServerStats_Table" title="Total Number of Games" data-sortasc="false" style="text-decoration:underline;">Games</th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_ServerStats_Table" title="Average Game Duration">Duration</th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_ServerStats_Table" title="Caps Per Minute">CPM</th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_ServerStats_Table" title="Score Difference">Score</th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_ServerStats_Table" title="Total Game Duration Per Hour">Load %</th>' +
' <th class="LPG_GameStats_Sort" data-target="LPG_ServerStats_Table" title="# Games Started Per Hour">GPH</th>' +
' <th style="padding:0; width:3px;"></th>' +
' </tr>' +
'</table>' +
'<table id="LPG_ServerStats_Table"></table></div>');
$('#LPG_GameStats_Container').append('<div id="LPG_WorldMap_Container"><img src="/images/worldmap.png" style="width:540px; filter:drop-shadow(#fff 0px -1px 3px) drop-shadow(#fff 0px 0px 5px) contrast(200%) sepia(100%);" title="This map shows nothing atm"></div>');
let allGamesCount = 0;
let firstGameStarted, lastGameStarted;
let doOnce = false;
for (let game of jsonData) {
let mapName = game.mapName;
let mapNameLC = mapName.toLowerCase();
let serverName = game.server;
// create data for `maps`
if (game.winner !== -1) { // ignore abandoned games
if (!allData.maps.hasOwnProperty(mapName)) {
let spawnRate = 0;
let weight = null;
let category = null;
if (mapNameLC && mapsData && mapsData.maps && mapsData.maps.hasOwnProperty(mapNameLC)) {
spawnRate = mapsData.maps[mapNameLC].weight / mapsData.counts.weightTotal * 100;
weight = mapsData.maps[mapNameLC].weight;
category = mapsData.maps[mapNameLC].category;
}
allData.maps[mapName] = { count: 0, wins: 0, duration: 0, redWins: 0, blueWins: 0, redTeamScore: 0, blueTeamScore: 0, spawnRate: spawnRate, weight: weight, category: category };
}
allData.maps[mapName].count++;
allData.maps[mapName].duration += game.duration;
allData.maps[mapName].redTeamScore += game.teams.red.score;
allData.maps[mapName].blueTeamScore += game.teams.blue.score;
allData.maps[mapName].redWins += game.winner === 1 ? 1 : 0;
allData.maps[mapName].blueWins += game.winner === 2 ? 1 : 0;
allGamesCount++;
}
// create data for `servers`
if (!allData.servers.hasOwnProperty(serverName)) {
allData.servers[serverName] = { count: 0, wins: 0, duration: 0, redWins: 0, blueWins: 0, redTeamScore: 0, blueTeamScore: 0, margin1: 0, margin2: 0, overtime: 0, mercy: 0, abandoned: 0 };
allData.serverGamesCount[serverName] = 0;
}
if (game.winner !== -1) {
allData.servers[serverName].count++;
allData.servers[serverName].duration += game.duration;
allData.servers[serverName].redTeamScore += game.teams.red.score;
allData.servers[serverName].blueTeamScore += game.teams.blue.score;
if (game.winner === 1) allData.servers[serverName].redWins++;
else if (game.winner === 2) allData.servers[serverName].blueWins++;
if (Math.abs(game.teams.red.score - game.teams.blue.score) === 3) allData.servers[serverName].mercy++;
else if (Math.round(game.duration / 1000) > 360) allData.servers[serverName].overtime++;
else if (Math.abs(game.teams.red.score - game.teams.blue.score) === 2) allData.servers[serverName].margin2++;
else if (Math.abs(game.teams.red.score - game.teams.blue.score) === 1) allData.servers[serverName].margin1++;
else allData.servers[serverName].normal++;
} else {
allData.servers[serverName].abandoned++;
}
// create data for `servers graph`
allData.serverGamesCount[serverName]++;
allData.serversGraph.push({ gameId: game.id, map:mapName, started: new Date(game.started).getTime(), server: game.server, duration: Math.floor(game.duration / 1000), winner: game.winner, redTeamScore: game.teams.red.score, blueTeamScore: game.teams.blue.score, myTeam: game.myTeam, myTeamWon: game.myTeamWon, myTeamName: game.myTeamName });
}
firstGameStarted = dayjs(jsonData[0].started);
lastGameStarted = dayjs(jsonData[jsonData.length - 1].started);
const timePeriodMs = Date.now() - allData.serversGraph[allData.serversGraph.length - 1].started;
const timePeriodMins = timePeriodMs / 1000 / 60;
let redWinsTotal = 0;
let blueWinsTotal = 0;
let mercyTotal = 0;
let margin1Total = 0;
let margin2Total = 0;
let overtimesTotal = 0;
for (let map in allData.maps) {
redWinsTotal += allData.maps[map].redWins;
blueWinsTotal += allData.maps[map].blueWins;
let v1 = allData.maps[map].redTeamScore / allData.maps[map].count;
let v2 = allData.maps[map].blueTeamScore / allData.maps[map].count;
let scoreDiff = (Math.abs(v1 - v2) / ((v1 + v2) / 2)) * 100;
let scoreDiffText = '<span title="Red ' + v1.toFixed(2) + ' : ' + v2.toFixed(2) + ' Blue" style="' + (v1 < v2 ? 'color:dodgerblue;' : v1 > v2 ? 'color:orangered;' : 'color:#ddd;') + '">' + scoreDiff.toFixed(2) + '%</span>';
v1 = allData.maps[map].count / allGamesCount * 100;
v2 = allData.maps[map].spawnRate;
let spawnRateDiff = (Math.abs(v1 - v2) / ((v1 + v2) / 2)) * 100;
let spawnRateText = '<span title="Current Spawn Rate: ' + v1.toFixed(2) + ' (Target: ' + v2.toFixed(2) + ')">' + (allData.maps[map].count < 10 ? '-' : (v1 > v2 ? '+' : v1 < v2 ? '-' : '') + spawnRateDiff.toFixed(2) + '%') + '</td>'; // (v1 > v2 ? 'color:limegreen;' : v1 < v2 ? 'color:cyan;' : 'color:#ddd;')
let rotationClass = allData.maps[map].category ? 'LPG_Rotation_' + allData.maps[map].category : '';
$('#LPG_GameStats_Table').append('<tr>' +
' <td data-sortby="' + allData.maps[map].weight * 100 + '_' + allData.maps[map].category + '" class="' + rotationClass + '" style="width:15px; max-width:15px;" title="' + capitalize(allData.maps[map].category === 'retired' ? 'throwback' : allData.maps[map].category) + ' ' + allData.maps[map].weight.toFixed(1) + '">&#9679;</td>' +
' <td data-sortby="' + map + '" class="LPG_GameStats_ShowMapGames">' + map + '</td>' +
' <td data-sortby="' + allData.maps[map].count + '">' + allData.maps[map].count + '</td>' +
' <td data-sortby="' + allData.maps[map].duration / 1000 / allData.maps[map].count + '">' + tagpro.helpers.timeFromSeconds(Math.floor(allData.maps[map].duration / 1000 / allData.maps[map].count), true) + '</td>' +
' <td data-sortby="' + (allData.maps[map].redTeamScore + allData.maps[map].blueTeamScore) / (allData.maps[map].duration / 1000 / 60) + '">' + ((allData.maps[map].redTeamScore + allData.maps[map].blueTeamScore) / (allData.maps[map].duration / 1000 / 60)).toFixed(2) + '</td>' +
' <td data-sortby="' + scoreDiff + '">' + scoreDiffText + '</td>' +
' <td data-sortby="' + (allData.maps[map].count < 10 ? 0 : spawnRateDiff * (v1 < v2 ? -1 : 1)) + '">' + spawnRateText + '</td>' +
'</tr>');
}
for (let server in allData.servers) {
let v1 = allData.servers[server].redTeamScore / allData.servers[server].count;
let v2 = allData.servers[server].blueTeamScore / allData.servers[server].count;
let scoreDiff = (Math.abs(v1 - v2) / ((v1 + v2) / 2)) * 100;
let scoreDiffText = '<span title="Red ' + v1.toFixed(2) + ' : ' + v2.toFixed(2) + ' Blue" style="' + (v1 < v2 ? 'color:dodgerblue;' : v1 > v2 ? 'color:orangered;' : 'color:#ddd;') + '">' + scoreDiff.toFixed(2) + '%</span>';
let countTitle = 'Includes:\n';
if (allData.servers[server].mercy) countTitle += ' &middot; ' + allData.servers[server].mercy + 'x Wins by Mercy\n';
if (allData.servers[server].margin1) countTitle += ' &middot; ' + allData.servers[server].margin1 + 'x Wins by 1 Cap\n';
if (allData.servers[server].margin2) countTitle += ' &middot; ' + allData.servers[server].margin2 + 'x Wins by 2 Caps\n';
if (allData.servers[server].overtime) countTitle += ' &middot; ' + allData.servers[server].overtime + 'x Overtime Games\n';
if (allData.servers[server].abandoned) countTitle += 'Excludes:\n &middot; ' + allData.servers[server].abandoned + 'x Abandoned Games\n';
mercyTotal += allData.servers[server].mercy;
margin1Total += allData.servers[server].margin1;
margin2Total += allData.servers[server].margin2;
overtimesTotal += allData.servers[server].overtime;
if (allData.servers[server].count) {
$('#LPG_ServerStats_Table').append('<tr>' +
' <td style="padding:0; width:0px; max-width:0px;"></td>' +
' <td data-sortby="' + server + '">' + server + '</td>' +
' <td data-sortby="' + allData.servers[server].count + '"' + (countTitle ? ' title="' + countTitle + '"' : '') + '>' + allData.servers[server].count + '</td>' +
' <td data-sortby="' + allData.servers[server].duration / 1000 / allData.servers[server].count + '">' + tagpro.helpers.timeFromSeconds(Math.floor(allData.servers[server].duration / 1000 / allData.servers[server].count), true) + '</td>' +
' <td data-sortby="' + (allData.servers[server].redTeamScore + allData.servers[server].blueTeamScore) / (allData.servers[server].duration / 1000 / 60)+ '">' + ((allData.servers[server].redTeamScore + allData.servers[server].blueTeamScore) / (allData.servers[server].duration / 1000 / 60)).toFixed(2) + '</td>' +
' <td data-sortby="' + scoreDiff + '">' + scoreDiffText + '</td>' +
' <td data-sortby="' + allData.servers[server].duration / timePeriodMs + '">' + (allData.servers[server].duration / timePeriodMs * 100).toFixed(1) + '%</td>' +
' <td data-sortby="' + allData.servers[server].count / timePeriodMins + '">' + (allData.servers[server].count / timePeriodMins * 60).toFixed(2) + '</td>' +
'</tr>');
}
}
// console.log('LPG:: showGamesStats() allData:', allData);
let winsTotal = redWinsTotal + blueWinsTotal;
$('#LPG_Extra_Info').append('<div style="font-size:15px; font-weight:bold; margin:0 0 4px;">All Games' + ' (' + allData.serversGraph.length + ')</div>');
$('#LPG_Extra_Info').append('<table id="LPG_Extra_Info_Table"></table>');
$('#LPG_Extra_Info_Table').append('<tr><td>Period</td><td>' + getYMDHOld(lastGameStarted, firstGameStarted).replace(' old', '') + '</td></tr>');
$('#LPG_Extra_Info_Table').append('<tr><td>Game Rate</td><td>' + (allData.serversGraph.length / timePeriodMins * 60).toFixed(2) + ' per hour</td></tr>');
$('#LPG_Extra_Info_Table').append('<tr><td>Mercy Wins</td><td>' + (mercyTotal / winsTotal * 100).toFixed(1) + '% (' + mercyTotal + ')</td></tr>');
$('#LPG_Extra_Info_Table').append('<tr><td>1-Cap Wins</td><td>' + (margin1Total / winsTotal * 100).toFixed(1) + '% (' + margin1Total + ')</td></tr>');
$('#LPG_Extra_Info_Table').append('<tr><td>2-Cap Wins</td><td>' + (margin2Total / winsTotal * 100).toFixed(1) + '% (' + margin2Total + ')</td></tr>');
$('#LPG_Extra_Info_Table').append('<tr><td>Overtime Games</td><td>' + (overtimesTotal / winsTotal * 100).toFixed(1) + '% (' + overtimesTotal + ')</td></tr>');
$('#LPG_Extra_Info_Table').append('<tr><td>Abandoned Games</td><td>' + ((allData.serversGraph.length - winsTotal) / winsTotal * 100).toFixed(1) + '% (' + (allData.serversGraph.length - winsTotal) + ')</td></tr>');
$('#LPG_Extra_Info_Table').append('<tr><td>Red Wins</td><td>' + (redWinsTotal / winsTotal * 100).toFixed(1) + '% (' + redWinsTotal + ')</td></tr>');
$('#LPG_Extra_Info_Table').append('<tr><td>Blue Wins</td><td>' + (blueWinsTotal / winsTotal * 100).toFixed(1) + '% (' + blueWinsTotal + ')</td></tr>');
$('#LPG_GameStats_Container').append('<div id="LPG_GameStats_Graph_Container"></div>');
$('#LPG_GameStats_Container').append('<div id="LPG_GameStats_Graph_ToolTip"></div>');
showGameStatsActivityGraph(allData);
$('#LPG_GameStats_Table tbody tr').sort(function(a, b) {
return b.children[2].dataset.sortby - a.children[2].dataset.sortby;
}).appendTo( $('#LPG_GameStats_Table') );
$('#LPG_ServerStats_Table tbody tr').sort(function(a, b) {
return b.children[2].dataset.sortby - a.children[2].dataset.sortby;
}).appendTo( $('#LPG_ServerStats_Table') );
$('#LPG_GameStats_Container').show();
};
const graphWidthTotal = 1080;
const graphPaddingLeft = 100;
const graphWidth = graphWidthTotal - graphPaddingLeft;
let showGameStatsActivityGraph = function(allData) {
let firstHourDate = new Date();
firstHourDate.setHours(0, 0, 0, 0);
let nextHourDate = new Date();
nextHourDate.setHours(nextHourDate.getHours() + 1, 0, 0, 0);
let timePeriodMs = nextHourDate.getTime() - firstHourDate.getTime();
let lastHour = new Date().getHours();
let serverCounts = [];
for (let server in allData.servers) {
serverCounts.push({ name:server, count:allData.servers[server].count });
}
serverCounts.sort(function(a, b) {
return b.count - a.count;
});
const graphHeight = (serverCounts.length + 1) * 20;
let svg = '<svg id="LPG_GameStats_Graph_SVG" width="' + graphWidthTotal + '" height="' + graphHeight + '" xmlns="http://www.w3.org/2000/svg" style="shape-rendering:crispEdges;">';
// y-axis (server names & row positions)...
let serverPositions = {};
for (let i in serverCounts) {
serverPositions[serverCounts[i].name] = { count:serverCounts[i].count, position:(+i + 1) };
svg += '<text x="' + (0 + 0) + '" y="' + ((+i + 1) * 20 - 7) + '" class="LPG_GameStats_Graph_LabelY">' + serverCounts[i].name + '</text>';
}
// x-axis (times)...
svg += '<text x="' + (graphPaddingLeft - 5) + '" y="' + (graphHeight - 3) + '" class="LPG_GameStats_Graph_LabelX" text-anchor="middle">0</text>';
svg += '<line x1="' + (graphPaddingLeft - 1) + '" y1="0" x2="' + (graphPaddingLeft - 1) + '" y2="' + (graphHeight - 10) + '" class="LPG_GameStats_Graph_Guide" />';
for (let x = 1; x <= lastHour + 1; x++) {
let x1 = graphPaddingLeft + (x / (lastHour + 1)) * graphWidth;
svg += '<text x="' + (x1 - 5) + '" y="' + (graphHeight - 3) + '" class="LPG_GameStats_Graph_LabelX" text-anchor="middle">' + (x === 24 ? '0' : x) + '</text>';
svg += '<line x1="' + x1 + '" y1="0" x2="' + x1 + '" y2="' + (graphHeight - 10) + '" class="LPG_GameStats_Graph_Guide" />';
}
// games data...
for (let game of allData.serversGraph) {
let x1 = graphPaddingLeft + ((game.started - firstHourDate.getTime()) / timePeriodMs) * graphWidth;
let x2 = graphPaddingLeft + ((game.started - firstHourDate.getTime() + game.duration * 1000) / timePeriodMs) * graphWidth;
let y = serverPositions[game.server].position * 20 - 10;
svg += '<line x1="' + x1 + '" y1="' + y + '" x2="' + x2 + '" y2="' + y + '" class="LPG_GameStats_Graph_Details ' + (game.winner === 1 ? 'LPG_GameStats_Graph_Duration_Red' : game.winner === 2 ? 'LPG_GameStats_Graph_Duration_Blue' : 'LPG_GameStats_Graph_Duration_Abandoned') + '" data-gameid="' + game.gameId + '" data-map="' + game.map + '" data-name="' + game.server + '" data-date="' + dayjs(game.started).format('DD-MMM-YYYY h:mma') + '" data-duration="' + tagpro.helpers.timeFromSeconds(game.duration, true) + '" data-score="' + (game.redTeamScore + ':' + game.blueTeamScore) + '" data-winner="' + game.winner + '"' + (game.myTeam ? ' data-myteam="' + game.myTeam + '"' : '') + (game.myTeamWon ? ' data-myteamwon="' + game.myTeamWon + '"' : '') + (game.myTeamName ? ' data-myteamname="' + game.myTeamName + '"' : '') + ' />';
if (game.myTeam) {
if (game.myTeamWon) {
svg += '<circle cx="' + x1 + '" cy="' + (y - 6) + '" r="2" class="LPG_GameStats_MyGame" fill="#0e2" opacity="0.65" shape-rendering="auto" title="My Game (Win)" />'
} else {
svg += '<circle cx="' + x1 + '" cy="' + (y - 6) + '" r="2" class="LPG_GameStats_MyGame" fill="#bbb" opacity="0.65" shape-rendering="auto" title="My Game" />'
}
}
}
$('#LPG_GameStats_Graph_SVG').remove();
$('#LPG_GameStats_Graph_Container').append(svg + '</svg>');
};
let createScoreboardData = function(players) {
let data = [];
for (let playerId in players) {
if (players[playerId].finished) {
data.push({
id: playerId,
name: players[playerId].name,
team: players[playerId].team,
userId: players[playerId].userId,
auth: players[playerId].auth,
tags: players[playerId]["s-tags"],
pops: players[playerId]["s-pops"],
grabs: players[playerId]["s-grabs"],
drops: players[playerId]["s-drops"],
hold: players[playerId]["s-hold"],
captures: players[playerId]["s-captures"],
prevent: players[playerId]["s-prevent"],
returns: players[playerId]["s-returns"],
support: players[playerId]["s-support"],
powerups: players[playerId]["s-powerups"],
score: players[playerId].score,
points: players[playerId].points,
otspawn: players[playerId].otspawn || 0,
});
}
}
data.sort(function(a, b) {
return (b.score - a.score ? b.score - a.score : a.id - b.id);
});
return data;
};
let appendScoreboard = function(gameId, targetElement = '#LPG_ChatLog_Chat') {
let playersSorted = createScoreboardData(cachedGames[gameId].players);
let isOvertime = cachedGames[gameId].metadata.overtimeStartedAt;
let stats = [ 'score', 'tags', 'pops', 'grabs', 'drops', 'hold', 'captures', 'prevent', 'returns', 'support', 'powerups', 'points', 'otspawn' ];
let rStats = { score:0, tags:0, pops:0, grabs:0, drops:0, hold:0, captures:0, prevent:0, returns:0, support:0, powerups:0, points:0, otspawn:0 };
let bStats = { score:0, tags:0, pops:0, grabs:0, drops:0, hold:0, captures:0, prevent:0, returns:0, support:0, powerups:0, points:0, otspawn:0 };
let lastColumn = isOvertime ? stats.length + 1 : stats.length;
let scoreboard = '';
scoreboard += '<table id="LPG_Scoreboard"><tr id="LPG_Scoreboard_Header"><td style="width:72px;"></td><td>Score</td><td>Tags</td><td>Pops</td><td>Grabs</td><td>Drops</td><td>Hold</td><td>Caps</td><td>Prevent</td><td>Returns</td><td>Support</td><td>Pups</td><td>Points</td>' + (isOvertime ? '<td>O/T</td>' : '') + '</tr>';
$.each(playersSorted, function(key, value) {
scoreboard += '<tr data-team="' + value.team + '"><td style="color:' + (value.team === 1 ? redTextColor : blueTextColor) + '">' + (value.auth ? '<span class="LPG_Auth">✔</span>' : '') + value.name + '</td><td data-raw="'+value.score+'">'+value.score+'</td><td data-raw="'+value.tags+'">'+value.tags+'</td><td data-raw="'+value.pops+'">'+value.pops+'</td><td data-raw="'+value.grabs+'">'+value.grabs+'</td><td data-raw="'+value.drops+'">'+value.drops+'</td><td data-raw="'+value.hold+'">'+tagpro.helpers.timeFromSeconds(value.hold, true)+'</td><td data-raw="'+value.captures+'">'+value.captures+'</td><td data-raw="'+value.prevent+'">'+tagpro.helpers.timeFromSeconds(value.prevent, true)+'</td><td data-raw="'+value.returns+'">'+value.returns+'</td><td data-raw="'+value.support+'">'+value.support+'</td><td data-raw="'+value.powerups+'">'+value.powerups+'</td><td data-raw="'+value.points+'">'+(value.points ? value.points : '-')+'</td>' + (isOvertime ? '<td data-raw="'+value.otspawn+'">'+(value.otspawn ? value.otspawn : '-')+'</td>' : '') + '</tr>';
for (let v of stats) {
if (value.team === 1) {
rStats[v] += value[v];
} else {
bStats[v] += value[v];
}
}
});
scoreboard += '</table>';
$(targetElement).append(scoreboard);
setTimeout(() => {
for (let i = 2; i <= lastColumn; i++) {
let column = $('#LPG_Scoreboard tr:gt(0) td:nth-child(' + i + ')');
let prevMax = 0;
$(column).each(function(k, v) {
if ($(this).data('raw') > prevMax) {
$(column).removeClass('LPG_SB_Max_Red LPG_SB_Max_Blue');
$(this).addClass($(this).parent().data('team') === 1 ? 'LPG_SB_Max_Red' : 'LPG_SB_Max_Blue');
prevMax = $(this).data('raw');
} else if ($(this).data('raw') === prevMax && prevMax !== 0) {
$(this).addClass($(this).parent().data('team') === 1 ? 'LPG_SB_Max_Red' : 'LPG_SB_Max_Blue');
}
});
}
$('#LPG_Scoreboard').append('<tr><td colspan="' + lastColumn + '" style="background:#696969;"></td></tr>');
$('#LPG_Scoreboard').append('<tr><td style="color:' + redTextColor + '; text-align:right">Red:</td><td'+(rStats.score>bStats.score?' class="LPG_SB_Max_Red"':'')+'>'+rStats.score+'</td><td'+(rStats.tags>bStats.tags?' class="LPG_SB_Max_Red"':'')+'>'+rStats.tags+'</td><td'+(rStats.pops>bStats.pops?' class="LPG_SB_Max_Red"':'')+'>'+rStats.pops+'</td><td'+(rStats.grabs>bStats.grabs?' class="LPG_SB_Max_Red"':'')+'>'+rStats.grabs+'</td><td'+(rStats.drops>bStats.drops?' class="LPG_SB_Max_Red"':'')+'>'+rStats.drops+'</td><td'+(rStats.hold>bStats.hold?' class="LPG_SB_Max_Red"':'')+'>'+tagpro.helpers.timeFromSeconds(rStats.hold, true)+'</td><td'+(rStats.captures>bStats.captures?' class="LPG_SB_Max_Red"':'')+'>'+rStats.captures+'</td><td'+(rStats.prevent>bStats.prevent?' class="LPG_SB_Max_Red"':'')+'>'+tagpro.helpers.timeFromSeconds(rStats.prevent, true)+'</td><td'+(rStats.returns>bStats.returns?' class="LPG_SB_Max_Red"':'')+'>'+rStats.returns+'</td><td'+(rStats.support>bStats.support?' class="LPG_SB_Max_Red"':'')+'>'+rStats.support+'</td><td'+(rStats.powerups>bStats.powerups?' class="LPG_SB_Max_Red"':'')+'>'+rStats.powerups+'</td><td'+(rStats.points>bStats.points?' class="LPG_SB_Max_Red"':'')+'>'+(rStats.points ? rStats.points : '-')+'</td>' + (isOvertime ? '<td'+(rStats.otspawn>bStats.otspawn?' class="LPG_SB_Max_Red"':'')+'>'+(rStats.otspawn ? rStats.otspawn : '-')+'</td>' : '') + '</tr>');
$('#LPG_Scoreboard').append('<tr><td style="color:' + blueTextColor + '; text-align:right">Blue:</td><td'+(bStats.score>rStats.score?' class="LPG_SB_Max_Blue"':'')+'>'+bStats.score+'</td><td'+(bStats.tags>rStats.tags?' class="LPG_SB_Max_Blue"':'')+'>'+bStats.tags+'</td><td'+(bStats.pops>rStats.pops?' class="LPG_SB_Max_Blue"':'')+'>'+bStats.pops+'</td><td'+(bStats.grabs>rStats.grabs?' class="LPG_SB_Max_Blue"':'')+'>'+bStats.grabs+'</td><td'+(bStats.drops>rStats.drops?' class="LPG_SB_Max_Blue"':'')+'>'+bStats.drops+'</td><td'+(bStats.hold>rStats.hold?' class="LPG_SB_Max_Blue"':'')+'>'+tagpro.helpers.timeFromSeconds(bStats.hold, true)+'</td><td'+(bStats.captures>rStats.captures?' class="LPG_SB_Max_Blue"':'')+'>'+bStats.captures+'</td><td'+(bStats.prevent>rStats.prevent?' class="LPG_SB_Max_Blue"':'')+'>'+tagpro.helpers.timeFromSeconds(bStats.prevent, true)+'</td><td'+(bStats.returns>rStats.returns?' class="LPG_SB_Max_Blue"':'')+'>'+bStats.returns+'</td><td'+(bStats.support>rStats.support?' class="LPG_SB_Max_Blue"':'')+'>'+bStats.support+'</td><td'+(bStats.powerups>rStats.powerups?' class="LPG_SB_Max_Blue"':'')+'>'+bStats.powerups+'</td><td'+(bStats.points>rStats.points?' class="LPG_SB_Max_Blue"':'')+'>'+(bStats.points ? bStats.points : '-')+'</td>' + (isOvertime ? '<td'+(bStats.otspawn>rStats.otspawn?' class="LPG_SB_Max_Blue"':'')+'>'+(bStats.otspawn ? bStats.otspawn : '-')+'</td>' : '') + '</tr>');
}, 100);
};
let drawLine = function(ctx, x1, y1, x2, y2, color='#ffffff', lineWidth=1, dash=[]) {
ctx.setLineDash(dash);
ctx.beginPath();
ctx.lineWidth = lineWidth;
ctx.strokeStyle = color;
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.closePath();
};
let appendCapsTimeline = function(gameId, totalWidth = 380, targetElement = '#LPG_CapTimeline_Container') {
const metadata = cachedGames[gameId].metadata;
const totalHeight = 50;
const centerY = totalHeight / 2;
const minuteMarkerLargeSize = 7;
const offsetYMarkerLargeRed = centerY - minuteMarkerLargeSize;
const offsetYMarkerLargeBlue = centerY + minuteMarkerLargeSize;
const minuteMarkerSmallSize = 4;
const offsetYMarkerSmallRed = centerY - minuteMarkerSmallSize;
const offsetYMarkerSmallBlue = centerY + minuteMarkerSmallSize;
// S3 = Pre-Game; S1 = Normal Time; S5 = Overtime
const expectedS1Duration = metadata.expectedGameDuration; // 360000 = 6 mins for pubs
const actualS3Duration = metadata.preGameDuration;
const actualS5Duration = metadata.overtimeStartedAt ? metadata.started + actualS3Duration + metadata.duration - metadata.overtimeStartedAt : 0;
const actualS1Duration = metadata.duration - actualS5Duration;
const expectedS3S1Duration = actualS3Duration + expectedS1Duration;
const actualS3S1Duration = actualS3Duration + actualS1Duration;
const actualS1S5Duration = expectedS1Duration + actualS5Duration;
const actualS3S1S5Duration = actualS3Duration + expectedS1Duration + actualS5Duration;
const s1GameLengthMins = Math.ceil((expectedS1Duration) / 1000 / 60);
const s1S5GameLengthMins = Math.ceil(actualS1S5Duration / 1000 / 60);
const s1StartPoint = actualS3Duration / actualS3S1S5Duration;
const s1EndPointExpected = expectedS3S1Duration / actualS3S1S5Duration;
const s1EndPointActual = actualS3S1Duration / actualS3S1S5Duration;
const minutesInterval = (totalWidth - (s1StartPoint * totalWidth)) / (actualS1S5Duration / 60000);
$(targetElement).append('<canvas width="' + (totalWidth + 20) + '" height="' + totalHeight + '"></div>');
let canvas = $(targetElement + ' canvas');
let ctx = canvas.get(0).getContext('2d');
ctx.translate(10.5, 0.5);
// minute markers...
let count = 0;
for (let x = 0; x <= totalWidth; x += minutesInterval) {
if (x === 0) { // start marker
drawLine(ctx, s1StartPoint * totalWidth, offsetYMarkerLargeRed, s1StartPoint * totalWidth, offsetYMarkerLargeBlue, 'white');
} else if (s1GameLengthMins % 2 === 0 && count === s1GameLengthMins / 2) { // halfway marker in normal time (only if even minutes)
drawLine(ctx, s1StartPoint * totalWidth + x, offsetYMarkerLargeRed, s1StartPoint * totalWidth + x, offsetYMarkerLargeBlue, 'white');
} else if (count === s1GameLengthMins || count === s1S5GameLengthMins) { // end of normal time marker
drawLine(ctx, s1StartPoint * totalWidth + x, offsetYMarkerLargeRed, s1StartPoint * totalWidth + x, offsetYMarkerLargeBlue, 'white');
} else { // minute marker
drawLine(ctx, s1StartPoint * totalWidth + x, offsetYMarkerSmallRed, s1StartPoint * totalWidth + x, offsetYMarkerSmallBlue, 'white');
}
count++;
}
// horizontal game-section lines...
drawLine(ctx, 0, centerY, s1StartPoint * totalWidth, centerY, 'red', 1, [2, 2]); // pre-game
if (actualS5Duration) { // overtime
drawLine(ctx, s1StartPoint * totalWidth, centerY, s1EndPointExpected * totalWidth, centerY, '#eee'); // normal game time played
drawLine(ctx, s1EndPointExpected * totalWidth, centerY, totalWidth, centerY, 'orange'); // overtime played
drawLine(ctx, totalWidth, offsetYMarkerLargeRed, totalWidth, offsetYMarkerLargeBlue, 'orange'); // end of overtime vertical marker
} else {
drawLine(ctx, s1StartPoint * totalWidth, centerY, s1EndPointActual * totalWidth, centerY, '#eee'); // normal game time played
drawLine(ctx, s1EndPointActual * totalWidth, centerY, s1EndPointExpected * totalWidth, centerY, '#666', 1, [2, 2]); // post-game time (not played)
}
// caps...
ctx.globalAlpha = 0.85;
for (let chat of cachedGames[gameId].chatlog) {
if (chat.cap) {
const x = Math.min((chat.time / actualS3S1S5Duration * totalWidth), totalWidth);
ctx.fillStyle = chat.cap === 1 ? '#ff4747' : '#4494f2';
ctx.beginPath();
ctx.arc(x, centerY, 3.5, 0, Math.PI * 2);
ctx.fill();
}
}
// joiners/leavers/switchers...
ctx.globalAlpha = 0.85;
for (const player of cachedGames[gameId].joiners) {
const color = player.team === 1 ? '#ff4747' : '#4494f2';
const x = Math.round(Math.min((player.time / actualS3S1S5Duration * totalWidth), totalWidth));
if (player.type === 'left') { //add '-' where player left...
const y = player.team === 1 ? centerY - 12 : centerY + 12;
drawLine(ctx, x - 1.5, y, x + 1.5, y, color);
} else if (player.type === 'joined') { //add '+' where player joined...
let y = player.team === 1 ? centerY - 9 : centerY + 9;
drawLine(ctx, x, y - 1.5, x, y + 1.5, color);
drawLine(ctx, x - 1.5, y, x + 1.5, y, color);
} else if (player.type === 'switched') { //mark where player switched teams...
const fromTeamColor = player.newTeam === 1 ? '#4494f2' : '#ff4747';
const toTeamColor = player.newTeam === 1 ? '#ff4747' : '#4494f2';
let y = player.newTeam === 1 ? centerY - 16 : centerY + 16;
drawLine(ctx, x + 0, y - 1.5, x + 0, y + 1.5, toTeamColor);
drawLine(ctx, x + 1, y - 1.5, x + 1, y + 1.5, toTeamColor);
}
}
ctx.translate(-10.5, -0.5);
};
let saveChatEntry = function(chatlog, data, time, players, currentState, selectedFlair) {
let name = null;
let team = null;
if (data.from > 0) {
name = players['' + data.from].name;
team = players['' + data.from].team;
} else {
name = data.from;
}
chatlog.push({ from:data.from, message:data.message, to:data.to, c:data.c, mod:data.mod, monitor:data.monitor, time:time, name:name, team:team, state:currentState, flair:selectedFlair });
};
let showChatLog = function(gameId, addReplayLink = false) {
let chatlog = cachedGames[gameId].chatlog;
let metadata = cachedGames[gameId].metadata;
let gameStartedAt = new Date(metadata.started);
let overtimeStartedAt = metadata.overtimeStartedAt;
if (!$('#LPG_ChatLog').length) {
addChatLogContainer();
}
$('#LPG_CapTimeline_Container').empty();
$('#LPG_ChatLog_Chat').empty();
$('#LPG_ChatLog_Chat').append('<div class="LPG_Header">' + gameStartedAt.toDateString() + ', ' + gameStartedAt.toLocaleTimeString() + '</div>');
$('#LPG_ChatLog_Chat').append('<div class="LPG_Header">' + metadata.mapName + '</div>');
$('#LPG_ChatLog_Chat').append('<div class="LPG_Header">Game ID: ' + metadata.gameId + ' on ' + metadata.serverName + '</div>');
$('#LPG_ChatLog_Chat').append('<div class="LPG_Header">Final Score: ' + metadata.teams.red.score + ':' + metadata.teams.blue.score + '</div>');
if (addReplayLink) {
let replayUrl = getReplayUrl(gameId);
$('#LPG_ChatLog_Chat').append('<div class="LPG_Header"><a href="' + replayUrl + '" class="LPG_ViewReplayLink" data-gameid="' + gameId + '" style="color:#689F38; text-decoration:none;" target="_blank">View Replay</a></div>');
}
$('#LPG_ChatLog_Chat').append('<div style="margin:3px 80px; border-bottom:1px solid #555;"></div>');
for (let i = 0; i < chatlog.length; i++) {
let fromColor = '';
let messageColor = '';
let timerText = '';
let flair;
if (chatlog[i].to === 'team') {
fromColor = chatlog[i].team === 1 ? redTextColor : blueTextColor;
messageColor = fromColor;
} else if (chatlog[i].to === 'group') {
fromColor = chatlog[i].team === 1 ? redTextColor : blueTextColor;
messageColor = '#e7e700';
} else if (chatlog[i].to === 'mods') {
fromColor = chatlog[i].team === 1 ? redTextColor : blueTextColor;
messageColor = '#00b900';
} else if (chatlog[i].mod) {
fromColor = '#00b900';
messageColor = fromColor;
} else if (chatlog[i].monitor) {
fromColor = '#00b7a5';
messageColor = fromColor;
}
if (chatlog[i].c) {
fromColor = chatlog[i].c;
messageColor = fromColor;
}
if (chatlog[i].state === 2 || chatlog[i].state === 3) {
timerText = '--:--';
} else if (metadata.gameEndsAt) {
let timeLeft = Math.round(((metadata.gameEndsAt - metadata.gameStartedAt + metadata.preGameDuration) - chatlog[i].time) / 1000);
if (timeLeft < 0) {
timerText = '+' + tagpro.helpers.timeFromSeconds(Math.abs(timeLeft), true);
} else {
timerText = tagpro.helpers.timeFromSeconds(timeLeft, true);
}
}
if (chatlog[i].flair) {
flair = '<span class="flair ' + chatlog[i].flair.className + '" style="background-position: -' + (chatlog[i].flair.x * 16) + 'px -' + (chatlog[i].flair.y * 16) + 'px; position:absolute; scale:75%; margin:-1px 0 0 2px;"></span>';
}
$('#LPG_ChatLog_Chat').append('<div class="LPG_Chat"' + (chatlog[i].cap ? ' data-score="' + chatlog[i].scoreRed + ':' + chatlog[i].scoreBlue + '"' : '') + '>' +
' <span class="LPG_Timer' + (chatlog[i].cap ? (chatlog[i].cap === 1 ? ' LPG_Cap_Red' : ' LPG_Cap_Blue') : '') + '"' + ' data-time="' + chatlog[i].time + '">' + timerText + '</span>' +
' <span class="LPG_Message"' + (messageColor ? ' style="color:' + messageColor + ';"' : '') + '>' +
(chatlog[i].name ? '<span class="' + (chatlog[i].team === 1 ? 'LPG_TeamChatRed' : 'LPG_TeamChatBlue') + '"' + (fromColor ? ' style="color:' + fromColor + ';"' : '') + '>' + chatlog[i].name + '</span><span style="color:#ccc;">:</span> ' : '') +
' <span class="LPG_ActualMessage"' + (messageColor ? ' style="color:' + messageColor + ';"' : '') + '>' + chatlog[i].message + (flair ? flair : '') + '</span>' +
' </span>' +
'</div>');
}
$('#LPG_ChatLog_Chat').prepend('<div style="margin:20px 80px 10px; border-bottom:1px solid #555;"></div>');
appendScoreboard(gameId, '#LPG_ChatLog_Chat');
$('#LPG_ChatLog_Chat').append('<div style="margin:10px 80px 30px; border-bottom:1px solid #555;"></div>');
appendCapsTimeline(gameId, 380);
$('#LPG_ChatLog').fadeIn(200);
};
let getReplay = function(gameId) {
let url = '/history/gameFile?gameId=' + gameId;
return $.get(url).done(function(data) {
if (data.error) {
$('#LPG_ChatLog_Chat').append('<div style="margin:10px auto; text-align:center; font-size:12px; color:#c00;">A server error has occured: ' + data.error + '</div>');
console.log('LPG:: A server error has occured (0):\n' + data.error);
return false;
} else {
lastGet = Date.now();
return data;
}
}).fail(function(jqxhr, textStatus, error) {
$('#LPG_ChatLog_Chat').append('<div style="margin:10px auto; text-align:center; font-size:12px; color:#c00;">A server error has occured: ' + error + '</div>');
console.log('LPG:: A server error has occured (1):\nStatus:', jqxhr.status, '\nError:', error);
return jqxhr;
});
};
let processReplay = function(data, gameId) {
let chatlog = [];
let players = {};
let currentPlayers = {}; // this is to track each player's name and team during the game
let joiners = []; // and leavers and switchers
let metadata = null;
let currentId = null;
let currentState = null;
let currentRedCount = 0;
let currentBlueCount = 0;
let currentScoreRed, currentScoreBlue;
let stats = ['auth','points','score','s-tags','s-pops','s-grabs','s-drops','s-hold','s-captures','s-prevent','s-returns','s-support','s-powerups'];
let packets;
let skipCapturesMaps = ['Easter Race']; // names of maps to not process the 'p' message for (usually event maps where the stat is used for something else)
let skipCaptures = false;
if (Array.isArray(data)) {
packets = data;
} else {
packets = data.split('\n');
}
for (let i = 0; i < packets.length; i++) {
const packet = Array.isArray(packets[i]) ? packets[i] : JSON.parse(packets[i]);
if (packet[1] === 'p') {
const arr = packet[2];
for (let idx in arr) {
const obj = arr[idx];
for (let key in obj) {
if (stats.indexOf(key) >= 0) {
if (!skipCaptures && key === 's-captures' && obj[key] && players[obj.id][key] !== obj[key] && currentPlayers[obj.id]) {
chatlog.push({ from:null, message:(currentPlayers[obj.id].team === 1 ? metadata.teams.red.name : metadata.teams.blue.name) + ' Caps! ' + currentPlayers[obj.id].name + ' (' + currentScoreRed + ':' + currentScoreBlue + '), ' + currentRedCount + 'v' + currentBlueCount, to:'all', c:'#7fff00', mod:null, monitor:null, time:packet[0], state:currentState, cap:currentPlayers[obj.id].team, scoreRed:currentScoreRed, scoreBlue:currentScoreBlue });
}
players[obj.id][key] = obj[key];
}
if (key === 'flair') {
players[obj.id][key] = obj[key];
}
}
}
} else if (packet[1] === 'mark') {
chatlog.push({ from:null, message:'<span style="display:flex; align-items:center; background:#000; padding:0 10px; font-weight:bold; border-radius:5px;">- MARK -</span>', to:'all', c:'#b0b', mod:null, monitor:null, time:packet[0], state:currentState });
} else if (packet[1] === 'chat') {
if (packet[2].from !== null) {
saveChatEntry(chatlog, packet[2], packet[0], players, currentState);
} else if (packet[2].from === null && (packet[2].to === 'all' || packet[2].to == packet[2].for) && packet[2].c !== '#63FE22') { // hide tips
if (packet[2].from === null) {
if (packet[2].to === 'all') {
let selectedFlair;
if (currentState !== 2) {
if (packet[2].message.includes('has joined')) {
const name = packet[2].message.slice(0, packet[2].message.indexOf(' has joined'));
if (packet[2].message.includes('the ' + metadata.teams.red.name + ' team')) {
currentRedCount++;
currentPlayers[packet[2].for] = { name:name, team:1 };
joiners.push({ time:packet[0], id:packet[2].for, team:1, type:'joined' });
} else if (packet[2].message.includes('the ' + metadata.teams.blue.name + ' team')) {
currentBlueCount++;
currentPlayers[packet[2].for] = { name:name, team:2 };
joiners.push({ time:packet[0], id:packet[2].for, team:2, type:'joined' });
}
selectedFlair = players[packet[2].for] && players[packet[2].for].flair ? players[packet[2].for].flair : false;
} else if (packet[2].message.includes('has left')) {
const name = packet[2].message.slice(0, packet[2].message.indexOf(' has left'));
if (packet[2].message.includes('the ' + metadata.teams.red.name + ' team')) {
currentRedCount--;
currentPlayers[packet[2].for] = { name:name, team:1 };
joiners.push({ time:packet[0], id:packet[2].for, team:1, type:'left' });
} else if (packet[2].message.includes('the ' + metadata.teams.blue.name + ' team')) {
currentBlueCount--;
currentPlayers[packet[2].for] = { name:name, team:2 };
joiners.push({ time:packet[0], id:packet[2].for, team:2, type:'left' });
}
} else if (packet[2].message.includes('has switched')) {
const name = packet[2].message.slice(0, packet[2].message.indexOf(' has switched'));
if (packet[2].message.includes('the ' + metadata.teams.red.name + ' team')) {
currentRedCount++;
currentBlueCount--;
currentPlayers[packet[2].for] = { name:name, team:1 };
joiners.push({ time:packet[0], id:packet[2].for, newTeam:1, type:'switched' });
} else if (packet[2].message.includes('the ' + metadata.teams.blue.name + ' team')) {
currentBlueCount++;
currentRedCount--;
currentPlayers[packet[2].for] = { name:name, team:2 };
joiners.push({ time:packet[0], id:packet[2].for, newTeam:2, type:'switched' });
}
}
}
saveChatEntry(chatlog, packet[2], packet[0], players, currentState, selectedFlair);
} else if (packet[2].to == packet[2].for) {
if (packet[2].message.includes('During overtime, each death increases time to respawn. Currently:')) {
players[packet[2].to].otspawn = +packet[2].message.slice(packet[2].message.lastIndexOf(':') + 2);
}
}
}
}
} else if (packet[1] === 'id') {
currentId = packet[2]; // not used by this script anymore
} else if (packet[1] === 'score') {
currentScoreRed = packet[2].r;
currentScoreBlue = packet[2].b;
} else if (packet[1] === 'time' && currentState !== packet[2].state) {
currentState = packet[2].state;
if (currentState === 1) {
metadata.gameEndsAt = metadata.gameStartedAt + packet[2].time;
metadata.expectedGameDuration = packet[2].time;
chatlog.push({ from:null, message: '<span style="background:#ddd; padding:0 10px; font-weight:bold; border-radius:5px;">Start of Game</span>', to:'all', c:'#000', mod:null, monitor:null, time:packet[0], state:currentState });
} else if (currentState === 3) {
metadata.preGameDuration = packet[2].time;
metadata.gameStartedAt = metadata.started + packet[2].time;
} else if (currentState === 5) {
metadata.overtimeStartedAt = metadata.gameEndsAt;
}
} else if (packet[1] === 'end') {
const winnerTeamText = packet[2].winner === 'red' ? 'Red' : 'Blue';
const winnerIconText = packet[2].winner === 'red' ? '<span style="margin:0 5px; font-size:8px;">🔴</span>' : '<span style="margin:0 5px; font-size:8px;">🔵</span>';
currentState = 2;
chatlog.push({ from:null, message:'<span style="display:flex; align-items:center; background:#ddd; padding:0 10px; font-weight:bold; border-radius:5px;"><span>End of Game</span>' + winnerIconText + '<span>' + winnerTeamText + ' Wins ' + metadata.teams.red.score + ':' + metadata.teams.blue.score + '</span>' + winnerIconText, to:'all', c:'#000', mod:null, monitor:null, time:packet[0], state:currentState });
} else if (packet[1] === 'recorder-metadata') {
for (let j = 0; j < packet[2].players.length; j++) {
players[packet[2].players[j].id] = { name:packet[2].players[j].displayName, team:packet[2].players[j].team, userId:packet[2].players[j].userId, finished:packet[2].players[j].finished };
}
metadata = packet[2];
if (skipCapturesMaps.indexOf(metadata.mapName) >= 0) {
skipCaptures = true;
}
}
}
cachedGames[gameId] = {
dateCached: Date.now(),
chatlog: chatlog,
metadata: metadata,
players: players,
joiners: joiners,
};
GM_setValue('cachedGames', cachedGames);
};
let getAndProcessReplay = function(gameId) {
getReplay(gameId).then(function(data) {
if (data.error) {
console.log('LPG:: getAndProcessReplay() data.error:', data);
} else {
processReplay(data, gameId);
}
}).fail(function(jqxhr) {
console.log('LPG:: getAndProcessReplay() .fail() jqxhr:', jqxhr);
if (jqxhr.status === 500) {
console.log('LPG:: getAndProcessReplay() .fail() 500 Internal Server Error');
}
});
};
let getProcessAndShowReplayChat = function(gameId, addReplayLink = false) {
$('a.LPG_ShowChat[data-gameid="' + gameId + '"]').find('i').removeClass('fa-comment-dots').addClass('LPG_Loading_Animation');
getReplay(gameId).then(function(data) {
if (data.error) {
console.log('LPG:: getProcessAndShowReplayChat() data.error:', data);
} else {
$('#LPG_CapTimeline_Container').empty();
$('#LPG_ChatLog_Chat').append('<div style="margin:50px auto; text-align:center; font-size:14px; color:#0b4;">Getting data...</div>');
$('#LPG_ChatLog').fadeIn(200);
processReplay(data, gameId);
showChatLog(gameId, addReplayLink);
$('a.LPG_ShowChat[data-gameid="' + gameId + '"]').find('i').addClass('LPG_Cached');
}
}).fail(function(jqxhr) {
console.log('LPG:: getProcessAndShowReplayChat() .fail() jqxhr:', jqxhr);
if (jqxhr.status === 500) {
console.log('LPG:: getProcessAndShowReplayChat() .fail() 500 Internal Server Error');
}
}).always(function() {
console.log('getProcessAndShowReplayChat() .always');
$('.LPG_Loading_Animation').removeClass('LPG_Loading_Animation').addClass('fa-comment-dots');
});
};
let createMenu = function() {
GM_addStyle('#LPG_Container { position:relative; margin:0 0 20px 0; padding:10px 5px; width:100%; color:#eee; font-size:11px; border:1px outset #466230; border-radius:7px; background:linear-gradient(315deg, #363d2f, #1a3618); }');
GM_addStyle('#LPG_Container_Inner { position:relative; display:block; height:240px; margin:20px auto 10px; padding:5px 10px 8px; background:#1a1a1a; border:1px outset #555; border-radius:12px; overflow-y:auto; overflow-x:hidden; }');
GM_addStyle('#LPG_Container_Inner::-webkit-scrollbar { width:4px; }');
GM_addStyle('#LPG_Container_Inner::-webkit-scrollbar-thumb { background:#8943b5; border-radius:4px; }');
GM_addStyle('#LPG_Container_Inner::-webkit-scrollbar-track { background:#ddd; border-radius:4px; }');
GM_addStyle('#LPG_Container_Table { margin:10px; width:100%; width:-webkit-fill-available; }');
GM_addStyle('#LPG_Container_Table tr:hover { background:#334433; }');
GM_addStyle('#LPG_Container_Table td { padding:0 2px; }');
GM_addStyle('.LPG_MyGameWon { border-right:2px solid #24b53b; }');
GM_addStyle('.LPG_MyGameLost { border-right:2px solid #cb4f4f; }');
GM_addStyle('.LPG_MyGameUnknown { border-right:2px solid #777777; }');
GM_addStyle('.LPG_Winner_Red { border-left:5px solid #993333; }');
GM_addStyle('.LPG_Winner_Blue { border-left:5px solid #333399; }');
GM_addStyle('.LPG_Winner_Tie { border-left:5px solid #a1661c; }');
GM_addStyle('.LPG_Winner_Unknown { border-left:5px solid #666666; }');
GM_addStyle('.LPG_NewHourMarker { border-top:1px solid #444444; }');
GM_addStyle('.LPG_HideNewHourMarker { border-top:none !important; }');
GM_addStyle('.LPG_Winner_Score_Red_Loss { color:#ccc; margin:0 3px 0 0; }');
GM_addStyle('.LPG_Winner_Score_Red_Win { color:#e16e6e; margin:0 3px 0 0; }');
GM_addStyle('.LPG_Winner_Score_Blue_Loss { color:#ccc; margin:0 0 0 3px; }');
GM_addStyle('.LPG_Winner_Score_Blue_Win { color:#5b88eb; margin:0 0 0 3px; }');
GM_addStyle('.LPG_Show_Games { padding:4px 10px; color:#777; background:#444; width:80px; text-align:center; margin:2px 5px; border:1px outset #777; border-radius:3px; cursor:pointer; }');
GM_addStyle('.LPG_Show_Games:hover { background:#555; }');
GM_addStyle('.LPG_GPH { margin:3px 5px; width:80px; font-size:10px; color:#ccc; text-align:center; }');
GM_addStyle('.LPG_HiddenRow { display:none; }');
GM_addStyle('#LPG_LastUpdated { margin:-2px 10px 0 0; color:#666; font-style:italic; text-align:right; font-size:10px; }');
GM_addStyle('#LPG_ChatLog { display:none; position:absolute; padding:2px; color:#888; right:-345px; top:0px; margin:2px 0 0 0; width:520px; background:#151515; border-radius:8px; box-shadow:8px 8px 10px black; font-size:11px; border:1px outset #88c; overflow:hidden; z-index:1; }');
GM_addStyle('#LPG_ChatLog_Header { height:52px; }');
GM_addStyle('#LPG_ChatLog_Chat { position:relative; height:420px; overflow-y:auto; overflow-x:hidden; }');
GM_addStyle('#LPG_ChatLog_Chat::-webkit-scrollbar { width:4px; }');
GM_addStyle('#LPG_ChatLog_Chat::-webkit-scrollbar-thumb { background:#292; border-radius:4px; }');
GM_addStyle('#LPG_ChatLog_Chat::-webkit-scrollbar-track { background:#ddd; border-radius:4px; }');
GM_addStyle('#LPG_ChatLog_Close { position:absolute; width:17px; height:17px; top:3px; right:2px; padding:0 0 0 1px; color:red; border:1px solid darkred; text-align:center; font-size:12px; font-weight:bold; border-radius:10px; cursor:pointer; }');
GM_addStyle('#LPG_CapTimeline_Container { position:relative; text-align:center; height:50px; width:400px; margin:5px auto; background:rgba(0,0,0,0.3); border-radius:8px; }');
GM_addStyle('.LPG_Cap_Red { background:' + redBackgroundColor + '; color:#fff !important; }');
GM_addStyle('.LPG_Cap_Blue { background:' + blueBackgroundColor + '; color:#fff !important; }');
GM_addStyle('.LPG_ShowChat { color:#ccc; margin:0 0 0 5px; }');
GM_addStyle('.LPG_ShowChat i { color:#ccc; }');
GM_addStyle('.LPG_Loading_Animation { width:11px; height:11px; border:2px dashed #41cd31; border-radius:50%; display:inline-block; position:relative; box-sizing:border-box; animation:LPG_Loading_Frames_Rotation 1.6s linear infinite; }');
GM_addStyle('@keyframes LPG_Loading_Frames_Rotation { 0% { transform:rotate(0deg); } 100% { transform:rotate(360deg); } }');
GM_addStyle('.LPG_Cached { color:#666 !important; }');
GM_addStyle('.LPG_Watched { color:#666 !important; }');
GM_addStyle('.LPG_Header { text-align:center; }');
GM_addStyle('.LPG_Timer { display:inline-block; color:#da981f; width:35px; min-width:35px; text-align:right; margin-right:3px; }');
GM_addStyle('.LPG_Chat { display:flex; color:#ccc; }');
GM_addStyle('.LPG_LastChatSync { background:#244b20; }');
GM_addStyle('.LPG_TimerClick { text-decoration: underline; }');
GM_addStyle('.LPG_TeamChatRed { color:' + redTextColor + '; }');
GM_addStyle('.LPG_TeamChatBlue { color:' + blueTextColor + '; }');
GM_addStyle('.LPG_Auth { font-size:9px; color:#77ff00; }');
GM_addStyle('#LPG_Scoreboard { margin:5px auto; font-size:11px; color:#bbb; width:95%; text-align:center; background:#181818; }');
GM_addStyle('#LPG_Scoreboard_Header { color:#fff; background:#696969; font-size:10px;}');
GM_addStyle('#LPG_Scoreboard td { border-bottom:1px solid #000; padding:2px 1px 1px; white-space:nowrap; }');
GM_addStyle('#LPG_Scoreboard td:nth-child(1) { font-family:monospace; }');
GM_addStyle('.LPG_SB_Max_Red { color:white; background:' + redBackgroundColor + '; }');
GM_addStyle('.LPG_SB_Max_Blue { color:white; background:' + blueBackgroundColor + '; }');
GM_addStyle('#LPG_GameStats_Show { position:absolute; right:7px; top:3px; color:#b655ff; font-size:12px; opacity:0.6; cursor:pointer; }');
GM_addStyle('#LPG_GameStats_Show:hover { opacity:1; }');
GM_addStyle('.LPG_Overtime { color:#d3a857; }');
GM_addStyle('.LPG_Overtime_Extreme { color:#d34557; }');
GM_addStyle('.LPG_RowNumber { color:#aaa; font-size:11px; margin-right:4px; }');
GM_addStyle('#LPG_ScoreBoard_Modal { margin:0 0 0 -335px; position:absolute; box-shadow:5px 5px 10px black; padding:10px; border:1px solid white; border-radius:5px; background:#222; }');
GM_addStyle('#LPG_ScoreBoard_Modal_Close { position:absolute; width:13px; height:13px; top:3px; right:2px; padding:1px 2px 0 0px; color:red; border:1px solid darkred; text-align:center; font-size:8px; font-weight:bold; border-radius:10px; cursor:pointer; }');
GM_addStyle('.LPG_TD_Score { text-align:center; cursor:pointer; }');
GM_addStyle('.LPG_TD_Winner { text-align:center; }');
GM_addStyle('#LPG_HourTotalsGraphContainer { position:relative; display:flex; align-items:flex-end; margin:10px auto 10px; padding:12px 0 12px 0; font-size:9px; background:rgba(0,0,0,0.2); box-shadow:2px 2px 5px black, 0px 0px 2px black; width:532px; height:224px; }');
GM_addStyle('.LPG_HoursGraphBar { position:absolute; width:20px; background:linear-gradient(#ab3,#c70,#000); box-shadow:1px 1px 0px black; }');
GM_addStyle('.LPG_HoursGraphBar:hover { background:linear-gradient(#bc4,#f70,#000); }');
GM_addStyle('.LPG_HoursGraphValue { position:absolute; margin:0 0 12px 0; width:20px; background:none; color:#fff; text-align:center; }');
GM_addStyle('.LPG_HoursGraphLabel { position:absolute; margin:0 0 -12px 0; width:24px; background:none; color:#fff; border-top:1px solid #111; text-align:center; }');
GM_addStyle('.LPG_Totals_Divider { margin:0 5px; color:#666; }');
let LPG_Container = '<div id="LPG_Container">' +
' <div id="LPG_GameStats_Show" title="Show Stats & Graph"><i class="fa-solid fa-server"></i></div>' +
' <div id="LPG_AllGamesTotals"></div>' +
' <div id="LPG_HourTotalsGraphContainer"></div>' +
' <span id="LPG_GPH_L100" class="LPG_GPH" data-type="L100" title="Last 100 Public Games Per Hour" style="position:absolute; width:auto; top:30px; right:10px; color:#8b8;"></span>' +
' <div id="LPG_LastUpdated"></div>' +
' <div id="LPG_LastNGamesTotals"></div>' +
' <div id="LPG_Container_Inner">' +
' <table id="LPG_Container_Table"></table>' +
' </div>' +
' <div id="LPG_Options" style="display:flex; justify-content:flex-end; margin:-4px 4px -4px 4px;">' +
' <input type="text" id="LPG_FilterInput" placeholder="Map Name or Server" style="margin:2px 50px 0 0; width:180px; height:22px; background:#161616; color:#a2dd74; font-size:12px; font-weight:bold; text-align:center;" \>' +
' <span class="LPG_Show_Games" data-type="all" title="All Public Games">All</span>' +
' <span class="LPG_Show_Games" data-type="mine" title="My Public Games">Mine</span>' +
' </div>' +
'</div>';
if ($('.video-container').length) { //logged out
$('.video-container').before(LPG_Container);
} else {
$('#home-news').before(LPG_Container);
}
};
let addMenuListeners = function() {
$('#LPG_Container_Table').on('click', '.LPG_ViewReplay', function() {
let gameId = this.dataset.gameid;
watchedGames[gameId] = { dateWatched: Date.now() };
GM_setValue('watchedGames', watchedGames);
$(this).find('i').addClass('LPG_Watched');
});
$('#LPG_Container_Table').on('click', '.LPG_ShowChat', function(e) {
e.preventDefault();
let gameId = this.dataset.gameid;
$('#LPG_ChatLog').css('top', e.offsetY + 26);
if (cachedGames[gameId]) {
showChatLog(gameId, true);
$('a.LPG_ShowChat[data-gameid="' + gameId + '"]').find('i').addClass('LPG_Cached');
} else {
getProcessAndShowReplayChat(gameId, true);
}
});
$('#LPG_Container').on('click', '.LPG_Show_Games', function() {
$('#LPG_FilterInput').val('');
$('#LPG_Container_Table').removeAttr('data-servername').removeAttr('data-mapname');
showReplaysType = this.dataset.type;
if (this.dataset.type === 'mine' || gameStatsLoadedDate < Date.now() - 15 * 60 * 1000) {
getAndShowLastNGames();
} else if (this.dataset.type === 'all') {
sortSavedGames();
showLastNRows();
}
});
$('#LPG_Container').on('click', '.LPG_ViewReplayLink', function() {
let gameId = this.dataset.gameid;
watchedGames[gameId] = { dateWatched: Date.now() };
GM_setValue('watchedGames', watchedGames);
$('a.LPG_ViewReplay[data-gameid="' + gameId + '"]').find('i').addClass('LPG_Watched');
});
$('#LPG_Container').on('keyup', '#LPG_FilterInput', function() {
filterByText(this.value.toLowerCase());
});
$('#LPG_Container_Table').on('click', '.LPG_Row_FilterBy', function() {
let thisServerName = $(this).attr('data-servername') || null;
let prevServerName = $('#LPG_Container_Table').attr('data-servername') || null;
let thisMapName = $(this).attr('data-mapname') || null;
let prevMapName = $('#LPG_Container_Table').attr('data-mapname') || null;
let textFilter = $('#LPG_FilterInput').val().toLowerCase();
if (textFilter) {
if (textFilter === thisServerName) {
prevServerName = thisServerName;
}
if (textFilter === thisMapName) {
prevMapName = thisMapName;
}
$('#LPG_FilterInput').val('');
}
$('#LPG_Container_Table .LPG_HiddenRow').removeClass('LPG_HiddenRow');
$('#LPG_Container_Table tr.LPG_HideNewHourMarker').removeClass('LPG_HideNewHourMarker');
if (thisServerName) {
if (thisServerName === prevServerName) {
$('#LPG_Container_Table').removeAttr('data-servername');
} else {
$('#LPG_Container_Table').attr('data-servername', thisServerName);
$('#LPG_Container_Table tr').not('[data-servername="' + thisServerName + '"]').addClass('LPG_HiddenRow');
$('.LPG_NewHourMarker').addClass('LPG_HideNewHourMarker');
}
if (prevMapName) {
$('#LPG_Container_Table tr').not('[data-mapname="' + prevMapName + '"]').addClass('LPG_HiddenRow');
}
}
if (thisMapName) {
if (thisMapName === prevMapName) {
$('#LPG_Container_Table').removeAttr('data-mapname');
} else {
$('#LPG_Container_Table').attr('data-mapname', thisMapName);
$('#LPG_Container_Table tr').not('[data-mapname="' + thisMapName + '"]').addClass('LPG_HiddenRow');
$('.LPG_NewHourMarker').addClass('LPG_HideNewHourMarker');
}
if (prevServerName) {
$('#LPG_Container_Table tr').not('[data-servername="' + prevServerName + '"]').addClass('LPG_HiddenRow');
}
}
setTimeout(showVisibleTotals, 100);
});
$('body').on('click', '#LPG_ChatLog_Close', function() {
$('#LPG_ChatLog').fadeOut(100, function() {
$('#LPG_Container').append( $('#LPG_ChatLog') );
});
});
$('#LPG_GameStats_Show').on('click', function() {
if (initGameStatsNeeded) {
initGameStats();
initGameStatsNeeded = false;
}
if (showReplaysType === 'mine') {
sortSavedGames();
}
if (jsonData.length) {
showGamesStats();
}
});
$('#LPG_Container').on('click', '.LPG_TD_Score', function() {
showScoreboardModal(this.dataset.gameid);
});
$('#LPG_Container').on('click', '#LPG_ScoreBoard_Modal_Close', function() {
$('#LPG_ScoreBoard_Modal').fadeOut(100);
});
};
let filterByText = function(searchValue) {
$('#LPG_Container_Table .LPG_HiddenRow').removeClass('LPG_HiddenRow');
$('#LPG_Container_Table').removeAttr('data-servername').removeAttr('data-mapname');
$('#LPG_Container_Table tr.LPG_HideNewHourMarker').removeClass('LPG_HideNewHourMarker');
if (searchValue) {
$('#LPG_Container_Table tr').filter(function() {
return !$(this).data('searchable').includes(searchValue);
}).addClass('LPG_HiddenRow');
$('.LPG_NewHourMarker').addClass('LPG_HideNewHourMarker');
}
setTimeout(showVisibleTotals, 100);
};
//GM_deleteValue('savedGamesData');
//GM_deleteValue('lastSavedGame');
//GM_deleteValue('savedGamesData_GotDate');
let savedGamesData = new Map(GM_getValue('savedGamesData', []));
let showAllTotals = function() {
const nowMs = Date.now();
let totals = {
all: {
wins: { red: 0, blue: 0 },
duration: 0,
abandoned: 0,
overtimes: 0,
unknown: 0,
},
last100: {
wins: { red: 0, blue: 0 },
duration: 0,
abandoned: 0,
overtimes: 0,
unknown: 0,
},
};
let values = []; //Array.from(savedGamesData.values());
for (let game of savedGamesData) {
if (game[1].winner !== -1) {
values.push(game[1]);
} else {
totals.all.abandoned++;
}
}
for (let i in values) {
const game = values[i];
if (game.winner === 1) {
totals.all.wins.red++;
if (i < 100) totals.last100.wins.red++;
} else if (game.winner === 2) {
totals.all.wins.blue++;
if (i < 100) totals.last100.wins.blue++;
} else { // usually a tie in events
totals.all.unknown++;
if (i < 100) totals.last100.unknown++;
}
totals.all.duration += game.duration;
if (i < 100) totals.last100.duration += game.duration;
if (Math.round(game.duration / 1000) > 360) {
totals.all.overtimes++;
if (i < 100) totals.last100.overtimes++;
}
}
totals.lastGameStarted = values[0].started;
totals.all.firstGameStarted = dayjs(values[values.length - 1].started);
totals.all.timePeriodMins = (nowMs - new Date(values[values.length - 1].started).getTime()) / 1000 / 60;
totals.last100.firstGameStarted = dayjs(values[99].started);
totals.last100.timePeriodMins = (nowMs - new Date(values[99].started).getTime()) / 1000 / 60;
totals.all.winsTotal = totals.all.wins.red + totals.all.wins.blue;
totals.all.gamesPerHour = totals.all.winsTotal / totals.all.timePeriodMins * 60;
totals.last100.winsTotal = totals.last100.wins.red + totals.last100.wins.blue;
totals.last100.gamesPerHour = totals.last100.winsTotal / totals.last100.timePeriodMins * 60;
$('#LPG_AllGamesTotals').empty();
$('#LPG_AllGamesTotals').append('<div id="LPG_AllGamesTotals" style="text-align:center;">' +
' <span>' + totals.all.winsTotal + ' Games</span>' +
' <span class="LPG_Totals_Divider">|</span><span>' + getYMDHOld(totals.lastGameStarted, totals.all.firstGameStarted).replace('in ', '').replace(' old', '').replace('hours', 'Hours') + '</span>' +
' <span class="LPG_Totals_Divider">|</span><span title="Games Per Hour">' + totals.all.gamesPerHour.toFixed(2) + ' GPH</span>' +
' <span class="LPG_Totals_Divider">|</span><span>' + tagpro.helpers.timeFromSeconds(Math.floor(totals.all.duration / 1000 / totals.all.winsTotal), true) + ' Average</span>' +
(totals.all.unknown ? '<span class="LPG_Totals_Divider">|</span><span>Unknown: ' + totals.all.unknown + '</span>' : '') +
(totals.all.overtimes ? '<span class="LPG_Totals_Divider">|</span><span>' + (totals.all.overtimes / totals.all.winsTotal * 100).toFixed(1) + '% O/T\'s</span>' : '') +
' <span class="LPG_Totals_Divider">|</span><span>Red: ' + (totals.all.wins.red / totals.all.winsTotal * 100).toFixed(1) + '%' +
' <span class="LPG_Totals_Divider">|</span><span>Blue: ' + (totals.all.wins.blue / totals.all.winsTotal * 100).toFixed(1) + '%</span>' +
'</div>');
$('#LPG_GPH_L100').text(totals.last100.gamesPerHour.toFixed(2) + ' GPH');
};
let showVisibleTotals = function() {
const nowMs = Date.now();
let totals = {
visible: {
wins: { red: 0, blue: 0 },
winsTotal: 0,
timePeriodMins: 0,
gamesPerHour: 0,
firstGameStarted: 0,
duration: 0,
abandoned: 0,
overtimes: 0,
unknown: 0,
},
lastGameStarted: 0,
};
let visibleRows = $('#LPG_Container_Table tr').not('.LPG_HiddenRow');
let visibleRowsCount = visibleRows.length;
let totalRowsCount = $('#LPG_Container_Table tr').length;
$(visibleRows).each(function() {
const winner = +this.dataset.winner;
const started = this.dataset.started;
const duration = +this.dataset.duration;
if (winner === 1) {
totals.visible.wins.red++;
} else if (winner === 2) {
totals.visible.wins.blue++;
} else { // usually a tie in events
totals.visible.unknown++;
}
totals.visible.duration += duration;
if (Math.round(duration / 1000) > 360) {
totals.visible.overtimes++;
}
});
let row = $(visibleRows).first();
let started = $(row).data('started');
totals.lastGameStarted = dayjs(started);
row = $(visibleRows).last();
started = $(row).data('started');
totals.visible.firstGameStarted = dayjs(started);
totals.visible.timePeriodMins = (nowMs - new Date(started).getTime()) / 1000 / 60;
totals.visible.winsTotal = totals.visible.wins.red + totals.visible.wins.blue;
totals.visible.gamesPerHour = totals.visible.winsTotal / totals.visible.timePeriodMins * 60;
let hoursOrPC = '';
if (visibleRowsCount === totalRowsCount) {
hoursOrPC = ' <span class="LPG_Totals_Divider">|</span><span>' + getYMDHOld(totals.lastGameStarted, totals.visible.firstGameStarted).replace('in ', '').replace(' old', '').replace('hours', 'Hours') + '</span>';
} else {
hoursOrPC = ' <span class="LPG_Totals_Divider">|</span><span>' + (visibleRowsCount / totalRowsCount * 100).toFixed(1) + '%</span>';
}
$('#LPG_LastNGamesTotals').empty();
$('#LPG_LastNGamesTotals').append('<div id="LPG_LastNGamesTotals" style="text-align:center; margin:10px auto -15px;">' +
' <span>' + totals.visible.winsTotal + ' Games</span>' +
hoursOrPC +
' <span class="LPG_Totals_Divider">|</span><span title="Games Per Hour">' + totals.visible.gamesPerHour.toFixed(2) + ' GPH</span>' +
' <span class="LPG_Totals_Divider">|</span><span>' + tagpro.helpers.timeFromSeconds(Math.floor(totals.visible.duration / 1000 / totals.visible.winsTotal), true) + ' Average</span>' +
(totals.visible.unknown ? '<span class="LPG_Totals_Divider">|</span><span>Unknown: ' + totals.visible.unknown + '</span>' : '') +
(totals.visible.overtimes ? '<span class="LPG_Totals_Divider">|</span><span>' + (totals.visible.overtimes / totals.visible.winsTotal * 100).toFixed(1) + '% O/T\'s</span>' : '') +
' <span class="LPG_Totals_Divider">|</span><span>Red: ' + (totals.visible.wins.red / totals.visible.winsTotal * 100).toFixed(1) + '%' +
' <span class="LPG_Totals_Divider">|</span><span>Blue: ' + (totals.visible.wins.blue / totals.visible.winsTotal * 100).toFixed(1) + '%</span>' +
'</div>');
$('#LPG_LastUpdated').text('Updated: ' + dayjs(gameStatsLoadedDate).format('DD-MMM-YYYY h:mma'));
};
async function getMapsJson() {
let mapsRaw = await fetch('https://tagpro.koalabeast.com/maps.json');
let mapsJson = await mapsRaw.json();
for (let rotation in mapsJson) {
for (let mapId in mapsJson[rotation]) {
if (mapsJson[rotation][mapId].weight) {
const mapKey = mapsJson[rotation][mapId].name.toLowerCase();
mapsData.maps[mapKey] = {
id: mapId,
name: mapsJson[rotation][mapId].name,
author: mapsJson[rotation][mapId].author,
category: mapsJson[rotation][mapId].category,
weight: mapsJson[rotation][mapId].weight,
};
if (!mapsData.counts.hasOwnProperty(rotation)) {
mapsData.counts[rotation] = 0;
}
mapsData.counts[rotation]++;
mapsData.counts.all++;
mapsData.counts.weightTotal += mapsJson[rotation][mapId].weight;
}
}
}
// console.log('LPG:: getMapsJson() mapsData:', mapsData);
};
let showScoreboardModal = function(gameId) {
if ($('#LPG_ScoreBoard_Modal').length && $('#LPG_ScoreBoard_Modal').attr('data-gameid') === gameId) {
if ($('#LPG_ScoreBoard_Modal').is(':visible')) {
$('#LPG_ScoreBoard_Modal').fadeOut(50);
} else {
$('#LPG_ScoreBoard_Modal').fadeIn(100);
}
return;
}
$('#LPG_ScoreBoard_Modal').remove();
let $target = $('td.LPG_TD_Score[data-gameid="' + gameId + '"]');
let mapName = $target.html();
let LPG_ScoreBoard_Modal = '<div id="LPG_ScoreBoard_Modal" data-gameid="' + gameId + '"><span id="LPG_ScoreBoard_Modal_Close" title="Close">X</span></div>';
if (cachedGames[gameId]) {
$target.append(LPG_ScoreBoard_Modal);
appendCapsTimeline(gameId, 380, '#LPG_ScoreBoard_Modal');
appendScoreboard(gameId, '#LPG_ScoreBoard_Modal');
} else {
$target.text('...').css('color', 'yellow');
getReplay(gameId).then(function(data) {
if (data.error) {
console.log('LPG:: showScoreboardModal() data.error:', data);
$target.text('Err!').css('color', 'darkred');
} else {
processReplay(data, gameId);
$target.html(mapName).css('color', 'inherit');
$('#LPG_ScoreBoard_Modal').remove(); // this is in case of multiple clicks
$target.append(LPG_ScoreBoard_Modal);
appendCapsTimeline(gameId, 380, '#LPG_ScoreBoard_Modal');
appendScoreboard(gameId, '#LPG_ScoreBoard_Modal');
}
}).fail(function(jqxhr) {
console.log('LPG:: showScoreboardModal() .fail() jqxhr:', jqxhr);
$target.text('Error!').css('color', 'darkred');
if (jqxhr.status === 500) {
console.log('LPG:: showScoreboardModal() .fail() 500 Internal Server Error');
$target.html('Err!').css('color', 'darkred');
}
});
}
};
async function removeOldSavedGames() {
const nowDate = new Date().getDate();
for (let entry of savedGamesData) {
const thisDate = new Date(entry[1].started).getDate();
if (thisDate !== nowDate) {
savedGamesData.delete(entry[0]);
}
}
}
let showHoursGraph = function() {
let dateNow = new Date();
let nowHour = dateNow.getHours();
let nowDate = dateNow.getDate();
let nowMonth = dateNow.getMonth();
let maxMinutesPlayedPerHour = 0;
let hoursGraphData = new Map();
let durations = [];
$('#LPG_HourTotalsGraphContainer').empty();
$('#LPG_TestHourTotals2 table').empty();
// build the hours data...
for (let game of savedGamesData.values()) {
if (game.winner !== -1) {
let dateStarted = new Date(game.started);
let thisHour = dateStarted.getHours();
let thisDate = dateStarted.getDate();
if (thisDate === nowDate) { // today - allocate by hour
hoursGraphData.set(thisHour, {
games: (hoursGraphData.has(thisHour) ? hoursGraphData.get(thisHour).games + 1 || 1 : 1),
duration: (hoursGraphData.has(thisHour) ? hoursGraphData.get(thisHour).duration + game.duration || game.duration : game.duration)
});
}
}
}
// find the maximums...
for (let i = 0; i < 24; i++) {
const currentGamesValue = hoursGraphData.has(i) ? (hoursGraphData.get(i).games || 0) : 0;
const currentMinutesValue = hoursGraphData.has(i) ? (hoursGraphData.get(i).duration / 1000 / 60 || 0) : 0;
if (i !== nowHour) {
if (currentMinutesValue > maxMinutesPlayedPerHour) {
maxMinutesPlayedPerHour = currentMinutesValue;
}
if (currentMinutesValue > 0) {
durations.push(currentMinutesValue);
}
}
}
// console.log('LPG:: showHoursGraph() hoursGraphData:', hoursGraphData);
// create the bar graph...
let normalizeNow = (dateNow.getMinutes() + 1) / 60;
let scaleY = (maxMinutesPlayedPerHour + 12) / 200;
let medianMinutes = getMedian(durations);
$('#LPG_HourTotalsGraphContainer').append('<div style="position:absolute; left:1px; color:#999; margin-bottom:' + ((medianMinutes / scaleY).toFixed(2) + 1) + 'px;" title="Median ' + medianMinutes.toFixed(0) + '">' + medianMinutes.toFixed(0) + '</div>');
$('#LPG_HourTotalsGraphContainer').append('<div style="left:1px; border-top:1px dashed #777; width:100%; margin-bottom:' + (medianMinutes / scaleY).toFixed(2) + 'px;" title="Median ' + medianMinutes.toFixed(0) + '"></div>');
for (let i = 0; i < 24; i++) {
let value = 0;
let value2 = 0;
let title = '';
if (hoursGraphData.has(i)) {
if (i === nowHour) { // part way through the CURRENT hour - quick estimate full hour
value = (hoursGraphData.get(i).games / normalizeNow).toFixed(0);
value2 = (hoursGraphData.get(i).duration / 1000 / 60 / normalizeNow).toFixed(0);
title = 'Estimated (actual is ' + hoursGraphData.get(i).games + ' games started and ' + (hoursGraphData.get(i).duration / 1000 / 60).toFixed(0) + ' minutes played so far)';
} else {
value = hoursGraphData.get(i).games;
value2 = (hoursGraphData.get(i).duration / 1000 / 60).toFixed(0);
title = hoursGraphData.get(i).games + ' games started (' + tagpro.helpers.timeFromSeconds(Math.floor(value2 / value * 60), true) + ' average duration)';
}
}
const scaledHeight = (value2 / scaleY).toFixed(2);
if (value2 > 0) $('#LPG_HourTotalsGraphContainer').append('<div class="LPG_HoursGraphValue" style="left:' + (i * 22 + 2) + 'px; height:' + scaledHeight + 'px;' + (i === nowHour ? ' opacity:0.4;' : '') + '">' + value2 + '</div>');
$('#LPG_HourTotalsGraphContainer').append('<div class="LPG_HoursGraphBar"' + (title ? ' title="' + title + '"' : '') + ' style="left:' + (i * 22 + 2) + 'px; height:' + scaledHeight + 'px;' + (i === nowHour ? ' opacity:0.4;' : '') + '"></div>');
$('#LPG_HourTotalsGraphContainer').append('<div class="LPG_HoursGraphLabel" style="left:' + (i * 22) + 'px;">' + i + '</div>');
}
showAllTotals();
};
let sortSavedGames = function() {
jsonData = Array.from(savedGamesData.values());
jsonData.sort(function(a, b) {
return b.startedMs - a.startedMs;
});
savedGamesData.clear();
for (let i = 0; i < jsonData.length; i++) {
savedGamesData.set(jsonData[i].id, jsonData[i]);
}
// console.log('LPG:: sortSavedGames() savedGamesData-SORTED:', savedGamesData);
GM_setValue('savedGamesData', Array.from(savedGamesData));
GM_setValue('lastSavedGame', { id:jsonData[0].id, started:jsonData[0].started });
};
let datePageLoaded = new Date();
let gotAllOldData = false;
async function getGamesData3(page, hourStart, hourStop = 24, intHours = 1, intMinutes = 0) {
if (showReplaysType === 'mine' && loggedInUserId) {
const response = await fetch('/history/data?page=0&pageSize=50&userId=' + loggedInUserId);
const data = await response.json();
gameStatsLoadedDate = Date.now();
jsonData = data.games;
gotAllOldData = true;
} else {
const nowHour = new Date().getHours();
if (hourStart > nowHour || nowHour === hourStop) { // finished
sortSavedGames();
GM_setValue('savedGamesData', Array.from(savedGamesData.entries()));
gameStatsLoadedDate = Date.now();
gotAllOldData = true;
return;
}
const response = await fetch('/history/data?page=' + page + '&pageSize=50&dateStart=' + datePageLoaded.setHours(hourStart, 0, 0, 0) + '&dateEnd=' + datePageLoaded.setHours(hourStart + intHours, intMinutes, 0, 0));
const data = await response.json();
const dataLength = Object.keys(data.games).length;
if (dataLength) {
for (let game of data.games) {
if (!savedGamesData.has(game.id)) {
game.startedMs = new Date(game.started).getTime();
savedGamesData.set(game.id, game); //{ started:game.started, server:game.server, mapName:game.mapName, duration:game.duration });
}
}
if (dataLength === 50) { // there is probably more data to get; get next page
if (page < 5) { // no problem
page++;
getGamesData3(page, hourStart, hourStop, intHours, intMinutes);
} else { // we can only ask for up to 5 pages
// TODO:: this is untested and is dumb
console.log('LPG:: getGamesData3() more than 250 games in this hour - eek! page:'+page, 'hourStart:'+hourStart, 'nowHour:'+nowHour, 'intHours:'+intHours, 'intMinutes:'+intMinutes);
//alert('LPG:: getGamesData3() more than 250 games in this hour - eek! nowHour:' + nowHour);
page = 0;
getGamesData3(page, hourStart, hourStop, 0, 30);
}
} else { // finished getting data for this hour; get next hour
page = 0;
hourStart++;
getGamesData3(page, hourStart, hourStop, 1, 0);
}
} else { // no data this hour; get next hour
page = 0;
hourStart++;
getGamesData3(page, hourStart, hourStop, 1, 0);
}
}
}
let showLastNRows = function() {
$('#LPG_Container_Table').empty();
let prevHour;
for (let game of jsonData) { // savedGamesData.values()) {
if (game.winner !== -1 && game.visibility === 'Public') { // don't show abandoned games; only show public games
let mapName = game.mapName;
let serverName = reformatServerName(game.server);
let durationSecs = Math.floor(game.duration / 1000);
let replayUrl = getReplayUrl(game.id);
let thisHour = new Date(game.started).getHours();
$('#LPG_Container_Table').append('<tr class="' + (game.winner === 1 ? 'LPG_Winner_Red' : game.winner === 2 ? 'LPG_Winner_Blue' : game.winner === 0 ? 'LPG_Winner_Tie' : 'LPG_Winner_Unknown') + (game.myTeam && (game.winner === 1 || game.winner === 2) ? (game.myTeamWon ? ' LPG_MyGameWon' : ' LPG_MyGameLost') : game.myTeam && game.winner === 0 ? ' LPG_MyGameUnknown' : '') + (prevHour && prevHour !== thisHour ? ' LPG_NewHourMarker' : '') + '" data-searchable="' + serverName.toLowerCase() + ' ' + mapName.toLowerCase() + '" data-servername="' + serverName.toLowerCase() + '" data-mapname="' + mapName.toLowerCase() + '" data-started="' + game.started + '" data-duration="' + game.duration + '" data-winner="' + game.winner + '">' +
' <td class="LPG_Row_FilterBy" data-servername="' + serverName.toLowerCase() + '">' + serverName + '</td>' +
' <td class="LPG_Row_FilterBy" data-mapname="' + mapName.toLowerCase() + '">' + mapName + '</td>' +
' <td class="LPG_Mins_Ago" data-started="' + game.started + '" title="' + dayjs(game.started).format('DD-MMM-YYYY h:mma') + '">' + getYMDHOld(game.started, Date.now(), false, true).replace('minutes', 'mins').replace('old', 'ago') + '</td>' +
' <td class="LPG_TD_Score" data-gameid="' + game.id +'" title="Show Scoreboard">' + '<span class="' + (game.winner === 1 ? 'LPG_Winner_Score_Red_Win' : 'LPG_Winner_Score_Red_Loss') + '">' + game.teams.red.score + '</span>:<span class="' + (game.winner === 2 ? 'LPG_Winner_Score_Blue_Win' : 'LPG_Winner_Score_Blue_Loss') + '">' + game.teams.blue.score + '</span></td>' +
' <td class="LPG_TD_Winner">' + (game.winner === 1 ? 'Red' : game.winner === 2 ? 'Blue' : game.winner === 0 ? 'Tie' : '???') + '</td>' +
' <td' + (durationSecs > 720 ? ' class="LPG_Overtime_Extreme"' : durationSecs > 360 ? ' class="LPG_Overtime"' : '') + '>' + tagpro.helpers.timeFromSeconds(durationSecs, true) + '</td>' +
' <td><a href="' + replayUrl + '" class="LPG_ViewReplay" data-gameid="' + game.id + '" target="_blank" title="View Replay"><i class="fa fa-circle-play' + (watchedGames.hasOwnProperty(game.id) ? ' LPG_Watched' : '') + '" style="color:#ccc;"></i></a></td>' +
' <td><a href="javascript:void(0);" class="LPG_ShowChat" data-gameid="' + game.id + '" title="Show Chat"><i class="fa-solid fa-comment-dots' + (cachedGames.hasOwnProperty(game.id) ? ' LPG_Cached' : '') + '"></i></a></td>' +
'</tr>');
if (showReplaysType === 'all') {
prevHour = thisHour;
}
}
}
// re-filter data...
if ($('#LPG_FilterInput').val()) {
filterByText($('#LPG_FilterInput').val().toLowerCase());
}
showVisibleTotals();
$('.LPG_Show_Games').css('color', '#777');
$('.LPG_Show_Games[data-type="' + showReplaysType + '"]').css('color', '#fff');
};
let getLastNGames = function(hourStart = 0) {
if (showReplaysType === 'all' && hourStart >= 0 && hourStart <= 23 || showReplaysType === 'mine') {
//$('#LPG_HourTotalsGraphContainer').empty().append('<div style="position:absolute; width:100%; height:150px; text-align:center; font-size:16px; font-style:italic;">Fetching Data...</div><div style="position:absolute; width:100%; height:124px; text-align:center;"><span class="LPG_Loading_Animation" style="width:18px; height:18px;"></span></div>');
(async () => {
await getGamesData3(0, hourStart); // this is done synchronously in order to make fewer requests (the next request is conditional on the previous request's results)
while (!gotAllOldData) {
await new Promise(resolve => setTimeout(resolve, 20));
}
showLastNRows();
if (showReplaysType === 'all') {
showHoursGraph();
}
})();
}
};
let getAndShowLastNGames = function() {
let lastSavedGame = GM_getValue('lastSavedGame');
gotAllOldData = false;
getLastNGames(lastSavedGame ? new Date(lastSavedGame.started).getHours() : 0);
};
let clearable_showGames;
let updateMinsAgo = function() {
$('#LPG_Container_Table .LPG_Mins_Ago').each(function() {
$(this).text( getYMDHOld(this.dataset.started, Date.now(), false, true).replace('minutes', 'mins').replace('old', 'ago') );
});
};
let setSetIntervals = function() {
clearable_showGames = setInterval(() => {
// reload the data if it's older than 15 minutes...
if (gameStatsLoadedDate < Date.now() - 15 * 60 * 1000) {
getAndShowLastNGames();
}
// update the "ago" times every minute...
updateMinsAgo();
}, 1 * 60 * 1000);
};
tagpro.ready(function() {
getMapsJson();
createMenu();
addChatLogContainer();
addMenuListeners();
if (GM_getValue('savedGamesData_GotDate', false) !== new Date().getDate()) { // first time visited today
(async () => {
await removeOldSavedGames();
GM_deleteValue('lastSavedGame');
getAndShowLastNGames();
GM_setValue('savedGamesData_GotDate', new Date().getDate());
})();
} else { // not the first page visit today
let lastSavedGame = GM_getValue('lastSavedGame');
if (lastSavedGame) {
getLastNGames(new Date(lastSavedGame.started).getHours());
}
}
setSetIntervals();
document.addEventListener('visibilitychange', () => {
clearInterval(clearable_showGames);
if (document.visibilityState === 'visible') {
// reload the data if it's older than 15 minutes...
if (gameStatsLoadedDate < Date.now() - 15 * 60 * 1000) {
getAndShowLastNGames();
}
updateMinsAgo();
// reset the setIntervals...
setSetIntervals();
}
});
});
function toSeconds(value) {
return value.split(':').reduce((acc, time) => (60 * acc) + +time);
}
function capitalize(value) {
return value ? value.charAt(0).toUpperCase() + value.slice(1) : value;
}
function daysInThisMonth() {
let now = new Date();
return new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
}
const getMedian = arr => {
const mid = Math.floor(arr.length / 2);
const nums = [...arr].sort((a, b) => a - b);
return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
};
function getYMDHOld(date, date2 = Date.now(), useShortFormat = false, round = false) {
let dateNew = dayjs(date2).diff(date, 'year', true).toFixed(round ? 0 : 1);
let isFuture = dateNew < 0;
dateNew = Math.abs(dateNew);
if (dateNew > 1) {
dateNew = (isFuture ? 'in ' : '') + dateNew + (useShortFormat ? 'y' : ' years' + (isFuture ? '' : ' old'));
} else {
let months = dayjs(date2).diff(date, 'month', true).toFixed(round ? 0 : 1);
isFuture = months < 0;
months = Math.abs(months);
if (months > 1) {
dateNew = (isFuture ? 'in ' : '') + months + (useShortFormat ? 'M' : ' months' + (isFuture ? '' : ' old'));
} else {
let days = dayjs(date2).diff(date, 'day', true).toFixed(round ? 0 : 1);
isFuture = days < 0;
days = Math.abs(days);
if (days > 1) {
dateNew = (isFuture ? 'in ' : '') + days + (useShortFormat ? 'd' : ' days' + (isFuture ? '' : ' old'));
} else {
let hours = dayjs(date2).diff(date, 'hour', true).toFixed(round ? 0 : 1);
isFuture = hours < 0;
hours = Math.abs(hours);
if (hours > 1) {
dateNew = (isFuture ? 'in ' : '') + hours + (useShortFormat ? 'h' : ' hours' + (isFuture ? '' : ' old'));
} else {
let minutes = dayjs(date2).diff(date, 'minutes', true).toFixed(round ? 0 : 1);
isFuture = minutes < 0;
minutes = Math.abs(minutes);
dateNew = (isFuture ? 'in ' : '') + minutes + (useShortFormat ? 'm' : ' minutes' + (isFuture ? '' : ' old'));
}
}
}
}
return dateNew;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment