· 6 years ago · Jun 13, 2019, 01:26 AM
1// ==UserScript==
2// @name TagPro Competitive Stats
3// @author Poeticalto
4// @namespace https://poeticalto.github.io/
5// @website https://github.com/poeticalto/tagpro-comp-stats
6// @supportURL https://www.reddit.com/message/compose/?to=Poeticalto
7// @include *://*.koalabeast.com*
8// @description Sets up an Autoscore/Backscore compatible no-script group and sends cap updates/stats while in game
9// @updateURL https://github.com/Poeticalto/tagpro-comp-stats/raw/stable/tagpro_competitive_stats.user.js
10// @downloadURL https://github.com/Poeticalto/tagpro-comp-stats/raw/stable/tagpro_competitive_stats.user.js
11// @grant GM_getValue
12// @grant GM_setValue
13// @version 0.4000
14// ==/UserScript==
15
16// Special thanks to Destar, Some Ball -1, Ko, and ballparts for their work in this userscript!
17// If your abbreviations/jerseys are out of date, message /u/Poeticalto using the support link above so he can update them or make a pull request on the corresponding GitHub repo (pull from the stable branch).
18
19///////////////////////////////////////////////////////////////////////////////////////////////////////////
20// Custom Options can be accessed through the following steps: //
21// 1. Create a private group as the leader. //
22// 2. Click on the League Modifier Box. (Underneath the Swap Teams button) //
23// 3. At the bottom of the list, click on the option you want to toggle. //
24// The option's current state will be shown in brackets. (like [currently enabled]) //
25// Current Options: //
26// Enable/Disable Jerseys = Enables/Disables team jerseys in spectator mode //
27// Enable/Disable Jersey Spin = Enables/Disables the spin of jerseys in spectator mode //
28// Enable/Disable Save Stats Locally = Allows the user to locally save game stats after leaving the game //
29// Enable/Disable Abbreviation Checks = Enables/Disables warnings for when team names are not set //
30// Enable/Disable Sound Checks = Enables/Disables automatic reset of volume to 0 when sounds are muted //
31///////////////////////////////////////////////////////////////////////////////////////////////////////////
32
33// Log script in console
34console.log(GM_info.script.name + ' active (Version: ' + GM_info.script.version + ')');
35
36// Start Script (Group functions)
37if (window.location.href.split(".com")[1].match(/^\/groups\/[a-z]{8}\/*#*[cr]*g*-*[ 0-z]*$/)) { // This gets your unique ID to determine which team you are in the group
38 //This is separate from the main functions because the 'you' event may get sent before the rest of the script has loaded
39 tagpro.ready(function() {
40 tagpro.group.socket.on("you", function(p) {
41 GM_setValue("tpUserId", p);
42 });
43 });
44}
45
46(function(window) {
47 'use strict';
48 if (GM_getValue("tpcsLastHref",0) != 0) { // This is a refresh condition to check if the player has re-entered the game.
49 if (GM_getValue("tpcsLastHref",0) != window.location.href) { // player left the game, so clear the checks
50 GM_setValue("compCheck", 0); // Set comp check to 0 to avoid accidentally triggering spec mode
51 GM_setValue("tpcsLastHref",0);
52 GM_setValue("tpcsStartTime",0);
53 }
54 }
55 // Group Functions, or the functions which run when user is on the group page
56 if (window.location.href.split(".com")[1].match(/^\/groups\/#[cr]g-*[ 0-z]*$/)) { // If #cg/#tg/#rg is passed through, creates new group with competitive settings
57 if (window.location.href.split(new RegExp("-", "gi")).length == 3) {
58 GM_setValue("setMap", window.location.href.split("-")[2]); // sets a global var to remember the map name passed through
59 }
60 document.getElementsByTagName("input")[1].checked = false; // ensures private group
61 GM_setValue("makepug", true); // makepug is the flag to automatically set competitive settings
62 document.getElementById("create-group-btn").click(); // create group
63 }
64 else if (window.location.href.split(".com")[1].match(/^\/groups\/[a-z]{8}\/*#*[cr]*g*-*[ 0-z]*$/) && Array.apply(null, document.getElementsByClassName("js-leader")).length > 0) { // the fancy stuff for the first condition allows for a map to be passed in
65 leaderReady(); // runs function to set up leader stuff
66 groupReady(true); // runs function to grab group info
67 }
68 else if (window.location.pathname.match(/^\/groups\/[a-z]{8}$/) && Array.apply(null, document.getElementsByClassName("js-leader")).length === 0) { // non-leader in group
69 // spectator shouldn't need arguments, so there's no need to parse group type/map choice
70 GM_setValue("groupId", window.location.href.split("/")[4]); // the leader function already sets groupId, so there's no need to set it again
71 console.log("Spectator/Player detected, skipping group setup");
72 groupReady(false); // runs function to grab group info
73 var redundantCount = 0;
74 var redundantLeadCheck = setInterval(function () { // Recheck for leader every half second for ten seconds in case leader functions didn't load
75 if (Array.apply(null, document.getElementsByClassName("js-leader")).length > 0) {
76 if (!document.getElementById("autoscoreLeague")) { // if leader stuff wasn't activated by something else, start leader functions
77 changeLeader(true);
78 }
79 window.clearInterval(redundantLeadCheck); // clear interval since you're done checking
80 }
81 else if (redundantCount > 20) { // clear interval after ten seconds
82 window.clearInterval(redundantLeadCheck);
83 }
84 else { // increment up if it hasn't been ten seconds yet
85 redundantCount++;
86 }
87 }, 500);
88 }
89 // Game functions, or functions which run when user is in game
90 else if (!window.tagpro && GM_getValue("compCheck", 0) >= 1 && tagproConfig && tagproConfig.gameSocket) { //comp game is detected when the tagpro object does not exist and gameSocket exists in the tagproConfig object
91 // Because the tagpro object is not defined, this already defines a comp eligible game, so there's no need for redundant checks
92 var gameLink = tagproConfig.gameSocket;
93 var groupServer = gameLink.split("-")[1].split(".")[0];
94 var groupPort = gameLink.split(":")[1];
95 var userTeam = GM_getValue("userTeam", "none");
96 var m = new Date();
97 var startTime = (Math.floor(m.getTime() / 1000) + m.getTimezoneOffset() * 60);
98 console.log("Comp game detected on " + groupServer + ":" + groupPort + ", player mode activated with team " + userTeam);
99 var backscoreRedCaps = 0; //backscore is taken directly from scoreboard, so it can be trusted
100 var backscoreBlueCaps = 0;
101 var updateRedCaps = 0; //auto is guessed from sound events, so it can't be trusted completely [used for cap updates]
102 var updateBlueCaps = 0;
103 var firstSound = true;
104 var tableExport = []; // tableExport will send the scoreboard data
105 var scoreboardCaps = [0, 0]; // scoreboard caps keeps count of each team's caps
106 var teamNum = []; // teamNum represents the team of each player: Red = 1 and Blue = 2
107 var sendCheck = GM_getValue("tpcsConfirmation", false);
108 var localCheck = GM_getValue("backLocalStorage", false);
109 var soundCheck = GM_getValue("tpcsSoundCheck", true);
110 var playerCompCheck = GM_getValue("compCheck", 0);
111 var playerLate = GM_getValue("tpcsLateFlag", false);
112 document.getElementById("cheering").addEventListener("play", function() { // cheering plays when the game starts or when your team caps
113 if (firstSound === true) { //the first cheering sound starts the game, so don't increment cap counter
114 console.log("Start of comp game detected");
115 var x = new Date();
116 if (tagproAnalyticsCollector && tagproAnalyticsCollector.data && tagproAnalyticsCollector.data.date) {
117 // if the analytics collector is available, get the server defined start time
118 startTime = tagproAnalyticsCollector.data.date+x.getTimezoneOffset() * 60;
119 }
120 else {
121 startTime = (Math.floor(x.getTime() / 1000) + x.getTimezoneOffset() * 60); // gets start time in UTC to avoid timezone confusion
122 }
123 GM_setValue("tpcsStartTime", startTime);
124 }
125 else if (userTeam == 1) { // adds cap to Red team
126 updateRedCaps += 1;
127 }
128 else if (userTeam == 2) { // adds cap to Blue team
129 updateBlueCaps += 1;
130 }
131 if ((userTeam == 1 || userTeam == 2) && (updateRedCaps != 0 || updateBlueCaps != 0 || firstSound === true)) { // enter the cap update if the above scenarios are met
132 if (sendCheck === true && playerLate === false && playerCompCheck > 0) { // if the user has allowed sending data, send cap update
133 capUpdate(updateRedCaps, updateBlueCaps, startTime, groupPort, tableExport, teamNum, groupServer, false);
134 }
135 if (firstSound === true) {
136 firstSound = false;
137 }
138 }
139 }, false); //Note: play event does not activate if sounds are muted
140 document.getElementById("sigh").addEventListener("play", function() { // sigh plays when other team caps
141 if (userTeam == 1) { // adds cap to Blue team
142 updateBlueCaps += 1;
143 }
144 else if (userTeam == 2) { // adds cap to Red team
145 updateRedCaps += 1;
146 }
147 if (userTeam == 1 || userTeam == 2) { // enter the cap update if the above scenarios are met
148 if (sendCheck === true && playerLate === false && playerCompCheck > 0) { // if the user has allowed sending data, send cap update
149 capUpdate(updateRedCaps, updateBlueCaps, startTime, groupPort, tableExport, teamNum, groupServer, false);
150 }
151 }
152 }, false); // However, play event does activate is volume is set to 0 (but no mute)
153 var forceChangeEvent = new Event('change'); // define a change event to force volume update
154 if (soundCheck === true) { // if user has enabled sound checks
155 setTimeout(function(){ // check for
156 if (document.getElementById("soundEffects").className == "off") {
157 // manually reset volume if it is off
158 document.getElementById("soundEffects").click();
159 document.getElementById("volumeSlider").value = -50;
160 document.getElementById("volumeSlider").dispatchEvent(forceChangeEvent);
161 }
162 }, 500);
163 document.getElementById("soundEffects").addEventListener("click", function() {
164 setTimeout(function(){
165 if (document.getElementById("soundEffects").className == "off") {
166 document.getElementById("soundEffects").click();
167 document.getElementById("volumeSlider").value = -50;
168 document.getElementById("volumeSlider").dispatchEvent(forceChangeEvent);
169 }
170 }, 500);
171 }, false);
172 }
173 if (GM_getValue("tpcsStartTime",0) > 0) { // If the start time was previously saved (i.e. player refreshed), call back
174 startTime = GM_getValue("tpcsStartTime");
175 updateRedCaps = GM_getValue("tpcsRefreshRed", 0);
176 updateBlueCaps = GM_getValue("tpcsRefreshBlue", 0);
177 playerLate = false;
178 firstSound = false;
179 }
180 setInterval(function() {
181 if (document.getElementById("options").style.display == "block") { // If the table is open, save stats
182 var playerStats = getStats(); // This was split into a function because I'm trying to see if the stats table can be updated without having the table open
183 tableExport = playerStats[0];
184 teamNum = playerStats[1];
185 backscoreRedCaps = playerStats[2][0];
186 backscoreBlueCaps = playerStats[2][1];
187 }
188 }, 100); // update ten times per second
189 document.onkeydown = function() { // This function sends a backup of the scoreboard in case partial stats are needed or stats need to be recreated.
190 if (event.keyCode == 27) { // 27 corresponds to escape key
191 setTimeout(function() { // setTimeout is used to ensure the scoreboard is updated before the stats get sent
192 if (document.getElementById("options").style.display == "block") { // checks if scoreboard is open
193 if (sendCheck === true) { // sends partial stats if the user has allowed sending data
194 capUpdate(backscoreRedCaps, backscoreBlueCaps, startTime, groupPort, tableExport, teamNum, groupServer, false);
195 }
196 }
197 }, 500);
198 }
199 };
200 window.onbeforeunload = function() { //send stats before exiting the game
201 GM_setValue("tpcsLateFlag", true); // set the late flag to true in case the user refreshes.
202 GM_setValue("tpcsRefreshRed", updateRedCaps);
203 GM_setValue("tpcsRefreshBlue", updateBlueCaps);
204 if (typeof(backscoreRedCaps) == "undefined") { // undefined happens when there is no player on a team, so redefine to 0.
205 backscoreRedCaps = 0;
206 }
207 if (typeof(backscoreBlueCaps) == "undefined") {
208 backscoreBlueCaps = 0;
209 }
210 if (sendCheck === true && localCheck === false) { // send stats, but do not save locally
211 submitStats(backscoreRedCaps, backscoreBlueCaps, tableExport, teamNum, startTime, groupPort, groupServer, false, 0);
212 }
213 else if (sendCheck === true && localCheck === true) { // send stats AND save locally
214 submitStats(backscoreRedCaps, backscoreBlueCaps, tableExport, teamNum, startTime, groupPort, groupServer, false, 1);
215 }
216 else { // only save stats locally
217 submitStats(backscoreRedCaps, backscoreBlueCaps, tableExport, teamNum, startTime, groupPort, groupServer, false, 2);
218 }
219 };
220 }
221 else if (GM_getValue("compCheck", 0) >= 1 && tagproConfig && tagproConfig.gameSocket) { // Spectator mode
222 // A check is needed here because there is no difference between this and a regular public game
223 // Note: On the test server, the tagpro object is currently disabled as a spec, meaning that the script will trigger player mode for spectators
224 GM_setValue("tpcsLateFlag", false); // It doesn't matter if a spectator is late since they have access to the tagpro object
225 var specLink = tagproConfig.gameSocket;
226 var specServer = specLink.split("-")[1].split(".")[0];
227 var specGroupPort = specLink.split(":")[1];
228 var ma = new Date();
229 var specStartTime = (Math.floor(ma.getTime() / 1000) + ma.getTimezoneOffset() * 60) + 20; // set redundant start time
230 var specRedCaps = 0;
231 var specBlueCaps = 0;
232 var endSubmit = false; // endSubmit is a flag for the end event
233 var firstUpdate = false;
234 var specUpdateCheck = GM_getValue("tpcsConfirmation", false);
235 var specLocalCheck = GM_getValue("backLocalStorage", false);
236 console.log("TagPro Competitive Stats is now running in Spectator mode on " + specServer + ":" + specGroupPort);
237 tagpro.ready(function() {
238 if ((GM_getValue("backRedJersey", false) || GM_getValue("backBlueJersey", false)) && GM_getValue("backJerseyFlag", true)) { // adapted version of Some Ball -1's jersey script
239 var red = GM_getValue("backRedJersey"); // grab jersey data from the group
240 var blue = GM_getValue("backBlueJersey");
241 var jersey = [red === "none" ? false : red, blue === "none" ? false : blue, GM_getValue("ballRedTrans", 1), GM_getValue("ballBlueTrans", 1), GM_getValue("jerseyRedTrans", 1), GM_getValue("jerseyBlueTrans", 1)]; // set an array for jersey data for easy processing
242 if (jersey[0] || jersey[1]) { // If either team has jerseys, get the jersey image
243 var tr = tagpro.renderer,
244 oldUPSP = tr.updatePlayerSpritePosition;
245 tr.createJersey = function(player) {
246 if (!jersey[player.team - 1]) { // make empty container if one team doesn't have a jersey
247 if (player.sprites.jersey) player.sprites.ball.removeChild(player.sprites.jersey);
248 player.sprites.jersey = new PIXI.DisplayObjectContainer();
249 player.sprites.jersey.team = player.team;
250 player.sprites.ball.addChildAt(player.sprites.jersey, 1);
251 }
252 else { // make container for jersey
253 if (player.sprites.jersey) {
254 player.sprites.ball.removeChild(player.sprites.jersey);
255 }
256 player.sprites.jersey = new PIXI.Sprite(PIXI.Texture.fromImage("http://i.imgur.com/" + jersey[player.team - 1] + ".png"));
257 player.sprites.jersey.team = player.team;
258 player.sprites.ball.addChildAt(player.sprites.jersey, 1); //add on top of ball, below other stuff
259 player.sprites.jersey.anchor.x = 0.5;
260 player.sprites.jersey.anchor.y = 0.5;
261 player.sprites.jersey.x = 20;
262 player.sprites.jersey.y = 20;
263 if (jersey[player.team + 1] < 1 && jersey[player.team + 1] >= 0) { // set transparency value for actual ball
264 player.sprites.actualBall.alpha = jersey[player.team + 1];
265 }
266 else { // reset
267 player.sprites.actualBall.alpha = 1;
268 }
269 if (jersey[player.team + 3] < 1 && jersey[player.team + 3] >= 0) { // set transparency value for jersey
270 player.sprites.jersey.alpha = jersey[player.team + 3];
271 }
272 else { // reset
273 player.sprites.jersey.alpha = 1;
274 }
275 }
276 };
277 tr.updatePlayerSpritePosition = function(player) {
278 if (!player.sprites.jersey) {
279 tr.createJersey(player);
280 }
281 if (player.sprites.jersey.team !== player.team) {
282 tr.createJersey(player);
283 }
284 var index = player.sprites.ball.getChildIndex(player.sprites.actualBall) + 1;
285 if (index !== player.sprites.ball.getChildIndex(player.sprites.jersey)) {
286 player.sprites.ball.setChildIndex(player.sprites.jersey, index);
287 }
288 if (GM_getValue("backJerseySpin", true)) {
289 player.sprites.jersey.rotation = player.angle;
290 }
291 oldUPSP(player);
292 };
293 }
294 }
295 setTimeout(function () {
296 var tempStartTime = Math.floor(tagpro.gameEndsAt.getTime() / 1000) + tagpro.gameEndsAt.getTimezoneOffset() * 60; // returns UTC
297 if (tempStartTime <= specStartTime + 10) { // If you're in the 20 second waiting period, tagpro.gameEndsAt will return when the game starts
298 // A 10 second buffer is added for people who have faster balls.
299 // Note that the 10 second buffer does not affect which statement is triggered since if the user is in game, gameEndsAt will return a completely different time.
300 specStartTime = tempStartTime;
301 }
302 else { // user is in game, subtract game length from designated end time
303 specStartTime = tempStartTime - (parseInt(GM_getValue("groupTime", "10")) * 60);
304 }
305 var currentId = sortByScore(Object.getOwnPropertyNames(tagpro.players));
306 var capStats = getSpecStats(currentId);
307 if (specUpdateCheck === true) { // the first cap update comes from here since it has the correct start time
308 capUpdate(tagpro.score.r, tagpro.score.b, specStartTime, specGroupPort, capStats[0], capStats[1], specServer, true);
309 }
310 firstUpdate = true;
311 }, 1000); // tagpro.gameEndsAt is not immediately available, so ping a little after
312 tagpro.socket.on("score", function(data) { // Cap update condition
313 if (firstUpdate === true && (tagpro.score.r != 0 || tagpro.score.b != 0)) { // score event gets spammed on occasion before the beginning of the game due to bad connection, so only process if caps are not zero
314 if ('r' in data) { // process red
315 specRedCaps = data.r;
316 }
317 if ('b' in data) { // process blue
318 specBlueCaps = data.b;
319 }
320 var currentId = sortByScore(Object.getOwnPropertyNames(tagpro.players));
321 var capStats = getSpecStats(currentId);
322 if (specUpdateCheck === true) { // send data if user has allowed it
323 capUpdate(specRedCaps, specBlueCaps, specStartTime, specGroupPort, capStats[0], capStats[1], specServer, true);
324 }
325 }
326 });
327 tagpro.socket.on("end", function(data) { // submit stats when the end event is sent by the server
328 var finalId = sortByScore(Object.getOwnPropertyNames(tagpro.players)); // sort IDs of players in the game by their score
329 var specStats = getSpecStats(finalId); // get the stats using finalId
330 if (specUpdateCheck === true && specLocalCheck === false) { // send stats, but do not save locally
331 submitStats(specRedCaps, specBlueCaps, specStats[0], specStats[1], specStartTime, specGroupPort, specServer, true, 0);
332 }
333 else if (specUpdateCheck === true && specLocalCheck === true) { // send stats AND save locally
334 submitStats(specRedCaps, specBlueCaps, specStats[0], specStats[1], specStartTime, specGroupPort, specServer, true, 1);
335 }
336 else { // only save stats locally
337 submitStats(specRedCaps, specBlueCaps, specStats[0], specStats[1], specStartTime, specGroupPort, specServer, true, 2);
338 }
339 endSubmit = true;
340 });
341 /*tagpro.socket.on("playerLeft", function (id) {
342 // support for players leaving will be added in a future update.
343 });*/
344 });
345 window.onbeforeunload = function() { // sends stats if you leave the game for some reason before the end event, or if stats fail to send during the end event
346 if (endSubmit === false) {
347 var finalId = sortByScore(Object.getOwnPropertyNames(tagpro.players));
348 var specStats = getSpecStats(finalId);
349 if (specUpdateCheck === true && specLocalCheck === false) { // send stats, but do not save locally
350 submitStats(specRedCaps, specBlueCaps, specStats[0], specStats[1], specStartTime, specGroupPort, specServer, false, 0);
351 }
352 else if (specUpdateCheck === true && specLocalCheck === true) { // send stats AND save locally
353 submitStats(specRedCaps, specBlueCaps, specStats[0], specStats[1], specStartTime, specGroupPort, specServer, false, 1);
354 }
355 else { // only save stats locally
356 submitStats(specRedCaps, specBlueCaps, specStats[0], specStats[1], specStartTime, specGroupPort, specServer, false, 2);
357 }
358 }
359 };
360 }
361 else { // script has been called on a page that is not a comp game or relating to groups
362 console.log(GM_info.script.name + " running in non-league mode.");
363 }
364})(unsafeWindow);
365
366// Misc Functions, alphabetical order by name of function
367function capUpdate(updateRedCaps, updateBlueCaps, startTime, groupPort, tableExport, teamNum, groupServer, specFlag) { // send cap update
368 var y = new Date();
369 var currentTime = (Math.floor(y.getTime() / 1000) + y.getTimezoneOffset() * 60); // gets start time in UTC
370 var backscoreUpdate = "https://docs.google.com/forms/d/e/1FAIpQLSe57NOVRdas-tzT4MZ8-XPSkNO3MyKCTrAOyFGXp4PtNQcdkQ/formResponse?entry.133949532=" + GM_getValue("backscoreRedAbr", "Red") + "&entry.454687569=" + GM_getValue("backscoreBlueAbr", "Blue") + "&entry.184122371=" + updateRedCaps + "&entry.1906941178=" + updateBlueCaps + "&entry.2120828603=" + groupServer + "&entry.1696460484=" + GM_getValue("groupId", "none") + "&entry.968816448=" + GM_getValue("groupMap", "none") + "&entry.1523561265=" + startTime + "&entry.1474408630=" + currentTime + "&entry.1681155627=" + groupPort + "&entry.1189129646=" + GM_getValue("groupTime", "none") + "&entry.2065162742=" + encodeURIComponent(tableExport.toString()) + "&entry.2098213735=" + teamNum.toString();
371 if (currentTime - startTime < GM_getValue("groupTime", 0) * 60 && GM_getValue("backscoreBlueAbr", "Blue") != "Blue" && GM_getValue("backscoreRedAbr", "Red") != "Red") { // don't send a cap update if any of the team names are default
372 var capUpdateRequest = new XMLHttpRequest();
373 capUpdateRequest.open("POST", backscoreUpdate + "&entry.197322272=" + GM_getValue("backscorePlayer", "Some%20Ball") + ((specFlag === true) ? "%20[S]" : "") + "&submit=Submit");
374 capUpdateRequest.send();
375 console.log("Cap detected, score update sent and is now " + updateRedCaps + "-" + updateBlueCaps);
376 }
377}
378
379function changeLeader(status) {
380 if (status) { // status returns whether or not the user is the leader of the group
381 if (!!document.getElementById("autoscoreLeague") && !!document.getElementById("redTeamAbr")) { // unhide leader elements if the user already had them loaded
382 document.getElementById("autoscoreLeague").style.display = "block";
383 document.getElementById("redTeamAbr").style.display = "block";
384 document.getElementById("blueTeamAbr").style.display = "block";
385 }
386 else { // if the leader elements are not loaded, run the leaderReady function
387 leaderReady();
388 }
389 }
390 else { // changeLeader(false) only happens when the leader elements already exist, so hide leader elements
391 if (!!document.getElementById("autoscoreLeague") && !!document.getElementById("redTeamAbr")) {
392 document.getElementById("autoscoreLeague").style.display = "none";
393 document.getElementById("redTeamAbr").style.display = "none";
394 document.getElementById("blueTeamAbr").style.display = "none";
395 }
396 }
397}
398
399function compCheck() {
400 var checkSum = 0;
401 var checkToggles = 0;
402 var extraSettingsNum = document.getElementsByClassName("js-setting-value").length;
403 var defaultSettings = ["Random", "10 Minutes", "No Capture Limit", "100% (Default)", "100% (Default)", "100% (Default)", "3 Seconds (Default)", "10 Seconds (Default)", "30 Seconds (Default)", "1 Minute (Default)", "Enabled", "Disabled (Default)", "Disable", "Disable"]
404 for (var i = 1; i < extraSettingsNum; i++) {
405 var extraSetting = document.getElementsByClassName("js-setting-value")[i].innerText;
406 if (extraSetting == defaultSettings[i]) {
407 checkSum++;
408 }
409 if (extraSetting == defaultSettings[i] && i >= 12) {
410 checkToggles++;
411 }
412 }
413 return [checkSum, checkToggles];
414}
415
416function download(content, fileName, contentType) { // this function exports game data into a json file
417 var a = document.createElement("a");
418 var file = new Blob([content], {type: contentType});
419 a.href = URL.createObjectURL(file);
420 a.download = fileName;
421 a.click();
422}
423
424function getJerseys() { // set jerseys for each team
425 var specRedTeam = GM_getValue("backscoreRedAbr", "none");
426 var specBlueTeam = GM_getValue("backscoreBlueAbr", "none");
427 var teamJersey = GM_getValue("jerseyLinks");
428 if (teamJersey.hasOwnProperty(specRedTeam)) { // If jersey exists, set jersey
429 GM_setValue("backRedJersey", teamJersey[specRedTeam][0]);
430 GM_setValue("ballRedTrans", teamJersey[specRedTeam][2]);
431 GM_setValue("jerseyRedTrans", teamJersey[specRedTeam][4]);
432 }
433 else { // otherwise, set to false to avoid issues
434 GM_setValue("backRedJersey", false);
435 GM_setValue("ballRedTrans", 1);
436 GM_setValue("jerseyRedTrans", 1);
437 }
438 if (teamJersey.hasOwnProperty(specBlueTeam)) { // repeat for blue
439 GM_setValue("backBlueJersey", teamJersey[specBlueTeam][1]);
440 GM_setValue("ballBlueTrans", teamJersey[specBlueTeam][3]);
441 GM_setValue("jerseyBlueTrans", teamJersey[specBlueTeam][5]);
442 }
443 else {
444 GM_setValue("backBlueJersey", false);
445 GM_setValue("ballBlueTrans", 1);
446 GM_setValue("jerseyBlueTrans", 1);
447 }
448}
449
450function getSpecStats(finalId) {
451 var specExport = [];
452 var specTeamExport = [];
453 for (var i in finalId) {
454 if (tagpro.players[finalId[i]]) {
455 var playerrow = [];
456 var playerObject = tagpro.players[finalId[i]];
457 playerrow = [playerObject.auth ? "✓" + playerObject.name : playerObject.name, playerObject.score, playerObject["s-tags"], playerObject["s-pops"], playerObject["s-grabs"], playerObject["s-drops"], timeFromSeconds(playerObject["s-hold"], true), playerObject["s-captures"], timeFromSeconds(playerObject["s-prevent"], true), playerObject["s-returns"], playerObject["s-support"], playerObject["s-powerups"]];
458 specExport.push(playerrow);
459 specTeamExport.push(playerObject.team);
460 }
461 }
462 return [specExport, specTeamExport];
463}
464
465function getStats() {
466 var statPlayers = document.getElementsByTagName("table").stats.rows.length;
467 var tableExport = [];
468 var scoreboardCaps = [0, 0];
469 var teamNum = [];
470 for (var i = 2; i < statPlayers; i++) { // This part pushes the stats table into an array to be exported later.
471 var playerPush = [];
472 var playerTeam = document.getElementsByTagName("table").stats.rows[i].getElementsByClassName("team-blue").length;
473 teamNum.push(playerTeam + 1);
474 for (var j = 0; j <= 11; j++) {
475 var editVal = document.getElementsByTagName("table").stats.rows[i].cells[j].innerText;
476 if (j == 0) {
477 if (editVal.substring(0, 1) == "✓") { // remove whitespace between checkmark and name
478 editVal = "✓".concat(editVal.substring(1).trim());
479 }
480 else {
481 editVal = editVal.trim();
482 }
483 }
484 else if (j == 7) { // add team caps
485 scoreboardCaps[playerTeam] += parseInt(editVal);
486 }
487 playerPush.push(editVal);
488 }
489 tableExport.push(playerPush);
490 }
491 return [tableExport, teamNum, scoreboardCaps];
492}
493
494function groupEscape(group, checkVersion) {
495 var groupPlayers = Object.keys(tagpro.group.players);
496 var pubCount = 0;
497 for (var g = 0; g < groupPlayers.length; g++) { // This function checks if anyone is in pub team (team 0)
498 if (tagpro.group.players[groupPlayers[g]].team == 0)
499 {
500 pubCount++;
501 }
502 }
503 if (pubCount == 0) {
504 if (typeof group != "undefined" && typeof group.self != "undefined" && typeof group.players != "undefined") {
505 GM_setValue("userTeam", group.players[group.self].team);
506 }
507 else if (typeof group.self == "undefined") {
508 GM_setValue("userTeam", "none");
509 }
510 GM_setValue("backscoreRedAbr", document.getElementsByTagName("input").redTeamName.value);
511 GM_setValue("backscoreBlueAbr", document.getElementsByTagName("input").blueTeamName.value);
512 GM_setValue("groupMap", document.getElementsByTagName("select").map.value);
513 GM_setValue("groupTime", document.getElementsByTagName("select").time.value);
514 GM_setValue("groupCapLimit", document.getElementsByTagName("select").caps.value);
515 GM_setValue("tpcsStartTime", 0);
516 if (tagpro.group.players[GM_getValue("tpUserId", undefined)]) { // set the name of the user based on their name in group
517 GM_setValue("backscorePlayer", encodeURIComponent(tagpro.group.players[GM_getValue("tpUserId", undefined)].name + " (" + checkVersion + ")"));
518 }
519 else { // if the group var is corrupt, set name to Some Ball
520 GM_setValue("backscorePlayer", encodeURIComponent("Some Ball (" + checkVersion + ")"));
521 }
522
523 var escapeCheck = compCheck();
524 if (escapeCheck[0] >= 12 && escapeCheck[1] == 2) {
525 if (escapeCheck[0] == 13 || document.getElementsByClassName("js-setting-value")[1].innerText != "10 Minutes") { // If minutes is the only thing which doesn't match, then it's still a legal comp game
526 GM_setValue("compCheck", 2); // state 2 matches a standard comp game
527 getJerseys();
528 }
529 else {
530 GM_setValue("compCheck", 1); // state 1 matches a comp game with non-default settings (ex. 1 second boost)
531 getJerseys();
532 }
533 }
534 else if (escapeCheck[1] == 2) {
535 GM_setValue("compCheck", 1);
536 getJerseys();
537 }
538 else { // If not enough checks passed or if a player was detected on the pub team, comp check fails.
539 GM_setValue("compCheck", 0); // state 0 matches a pub group or a group which does not have comp toggle activated
540 }
541 }
542 else {
543 GM_setValue("compCheck", 0);
544 }
545}
546
547function groupReady(isLeader) { // grab necessary info from the group
548 tagpro.ready(function() {
549 var jerseyRequest = new XMLHttpRequest();
550 jerseyRequest.open("GET", "https://raw.githubusercontent.com/Poeticalto/tagpro-comp-stats/stable/jerseys.json"); // This json contains a master list of jerseys
551 jerseyRequest.responseType = "json";
552 jerseyRequest.send();
553 jerseyRequest.onload = function() {
554 GM_setValue("jerseyLinks", jerseyRequest.response);
555 }
556 var group = tagpro.group = Object.assign(tagpro.group, {
557 self: GM_getValue("tpUserId", undefined),
558 players: {}
559 });
560 var socket = group.socket;
561 socket.on("member", function(member) {
562 group.players[member.id] = Object.assign(group.players[member.id] || {}, member);
563 if (typeof tagpro.group.players[GM_getValue("tpUserId", undefined)] != "undefined") {
564 if (tagpro.group.players[GM_getValue("tpUserId")].leader != isLeader) {
565 isLeader = tagpro.group.players[GM_getValue("tpUserId")].leader;
566 changeLeader(isLeader);
567 }
568 }
569 });
570 var checkVersion = GM_getValue("tpcsCurrentVer",0);
571 setTimeout(function(){
572 if (checkVersion != GM_info.script.version || GM_getValue("tpcsConfirmation", false) === false) {
573 checkVersion = GM_info.script.version;
574 GM_setValue("tpcsCurrentVer",checkVersion);
575 var updateNotes = "The TagPro Competitive Stats Userscript has been updated to V" + GM_info.script.version + "!\nHere is a summary of updates:\n1. Update include clause to match updated TagPro URL.\n2. Change Update/Download URL to point to GitHub Repo.\n3. Update Tournament Abbreviations.\n4. change url of teams.json to point to proper location.\n5. General Cleanup\nClicking Ok means you accept the changes to this script and the corresponding privacy policy.\nThe full privacy policy and change log can be found by going to the script homepage through the Tampermonkey menu."
576 GM_setValue("tpcsConfirmation", window.confirm(updateNotes));
577 }
578 },1000);
579 socket.on("play", function() { // play event
580 groupEscape(group, checkVersion); // groupEscape grabs the necessary data from the group page
581 GM_setValue("tpcsLateFlag", false);
582 });
583 document.getElementById("join-game-btn").onclick = function() { // join button, or player enters game late
584 // note: If a player enters the game late using the join game button, any stats they send when they leave will be marked incomplete due to time.
585 // This can be corrected on the server side if needed.
586 groupEscape(group, checkVersion);
587 GM_setValue("tpcsLateFlag", true);
588 };
589 });
590}
591
592function leaderReady() {
593 console.log("Group leader detected, setting up group");
594 if (window.location.href.split(".com")[1].match(/^\/groups\/[a-z]{8}\/#tg-*[ 0-z]*$/) || GM_getValue("setMap", "none") != "none") { // set up map if passed through
595 var mapName = "";
596 var mapList = document.getElementsByClassName("form-control js-socket-setting")[0];
597 if (GM_getValue("setMap", "none") == "none") { // map name is in the url
598 mapName = window.location.href.split("-")[2].replace(" ", "_").toLowerCase();
599 }
600 else { // map name is in "setMap"
601 mapName = GM_getValue("setMap", "none").replace(" ", "_").toLowerCase();
602 }
603 GM_setValue("setMap", "none");
604 var mapNameKey = { // This is the list of TagPro maps which does not follow standard naming conventions
605 "angry_pig": "AngryPig",
606 "bombing_run": "bomber",
607 "center_flag": "centerflag",
608 "command_center": "CommandCenter",
609 "danger_zone_3": "DangerZone",
610 "geokoala": "teamwork",
611 "hurricane": "Hurricane2",
612 "hyper_reactor": "HyperReactor",
613 "mars_ball_explorer": "WelcomeToMars",
614 "mars_game_mode": "GameMode",
615 "mode_7": "Mode7",
616 "snes_v2": "snes",
617 "thinking_with_portals": "ThinkingWithPortals",
618 "big_vird": "vee2",
619 "blast_off": "blastoff",
620 "boostsv2.1": "Boosts",
621 "contain_masters": "ContainMasters",
622 "diamond_faces": "Diamond",
623 "dumbell": "fullspeed",
624 "event_horizon": "eventhorizon",
625 "event_horizon_2": "eventhorizon2",
626 "figure_8": "map2-2",
627 "glory_hole": "RiskAndReward",
628 "grail_of_speed": "GrailOfSpeed",
629 "open_field_masters": "OFM",
630 "pokeball": "community1",
631 "push_it": "PushIt",
632 "the_holy_see": "HolySee",
633 "holy_see": "HolySee",
634 "vee": "bird",
635 "whirlwind_2": "whirlwind",
636 "yiss_3.2": "yiss 3.2",
637 "egg_ball": "mode/eggball"
638 };
639 Array.apply(null, mapList).forEach(function(mapOption) { // get the rest of the map names from the group
640 var name = mapOption.value;
641 mapNameKey[name.toLowerCase()] = name;
642 });
643 var map = mapNameKey[mapName] || ""; // defaults to random if the map name is not found
644 tagpro.group.socket.emit("setting", {name: "map", value: map}); // syncs map change to server
645 }
646 if (GM_getValue("makepug", false) === true) { // If the group has been passed through with a toggle, automatically set competitive settings
647 console.log("Automated new group detected, setting comp settings");
648 document.getElementById("pug-btn").click();
649 document.getElementsByName("competitiveSettings")[0].click();
650 GM_setValue("makepug", false);
651 }
652 setTimeout(function(){
653 document.getElementById("pug-btn").onclick = function() { // If group is private, turn the group into a comp game
654 console.log("Private group detected, setting up comp settings");
655 if (document.getElementsByName("competitiveSettings")[0].checked === false) {
656 document.getElementsByName("competitiveSettings")[0].click(); // Turns on competitive settings
657 }
658 if (!!document.getElementById("autoscoreLeague") && !!document.getElementById("redTeamAbr")) { // unhide leader elements if the user already had them loaded
659 document.getElementById("autoscoreLeague").style.display = "block";
660 document.getElementById("redTeamAbr").style.display = "block";
661 document.getElementById("blueTeamAbr").style.display = "block";
662 }
663 }
664 },100);
665 GM_setValue("groupId", window.location.href.split("/")[4]);
666 var buttonSettings = document.getElementsByClassName("pull-left player-settings")[0];
667 var selectList = document.createElement("select"); // selectList is the League selector in group
668 selectList.id = "autoscoreLeague";
669 buttonSettings.appendChild(selectList);
670 selectList.className = "form-control js-socket-setting";
671 selectList.style.margin = "1% 0%";
672 if (document.getElementById("pub-btn").offsetParent === null) {
673 selectList.style.display = "none";
674 }
675 else {
676 selectList.style.display = "block";
677 }
678 selectList.title = "Click here to set a league for team abbreviations or change custom settings!";
679 var abbrRequest = new XMLHttpRequest();
680 abbrRequest.open("GET", "https://raw.githubusercontent.com/Poeticalto/tagpro-comp-stats/stable/teams.json"); // This json contains the abbreviations to use in group
681 abbrRequest.responseType = "json";
682 abbrRequest.send();
683 abbrRequest.onload = function() {
684 GM_setValue("autoscoreAbr", abbrRequest.response);
685 var array = abbrRequest.response.Leagues;
686 array["TagPro Competitive Stats Settings"] = [GM_getValue("backJerseyFlag", true) ? "Disable Jerseys [Currently Enabled]" : "Enable Jerseys [Currently Disabled]", GM_getValue("backJerseySpin", true) ? "Disable Jersey Spin [Currently Enabled]" : "Enable Jersey Spin [Currently Disabled]", GM_getValue("backLocalStorage", false) ? "Disable Saving Local Stats [Currently Enabled]" : "Enable Saving Local Stats [Currently Disabled]", GM_getValue("backAbbrCheck", true) ? "Disable Abbreviation Checks [Currently Enabled]" : "Enable Abbreviation Checks [Currently Disabled]", GM_getValue("tpcsSoundCheck", true) ? "Disable Sound Checks [Currently Enabled]" : "Enable Sound Checks [Currently Disabled]"];
687 var noneOption = document.createElement("option");
688 noneOption.value = "None";
689 noneOption.text = "None";
690 selectList.appendChild(noneOption);
691 var regionList = Object.keys(array);
692 var leagueList = [];
693 for (var i = 0; i < regionList.length; i++) { // Fill in the league selector with the leagues in the json
694 var optionParent = document.createElement("optgroup");
695 optionParent.label = regionList[i];
696 selectList.appendChild(optionParent);
697 var leaguesToAdd = array[regionList[i]];
698 for (var j = 0; j < leaguesToAdd.length; j++)
699 {
700 var optionToAdd = document.createElement("option");
701 optionToAdd.value = leaguesToAdd[j];
702 optionToAdd.text = leaguesToAdd[j];
703 optionParent.appendChild(optionToAdd);
704 leagueList.push(leaguesToAdd[j]);
705 }
706 }
707 if (leagueList.indexOf(GM_getValue("autoscoreImport", "None")) > -1) { // Standard Import Condition
708 selectList.value = GM_getValue("autoscoreImport", "None");
709 }
710 else { // This happens when the league has been removed from the teams json, usually because the season is over or league is dead
711 selectList.value = "None";
712 }
713 updateTeamAbr();
714 document.getElementById("autoscoreLeague").onchange = function() { // redo team names when league is changed
715 updateTeamAbr();
716 };
717 }
718 document.getElementById("launch-private-btn").onmouseover = function () {
719 var alertCheck = compCheck()[0];
720 if (alertCheck == 13) { // all checks passed, so enter abbr check
721 var checkTime = new Date();
722 var checkProcess = (Math.floor(checkTime.getTime() / 1000) + checkTime.getTimezoneOffset() * 60); // get the current time
723 var oldId = GM_getValue("launchGroupId","none"); // last group ID
724 var currentId = GM_getValue("groupId", "none"); // current group ID
725 var oldTime = GM_getValue("checkTime",0); // last time when notification was shown
726 var teamNameCheck = true; // default value, should pass
727 if (document.getElementsByTagName("input").redTeamName.value == "Red" || document.getElementsByTagName("input").blueTeamName.value == "Blue") { // change the check to fail if either of the team names are default (Red for red team, Blue for blue team)
728 teamNameCheck = false;
729 }
730 if (((checkProcess - oldTime) >= (15*60) || currentId != oldId) && teamNameCheck === false && GM_getValue("backAbbrCheck", true) === true) { // If it has been 15 minutes or the group has changed since the last check, and abbr check failed, and user allowed notifs, then execute
731 GM_setValue("checkTime", checkProcess);
732 GM_setValue("launchGroupId", currentId);
733 window.alert("A competitive game was detected without proper abbreviations!\nMake sure your abbreviations are set before launching!");
734 }
735 }
736 }
737 document.getElementById("pub-btn").onclick = function() {
738 document.getElementById("autoscoreLeague").style.display = "none";
739 if (!!document.getElementById("redTeamAbr")) { // In case element does not exist (i.e. function is called before the updateTeamAbr() function is called)
740 document.getElementById("redTeamAbr").style.display = "none";
741 document.getElementById("blueTeamAbr").style.display = "none";
742 }
743 }
744 document.getElementById("pug-btn").onclick = function() {
745 document.getElementById("autoscoreLeague").style.display = "block";
746 if (!!document.getElementById("redTeamAbr") && document.getElementById("autoscoreLeague").value != "None") { // Don't interrupt default behavior of none
747 document.getElementById("redTeamAbr").style.display = "block";
748 document.getElementById("blueTeamAbr").style.display = "block";
749 }
750 }
751}
752
753function openSettings(setting) {
754 var newSetting = "";
755 if (setting == "Disable Jerseys [Currently Enabled]") {
756 GM_setValue("backJerseyFlag", false);
757 newSetting = "Enable Jerseys [Currently Disabled]";
758 }
759 else if (setting == "Enable Jerseys [Currently Disabled]") {
760 GM_setValue("backJerseyFlag", true);
761 newSetting = "Disable Jerseys [Currently Enabled]";
762 }
763 else if (setting == "Disable Jersey Spin [Currently Enabled]") {
764 GM_setValue("backJerseySpin", false);
765 newSetting = "Enable Jersey Spin [Currently Disabled]";
766 }
767 else if (setting == "Enable Jersey Spin [Currently Disabled]") {
768 GM_setValue("backJerseySpin", true);
769 newSetting = "Disable Jersey Spin [Currently Enabled]";
770 }
771 else if (setting == "Enable Saving Local Stats [Currently Disabled]") {
772 GM_setValue("backLocalStorage", true);
773 newSetting = "Disable Saving Local Stats [Currently Enabled]";
774 }
775 else if (setting == "Disable Saving Local Stats [Currently Enabled]") {
776 GM_setValue("backLocalStorage", false);
777 newSetting = "Enable Saving Local Stats [Currently Disabled]";
778 }
779 else if (setting == "Disable Abbreviation Checks [Currently Enabled]") {
780 GM_setValue("backAbbrCheck", false);
781 newSetting = "Enable Abbreviation Checks [Currently Disabled]";
782 }
783 else if (setting == "Enable Abbreviation Checks [Currently Disabled]") {
784 GM_setValue("backAbbrCheck", true);
785 newSetting = "Disable Abbreviation Checks [Currently Enabled]";
786 }
787 else if (setting == "Disable Sound Checks [Currently Enabled]") {
788 GM_setValue("tpcsSoundCheck", false);
789 newSetting = "Enable Sound Checks [Currently Disabled]";
790 }
791 else if (setting == "Enable Sound Checks [Currently Disabled]") {
792 GM_setValue("tpcsSoundCheck", true);
793 newSetting = "Disable Sound Checks [Currently Enabled]";
794 }
795 var updateSettingsArray = document.getElementById("autoscoreLeague").getElementsByTagName("option");
796 for (var i = updateSettingsArray.length - 1; i >= 0; i--) { // loop backwards in the league selector array to update setting text
797 if (updateSettingsArray[i].value == setting) {
798 updateSettingsArray[i].value = newSetting;
799 updateSettingsArray[i].text = newSetting;
800 break;
801 }
802 }
803}
804
805//stolen from tagpro client code
806function pad(t, e) {
807 t = t.toString();
808 var i = (e = e.toString()) + t,
809 o = e.length > t.length ? e.length : t.length;
810 return i.substr(i.length - o);
811}
812
813function sortByScore(playerArr) { // bubble sort id array based on the score
814 var scoreArr = []; // create an array to sort
815 for (var k in playerArr) {
816 scoreArr.push(tagpro.players[playerArr[k]].score);
817 }
818 for (var i = 0; i < scoreArr.length; i++) { // basic (reverse) bubble sort because I'm boring
819 for (var j = scoreArr.length - 1; j >= i; j--) {
820 if (scoreArr[j - 1] < scoreArr[j]) {
821 var tempElementScore = scoreArr[j - 1];
822 var tempElementId = playerArr[j - 1];
823 scoreArr[j - 1] = scoreArr[j];
824 scoreArr[j] = tempElementScore;
825 playerArr[j - 1] = playerArr[j];
826 playerArr[j] = tempElementId;
827 }
828 }
829 }
830 return playerArr;
831}
832
833function submitStats(backscoreRedCaps, backscoreBlueCaps, tableExport, teamNum, startTime, groupPort, groupServer, endCheck, localCheck) { // submit stats at the end of the game
834 var endCompCheck = GM_getValue("compCheck", 0);
835 var submitRequest = new XMLHttpRequest();
836 var doneCheck = true;
837 var z = new Date();
838 var endTime = (Math.floor(z.getTime() / 1000) + z.getTimezoneOffset() * 60); // gets end time in UTC
839 var backscoreLink = "https://docs.google.com/forms/d/e/1FAIpQLSe57NOVRdas-tzT4MZ8-XPSkNO3MyKCTrAOyFGXp4PtNQcdkQ/formResponse?entry.133949532=" + GM_getValue("backscoreRedAbr", "Red") + "&entry.454687569=" + GM_getValue("backscoreBlueAbr", "Blue") + "&entry.184122371=" + backscoreRedCaps + "&entry.1906941178=" + backscoreBlueCaps + "&entry.2120828603=" + groupServer + "&entry.1696460484=" + GM_getValue("groupId", "none") + "&entry.968816448=" + GM_getValue("groupMap", "none") + "&entry.2065162742=" + encodeURIComponent(tableExport.toString()) + "&entry.2098213735=" + teamNum.toString() + "&entry.1523561265=" + startTime + "&entry.1474408630=" + endTime + "&entry.1681155627=" + groupPort + "&entry.1189129646=" + GM_getValue("groupTime", "none") + "&entry.197322272=" + GM_getValue("backscorePlayer", "Some%20Ball") + ((endCheck === true) ? "%20[S]" : "") + ((endCompCheck > 0) ? "" : "%20[F]");
840 var groupCapLimit = GM_getValue("groupCapLimit", -1);
841 if (groupCapLimit == 0) {
842 groupCapLimit = -1;
843 }
844 if (endCheck === true) { // This occurs when a spectator reaches the end of the game and the 'end' event is activated
845 submitRequest.open("POST", backscoreLink + "&entry.2031694514=" + "X" + "&submit=Submit");
846 console.log("Game detected as complete [End event], stats submitted");
847 GM_setValue("compCheck", 0);
848 GM_setValue("tpcsLastHref",0);
849 GM_setValue("tpcsStartTime",0);
850 }
851 else if ((endTime - startTime) > (GM_getValue("groupTime", 0) * 60)) { //This is the Time success condition, when stats are submitted after the game has ended
852 submitRequest.open("POST", backscoreLink + "&entry.2031694514=" + "X" + "&submit=Submit");
853 console.log("Game detected as complete [Time], stats submitted");
854 GM_setValue("compCheck", 0);
855 GM_setValue("tpcsLastHref",0);
856 GM_setValue("tpcsStartTime",0);
857 }
858 else if (backscoreRedCaps == groupCapLimit || backscoreBlueCaps == groupCapLimit) { //This is the Cap success condition, when stats are submitted when cap limit is reached
859 submitRequest.open("POST", backscoreLink + "&entry.2031694514=" + "X" + "&submit=Submit");
860 console.log("Game detected as complete [Cap Limit], stats submitted");
861 GM_setValue("compCheck", 0);
862 GM_setValue("tpcsLastHref",0);
863 GM_setValue("tpcsStartTime",0);
864 }
865 else { //Everything else means something went wrong, i.e. game ended early or the you left the game early
866 submitRequest.open("POST", backscoreLink + "&submit=Submit");
867 doneCheck = false;
868 console.log("Game detected as incomplete, stats submitted");
869 GM_setValue("tpcsLastHref", window.location.href);
870 }
871 if (localCheck <= 1) { // send stats to server
872 submitRequest.send();
873 }
874 if (localCheck >= 1) { // save stats locally
875 var dataJson = {
876 "complete": doneCheck,
877 "redTeamName": GM_getValue("backscoreRedAbr", "Red"),
878 "blueTeamName": GM_getValue("backscoreBlueAbr", "Red"),
879 "groupServer": groupServer,
880 "groupId": GM_getValue("groupId", "none"),
881 "groupMap": GM_getValue("groupMap", "none"),
882 "playerStats": tableExport.toString(),
883 "teamNum": teamNum.toString(),
884 "startTime": startTime,
885 "endTime": endTime,
886 "groupPort": groupPort,
887 "groupTime": GM_getValue("groupTime", "none")
888 };
889 download(JSON.stringify(dataJson),"tpcs-"+dataJson.startTime+"-"+dataJson.redTeamName+"-"+dataJson.blueTeamName+".json", "application/json");
890 }
891}
892
893//stolen from tagpro client code
894function timeFromSeconds(t, e) {
895 if (0 == e) e = !1;
896 var i = pad;
897 t = parseFloat(t);
898 var o = parseInt(t / 3600),
899 n = t % 60,
900 r = i(parseInt(t / 60) % 60, "00") + ":" + i(n, "00");
901 return (!e || o > 0) && (r = i(o, "00") + ":" + r), r;
902}
903
904function updateTeamAbr() { // This function fills in the team abbreviations on the group page
905 var abrJson = GM_getValue("autoscoreAbr");
906 var settingsList = ["Disable Jerseys [Currently Enabled]", "Enable Jerseys [Currently Disabled]", "Disable Jersey Spin [Currently Enabled]", "Enable Jersey Spin [Currently Disabled]", "Disable Saving Local Stats [Currently Enabled]", "Enable Saving Local Stats [Currently Disabled]", "Disable Abbreviation Checks [Currently Enabled]", "Enable Abbreviation Checks [Currently Disabled]","Disable Sound Checks [Currently Enabled]", "Enable Sound Checks [Currently Disabled]"];
907 if (settingsList.indexOf(document.getElementById("autoscoreLeague").value) >= 0) {
908 openSettings(document.getElementById("autoscoreLeague").value);
909 document.getElementById("autoscoreLeague").value = GM_getValue("autoscoreImport", "none");
910 document.getElementById("autoscoreLeague").text = GM_getValue("autoscoreImport", "none");
911 }
912 GM_setValue("autoscoreImport", document.getElementById("autoscoreLeague").value);
913 var redTeamName = document.getElementsByClassName("team-name")[2];
914 var blueTeamName = document.getElementsByClassName("team-name")[3];
915 var teams = [];
916 var redTeamAbr;
917 var blueTeamAbr;
918 if (!!document.getElementById("redTeamAbr")) {
919 redTeamAbr = document.getElementById("redTeamAbr");
920 blueTeamAbr = document.getElementById("blueTeamAbr");
921 }
922 else {
923 redTeamAbr = document.createElement("select");
924 blueTeamAbr = document.createElement("select");
925 redTeamAbr.id = "redTeamAbr";
926 blueTeamAbr.id = "blueTeamAbr";
927 redTeamAbr.title = "Click here to select the Red team's abbreviation!";
928 blueTeamAbr.title = "Click here to select the Blue team's abbreviation!";
929 redTeamName.appendChild(redTeamAbr);
930 blueTeamName.appendChild(blueTeamAbr);
931 }
932 if (document.getElementById("redTeamAbr").style.display == "none") {
933 document.getElementById("redTeamAbr").style.display = "block";
934 document.getElementById("blueTeamAbr").style.display = "block";
935 }
936 if (document.getElementById("pub-btn").offsetParent === null) {
937 document.getElementById("autoscoreLeague").style.display = "none";
938 document.getElementById("redTeamAbr").style.display = "none";
939 document.getElementById("blueTeamAbr").style.display = "none";
940 }
941 else {
942 document.getElementById("autoscoreLeague").style.display = "block";
943 document.getElementById("redTeamAbr").style.display = "block";
944 document.getElementById("blueTeamAbr").style.display = "block";
945 }
946 switch (document.getElementById("autoscoreLeague").value) {
947 // teams is the list which is shown on the group page
948 // teamsLabels is the list of labels to help differentiate teams (usually server or conference)
949 // teamsRaw is the list of abbreviations to put into the group
950 case "TToC": // tournaments follow the same procedure so flow one case because I'm lazy
951 case "DWT":
952 case "TBT":
953 var tourneyModifier;
954 if (document.getElementById("autoscoreLeague").value == "TToC") {
955 tourneyModifier = "T";
956 }
957 else if (document.getElementById("autoscoreLeague").value == "DWT") {
958 tourneyModifier = "D";
959 }
960 else if (document.getElementById("autoscoreLeague").value == "TBT") {
961 tourneyModifier = "B";
962 }
963 teams = {"Teams":{}};
964 for (let t = 1; t <= 16; t++) {
965 let addTeam = "";
966 if (t < 10) {
967 addTeam = tourneyModifier + "0" + t;
968 }
969 else {
970 addTeam = tourneyModifier + t;
971 }
972 teams.Teams[addTeam] = addTeam;
973 }
974 break;
975 case "None":
976 teams = [];
977 document.getElementById("redTeamAbr").style.display = "none";
978 document.getElementById("blueTeamAbr").style.display = "none";
979 break;
980 default:
981 teams = abrJson[GM_getValue("autoscoreImport")];
982 break;
983 }
984 while (redTeamAbr.firstChild) {
985 redTeamAbr.removeChild(redTeamAbr.firstChild);
986 blueTeamAbr.removeChild(blueTeamAbr.firstChild);
987 }
988 var divisionLabelList = Object.keys(teams);
989 var noneOptionR = document.createElement("option");
990 var noneOptionB = document.createElement("option");
991 noneOptionR.value = "";
992 noneOptionR.text = "";
993 noneOptionB.value = "";
994 noneOptionB.text = "";
995 redTeamAbr.appendChild(noneOptionR);
996 blueTeamAbr.appendChild(noneOptionB);
997
998 for (var k = 0; k < divisionLabelList.length; k++) {
999 var divisionLabelR = document.createElement("optgroup");
1000 var divisionLabelB = document.createElement("optgroup");
1001 divisionLabelR.label = divisionLabelList[k];
1002 divisionLabelB.label = divisionLabelList[k];
1003 var divisionTeams = Object.keys(teams[divisionLabelR.label]);
1004 for (var l = 0; l < divisionTeams.length; l++) {
1005 var optionr = document.createElement("option");
1006 var optionb = document.createElement("option");
1007 optionr.text = divisionTeams[l];
1008 optionr.value = teams[divisionLabelR.label][optionr.text];
1009 optionb.value = optionr.value;
1010 optionb.text = optionr.text;
1011 divisionLabelR.appendChild(optionr);
1012 divisionLabelB.appendChild(optionb);
1013 }
1014 redTeamAbr.appendChild(divisionLabelR);
1015 blueTeamAbr.appendChild(divisionLabelB);
1016 }
1017 redTeamAbr.className = "form-control js-socket-setting";
1018 blueTeamAbr.className = "form-control js-socket-setting";
1019 document.getElementById("redTeamAbr").onchange = function() {
1020 if (document.getElementById("redTeamAbr").value.length <= 0) {
1021 tagpro.group.socket.emit("setting", { name: "redTeamName", value: "Red" });
1022 }
1023 else {
1024 tagpro.group.socket.emit("setting", { name: "redTeamName", value: document.getElementById("redTeamAbr").value});
1025 }
1026 };
1027 document.getElementById("blueTeamAbr").onchange = function() {
1028 if (document.getElementById("blueTeamAbr").value.length <= 0) {
1029 tagpro.group.socket.emit("setting", { name: "blueTeamName", value: "Blue" });
1030 }
1031 else {
1032 tagpro.group.socket.emit("setting", { name: "blueTeamName", value: document.getElementById("blueTeamAbr").value});
1033 }
1034 };
1035}