Last active
April 17, 2024 05:51
-
-
Save nabbynz/759b3002563ab97a3f6a2a0f76ee8797 to your computer and use it in GitHub Desktop.
Shows a list of today's pubs on the TagPro Homepage
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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) + '">●</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 += ' · ' + allData.servers[server].mercy + 'x Wins by Mercy\n'; | |
if (allData.servers[server].margin1) countTitle += ' · ' + allData.servers[server].margin1 + 'x Wins by 1 Cap\n'; | |
if (allData.servers[server].margin2) countTitle += ' · ' + allData.servers[server].margin2 + 'x Wins by 2 Caps\n'; | |
if (allData.servers[server].overtime) countTitle += ' · ' + allData.servers[server].overtime + 'x Overtime Games\n'; | |
if (allData.servers[server].abandoned) countTitle += 'Excludes:\n · ' + 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