Skip to content

Instantly share code, notes, and snippets.

@nabbynz
Last active August 21, 2024 03:57
Show Gist options
  • Save nabbynz/0d56c5e8d9b7a2e19bafeeaf138413ae to your computer and use it in GitHub Desktop.
Save nabbynz/0d56c5e8d9b7a2e19bafeeaf138413ae to your computer and use it in GitHub Desktop.
Show Replay Chat
// ==UserScript==
// @name Show Replay Chat
// @description Shows only the chat from a game replay
// @version 0.5.1
// @match https://*.koalabeast.com/replays*
// @match https://*.koalabeast.com/game*
// @updateURL https://gist.github.com/nabbynz/0d56c5e8d9b7a2e19bafeeaf138413ae/raw/Show_Replay_Chat.user.js
// @downloadURL https://gist.github.com/nabbynz/0d56c5e8d9b7a2e19bafeeaf138413ae/raw/Show_Replay_Chat.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
// ==/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 */
/* eslint-disable no-multi-spaces, no-useless-concat */
const MINIMIZED_WIDTH = 300; // minimized width of chat log when in an actual replay (0 to not show)
const REMOVE_FROM_CACHE_AFTER = 3 * 24 * 60 * 60 * 1000; // remove cached games after 24 hours
const USE_LONG_SERVER_NAMES = true; // change "SWA" -> Seattle
const redTextColor = '#ffb5bd';
const blueTextColor = '#cfcfff';
const redBackgroundColor = '#a13636';
const blueBackgroundColor = '#194773';
let userId = $('#userId').val() || '';
let lastGet = 0;
let cachedGames = GM_getValue('cachedGames', {});
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;
}
}
if (doSave) {
GM_setValue('cachedGames', cachedGames);
}
};
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 addShowChatLinks = function() {
let currentTab = $('ul.tab-list').find('li.active').attr('data-tab');
let isCurrentTabAll = currentTab === 'all';
let mapNameWidth = ($('.table-teams tbody tr').eq(1).find('td').eq(2).width() - 89) + 'px';
$('.table-teams tbody tr').each(function(i) {
let gameHref = $(this).find('td').last().find('a[href*="/replays/"]').attr('href');
let gameId = '';
let replayHref = $(this).find('td').last().find('a[href*="/game?replay="]').attr('href');
let replayId = '';
gameId = gameHref.slice(gameHref.indexOf('gameId=') + 7);
gameId = gameId.slice(0, gameId.indexOf('&'));
replayId = replayHref.slice(replayHref.indexOf('?replay=') + 8);
$(this).find('td').eq(0).attr('data-sortby', i); // Start
$(this).find('td').eq(isCurrentTabAll ? 4 : 5).attr('data-sortby', toSeconds($(this).find('td').eq(isCurrentTabAll ? 4 : 5).text())); // Duration
$(this).find('td').last().append('<a href="/showchat?gameId=' + gameId + (userId ? '&userId=' + userId : '') + '" class="SRC_ShowChat" data-gameid="' + gameId + '" data-replayid="' + replayId + '" title="Show Chat"><i class="fa-solid fa-comment-dots' + (cachedGames.hasOwnProperty(gameId) ? ' SRC_Cached' : '') + '"></i></a>');
// highlight overtime games...
let type = (isCurrentTabAll ? 'Public' : $(this).find('td').eq(4).text().trim());
if (type === 'Public') {
let duration = toSeconds( $(this).find('td').eq(isCurrentTabAll ? 4 : 5).text().trim() );
if (duration > 360) {
$(this).find('td').eq(isCurrentTabAll ? 4 : 5).addClass('SRC_Overtime').attr('title', 'Overtime');
}
}
if (USE_LONG_SERVER_NAMES) {
let serverName = reformatServerName($(this).find('td').eq(currentTab === 'favorite' ? 5 : 3).attr('title'));
$(this).find('td').eq(currentTab === 'favorite' ? 5 : 3).text(serverName);
$(this).find('td').eq(2).css('max-width', mapNameWidth);
}
});
};
let checkTableTRs = function() {
if (!$('.table-teams').find('tbody tr').length && !$('ul.tab-list').length) { // rows haven't appeared yet...
return false;
}
return true;
};
async function waitForTRs() { // waits for the table rows to appear
$('#SRC_GameStats_Container').hide();
while (!checkTableTRs()) {
await new Promise(resolve => setTimeout(resolve, 200));
}
if (checkTableTRs()) {
addShowChatLinks();
}
}
let addMainContainer = function() {
$('#replays-app').append('<div id="SRC_ChatLog"><div id="SRC_ChatLog_Header"><div id="SRC_CapTimeline_Container"></div><span id="SRC_ChatLog_Close" title="Close">X</span></div><div id="SRC_ChatLog_Chat"></div></div>');
};
let jsonData = [];
let getGamesData = function(callback) {
const URL = '/replays/data';
const pages = ['?page=0&pageSize=50', '?page=1&pageSize=50', '?page=2&pageSize=50', '?page=3&pageSize=50', '?page=4&pageSize=50'];
const getGames = page => fetch(`${URL}/${page}`).then(response => response.json() );
const arrayOfPromises = pages.map(page => getGames(page));
const allPromisesWithErrorHandler = arrayOfPromises.map(promise => promise.catch(error => error), );
Promise.all(allPromisesWithErrorHandler).then(data => {
jsonData = [];
for (let i in data) {
jsonData = jsonData.concat(data[i].games);
}
gameStatsLoadedDate = Date.now();
callback(true);
});
};
let addExtraRowsToTable = function() {
let row = 1;
$('.table-teams tbody').empty();
for (let game of jsonData) {
let fDate = formatDate(game.started, false, true);
let fDateTitle = formatDate(game.started, true, false);
let replayKey = getReplayKey(game.id, userId);
let replayUrl = getReplayUrl(game.id, userId, null, game.redirectHost);
let favorite = game.favorite;
$('.table-teams tbody').append('<tr>' +
' <td class="nogrow" data-sortby="' + row + '" title="' + fDateTitle + '"><span class="SRC_RowNumber">[' + row + ']</span>' + fDate[0] + '</td>' +
' <td class="nogrow" title="' + fDateTitle + '">' + fDate[1] + '</td>' +
' <td>' + game.mapName + '</td>' +
' <td class="nogrow">' + reformatServerName(game.server) + '</td>' +
' <td class="nogrow' + (Math.floor(game.duration / 1000) > 360 ? ' SRC_Overtime' : '') + '" data-sortby="' + game.duration + '">' + tagpro.helpers.timeFromSeconds(Math.floor(game.duration / 1000), true) + '</td>' +
' <td class="nogrow ' + (game.myTeam && userId ? game.myTeam + '-team-text' : '') + '">' + (game.myTeam && userId ? game.myTeamName : '') + '</td>' +
' <td class="nogrow ' + (game.myTeam && userId ? game.myTeam + '-team-text' : '') + '">' + (game.myTeamWon ? '<i title="Winner" class="fa fa-trophy"></i>' : '') + '</td>' +
' <td class="nogrow text-center ' + (game.winner === 1 ? 'red-team' : 'red-team-text') + '">' + game.teams.red.score + '</td>' +
' <td class="nogrow text-center ' + (game.winner === 2 ? 'blue-team' : 'blue-team-text') + '">' + game.teams.blue.score + '</td>' +
' <td class="nogrow">' +
' <a href="' + replayUrl + '" target="replay"><i title="Play" class="fa fa-circle-play clickableText"></i></a>' +
' <a href="https://tagpro.eu/?match=' + game.uuid + '/" target="tagproeu"><i title="Game Info on tagpro.eu" class="fa fa-circle-info clickableText"></i></a>' +
(userId ? ' <a href="replays/gameFile?gameId=' + game.id + '&userid=' + userId + '/"><i title="Download" class="fa fa-circle-down clickableText"></i></a>' : '') +
(userId ? ' <i title="' + (game.favorite ? 'Remove from Favorites' : 'Add to Favorites') + '" class="fa-heart clickableText ' + (game.favorite ? 'fa' : 'fa-regular') + ' SRC_Favorite" data-key="' + replayKey + '"></i>' : '') +
'<a href="/showchat?gameId=' + game.id + (userId ? '&userId=' + userId : '') + '" class="SRC_ShowChat" data-gameid="' + game.id + '" data-replayid="' + replayKey + '" title="Show Chat"><i class="fa-solid fa-comment-dots' + (cachedGames.hasOwnProperty(game.id) ? ' SRC_Cached' : '') + '"></i></a>' +
' </td>' +
'</tr>');
row++;
}
$('.table-teams thead th').css('text-decoration', 'none');
$('.table-teams').next('div').remove();
};
let gameStatsCached = null;
let gameStatsLoadedDate = null;
let initGameStatsNeeded = true;
let initGameStats = function() {
GM_addStyle('#SRC_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('#SRC_GameStats_Reload { position:absolute; width:17px; height:17px; bottom:3px; right:3px; padding:0 0 0 1px; color:lightslategray; border:1px solid darkslateblue; font-size:12px; font-weight:bold; border-radius:10px; cursor:pointer; }');
GM_addStyle('#SRC_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('#SRC_GameStats_Header { width:fit-content; font-size:12px; background:#3f3f3f; height:40px; border-bottom:1px solid #444; }');
GM_addStyle('#SRC_GameStats_Table { width:fit-content; text-align:center; max-height:220px; overflow-y:scroll; display:block; }');
GM_addStyle('#SRC_ServerStats_Header { width:fit-content; margin:20px auto 0; font-size:12px; background:#3f3f3f; height:40px; border-bottom:1px solid #444; }');
GM_addStyle('#SRC_ServerStats_Table { width:fit-content; margin:0px auto 20px; text-align:center; max-height:220px; overflow-y:scroll; display:block; }');
GM_addStyle('#SRC_GameStats_Table tr, #SRC_ServerStats_Table tr { background:#2a2a2a; }');
GM_addStyle('#SRC_GameStats_Table tr:nth-child(2n), #SRC_ServerStats_Table tr:nth-child(2n) { background:#212121; }');
GM_addStyle('#SRC_GameStats_Table tbody tr:hover, #SRC_ServerStats_Table tbody tr:hover { background:#482d4e !important; }');
GM_addStyle('#SRC_GameStats_Table tr td, #SRC_GameStats_Header tr th, #SRC_ServerStats_Table tr td, #SRC_ServerStats_Header tr th { padding:1px 5px; }');
GM_addStyle('#SRC_GameStats_Table tr td:nth-child(1), #SRC_GameStats_Header tr th:nth-child(1), #SRC_ServerStats_Table tr td:nth-child(1), #SRC_ServerStats_Header tr th:nth-child(1) { width:260px; max-width:260px; text-align:left; text-overflow:ellipsis; overflow:hidden; white-space:nowrap; }');
GM_addStyle('#SRC_GameStats_Table tr td:nth-child(n+2), #SRC_GameStats_Header tr th:nth-child(n+2), #SRC_ServerStats_Table tr td:nth-child(n+2), #SRC_ServerStats_Header tr th:nth-child(n+2) { width:80px; max-width:80px; }');
GM_addStyle('#SRC_GameStats_Table::-webkit-scrollbar, #SRC_ServerStats_Table::-webkit-scrollbar { width:3px; }');
GM_addStyle('#SRC_GameStats_Table::-webkit-scrollbar-thumb, #SRC_ServerStats_Table::-webkit-scrollbar-thumb { background:#9566df; }');
GM_addStyle('#SRC_GameStats_Table::-webkit-scrollbar-track, #SRC_ServerStats_Table::-webkit-scrollbar-track { background:#555; }');
GM_addStyle('#SRC_Extra_Info { margin:20px auto; padding:10px 40px; border:1px outset #797979; border-radius:8px; height:fit-content; background:#222; }');
GM_addStyle('#SRC_Extra_Info_Table { width:250px; }');
GM_addStyle('#SRC_Extra_Info_Table td:nth-child(1) { width:50%; padding:0 6px; text-align:right; }');
GM_addStyle('#SRC_Extra_Info_Table td:nth-child(2) { width:50%; padding:0 6px; text-align:left; }');
GM_addStyle('#SRC_GameStats_Graph_Container { position:relative; margin:10px; padding:10px; background:#000; border:1px outset #888; border-radius:8px; }');
GM_addStyle('#SRC_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('.SRC_GameStats_Graph_LabelX { font-size:10px; font-weight:bold; fill:#ccc; }');
GM_addStyle('.SRC_GameStats_Graph_LabelY { font-size:10px; font-weight:bold; fill:#ccc; }');
GM_addStyle('.SRC_GameStats_Graph_MyTeam { stroke:#00ff00; stroke-width:2; opacity:0.4; }');
GM_addStyle('.SRC_GameStats_Graph_Duration_Red { stroke:rgb(255,121,121,0.65); stroke-width:1; filter:drop-shadow(0px -1px 4px #e00) drop-shadow(0px -1px 2px #e00); }');
GM_addStyle('.SRC_GameStats_Graph_Duration_Blue { stroke:rgba(141,171,255,0.65); stroke-width:1; filter:drop-shadow(0px 1px 4px #09f) drop-shadow(0px 1px 2px #09f); }');
GM_addStyle('.SRC_GameStats_Graph_Duration_Abandoned { stroke:#ffffff; stroke-width:1; opacity:0.4; }');
GM_addStyle('.SRC_GameStats_Graph_GameStart_Red { stroke:rgb(163 61 61); }');
GM_addStyle('.SRC_GameStats_Graph_GameStart_Blue { stroke:rgb(90 113 144); }');
GM_addStyle('.SRC_GameStats_Graph_GameStart_Abandoned { stroke:#ffffff; opacity:0.4; }');
GM_addStyle('.SRC_GameStats_Graph_Guide { stroke:#444; stroke-width:1; stroke-dasharray:4 6; opacity:1; }');
$('#replays-app').append('<div id="SRC_GameStats_Container"><span id="SRC_GameStats_Reload" title="Reload Data">R</span><span id="SRC_GameStats_Close" title="Close">X</span></div>');
$('#SRC_GameStats_Container').hide();
$('#SRC_GameStats_Container').on('click', '.SRC_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 === 0) {
if (sortasc) {
return a.children[index].innerText.localeCompare(b.children[index].innerText);
} else {
return b.children[index].innerText.localeCompare(a.children[index].innerText);
}
} 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('.SRC_GameStats_Sort').css('text-decoration', 'none');
$(this).css('text-decoration', 'underline');
});
$('#SRC_GameStats_Reload').on('click', function() {
$('#SRC_GameStats_Container').fadeOut(50, function() {
getGamesData(function(success) {
if (success) {
showGamesStats();
}
});
});
});
$('#SRC_GameStats_Close').on('click', function() {
$('#SRC_GameStats_Container').fadeOut(100);
});
$('#SRC_GameStats_Container').on('click', '.SRC_GameStats_Graph_Details', function() {
let gameId = this.dataset.gameid;
//let replayId = this.dataset.replayid;
$('#SRC_GameStats_Container').prepend( $('#SRC_ChatLog') );
if (cachedGames[gameId]) {
showChatLog(gameId, true);
} else {
if (!gameId || gameId.length !== 24) {
return;
}
getProcessAndShowReplay(gameId, true);
}
});
$('#SRC_GameStats_Container').on('mouseover', '.SRC_GameStats_Graph_Details', function(e) {
$('#SRC_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>' : ''));
$('#SRC_GameStats_Graph_ToolTip').css({ 'left':e.offsetX - 100, 'top':e.clientY - 215 }).show();
});
$('#SRC_GameStats_Container').on('mouseleave', '.SRC_GameStats_Graph_Details', function() {
$('#SRC_GameStats_Graph_ToolTip').hide();
});
$('#SRC_GameStats_Container').on('click', '.SRC_GameStats_ShowMapGames', function() {
let wasSelected = this.dataset.selected;
$('.SRC_GameStats_ShowMapGames').css('color', 'inherit').attr('data-selected', '');
if (wasSelected) {
$('#SRC_GameStats_Graph_Container').find('.SRC_GameStats_Graph_Details').fadeIn(50);
} else {
$(this).css('color', '#a27ed3');
this.dataset.selected = 'selected';
$('#SRC_GameStats_Graph_Container').find('.SRC_GameStats_Graph_Details').hide();
$('#SRC_GameStats_Graph_Container').find('.SRC_GameStats_Graph_Details[data-map="' + this.dataset.sortby + '"]').fadeIn(50);
}
});
};
let makeFavorite = function(name, redWins, blueWins) {
let text = '';
let sortby = 0;
let title = '';
let total = redWins + blueWins;
if (total <= 2) {
sortby = 0;
text = '-';
title = 'Not enough games' + ' (Red: ' + redWins + ', Blue: ' + blueWins + ')';
} else if (redWins > blueWins) {
sortby = redWins / total * 100;
text = 'Red';
title = 'Red Wins ' + sortby.toFixed(1) + '% of Games on ' + name + ' (Red: ' + redWins + ', Blue: ' + blueWins + ')';
} else if (redWins < blueWins) {
sortby = blueWins / total * 100;
text = 'Blue';
title = 'Blue Wins ' + sortby.toFixed(1) + '% of Games on ' + name + ' (Red: ' + redWins + ', Blue: ' + blueWins + ')';
} else {
sortby = 50;
text = '=';
title = 'Even (Red: ' + redWins + ', Blue: ' + blueWins + ')';
}
return { text: text, sortby: sortby, title: title };
};
let showGamesStats = function() {
let allData = { maps: {}, servers: {}, serversGraph: [], serverGamesCount:{} };
let currentTab = $('ul.tab-list').find('li.active').attr('data-tab');
let isCurrentTabAll = currentTab === 'all';
$('#SRC_GameStats_Container').children().not('#SRC_GameStats_Close, #SRC_GameStats_Reload').remove();
$('#SRC_GameStats_Container').append('<div><table id="SRC_GameStats_Header">' +
' <tr>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_GameStats_Table" title="Map Name">Map</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_GameStats_Table" title="Total Number of Games" data-sortasc="false" style="text-decoration:underline;">Games</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_GameStats_Table" title="Average Game Duration">Duration</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_GameStats_Table" title="Caps Per Minute">CPM</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_GameStats_Table" title="Average Red Score">Red Score</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_GameStats_Table" title="Average Blue Score">Blue Score</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_GameStats_Table" title="The Team That Wins Most">Favored</th>' +
' <th style="padding:0; width:3px;"></th>' +
' </tr>' +
'</table>' +
'<table id="SRC_GameStats_Table"></table></div>');
$('#SRC_GameStats_Container').append('<div id="SRC_Extra_Info"></div>');
$('#SRC_GameStats_Container').append('<div><table id="SRC_ServerStats_Header">' +
' <tr>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_ServerStats_Table" title="Average Game Duration">Server</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_ServerStats_Table" title="Total Number of Games" data-sortasc="false" style="text-decoration:underline;">Games</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_ServerStats_Table" title="Average Game Duration">Duration</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_ServerStats_Table" title="Caps Per Minute">CPM</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_ServerStats_Table" title="Average Red Score">Red Score</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_ServerStats_Table" title="Average Blue Score">Blue Score</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_ServerStats_Table" title="The Team That Wins Most">Favored</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_ServerStats_Table" title="Total Game Duration per Hour">Load %</th>' +
' <th class="SRC_GameStats_Sort" data-target="SRC_ServerStats_Table" title="# Games Started per Hour">Rate</th>' +
' <th style="padding:0; width:3px;"></th>' +
' </tr>' +
'</table>' +
'<table id="SRC_ServerStats_Table"></table></div>');
let firstGameStarted, lastGameStarted;
let doOnce = false;
for (let game of jsonData) {
let mapName = game.mapName;
let serverName = game.server;
// create data for `maps`
if (game.winner !== -1) { // abandoned game - happens when all players quit a game?
if (!allData.maps.hasOwnProperty(mapName)) {
allData.maps[mapName] = { count: 0, wins: 0, duration: 0, redWins: 0, blueWins: 0, redTeamScore: 0, blueTeamScore: 0 };
}
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;
}
// 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 favorite = makeFavorite(map, allData.maps[map].redWins, allData.maps[map].blueWins);
$('#SRC_GameStats_Table').append('<tr>' +
' <td data-sortby="' + map + '" class="SRC_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="' + allData.maps[map].redTeamScore / allData.maps[map].count + '">' + (allData.maps[map].redTeamScore / allData.maps[map].count).toFixed(1) + '</td>' +
' <td data-sortby="' + allData.maps[map].blueTeamScore / allData.maps[map].count + '">' + (allData.maps[map].blueTeamScore / allData.maps[map].count).toFixed(1) + '</td>' +
' <td data-sortby="' + favorite.sortby + '" title="' + favorite.title + '">' + favorite.text + '</td>' +
'</tr>');
}
for (let server in allData.servers) {
let favorite = makeFavorite(server, allData.servers[server].redWins, allData.servers[server].blueWins);
let countTitle = 'Includes:\n';
if (allData.servers[server].mercy) countTitle += ' &middot; ' + allData.servers[server].mercy + 'x Wins by Mercy\n';
if (allData.servers[server].margin1) countTitle += ' &middot; ' + allData.servers[server].margin1 + 'x Wins by 1 Cap\n';
if (allData.servers[server].margin2) countTitle += ' &middot; ' + allData.servers[server].margin2 + 'x Wins by 2 Caps\n';
if (allData.servers[server].overtime) countTitle += ' &middot; ' + allData.servers[server].overtime + 'x Overtime Games\n';
if (allData.servers[server].abandoned) countTitle += 'Excludes:\n &middot; ' + allData.servers[server].abandoned + 'x Abandoned Games\n';
mercyTotal += allData.servers[server].mercy;
margin1Total += allData.servers[server].margin1;
margin2Total += allData.servers[server].margin2;
overtimesTotal += allData.servers[server].overtime;
if (allData.servers[server].count) {
$('#SRC_ServerStats_Table').append('<tr>' +
' <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="' + allData.servers[server].redTeamScore / allData.servers[server].count + '">' + (allData.servers[server].redTeamScore / allData.servers[server].count).toFixed(1) + '</td>' +
' <td data-sortby="' + allData.servers[server].blueTeamScore / allData.servers[server].count + '">' + (allData.servers[server].blueTeamScore / allData.servers[server].count).toFixed(1) + '</td>' +
' <td data-sortby="' + favorite.sortby + '" title="' + favorite.title + '">' + favorite.text + '</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('SRC:: showGamesStats() allData:', allData);
let winsTotal = redWinsTotal + blueWinsTotal;
$('#SRC_Extra_Info').append('<div style="font-size:15px; font-weight:bold; margin:0 0 4px;">All Games' + ' (' + allData.serversGraph.length + ')</div>');
$('#SRC_Extra_Info').append('<table id="SRC_Extra_Info_Table"></table>');
$('#SRC_Extra_Info_Table').append('<tr><td>Period</td><td>' + getYMDHOld(lastGameStarted, firstGameStarted).replace(' old', '') + '</td></tr>');
$('#SRC_Extra_Info_Table').append('<tr><td>Game Rate</td><td>' + (allData.serversGraph.length / timePeriodMins * 60).toFixed(2) + ' per hour</td></tr>');
$('#SRC_Extra_Info_Table').append('<tr><td>Mercy Wins</td><td>' + (mercyTotal / winsTotal * 100).toFixed(1) + '% (' + mercyTotal + ')</td></tr>');
$('#SRC_Extra_Info_Table').append('<tr><td>1-Cap Wins</td><td>' + (margin1Total / winsTotal * 100).toFixed(1) + '% (' + margin1Total + ')</td></tr>');
$('#SRC_Extra_Info_Table').append('<tr><td>2-Cap Wins</td><td>' + (margin2Total / winsTotal * 100).toFixed(1) + '% (' + margin2Total + ')</td></tr>');
$('#SRC_Extra_Info_Table').append('<tr><td>Overtime Games</td><td>' + (overtimesTotal / winsTotal * 100).toFixed(1) + '% (' + overtimesTotal + ')</td></tr>');
$('#SRC_Extra_Info_Table').append('<tr><td>Abandoned Games</td><td>' + ((allData.serversGraph.length - winsTotal) / winsTotal * 100).toFixed(1) + '% (' + (allData.serversGraph.length - winsTotal) + ')</td></tr>');
$('#SRC_Extra_Info_Table').append('<tr><td>Red Wins</td><td>' + (redWinsTotal / winsTotal * 100).toFixed(1) + '% (' + redWinsTotal + ')</td></tr>');
$('#SRC_Extra_Info_Table').append('<tr><td>Blue Wins</td><td>' + (blueWinsTotal / winsTotal * 100).toFixed(1) + '% (' + blueWinsTotal + ')</td></tr>');
$('#SRC_GameStats_Container').append('<div id="SRC_GameStats_Graph_Container"></div>');
$('#SRC_GameStats_Container').append('<div id="SRC_GameStats_Graph_ToolTip"></div>');
showGameStatsActivityGraph(allData);
$('#SRC_GameStats_Table tbody tr').sort(function(a, b) {
return b.children[1].dataset.sortby - a.children[1].dataset.sortby;
}).appendTo( $('#SRC_GameStats_Table') );
$('#SRC_ServerStats_Table tbody tr').sort(function(a, b) {
return b.children[1].dataset.sortby - a.children[1].dataset.sortby;
}).appendTo( $('#SRC_ServerStats_Table') );
$('#SRC_GameStats_Container').fadeIn(200);
};
const graphWidthTotal = 1080;
const graphPaddingLeft = 100;
const graphWidth = graphWidthTotal - graphPaddingLeft;
let showGameStatsActivityGraph = function(allData) {
const now = Date.now();
const firstGameStarted = allData.serversGraph[allData.serversGraph.length - 1].started;
const timePeriodMs = now - firstGameStarted;
const timePeriodMins = timePeriodMs / 1000 / 60;
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="SRC_GameStats_Graph_SVG" width="' + graphWidthTotal + '" height="' + graphHeight + '" xmlns="http://www.w3.org/2000/svg" style="shape-rendering:crispEdges;">';
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="SRC_GameStats_Graph_LabelY">' + serverCounts[i].name + '</text>';
}
for (let server of allData.serversGraph) {
let x1 = graphPaddingLeft + ((server.started - firstGameStarted) / timePeriodMs) * graphWidth;
let x2 = graphPaddingLeft + ((server.started - firstGameStarted + server.duration * 1000) / timePeriodMs) * graphWidth;
let y = serverPositions[server.server].position * 20 - 10;
svg += '<line x1="' + x1 + '" y1="' + y + '" x2="' + x2 + '" y2="' + y + '" class="SRC_GameStats_Graph_Details ' + (server.winner === 1 ? 'SRC_GameStats_Graph_Duration_Red' : server.winner === 2 ? 'SRC_GameStats_Graph_Duration_Blue' : 'SRC_GameStats_Graph_Duration_Abandoned') + '" data-gameid="' + server.gameId + '" data-map="' + server.map + '" data-name="' + server.server + '" data-date="' + dayjs(server.started).format('DD-MMM-YYYY h:mma') + '" data-duration="' + tagpro.helpers.timeFromSeconds(server.duration, true) + '" data-score="' + (server.redTeamScore + ':' + server.blueTeamScore) + '" data-winner="' + server.winner + '"' + (server.myTeam ? ' data-myteam="' + server.myTeam + '"' : '') + (server.myTeamWon ? ' data-myteamwon="' + server.myTeamWon + '"' : '') + (server.myTeamName ? ' data-myteamname="' + server.myTeamName + '"' : '') + ' />';
svg += '<line x1="' + x1 + '" y1="' + (server.winner === 1 || server.winner === -1 ? y - 3 : y) + '" x2="' + x1 + '" y2="' + (server.winner === 2 || server.winner === -1 ? y + 3 : y) + '" class="SRC_GameStats_Graph_Details ' + (server.winner === 1 ? 'SRC_GameStats_Graph_GameStart_Red' : server.winner === 2 ? 'SRC_GameStats_Graph_GameStart_Blue' : 'SRC_GameStats_Graph_GameStart_Abandoned') + '" data-gameid="' + server.gameId + '" data-map="' + server.map + '" data-name="' + server.server + '" data-date="' + dayjs(server.started).format('DD-MMM-YYYY h:mma') + '" data-duration="' + tagpro.helpers.timeFromSeconds(server.duration, true) + '" data-score="' + (server.redTeamScore + ':' + server.blueTeamScore) + '" data-winner="' + server.winner + '"' + (server.myTeam ? ' data-myteam="' + server.myTeam + '"' : '') + (server.myTeamWon ? ' data-myteamwon="' + server.myTeamWon + '"' : '') + (server.myTeamName ? ' data-myteamname="' + server.myTeamName + '"' : '') + ' />';
if (server.myTeam) {
if (server.myTeamWon) {
svg += '<circle cx="' + x1 + '" cy="' + (y - 6) + '" r="2" fill="#0e2" opacity="0.5" shape-rendering="auto" title="My Game" />'
} else {
svg += '<circle cx="' + x1 + '" cy="' + (y - 6) + '" r="2" fill="#aaa" opacity="0.5" shape-rendering="auto" title="My Game" />'
}
}
}
svg += '<text x="' + (graphPaddingLeft - 30) + '" y="' + (graphHeight - 3) + '" class="SRC_GameStats_Graph_LabelX" text-anchor="start">' + dayjs(firstGameStarted).format('h:mma') + '</text>';
svg += '<line x1="' + (graphPaddingLeft - 1) + '" y1="0" x2="' + (graphPaddingLeft - 1) + '" y2="' + (graphHeight - 10) + '" class="SRC_GameStats_Graph_Guide" />';
const firstTime = new Date(firstGameStarted);
const firstHour = new Date(firstGameStarted).getHours() + 1;
const lastHour = new Date().getHours();
for (let x = 1; x <= lastHour - firstHour + 1; x++) {
firstTime.setHours(firstHour + x - 1, 0, 0, 0);
let x1 = graphPaddingLeft + ((firstTime.getTime() - firstGameStarted) / timePeriodMs) * graphWidth;
let hour = firstHour + x - 1;
svg += '<text x="' + (x === 1 ? x1 + 22 : x === lastHour - firstHour + 1 ? x1 - 22 : x1) + '" y="' + (graphHeight - 3) + '" class="SRC_GameStats_Graph_LabelX" text-anchor="middle">' + (hour > 12 ? hour - 12 + ':00pm' : hour + ':00am') + '</text>';
svg += '<line x1="' + x1 + '" y1="0" x2="' + x1 + '" y2="' + (graphHeight - 10) + '" class="SRC_GameStats_Graph_Guide" />';
}
svg += '<text x="' + (graphPaddingLeft + graphWidth - 3)+ '" y="' + (graphHeight - 3) + '" class="SRC_GameStats_Graph_LabelX" text-anchor="end">' + dayjs(now).format('hh:mma') + '</text>';
svg += '<line x1="' + (graphPaddingLeft + graphWidth) + '" y1="0" x2="' + (graphPaddingLeft + graphWidth) + '" y2="' + (graphHeight - 10) + '" class="SRC_GameStats_Graph_Guide" />';
$('#SRC_GameStats_Graph_SVG').remove();
$('#SRC_GameStats_Graph_Container').append(svg + '</svg>');
};
let createScoreboardData = function(players) {
let data = [];
let totals = {
tags: 0,
pops: 0,
grabs: 0,
drops: 0,
hold: 0,
captures: 0,
prevent: 0,
returns: 0,
support: 0,
powerups: 0,
};
for (let playerId in players) {
for (let stat in totals) {
totals[stat] += players[playerId]['s-' + stat];
}
}
for (let playerId in players) {
if (players[playerId].finished) {
let thisData = {
id: playerId,
name: players[playerId].name,
team: players[playerId].team,
auth: players[playerId].auth,
userId: players[playerId].userId,
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'],
oldscore: 0,
oldpos: '',
score: +players[playerId].score,
oscore: +players[playerId].oscore,
dscore: +players[playerId].dscore,
points: players[playerId].points,
otspawn: players[playerId].otspawn || 0,
joined: players[playerId].joined,
};
for (let stat in totals) {
thisData.oldscore += players[playerId]['s-' + stat] / (totals[stat] || 1) * (stat === 'pops' || stat === 'drops' ? -50 : 50);
}
data.push(thisData);
}
}
data.sort(function(a, b) {
return (b.oldscore - a.oldscore ? b.oldscore - a.oldscore : a.id - b.id);
});
for (let i = 0; i < data.length; i++) {
data[i].oldpos = i + 1;
}
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 = '#SRC_ChatLog_Chat') {
let playersSorted = createScoreboardData(cachedGames[gameId].players);
let actualGameStarted = cachedGames[gameId].metadata.started;
let actualGameDuration = cachedGames[gameId].metadata.duration;
let gameFinish = cachedGames[gameId].metadata.started + cachedGames[gameId].metadata.duration;
let isOvertime = cachedGames[gameId].metadata.overtimeStartedAt;
let stats = [ 'oldscore', 'score', 'oscore', 'dscore', 'tags', 'pops', 'grabs', 'drops', 'hold', 'captures', 'prevent', 'returns', 'support', 'powerups', 'points', 'otspawn' ];
let rStats = { oldscore:0, score:0, oscore:0, dscore: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 = { oldscore:0, score:0, oscore:0, dscore: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 = '<div style="margin:20px 80px 10px; border-bottom:1px solid #555;"></div>' +
'<table id="SRC_Scoreboard"><tr id="SRC_Scoreboard_Header"><td style="width:72px;"></td><td>Old</td><td>Score</td><td>O</td><td>D</td><td>Tags</td><td>Pops</td><td>Grabs</td><td>Drops</td><td>Hold</td><td>Caps</td><td>Prev</td><td>Rets</td><td>Supp</td><td>Pups</td><td>Pts</td>' + (isOvertime ? '<td>O/T</td>' : '') + '<td>Time</td></tr>';
$.each(playersSorted, function(key, value) {
let timePlayed = Math.floor(gameFinish - Math.max(value.joined, actualGameStarted));
scoreboard += '<tr data-team="' + value.team + '"><td style="color:' + (value.team === 1 ? redTextColor : blueTextColor) + '">' + (value.auth ? '<span class="SRC_Auth">✔</span>' : '') + (value.userId ? '<a href="/profile/' + value.userId + '" class="SRC_Scoreboard_UserId" target="_blank" title="View TagPro Profile (New Tab)">' + value.name + '</a>' : value.name) + '</td><td data-raw="'+value.oldpos+'" title="'+value.oldscore.toFixed(0)+'">'+value.oldpos.toFixed(0)+'</td><td data-raw="'+value.score+'">'+value.score+'</td><td data-raw="'+value.oscore+'">'+value.oscore+'</td><td data-raw="'+value.dscore+'">'+value.dscore+'</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>' : '') + '<td title="Played for ' + tagpro.helpers.timeFromSeconds(Math.floor(timePlayed / 1000), true) + '">' + (timePlayed / actualGameDuration * 100).toFixed(0) + '%</td></tr>';
for (let v of stats) {
if (value.team === 1) {
rStats[v] += value[v];
} else if (value.team === 2) {
bStats[v] += value[v];
}
}
});
scoreboard += '</table>';
scoreboard += '<div style="margin:10px 80px 30px; border-bottom:1px solid #555;"></div>';
$(targetElement).append(scoreboard);
setTimeout(() => {
for (let i = 2; i <= lastColumn; i++) {
let column = $('#SRC_Scoreboard tr:gt(0) td:nth-child(' + i + ')');
let prevMax = 0;
$(column).each(function(k, v) {
if ($(this).data('raw') > prevMax) {
$(column).removeClass('SRC_SB_Max_Red SRC_SB_Max_Blue');
$(this).addClass($(this).parent().data('team') === 1 ? 'SRC_SB_Max_Red' : 'SRC_SB_Max_Blue');
prevMax = $(this).data('raw');
} else if ($(this).data('raw') === prevMax && prevMax !== 0) {
$(this).addClass($(this).parent().data('team') === 1 ? 'SRC_SB_Max_Red' : 'SRC_SB_Max_Blue');
}
});
}
$('#SRC_Scoreboard').append('<tr><td colspan="100" style="background:#696969;"></td></tr>');
$('#SRC_Scoreboard').append('<tr><td style="color:' + redTextColor + '; text-align:right">Red:</td><td'+(rStats.oldscore>=bStats.oldscore?' class="SRC_SB_Max_Red"':'')+'>'+rStats.oldscore.toFixed(0)+'</td><td'+(rStats.score>=bStats.score?' class="SRC_SB_Max_Red"':'')+'>'+rStats.score+'</td><td'+(rStats.oscore>=bStats.oscore?' class="SRC_SB_Max_Red"':'')+'>'+rStats.oscore+'</td><td'+(rStats.dscore>=bStats.dscore?' class="SRC_SB_Max_Red"':'')+'>'+rStats.dscore+'</td><td'+(rStats.tags>=bStats.tags?' class="SRC_SB_Max_Red"':'')+'>'+rStats.tags+'</td><td'+(rStats.pops>=bStats.pops?' class="SRC_SB_Max_Red"':'')+'>'+rStats.pops+'</td><td'+(rStats.grabs>=bStats.grabs?' class="SRC_SB_Max_Red"':'')+'>'+rStats.grabs+'</td><td'+(rStats.drops>=bStats.drops?' class="SRC_SB_Max_Red"':'')+'>'+rStats.drops+'</td><td'+(rStats.hold>=bStats.hold?' class="SRC_SB_Max_Red"':'')+'>'+tagpro.helpers.timeFromSeconds(rStats.hold, true)+'</td><td'+(rStats.captures>=bStats.captures?' class="SRC_SB_Max_Red"':'')+'>'+rStats.captures+'</td><td'+(rStats.prevent>=bStats.prevent?' class="SRC_SB_Max_Red"':'')+'>'+tagpro.helpers.timeFromSeconds(rStats.prevent, true)+'</td><td'+(rStats.returns>=bStats.returns?' class="SRC_SB_Max_Red"':'')+'>'+rStats.returns+'</td><td'+(rStats.support>=bStats.support?' class="SRC_SB_Max_Red"':'')+'>'+rStats.support+'</td><td'+(rStats.powerups>=bStats.powerups?' class="SRC_SB_Max_Red"':'')+'>'+rStats.powerups+'</td><td'+(rStats.points>=bStats.points?' class="SRC_SB_Max_Red"':'')+'>'+(rStats.points ? rStats.points : '-')+'</td>' + (isOvertime ? '<td'+(rStats.otspawn>=bStats.otspawn?' class="SRC_SB_Max_Red"':'')+'>'+(rStats.otspawn ? rStats.otspawn : '-')+'</td>' : '') + '<td>-</td></tr>');
$('#SRC_Scoreboard').append('<tr><td style="color:' + blueTextColor + '; text-align:right">Blue:</td><td'+(bStats.oldscore>=rStats.oldscore?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.oldscore.toFixed(0)+'</td><td'+(bStats.score>=rStats.score?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.score+'</td><td'+(bStats.oscore>=rStats.oscore?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.oscore+'</td><td'+(bStats.dscore>=rStats.dscore?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.dscore+'</td><td'+(bStats.tags>=rStats.tags?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.tags+'</td><td'+(bStats.pops>=rStats.pops?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.pops+'</td><td'+(bStats.grabs>=rStats.grabs?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.grabs+'</td><td'+(bStats.drops>=rStats.drops?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.drops+'</td><td'+(bStats.hold>=rStats.hold?' class="SRC_SB_Max_Blue"':'')+'>'+tagpro.helpers.timeFromSeconds(bStats.hold, true)+'</td><td'+(bStats.captures>=rStats.captures?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.captures+'</td><td'+(bStats.prevent>=rStats.prevent?' class="SRC_SB_Max_Blue"':'')+'>'+tagpro.helpers.timeFromSeconds(bStats.prevent, true)+'</td><td'+(bStats.returns>=rStats.returns?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.returns+'</td><td'+(bStats.support>=rStats.support?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.support+'</td><td'+(bStats.powerups>=rStats.powerups?' class="SRC_SB_Max_Blue"':'')+'>'+bStats.powerups+'</td><td'+(bStats.points>=rStats.points?' class="SRC_SB_Max_Blue"':'')+'>'+(bStats.points ? bStats.points : '-')+'</td>' + (isOvertime ? '<td'+(bStats.otspawn>=rStats.otspawn?' class="SRC_SB_Max_Blue"':'')+'>'+(bStats.otspawn ? bStats.otspawn : '-')+'</td>' : '') + '<td>-</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) {
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);
$('#SRC_CapTimeline_Container').empty();
$('#SRC_CapTimeline_Container').append('<canvas id="SRC_CapTimelineCanvas" width="' + (totalWidth + 20) + '" height="' + totalHeight + '"></div>');
let canvas = $('#SRC_CapTimelineCanvas');
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 (!$('#SRC_ChatLog').length) {
addMainContainer();
}
$('#SRC_CapTimeline_Container').empty();
$('#SRC_ChatLog_Chat').empty();
$('#SRC_ChatLog_Chat').append('<div class="SRC_Header">' + gameStartedAt.toDateString() + ', ' + gameStartedAt.toLocaleTimeString() + '</div>');
$('#SRC_ChatLog_Chat').append('<div class="SRC_Header">' + metadata.mapName + '</div>');
$('#SRC_ChatLog_Chat').append('<div class="SRC_Header">Game ID: ' + metadata.gameId + ' on ' + metadata.serverName + '</div>');
$('#SRC_ChatLog_Chat').append('<div class="SRC_Header">Final Score: ' + metadata.teams.red.score + ':' + metadata.teams.blue.score + '</div>');
if (addReplayLink) {
let replayUrl = getReplayUrl(gameId, userId);
$('#SRC_ChatLog_Chat').append('<div class="SRC_Header"><a href="' + replayUrl + '" style="color:#689F38; text-decoration:none;" target="_blank">View Replay</a></div>');
}
$('#SRC_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; scale:70%; margin:-1px 1px -4px 1px;"></span>';
}
$('#SRC_ChatLog_Chat').append('<div class="SRC_Chat"' + (chatlog[i].cap ? ' data-score="' + chatlog[i].scoreRed + ':' + chatlog[i].scoreBlue + '"' : '') + '>' +
' <span class="SRC_Timer' + (chatlog[i].cap ? (chatlog[i].cap === 1 ? ' SRC_Cap_Red' : ' SRC_Cap_Blue') : '') + '"' + ' data-time="' + chatlog[i].time + '">' + timerText + '</span>' +
' <span class="SRC_Message"' + (messageColor ? ' style="color:' + messageColor + ';"' : '') + '>' +
(chatlog[i].name ? '<span class="' + (chatlog[i].team === 1 ? 'SRC_TeamChatRed' : 'SRC_TeamChatBlue') + '"' + (fromColor ? ' style="color:' + fromColor + ';"' : '') + '>' + chatlog[i].name + '</span><span style="color:#ccc;">:</span> ' : '') +
' <span class="SRC_ActualMessage"' + (messageColor ? ' style="color:' + messageColor + ';"' : '') + '>' + chatlog[i].message + '</span>' + (flair ? flair : '') +
' </span>' +
'</div>');
}
appendScoreboard(gameId);
appendCapsTimeline(gameId);
$('#SRC_ChatLog').fadeIn(200);
};
let getReplay = function(gameId) {
let url = '/replays/gameFile?gameId=' + gameId + (userId ? '&userId=' + userId : '');
$('#SRC_CapTimeline_Container').empty();
$('#SRC_ChatLog_Chat').empty();
$('#SRC_ChatLog_Chat').append('<div style="margin:50px auto; text-align:center; font-size:14px; color:#0b4;">Getting data...</div>');
$('#SRC_ChatLog').fadeIn(200);
return $.get(url).done(function(data) {
if (data.error) {
$('#SRC_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('SRC:: A server error has occured (0):\n' + data.error);
return false;
} else {
lastGet = Date.now();
return data;
}
}).fail(function(jqxhr, textStatus, error) {
$('#SRC_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('SRC:: 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','oscore','dscore','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 's-captures' message for (event maps where the captures stat is used for something else)
let skipCaptures = false;
let skipNextChatMessage = '';
if (Array.isArray(data)) {
packets = data;
} else {
packets = data.split('\n');
}
// console.log('SRC:: processReplay() packets:', packets);
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') {
// show ALL new flair messages (they're sometimes hidden if not "for" us)...
if (currentState === 2 && players[obj.id].flair && players[obj.id].flair.key !== obj.flair.key) { // currentPlayers[obj.id].name && obj.id === lastFlairReceiverId
chatlog.push({ from:null, message:obj.name + ' has ' + skipNextChatMessage + ' ' + obj.flair.description + ' flair!' + '<span class="flair ' + obj.flair.className + '" style="background-position: -' + (obj.flair.x * 16) + 'px -' + (obj.flair.y * 16) + 'px; scale:75%; margin:-1px 1px -4px 1px;"></span>' + obj.flair.extra, to:'all', c:'#BFFF00', mod:null, monitor:null, time:packet[0], state:currentState });
}
players[obj.id].flair = obj.flair;
skipNextChatMessage = '';
}
}
}
} 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') { // never show tips
if (packet[2].to === 'all') {
let selectedFlair;
let message = String(packet[2].message);
if (currentState !== 2) {
const hasJoined = message.lastIndexOf(' has joined');
const hasLeft = message.lastIndexOf(' has left');
const hasSwitched = message.lastIndexOf(' has switched');
if (hasJoined >= 0) {
const name = message.slice(0, hasJoined);
if (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 (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 (hasLeft >= 0) {
const name = message.slice(0, hasLeft);
if (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 (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 (hasSwitched >= 0) {
const name = message.slice(0, hasSwitched);
if (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 (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) {
let message = String(packet[2].message);
if (message.startsWith('During overtime, each death increases time to respawn.')) {
players[packet[2].for].otspawn = +message.slice(message.lastIndexOf(':') + 2);
} else if (message.startsWith('This is a save attempt!')) {
chatlog.push({ from:null, message:'<span>' + players[packet[2].for].name + ' - This is a save attempt! A loss will not negatively impact your win %.</span>', to:'all', c:'#fff', mod:null, monitor:null, time:packet[0], state:currentState });
}
}
}
} 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 ' : packet[2].winner === 'blue' ? 'Blue Wins ' : 'Tie ';
const winnerIconText = packet[2].winner === 'red' ? '<span style="margin:0 5px; font-size:8px;">🔴</span>' : packet[2].winner === 'blue' ? '<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 + 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, joined:packet[2].players[j].joined, 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 getProcessAndShowReplay = function(gameId, addReplayLink = false) {
getReplay(gameId).then(function(data) {
if (data.error) {
console.log('SRC:: getProcessAndShowReplay() data.error:', data);
} else {
processReplay(data, gameId);
showChatLog(gameId, addReplayLink);
$('a.SRC_ShowChat[data-gameid="' + gameId + '"]').find('i').addClass('SRC_Cached');
}
}).fail(function(jqxhr) {
console.log('SRC:: getProcessAndShowReplay() .fail() jqxhr:', jqxhr);
if (jqxhr.status === 500) {
console.log('SRC:: getProcessAndShowReplay() .fail() 500 Internal Server Error');
}
});
};
let clearChatSyncs = function() {
$('.SRC_LastChatSync').removeClass('SRC_LastChatSync');
$('.SRC_SeenChatSync').removeClass('SRC_SeenChatSync');
};
let seekTimelineTo = function(seekTo) {
$('#replaySeekBar').val(seekTo);
$('#replaySeekBar').trigger('mouseup');
};
let syncLastChat = function(gameId) {
const actualMessages = $('#SRC_ChatLog_Chat').find('.SRC_ActualMessage');
const teamNameRed = cachedGames[gameId].metadata.teams.red.name;
const teamNameBlue = cachedGames[gameId].metadata.teams.blue.name;
tagpro.socket.on('chat', function(data) {
$(actualMessages).each(function() {
if (!$(this).hasClass('SRC_SeenChatSync') && $(this).text() === data.message) {
$('.SRC_LastChatSync').removeClass('SRC_LastChatSync');
$(this).addClass('SRC_SeenChatSync');
$(this).parent().parent().addClass('SRC_LastChatSync');
return false;
}
});
});
tagpro.socket.on('score', function(data) {
const message = data.r + ':' + data.b;
const target = $('#SRC_ChatLog_Chat').find('.SRC_Chat[data-score="' + message + '"]');
if (target.length && !target.hasClass('SRC_SeenChatSync')) {
$('.SRC_LastChatSync').removeClass('SRC_LastChatSync');
target.addClass('SRC_SeenChatSync SRC_LastChatSync');
}
});
$('#replaySeekBar, #replaySkipBack, #replaySkipForward').on('click', function() {
tagpro.renderer.options.disableAllExplosions = false;
clearChatSyncs();
});
$('#SRC_ChatLog_Chat').on('click', '.SRC_Timer', function() {
const seekTo = Math.max(1, this.dataset.time - 3000);
if (seekTo) {
clearChatSyncs();
seekTimelineTo(seekTo);
$('.SRC_TimerClick').removeClass('SRC_TimerClick');
$(this).addClass('SRC_TimerClick');
}
});
$('#SRC_ChatLog_Header').on('click', '#SRC_CapTimelineCanvas', function(e) {
if (e.offsetX < 10 || e.offsetX > 390) {
return;
}
const actualS3S1Duration = cachedGames[gameId].metadata.preGameDuration + cachedGames[gameId].metadata.expectedGameDuration;
const seekTo = Math.max(1, Math.floor(((e.offsetX - 10) / ($('#SRC_CapTimelineCanvas').width() - 20)) * Math.max(actualS3S1Duration, tagpro.replayData.maxTSSeek)) - 3000);
if (seekTo) {
clearChatSyncs();
seekTimelineTo(seekTo);
}
});
$('#SRC_ChatLog_Close').on('click', function() {
$('#SRC_ChatLog').remove();
});
};
GM_addStyle('.table-teams tbody tr:hover { background:#244b20 !important; }');
GM_addStyle('#SRC_ChatLog { display:none; position:absolute; padding:2px; color:#888; right:10px; margin:8px -40px 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('#SRC_ChatLog_Header { height:52px; }');
GM_addStyle('#SRC_ChatLog_Chat { position:relative; height:420px; overflow-y:auto; overflow-x:hidden; }');
GM_addStyle('#SRC_ChatLog_Chat::-webkit-scrollbar { width:4px; }');
GM_addStyle('#SRC_ChatLog_Chat::-webkit-scrollbar-thumb { background:#292; border-radius:4px; }');
GM_addStyle('#SRC_ChatLog_Chat::-webkit-scrollbar-track { background:#ddd; border-radius:4px; }');
GM_addStyle('#SRC_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('#SRC_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('.SRC_Cap_Red { background:' + redBackgroundColor + '; color:#fff !important; }');
GM_addStyle('.SRC_Cap_Blue { background:' + blueBackgroundColor + '; color:#fff !important; }');
GM_addStyle('.SRC_ShowChat { color:#ccc; margin:0 0 0 5px; }');
GM_addStyle('.SRC_ShowChat i { color:#ccc; }');
GM_addStyle('.SRC_Cached { color:#666 !important; }');
GM_addStyle('.SRC_Header { text-align:center; }');
GM_addStyle('.SRC_Timer { display:inline-block; color:#da981f; width:35px; min-width:35px; text-align:right; margin-right:3px; }');
GM_addStyle('.SRC_Chat { display:flex; color:#ccc; }');
GM_addStyle('.SRC_LastChatSync { background:#244b20; }');
GM_addStyle('.SRC_TimerClick { text-decoration: underline; }');
GM_addStyle('.SRC_TeamChatRed { color:' + redTextColor + '; }');
GM_addStyle('.SRC_TeamChatBlue { color:' + blueTextColor + '; }');
GM_addStyle('.SRC_Auth { font-size:9px; color:#77ff00; }');
GM_addStyle('#SRC_Scoreboard { margin:5px auto; font-size:11px; color:#bbb; width:95%; text-align:center; background:#181818; }');
GM_addStyle('#SRC_Scoreboard td { border-bottom:1px solid #000; padding:2px 1px 1px; white-space:nowrap; }');
GM_addStyle('#SRC_Scoreboard td:nth-child(1) { font-family:monospace; }');
GM_addStyle('#SRC_Scoreboard td:nth-child(3) { color:#888; }');
GM_addStyle('#SRC_Scoreboard td:nth-child(4) { color:#888; }');
GM_addStyle('#SRC_Scoreboard_Header { color:#fff; background:#696969; font-size:10px; }');
GM_addStyle('#SRC_Scoreboard_Header td { color:#fff !important; }');
GM_addStyle('.SRC_SB_Max_Red { color:white !important; background:' + redBackgroundColor + '; }');
GM_addStyle('.SRC_SB_Max_Blue { color:white !important; background:' + blueBackgroundColor + '; }');
GM_addStyle('.SRC_Scoreboard_UserId { color:inherit; text-decoration:none; }');
GM_addStyle('.SRC_Scoreboard_UserId:hover, .SRC_Scoreboard_UserId:active, .SRC_Scoreboard_UserId:focus { color:inherit; text-decoration:1px dotted #999 underline; }');
GM_addStyle('#SRC_GameStats_Show { position:absolute; right:18px; top:20px; color:#b655ff; font-size:12px; opacity:0.6; cursor:pointer; }');
GM_addStyle('#SRC_GameStats_Show:hover { opacity:1; }');
GM_addStyle('#SRC_GameStats_AddRows { position:absolute; right:40px; top:20px; color:#bf8d1d; font-size:12px; opacity:0.6; cursor:pointer; }');
GM_addStyle('#SRC_GameStats_AddRows:hover { opacity:1; }');
GM_addStyle('.SRC_Overtime { color:#d3a857; }');
GM_addStyle('.SRC_RowNumber { color:#aaa; font-size:11px; margin-right:4px; }');
tagpro.ready(function() {
if (document.location.href.includes('/game?replay=')) {
if (MINIMIZED_WIDTH > 0) {
const replayId = document.location.search.replace('?replay=', ''); // this is different to the gameId used to download the game file (so it will be different in the cache)
$('#exit').after('<div id="SRC_ChatLog" style="width:' + MINIMIZED_WIDTH + 'px; top:150px; right:45px;"><div id="SRC_ChatLog_Header"><div id="SRC_CapTimeline_Container"></div><span id="SRC_ChatLog_Close" title="Close">X</span></div><div id="SRC_ChatLog_Chat"></div></div>');
$('#SRC_ChatLog').on('mouseenter', function() {
$('#SRC_ChatLog').animate({ width: 520 }, 150);
}).on('mouseleave', function() {
$('#SRC_ChatLog').animate({ width: MINIMIZED_WIDTH }, 150);
});
if (!cachedGames[replayId]) {
processReplay(tagpro.replayData.packets, replayId);
}
showChatLog(replayId);
syncLastChat(replayId);
}
} else if (document.location.pathname === '/replays') {
waitForTRs();
addMainContainer();
// add sort to existing tables...
$('#replays-app').on('click', '.table-teams th', function() {
let thIndex = this.cellIndex;
let tdIndex = 0;
let sortasc = true;
let currentTab = $('ul.tab-list').find('li.active').attr('data-tab');
let isCurrentTabAll = currentTab === 'all';
if (thIndex === 0) tdIndex = thIndex;
else if (thIndex === 1 || thIndex === 2) tdIndex = thIndex + 1;
else if (thIndex === 3) tdIndex = isCurrentTabAll ? thIndex + 1 : thIndex;
else if (thIndex === 4 && !isCurrentTabAll) tdIndex = thIndex + 1;
else return;
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;
}
$('.table-teams tbody tr').sort(function(a, b) {
if (thIndex === 1 || thIndex === 2 || thIndex === 3 && !isCurrentTabAll) {
if (sortasc) {
return a.children[tdIndex].innerText.localeCompare(b.children[tdIndex].innerText);
} else {
return b.children[tdIndex].innerText.localeCompare(a.children[tdIndex].innerText);
}
} else {
if (sortasc) {
return a.children[tdIndex].dataset.sortby - b.children[tdIndex].dataset.sortby;
} else {
return b.children[tdIndex].dataset.sortby - a.children[tdIndex].dataset.sortby;
}
}
}).appendTo( $('.table-teams tbody') );
$('.table-teams thead th').css('text-decoration', 'none');
$(this).css('text-decoration', 'underline');
});
$('#replays-app').css('font-size', '16px'); // font a bit smaller
$('#replays-app').on('click', 'li', function() {
if (this.dataset.tab === 'mine' || this.dataset.tab === 'favorite' || this.dataset.tab === 'all' || this.dataset.tab === 'search' || this.dataset.tab === 'upload' || this.dataset.tab === 'foruser') {
$('#replays-app').after( $('#SRC_ChatLog') );
setTimeout(waitForTRs, 200);
$('#SRC_GameStats_AddRows').hide();
if (this.dataset.tab === 'all') {
jsonData = [];
$('#SRC_GameStats_Show').show();
if (!$('#filter').is(':visible')) {
$('#SRC_GameStats_AddRows').show();
}
}
}
});
$('#replays-app').on('click', '.fa-xmark, .fa-angles-left, .fa-angles-right', function() {
$('#replays-app').after( $('#SRC_ChatLog') );
$('#SRC_GameStats_AddRows').show();
setTimeout(waitForTRs, 200);
});
$('#replays-app').on('click', '.btn-apply', function() {
$('#replays-app').after( $('#SRC_ChatLog') );
$('#SRC_GameStats_AddRows').hide();
setTimeout(waitForTRs, 200);
});
$('#replays-app').on('click', 'span', function() {
if ($(this).text().match('Newer') || $(this).text().match('Older')) { // there's no id or class
$('#replays-app').after( $('#SRC_ChatLog') );
setTimeout(waitForTRs, 200);
}
});
$('#replays-app').on('click', '.filterToggle', function(e) {
e.stopPropagation();
if ($('#filter').is(':visible')) {
$('#replays-app').after( $('#SRC_ChatLog') );
setTimeout(waitForTRs, 200);
}
let currentTab = $('ul.tab-list').find('li.active').attr('data-tab');
if (currentTab === 'all') {
$('#SRC_GameStats_AddRows').show();
} else {
$('#SRC_GameStats_AddRows').hide();
}
});
$('#replays-app').on('click', '.SRC_ShowChat', function(e) {
e.preventDefault();
let gameId = this.dataset.gameid;
//let replayId = this.dataset.replayid;
$(this).parent().append( $('#SRC_ChatLog') );
if (cachedGames[gameId]) {
showChatLog(gameId, true);
} else {
getProcessAndShowReplay(gameId, true);
}
});
$('#replays-app').on('click', '#SRC_ChatLog_Close', function() {
$('#SRC_ChatLog').fadeOut(100, function() {
$('#replays-app').append( $('#SRC_ChatLog') );
});
});
$('#replays-app').css('position', 'relative').append('<div id="SRC_GameStats_Show" title="Show the server stats for the last 250 public games"><i class="fa-solid fa-server"></i></div><div id="SRC_GameStats_AddRows" title="Show the last 250 public games in the table"><i class="fa-solid fa-table-list"></i></div>');
$('#SRC_GameStats_AddRows').hide();
$('#SRC_GameStats_Show').on('click', function() {
if (initGameStatsNeeded) {
initGameStats();
initGameStatsNeeded = false;
}
if (!jsonData.length || gameStatsLoadedDate < Date.now() - 60000) {
getGamesData(function(success) {
if (success) {
showGamesStats();
}
});
} else if (jsonData.length) {
showGamesStats();
}
});
$('#SRC_GameStats_AddRows').on('click', function() {
if (!jsonData.length || gameStatsLoadedDate < Date.now() - 60000) {
getGamesData(function(result) {
if (result) {
addExtraRowsToTable();
}
});
} else if (jsonData.length) {
addExtraRowsToTable();
}
});
$('#replays-app').on('click', '.SRC_Favorite', function() {
let isFavorite = !$(this).hasClass('fa-regular');
let replayKey = this.dataset.key;
let element = $(this);
if (!replayKey) {
return;
}
if (isFavorite) {
$.post('/replays/favoriteReplay', 'key=' + replayKey).done(function() {
element.removeClass('fa').addClass('fa-regular').attr('title', 'Add to Favorites');
});
} else {
$.post('/replays/favoriteReplay', 'key=' + replayKey + '&set=true&name=&notes=').done(function() {
element.removeClass('fa-regular').addClass('fa').attr('title', 'Remove from Favorites');
});
}
});
}
});
function toSeconds(value) {
return value.split(':').reduce((acc, time) => (60 * acc) + +time);
}
function getYMDHOld(date, date2 = Date.now(), useShortFormat = false) {
let dateNew = dayjs(date2).diff(date, 'year', true).toFixed(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(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(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(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(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