· 6 years ago · Dec 26, 2019, 12:52 AM
1/**
2 * Advanced Scoreboard - A better scoreboard plugin for BBLog (https://getbblog.com)
3 *
4 * @author Cr1N
5 * @version 1.1
6 */
7
8BBLog.handle("add.plugin", {
9 /* Plugin Infos */
10 id : "bf4-advanced-scoreboard-plugin-dev-1-0",
11 name : "Advanced Scoreboard",
12 version : '1.0',
13 build: '20151210',
14
15 /* Plugin load. This is called only once when battlelog is loaded. */
16 init : function(instance)
17 {
18 // Store the path at initizliation
19 instance.data.currentPath = location.pathname;
20 //instance.debug("Plugin Initialized on path " + location.pathname);
21
22 if (location.pathname.match(new RegExp("^/bf4/(|[a-z]{2}/)(servers|serverbrowserwarsaw)/show/pc/.*","i"))) {
23 //instance.debug("Server paged detected, initializing main handler");
24 instance.handler(instance);
25 }
26 },
27 /* Called each time the DOM changes.*/
28 domchange: function(instance) {
29 if (location.pathname == instance.data.currentPath) {
30 /*
31 $(document).ready(function () {
32 if (instance.data.pluginLoaded && $("#live-header").is(":visible")) {
33 console.log("Plugin is loaded though, scoreboard not initialised...");
34 instance.unloadPlugin(instance); //URL has changed, unload the plugin.
35 instance.handler(instance);
36 }
37 })
38 */
39 } else {
40
41 if (instance.data.pluginLoaded) {
42 instance.unloadPlugin(instance); // URL has changed, unload plugin.
43 }
44
45 instance.data.currentPath = location.pathname;
46
47 if (location.pathname.match(new RegExp("^/bf4/(|[a-z]{2}/)(servers|serverbrowserwarsaw)/show/pc/.*", "i"))) {
48 //instance.debug("Calling main handler");
49
50 $(document).ready(function() {
51 instance.handler(instance);
52 })
53 }
54
55 }
56 },
57
58 /**
59 * Starts and stops the ticker for the scoreboard refresh
60 *
61 */
62 ticker : {
63 isActive : false,
64 id : false,
65 start : function(instance) {
66 if (this.isActive || this.id) {
67 instance.debug(instance, 2, 'Attempted to start ticker when existing ticker is already active');
68 return false;
69 }
70 this.id = setInterval(function () {
71 instance.updateAll(instance);
72 }, 5000);
73 this.isActive = true;
74 instance.debug(instance, 0, 'Ticker has been started (' + this.id.toString() + ')');
75
76 return true;
77 },
78 stop : function(instance) {
79 if (!this.isActive || !this.id) {
80 instance.debug(instance, 2, 'Attempted to stop a ticker but no active ticker was found');
81
82 return false;
83 }
84 clearInterval(this.id);
85 this.id = false;
86 this.isActive = false;
87 instance.debug(instance, 0, 'Ticker has been stopped');
88 return false;
89 }
90 },
91
92 /**
93 * Wrapper to interface with native BattleLog functions
94 *
95 */
96
97 battlelog : {
98 //Holds lookup information
99 lookup : {},
100 /**
101 * Return the image class for a given vehicle
102 *
103 * @param guid Weapon GUID
104 * @param type Type of image, 0 -> lineart, 1 -> fancy
105 */
106 getVehicleImage : function(guid, type) {
107 var vehicleItems = window.items.game_data.compact.vehicles;
108 var vehicle = vehicleItems[guid];
109 var vehicleArt = vehicle.see[type];
110 var imageClass = vehicleItems[vehicleArt].imageConfig.slug;
111
112 return imageClass
113 },
114 /**
115 * Return the image class for a given vehicle
116 *
117 * @param guid Weapon GUID
118 * @param type Type of image, 0 -> lineart, 1 -> fancy
119 */
120 getWeaponImage : function(guid, type) {
121 var weaponItems = window.items.game_data.compact.weapons;
122 var weapon = weaponItems[guid];
123 var weaponArt = weapon.see[type];
124 var imageClass = weaponItems[weaponArt].imageConfig.slug;
125
126 return imageClass;
127 },
128 /**
129 * Return the map title from the short name
130 *
131 * @param mapName Short name for the map
132 *
133 */
134 getMapTitle : function(mapName) {
135 //Might want to refactor here
136 if (typeof this.lookup.gameserverwarsaw == 'undefined') {
137 this.lookup.gameserverwarsaw = gamedatawarsaw.function_warsawgameserver();
138 }
139
140 mapTitle = this.lookup.gameserverwarsaw.mapLookup[mapName].label;
141
142 return mapTitle;
143 },
144
145 /**
146 * Return the localized string for a game-mode
147 *
148 * @param gameMode The game mode
149 */
150 getGameMode : function(gameMode) {
151 return gamedata.function_localizedGameMode(2048, gameMode); //2048 = bf4
152 },
153
154 /**
155 * Return a JSON Object containing information about the server
156 *
157 * @param serverHash The unique hash identifying the server
158 * @param callback The function to be executed on success
159 */
160 getServerInfo: function(serverHash, callback) {
161 $.ajax({
162 url: 'https://battlelog.battlefield.com/bf4/servers/show/' + serverHash + "/?json=1",
163 type: 'GET',
164 async: true,
165 cache: false,
166 timeout: 30000,
167 success: function(data) {
168 callback(data);
169 }
170 });
171 }
172
173
174 },
175
176 data : {
177 advancedViewPlayer : 0, //Persona ID of player to show advanced statistics for
178 animationActive : false,
179 asLiveUpdate : false,
180 asTrackerUpdate : false,
181 charts : {"skillDistribution" : false, "tickets" : false,},
182 currentChart: false,
183 ticketsChart : false,
184 currentPath : '',
185 drawMode : "player",
186 gameServerWarsaw : gamedatawarsaw.function_warsawgameserver(),
187 latestScoreboardData : {}, //Holds the most recently retrieved set of scoreboard data to present unneccessary requests
188 mode : 'scoreboard', //Mode of the plugin
189 onServerPage : false,
190 pluginLoaded : false,
191 scoreboard : {
192 advancedViewPlayer : false,
193 animationActive : false,
194 },
195 server : {},
196 tracker : {"tickets" : {}, "kills" : {},} //Track various aspects of the round over time
197 },
198
199 //Holds player statistics
200 playerStats: {},
201 // Holds player vehicle statistics
202 playerVehicleStats : {},
203
204
205 // (REFACTOR) Add at least some translations where required
206 translations: {
207 "en":
208 {
209 "settings-title" : "Settings",
210 },
211 "de":
212 {
213 "settings-title" : "Einstellungen",
214 }
215 },
216
217 /* Main handler */
218 handler: function(instance) {
219
220 //instance.battlelog.getServerInfo()
221
222
223
224
225 // Allow in-browser debugging of the plugin instance
226 window.asDebug = instance;
227
228 // Clear any previous tickers
229 instance.ticker.isActive = false;
230 instance.ticker.id = false;
231
232 // If the plugin is not configured, run the default configuration options
233 if (!instance.storage('isConfigured')) {
234 instance.firstTimeRun(instance);
235 }
236
237 // Add the custom css
238 // (REFACTOR) (Ensure that there are no duplicate insertions of the same style sheet)
239 $('head').append('<link rel="stylesheet" href="https://ske.dog/dev/better-battlelog/style.css?v=12" type="text/css" />');
240 // $('head').append('<link rel="stylesheet" href="http://i242clan.com/plugin/c3.css" type="text/css" />');
241
242 // $('head').append('<link rel="stylesheet" href="http://i242clan.com/plugin/test/advanced_scoreboard.css" type="text/css" />');
243
244 // Load charting library
245 // (REFACTOR) (Make these includes optional or remove all)
246 $.ajax({
247 url: 'http://i242clan.com/plugin/d3.min.js',
248 success: instance.debug(instance, 0, 'Loaded D3'),
249 dataType: "script",
250 cache: true
251 });
252 $.ajax({
253 url: 'http://i242clan.com/plugin/c3.min.js',
254 success: function() {
255 instance.debug(instance, 0, 'Loaded C3');
256 instance.data.ticketsChart = c3.generate({
257 bindto: '#as-server-chart',
258 data: {
259 columns: [],
260 colors: {
261 'US': '#2f7191',
262 'RU': '#ff8e42',
263 'CN': '#ff8e42'
264 }
265 },
266 point: {
267 r: 0
268 },
269 axis: {
270 x: { show: false },
271 y: {
272 tick: {
273 values: function() {
274 var values = [];
275 for (var teamId in instance.data.tracker.tickets) {
276 var team = instance.data.tracker.tickets[teamId];
277 var teamName = team[0];
278 var records = team.length - 1;
279
280 if (records <= 50) {
281 var max = team[1];
282 var min = team[team.length - 1];
283 } else {
284 var max = values.push(team[team.length - 50]);
285 var min = values.push(team[team.length - 1]);
286 }
287
288 values.push(max);
289 if (Math.abs(max - min) > 100) {
290 values.push(min);
291 }
292
293 }
294
295 return values;
296 }
297 }
298 }
299 }
300 });
301 },
302 dataType: "script",
303 cache: true
304 });
305
306 //Load sizeof (debug only!)
307 $.ajax({
308 url: 'http://i242clan.com/plugin/sizeof.js',
309 success: instance.debug(instance, 0, 'Loaded sizeof.js'),
310 dataType: 'script',
311 cache: true
312 });
313
314 // Load items library from battlelog if not present
315 if (!window.items) {
316 var path = "/public/gamedatawarsaw/warsaw.items.js";
317 var loadedLib = $.ajax(
318 {
319 type: "GET",
320 dataType: "script",
321 url: base.asset(path),
322 cache: true
323 });
324 } else {
325 //instance.debug("Items Library already present!")
326 }
327
328 // Hide the default BattleLog scoreboard
329 $("#server-players-list").hide();
330
331 // Inject the container for the scoreboard
332 $("#serverbrowser-page").after('<div id="as-container"></div>');
333
334
335 var liveIndicator = '<div class="as-server-indicator"><div id="as-live-indicator"></div>Live</div>';
336
337 //var roundInfoHeader = instance.drawRoundInfo(instance);
338 var roundInfoHeader = '<div id="as-server-header"><div id="as-server-title">' + $(".server-title").text() + liveIndicator + '</div><div id="as-server-info"></div><div id="as-server-chart"></div></div>';
339 $("#as-container").html(roundInfoHeader);
340
341 // Create chart (REFACTOR)
342
343
344
345 var selectors = instance.drawSelectors(instance);
346 $("#as-container").append(selectors);
347
348 $("#as-container").append('<div id="as-scoreboard-container"></div>');
349
350 var debugWindow = '<div id="as-debug-window" style="display:'+ (instance.storage('debuggingEnabled') ? 'block' : 'none') + '"><h6 class="as-header">Debug Information</h6><div id="as-debug-output"></div></div>';
351 $("#as-container").append(debugWindow);
352
353 //Overlay DIV
354 $("#as-container").append('<div class="as-overlay"></div>');
355
356 instance.updateAll(instance);
357
358 //Live update interval
359 if (instance.storage('liveEnabled')) {
360 instance.ticker.start(instance);
361 }
362
363 //Attach event handlers
364
365 instance.attachEventHandlers(instance);
366 },
367
368 attachEventHandlers : function(instance) {
369 /** EVENT HANDLERS **/
370
371 //Change player view
372
373 $("#as-show-squads").click(function () {
374 instance.data.drawMode = "squad";
375 instance.updateHTML(instance);
376 instance.debug(instance, 0, 'Draw Mode set to SQUADS');
377 });
378
379 //Handler for selector hilighting
380
381 $(".view-selector").click(function ()
382 {
383 $(".view-selector").removeClass("btn-primary");
384 $(this).addClass("btn-primary");
385 });
386
387 // Handler for clicking on team join
388
389 $("#as-container").on('click', '.as-join-team', function() {
390 var teamId = $(this).attr('data-team-id');
391 var teams = instance.data.latestScoreboardData.teams;
392
393 for (var i = 0; i < teams.length; i++) {
394 var team = teams[i];
395 if (team.status.teamId == teamId) {
396 instance.joinTeam(team);
397 return;
398 }
399 }
400 });
401
402 //Handler for clicking on a player row
403
404 $("#as-container").on('click', '.as-player', function() {
405 var personaId = $(this).attr('personaid');
406 instance.debug(instance, 0 , ('Player row has been clicked. PersonaId: ' + personaId));
407
408 var thisRow = $(this);
409
410 if (thisRow.hasClass('as-advanced-stats-selected')) {
411 instance.data.advancedViewPlayer = false;
412 instance.data.scoreboard.animationActive = true;
413 $(".as-advanced-player-view").slideUp("fast", function() {
414 $(".as-scoreboard-advanced-stats-row").remove();
415 thisRow.removeClass("as-advanced-stats-selected");
416 instance.data.scoreboard.animationActive = false;
417 });
418
419 return;
420 }
421
422 //All open advanced stats view rows
423 var existingRows = $(".as-scoreboard-advanced-stats-row");
424
425 //If there are any stats view's open, close them
426 if (existingRows.length > 0) {
427 var attRows = $(".as-advanced-stats-selected");
428 $(".as-advanced-player-view").slideUp("fast", function() {
429 existingRows.remove();
430 attRows.removeClass("as-advanced-stats-selected");
431 });
432 }
433
434 instance.data.advancedViewPlayer = personaId;
435 var html = instance.createAdvancedPlayerView(instance, personaId, false);
436 thisRow.addClass('as-advanced-stats-selected').after(html);
437 instance.data.scoreboard.adimationActive = true;
438 $(".as-advanced-player-view").slideDown("fast", function() {
439 instance.data.scoreboard.animationActive = false;
440 });
441
442
443
444
445 });
446
447 //Handler for clicking a role title and expanding the top players
448
449 $("#as-container").on('click', '.as-role-title-row', function () {
450 console.log("Clicked role title row");
451 var roleRow = $(this).next("tr").find(".as-role-top-players");
452
453 if( roleRow.is(":visible") )
454 {
455 roleRow.slideUp("fast");
456 } else {
457 roleRow.slideDown("fast");
458 }
459 });
460
461
462 //Handler for clicking the player join button
463
464 $("#as-container").on('click', '#as-ao-join', function() {
465
466 var personaId = $(this).attr('persona-id');
467
468 instance.joinPlayer(personaId);
469
470 });
471
472
473 $("#as-container").on('click', '#as-show-squads', function () {
474 instance.data.drawMode = "squad";
475 instance.updateHTML(instance);
476 });
477
478 $("#as-container").on('click', '#as-show-players', function () {
479 instance.data.drawMode = "player";
480 instance.updateHTML(instance);
481 });
482
483 $("#as-container").on('click', '#as-show-roles', function () {
484 instance.data.drawMode = "role";
485 instance.updateHTML(instance);
486 });
487
488 $("#as-container").on('click', '#as-show-charts', function () {
489 instance.data.drawMode = "charts";
490 instance.drawCharts(instance);
491 });
492
493 $("#as-container").on('click', '#as-settings', function () {
494 instance.data.drawMode = "settings";
495 instance.drawSettings(instance);
496 });
497
498 $("#as-container").on('click', '#as-quit-game', function () {
499 var game = gamemanager.gameState.game;
500 console.log("Quitting game " + game);
501 gamemanager._killGame(gamemanager.gameState.game);
502 });
503
504 //Handler for hiding the stats window
505
506 $("#server-page").on('click', '.as-stats-close', function () {
507
508 $("#as-stats-container").animate({
509 opacity: 0,
510 height: 'toggle'
511 },
512 1000, function ()
513 {
514 $("#as-stats-container").remove();
515 instance.data.mode = 'scoreboard';
516 instance.data.scoreboard.animationActive = true;
517
518 $("#as-container").animate({
519 opacity: 1,
520 height: 'toggle'
521 }, 1000, function ()
522 {
523 instance.data.scoreboard.animationActive = false;
524 instance.updateHTML(instance);
525 });
526
527 $('html, body').animate({ scrollTop: $("#as-scoreboard-container").offset().top }, 1000);
528 });
529 });
530
531 //Settings
532
533 //Event handler for the display stat select menu
534 $("#as-select-display-stat").on('change', function(){
535
536 instance.storage('displayStat', this.value);
537 instance.updateHTML(instance);
538
539 });
540
541 $("#content").on('click', '#as-settings-close', function(){
542 $("#as-settings-container").remove();
543 });
544
545 //Sorting event handlers
546
547 $("#as-container").on('click', '.as-scoreboard-head td', function() {
548 var elem = $(this);
549
550
551 if( elem.hasClass("sort-desc") )
552 {
553 console.log("has sort-desc")
554 elem.removeClass("sort-desc").addClass("sort-asc");
555 instance.storage('sortMode', 'asc' );
556 }
557 else if( elem.hasClass("sort-asc") )
558 {
559 console.log("has sort-asc")
560 elem.removeClass("sort-asc").addClass("sort-desc");
561 instance.storage('sortMode', 'desc' );
562 }
563 else
564 {
565 console.log("unclassed")
566 elem.addClass("sort-desc");
567 instance.storage('sortMode', 'desc');
568 }
569 instance.storage('sortAttribute', this.getAttribute("sort"));
570 instance.updateHTML(instance);
571 });
572
573
574 //Event handler for hilighting checkbox
575
576 $("#as-container").on('change', '#as-enable-hilighting', function(){
577
578 if(this.checked) {
579 instance.storage('hilightingEnabled', true);
580 } else {
581 instance.storage('hilightingEnabled', false);
582 }
583 instance.updateHTML(instance);
584
585 });
586
587 //Event handler for friend hilighting
588
589 $("#as-enable-friend-hilighting").change(function() {
590
591 if (this.checked) {
592 instance.storage('hilightingEnabled', true);
593 } else {
594 instance.storage('hilightingEnabled', false);
595 }
596
597 instance.updateHTML(instance);
598
599 });
600
601 // Event handler for detailed vehicle overview
602
603 $("#content").on('change', '#as-detailed-vehicles', function() {
604 instance.modifySetting(instance, 'detailedVehicles', this.checked);
605 });
606
607 // Vehicle kill threshold change
608
609 $("#content").on('change', '#as-vehicle-threshold', function() {
610 var threshold = $(this).val();
611 if (threshold) {
612 instance.storage('vehicleThreshold', threshold);
613 }
614 });
615
616 //Scroll right in the advanced view
617
618 //Event handler for the live update checkbox
619 $("#content").on('change', '#as-enable-live', function() {
620 if (this.checked) {
621
622 $("#as-live-indicator").css({ "background-color": "#78c753" });
623
624 instance.storage('liveEnabled', true);
625
626 if (!instance.ticker.isActive) {
627
628 //Start the ticker
629 instance.ticker.start(instance);
630
631 //Immediately refresh the scoreboard
632 instance.updateAll(instance);
633
634 instance.debug(instance, 0, 'Live Scoreboard Enabled');
635 }
636 } else {
637 $("#as-live-indicator").css({"background-color": "red"});
638 instance.storage('liveEnabled', false);
639
640 if(instance.ticker.isActive) {
641 instance.ticker.stop(instance);
642
643 instance.debug(instance, 0, 'Live Scoreboard Disabled');
644 }
645 }
646
647 });
648
649 //Event handler - Enable debugging
650
651 $("#content").on('change', '#as-enable-debugging', function() {
652 if (this.checked) {
653 instance.storage('debuggingEnabled', true);
654 $("#as-debug-window").fadeIn();
655 } else {
656 instance.storage('debuggingEnabled', false);
657 $("#as-debug-window").fadeOut();
658 }
659 });
660
661 //Stats
662
663 $("#server-page").on('click', '.as-stats-select-weapons', function () {
664 $(".as-stats-vehicles").slideUp('fast', function () {
665 $(".as-stats-weapons").slideDown('fast');
666 });
667 });
668
669 $("#server-page").on('click', '.as-stats-select-vehicles', function () {
670 $(".as-stats-weapons").slideUp('fast', function () {
671 $(".as-stats-vehicles").slideDown('fast');
672 });
673 });
674
675 //Join on a specific team
676
677 $("#as-container").on('click', '.join-team', function () {
678 var teamId = $(this).attr('team-id');
679 //alert("I want to join " + teamId);
680
681 var teams = instance.data.latestScoreboardData.teams;
682 var team = {};
683 for (var i = 0; i < teams.length; i++) {
684 if(teams[i].status.teamId == teamId) {
685 team = teams[i];
686 break;
687 }
688 }
689
690 //Iterate team and find lowest ranked played
691 var lowestRank = 140;
692 var lowestPlayer = {};
693
694 for (var i = 0; i < team.players.length; i++) {
695 var player = team.players[i];
696 if (player.rank < lowestRank) {
697 lowestRank = player.rank;
698 lowestPlayer = player;
699 }
700 }
701
702 instance.joinPlayer(lowestPlayer.personaId);
703 });
704
705
706
707 $("#as-render-scorboard-button").click(function(){
708
709 instance.updateAll(instance);
710
711 });
712
713 // Choosing the custom stat
714
715 $("#as-select-stat").change(function() {
716 var value = $(this).val();
717 instance.storage('displayStat', value);
718 instance.updateHTML(instance);
719 });
720
721 },
722 /**
723 * Is fired when the plugin is run for the first time. Configures default options and presents a welcome window.
724 *
725 * @param instance Plugin instance
726 *
727 */
728 firstTimeRun : function(instance) {
729 instance.storage('hilightingEnabled', true);
730 instance.storage('liveEnabled', true);
731 instance.storage('displayStat', 'kdRatio');
732 instance.storage('isConfigured', true);
733 instance.storage('hilightFriends', true);
734 instance.storage('liveTracking', false);
735 instance.storage('sortAttribute', "score");
736 instance.storage('sortMode', "desc");
737 instance.storage('useResetKdr', false);
738 instance.storage('debuggingEnabled', false);
739 instance.storage('detailedVehicles', false);
740 instance.storage('vehicleThreshold', 500);
741 alert("Configuration Parameters Successfully Set");
742 },
743
744 /**
745 * Allows the user to join the server on any player regardless of if they are on your friends list
746 *
747 * @param personaId The Battlelog Persona ID of the player to join
748 */
749 joinPlayer : function(personaId) {
750 var elem = document.getElementById("server-page-join-buttons");
751 var guid = elem.getAttribute("data-guid");
752 var platform = elem.getAttribute("data-platform");
753 var game = elem.getAttribute("data-game");
754 window.gamemanager.joinServerByGuid(guid, platform, game, personaId, 1);
755 },
756
757 /**
758 * Changes a setting variable (BBLog storage)
759 *
760 * @param instance Plugin instance
761 * @param settingName The name of the setting to store or modify
762 * @param settingValue The desired value of the setting
763 */
764 modifySetting : function(instance, settingName, settingValue) {
765 instance.storage(settingName, settingValue);
766 },
767
768 /**
769 * Join on a specific team. Squad is chosen at random through selecting the lowest ranked player on the team who is not in a full squad.
770 */
771 joinTeam : function(team) {
772 // Create squads
773
774 var squads = {};
775
776 for (var i = 0; i < team.players.length; i++) {
777 var player = team.players[i];
778
779 if (!player.hasOwnProperty('squad')) { // Player is not in a squad
780 continue;
781 }
782 if (!squads.hasOwnProperty(player.squad)) {
783 squads[player.squad] = [];
784 }
785 squads[player.squad].push(player);
786
787 }
788
789 var elegiblePlayers = [];
790
791 for (var squadId in squads) {
792 var squad = squads[squadId];
793 var playerCount = squad.length;
794 if (playerCount < 5) {
795 for (var i = 0; i < playerCount; i++) {
796 elegiblePlayers.push(squad[i]);
797 }
798 }
799 }
800
801 // Sort by rank ascending
802 elegiblePlayers.sort(function(a, b) {
803 if (a.rank < b.rank)
804 return -1;
805 if (a.rank > b.rank)
806 return 1;
807 return 0;
808 });
809
810 var joinOn = elegiblePlayers[0].personaId;
811
812 var elem = document.getElementById("server-page-join-buttons");
813 var guid = elem.getAttribute("data-guid");
814 var platform = elem.getAttribute("data-platform");
815 var game = elem.getAttribute("data-game");
816 window.gamemanager.joinServerByGuid(guid, platform, game, joinOn, 1);
817
818 },
819
820 /* Queue Experimentation */
821
822 /*
823 * Scoreboard Object - Handles all scoreboard related functions, e.g. updating/refreshing
824 *
825 *
826 */
827 scoreboard: {
828
829 /**
830 * Returns the HTML for the team header (summary)
831 *
832 * @param instance Plugin instance
833 * @param scoreboardData Scoreboard Data
834 * @param team Team Object
835 */
836 getTeamHeader: function(instance, scoreboardData, team) {
837 // Get team attributes from their values
838 var teamName = instance.lookup.teamName(scoreboardData.gameMode, team.status);
839 var teamFlag = instance.lookup.teamFlag(teamName);
840
841 var teamFlagImg = '';
842 if (teamFlag) {
843 teamFlagImg = '<img alt="flag" class="as-team-flag" src="' + teamFlag + '"></img>';
844 }
845
846 // Create the progress bar illustrating tickets remaining
847 var progressBarWidth = Math.floor((team.status.tickets / team.status.ticketsMax) * 100);
848 var teamType = instance.lookup.teamType(team.status.teamId); // CSS property for whether the team is home or away
849 var progressBar = '<div title="' + team.status.tickets + '/' + team.status.ticketsMax + '" class="progress-bar thicker no-border ' + teamType + '" style="position: relative; display: inline-block; width: 100px; margin: 5px;"><div class="home progress-bar-inner" style="width:' + progressBarWidth + '%"></div></div>';
850
851 // Average statistics for the team
852 var teamAvg = {
853 'skill': (team.globalStats.totalSkill / team.playersLoaded),
854 'kdRatio': (team.globalStats.totalKd / team.playersLoaded),
855 'resetKdRatio': (team.globalStats.totalResetKd / team.playersLoaded),
856 'scorePerMinute': (team.globalStats.totalScorePerMinute / team.playersLoaded),
857 'killsPerMinute': (team.globalStats.totalKillsPerMinute / team.playersLoaded)
858 }
859
860 // Calculate "Strength" of the team
861 // (REFACTOR) (Better strength formula and calculation)
862
863 var teamStrength = (((teamAvg.killsPerMinute * teamAvg.resetKdRatio) * 10) + ((teamAvg.skill * teamAvg.scorePerMinute) / 10000)) * 10
864
865 var teamInfo = '<table class="as-team-summary">' +
866 '<tr><th>Team</th><th>Players</th><th>Tickets</th><th>K/D(G)</th><th>Skill</th><th>Strength</th><th>Join</th></tr>' +
867 '<tr>' +
868 '<td>' + teamFlagImg + teamName + '</td>' +
869 '<td>' + team.players.length + '/' + (scoreboardData.maxPlayers / 2).toFixed(0) + '</td>' +
870 '<td>' + progressBar + '</td>' +
871 '<td>' + teamAvg.kdRatio.toFixed(2) + '</td>' +
872 '<td>' + teamAvg.skill.toFixed(0) + '</td>' +
873 '<td>' + teamStrength.toFixed(0) + '</td>' +
874 '<td><button data-team-id="' + team.status.teamId + '" class="as-join-team btn btn-primary">Join</button></td>' +
875 '</tr>' +
876 '</table>';
877
878 return teamInfo;
879 },
880
881 /**
882 * Returns the HTML for a player row in the scoreboard
883 *
884 * @param instance Plugin instnace
885 * @param player Player Object
886 */
887 getPlayerRow : function(instance, player) {
888 // Player's rank icon
889 var pRank = '<div class="bf4-rank rank small r' + player.rank + ' rank_icon" data-rank="' + player.rank + '"></div>'
890
891 // Player's tags and name
892 var pName = player.tag.length > 0 ? '[' + player.tag + ']' + player.name : player.name;
893 pName = '<a target="_blank" href="/bf4/user/' + player.name + '/">' + pName + '</a>';
894
895 // Player's in-game K/D
896 var pKD = player.deaths == 0 ? player.kills : player.kills / player.deaths;
897
898 var hilightingType = false;
899
900 if (instance.storage('hilightingEnabled') && player.statsLoaded) {
901 hilightingType = instance.lookup.hilightingClass(instance.storage('displayStat'), player.pDisplayStat);
902 }
903
904 //var friends = comcenter.getFriendsListFromLs(); //REFACTOR
905
906 // The custom stat to display for the player
907 var displayStat = player.statsLoaded ? player.pDisplayStat.toFixed(2) : '<div class="loader small"></div>';
908
909 // Whether or not this player's stats are expanded
910 var statsExpanded = player.id == instance.data.advancedViewPlayer ? true : false;
911
912 //Create the HTML for this player's scoreboard row
913 var html = '<tr class="as-player' + (statsExpanded ? ' as-advanced-stats-selected' : '') + (hilightingType ? ' ' + hilightingType : '') + '" personaid="' + player.id + '"><td>' + pRank + '</td><td>' + pName + '</td><td>' + player.kills + '</td><td>' + player.deaths + '</td><td>' + player.score + '</td><td>' + pKD.toFixed(2) + '</td><td>' + displayStat + '</td></tr>'
914
915 return html;
916
917 },
918 /**
919 * Draw the scoreboard in the default player format
920 *
921 * @param instance Plugin instance
922 * @param scoreboardData Scoreboard data
923 */
924 drawPlayers : function(instance, scoreboardData) {
925 instance.debug(instance, 0, 'Drawing PLAYER scoreboard');
926
927 var s = scoreboardData;
928 var teams = instance.calculateTeamTotals(instance, scoreboardData);
929
930 /* Load in the BBLog radar and check if any of the players on the server match */
931 // (REFACTOR) (Better radar logic and implementation)
932
933 var html = "";
934
935 // For each team
936 for (i = 0; i < teams.length; i++) {
937 var team = teams[i];
938
939 // Create wrapper and table for team
940 html += '<div class="as-scoreboard-wrapper" teamId = "' + team.status.teamId + '">' +
941 '<table class="table as-scoreboard-table" teamId="' + team.status.teamId + '">' +
942 '<thead><tr class="as-scoreboard-details">' +
943 '<td colspan="7">';
944
945 // Get team header
946 html += this.getTeamHeader(instance, scoreboardData, team);
947
948 //HTML for table header
949 html += '<tr class="as-scoreboard-head">';
950
951 var columns = [
952 {header : "Rank", sortAttribute : "rank"},
953 {header : "Name", sortAttribute : "name"},
954 {header : "K", sortAttribute : "kills"},
955 {header : "D", sortAttribute : "deaths"},
956 {header : "Score", sortAttribute : "score"},
957 {header : "K/D", sortAttribute : "kd"},
958 {header : instance.lookup.displayStat(instance.storage('displayStat')), sortAttribute : "pDisplayStat"}
959 ]
960
961 var sortAttribute = instance.storage('sortAttribute');
962 var sortMode = instance.storage('sortMode');
963
964 //Iterate the defined columns and append to the HTML
965 for (var j = 0; j < columns.length; j++) {
966 var column = columns[j];
967 html += '<td ' + (column.sortAttribute == sortAttribute ? 'class="sort-' + sortMode + '"' : '') +' sort="' + column.sortAttribute + '">' + column.header + (column.sortAttribute == sortAttribute ? '<div class="sort-' + sortMode + '"></div>' : '') + '</td>';
968 }
969
970 html += '</tr></thead>';
971
972
973 //Sort by the defined attribute, descending or ascending
974 team.players.sort(function (a, b) {
975 if (sortMode == 'desc') {
976 return a[sortAttribute] == b[sortAttribute] ? 0 : +(a[sortAttribute] < b[sortAttribute]) || -1;
977 } else {
978 return a[sortAttribute] == b[sortAttribute] ? 0 : +(a[sortAttribute] > b[sortAttribute]) || -1;
979 }
980 });
981
982 //Iterate over the players on the team and create a table row for them
983 for (var j = 0; j < team.players.length; j++) {
984 var player = team.players[j];
985 html += this.getPlayerRow(instance, player);
986
987 //If a specific player is selected for the advanced view, inject the HTML here (Necessary so advanced information remains displayed on scoreboard refresh)
988 if (player.id == instance.data.advancedViewPlayer ) {
989 html += instance.createAdvancedPlayerView(instance, player.id, true);
990 }
991
992 }
993
994 //Create the table footer for the team
995
996 //Average rank of the player's on the team
997 var avgRank = Math.floor(team.totalRank/team.players.length);
998 var avgRankIcon = '<div class="bf4-rank rank small r' + avgRank + ' rank_icon" data-rank="' + avgRank +'"></div>';
999
1000 //Average in-game KDR of the player's on the team
1001 var avgKD = team.totalDeaths == 0 ? team.totalKills : team.totalKills/team.totalDeaths;
1002
1003 //Average of the custom-displayed statistic of the player's on the team
1004 var avgpDisplayStat = team.totalPDisplayStat == 0 ? '...' : (team.totalPDisplayStat/team.playersLoaded).toFixed(2);
1005
1006 //Create the HTML for the table footer
1007 html += '<tfoot><tr class="as-scoreboard-foot"><td>'+ avgRankIcon +'</td><td></td><td>'+ team.totalKills +'</td><td>' + team.totalDeaths + '</td><td>' + team.totalScore +'</td><td>' + avgKD.toFixed(2) + '</td><td>' + avgpDisplayStat + '</td></tr></tfoot>';
1008
1009 html += '</table>';
1010
1011 //Display the team's commander if one exists
1012 if (team.commander) { //Refactor
1013 var commander = team.commander;
1014
1015 var cRank = '<div class="bf4-rank rank small r' + commander.rank + ' rank_icon" data-rank="' + commander.rank + '"></div>'
1016
1017 var cKd = commander.deaths == 0 ? commander.kills : commander.kills / commander.deaths;
1018 //Player name including tags
1019 var cName = commander.tag.length > 0 ? '[' + commander.tag + ']' + commander.name : commander.name;
1020 pName = '<a target="_blank" href="/bf4/user/' + commander.name + '/">' + cName + '</a>';
1021 html += '<table class="table as-commander-scoreboard"><tbody><tr><td>' + cRank + '</td><td>' + cName + '</td><td>' + commander.kills + '</td><td>' + commander.deaths + '</td><td>' + commander.score + '</td><td>' + cKd + '</td><td>' + commander.pDisplayStat + '</td></tr></tbody></table>';
1022 }
1023
1024 html += '</div>';
1025 }
1026
1027 this.drawRoundHeader(instance, s);
1028
1029 if ($("#as-scoreboard-container").is(':visible')) { //REFACTOR
1030 $("#as-scoreboard-container").html(html)
1031 } else { //Catch issue with navigation
1032 instance.unloadPlugin(instance);
1033 instance.handler(instance);
1034 }
1035 },
1036 /**
1037 * Draw the scoreboard organised by squads
1038 *
1039 * @param instance Plugin instance
1040 * @param scoreboardData Scoreboard data retrieved from the remote gameserver
1041 */
1042 drawSquads : function(instance, scoreboardData) {
1043 instance.debug(instance, 0, 'Drawing SQUAD scoreboard');
1044
1045 var s = scoreboardData;
1046 var teams = instance.calculateTeamTotals(instance, scoreboardData); //Refactor
1047
1048 html = "";
1049
1050 for (var i = 0; i < teams.length; i++) {
1051 var team = teams[i];
1052
1053 //Create wrapper and table for team
1054 html += '<div class="as-scoreboard-wrapper" teamId = "' + team.status.teamId + '">' +
1055 '<table class="table as-scoreboard-table" teamId="' + team.status.teamId + '">' +
1056 '<thead><tr class="as-scoreboard-details">' +
1057 '<td colspan="7">';
1058
1059 html += this.getTeamHeader(instance, scoreboardData, team);
1060
1061 //Sort the players based on their squad and calculate squad totals/averages
1062 var squadIds = []; //For sorting
1063 var squads = {};
1064 for (j = 0; j < team.players.length; j++) {
1065 var player = team.players[j];
1066
1067 if (!squads.hasOwnProperty(player.squad)) { //First player in this squad, create squad object
1068 squads[player.squad] = {
1069 totalRank : player.rank,
1070 totalKills : player.kills,
1071 totalDeaths : player.deaths,
1072 totalScore : player.score,
1073 totalCustomStat : 0,
1074 players : [player],
1075 playersLoaded : 0,
1076 };
1077 //Add to the array of Ids
1078 squadIds.push(player.squad);
1079 } else { //Add player to existing squad and add stats to total
1080 var existingSquad = squads[player.squad];
1081 existingSquad.totalRank += player.rank;
1082 existingSquad.totalKills += player.kills;
1083 existingSquad.totalDeaths += player.deaths;
1084 existingSquad.totalScore += player.score;
1085 //Add player to list of squad's players
1086 existingSquad.players.push(player);
1087 }
1088
1089 //Add the custom stat if the player's global statistics have been loaded
1090 if (instance.playerStats.hasOwnProperty(player.id)) {
1091 var playerStats = instance.playerStats[player.id];
1092 var customStat = playerStats.overviewStats[instance.storage('displayStat')];
1093 squads[player.squad].totalCustomStat += customStat;
1094 squads[player.squad].playersLoaded++;
1095 }
1096 }
1097
1098 //Sort the array of squadnames alphabeticaly
1099 squadIds.sort();
1100
1101
1102 for (var j = 0; j < squadIds.length; j++) {
1103 var squadId = squadIds[j];
1104 var squad = squads[squadId];
1105 var squadName = instance.lookup.squadName(squadId);
1106
1107 if (squadId == 0) {
1108 continue; // Id 0 = not in squad
1109 }
1110
1111 //Squad header
1112 html += '<tr class="as-squad-row"><td colspan="7">' + squadName.toUpperCase() + ' [' + squad.players.length + '/5]</td/></tr>';
1113
1114 for (var k = 0; k < squad.players.length; k++) {
1115 var player = squad.players[k];
1116 // Generate table row
1117 html += this.getPlayerRow(instance, player);
1118 //If a specific player is selected for the advanced view, inject the HTML here (Necessary so advanced information remains displayed on scoreboard refresh)
1119
1120 if (player.id == instance.data.advancedViewPlayer) {
1121 html += instance.createAdvancedPlayerView(instance, player.id, true);
1122 }
1123 }
1124
1125 if (squad.players.length < 5) {
1126
1127 for (var k = 0; k < (5 - squad.players.length) ; k++) {
1128 html += '<tr class="as-blank"><td colspan="7"></td></tr>';
1129 }
1130
1131 }
1132
1133 //Calculate squad averages
1134 var avgSquadRank = Math.floor(squad.totalRank / squad.players.length);
1135 var avgSquadRankIcon = '<div class="bf4-rank rank small r' + avgSquadRank + ' rank_icon" data-rank="' + avgSquadRank + '"></div>';
1136 var avgSquadKd = squad.totalDeaths > 0 ? squad.totalKills/squad.totalDeaths : squad.totalKills;
1137 var avgSquadCustomStat = squad.playersLoaded > 0 ? squad.totalCustomStat/squad.playersLoaded : 0;
1138
1139 html += '<tr class="as-squad-summary"><td>' + avgSquadRankIcon + '<td></td><td>' + squad.totalKills + '</td><td>' + squad.totalDeaths + '</td><td>' + squad.totalScore + '</td><td>' + avgSquadKd.toFixed(2) + '</td><td>' + avgSquadCustomStat.toFixed(2) + '</td></tr>';
1140
1141 if (j < squadIds.length - 1) {
1142 html += '<tr class="as-squad-spacer"><td colspan="7"></td></tr>';
1143 }
1144 }
1145 /*** Create tfoot from averages ***/
1146
1147 //Calculate team averages
1148 var avgRank = Math.floor(team.totalRank/team.totalPlayers)
1149 var avgRankIcon = '<div class="bf4-rank rank small r' + Math.floor(team.totalRank/team.totalPlayers) + ' rank_icon" data-rank="' + Math.floor(team.totalRank/team.totalPlayers) +'"></div>'
1150
1151 var avgKD = team.totalDeaths == 0 ? team.totalKills : team.totalKills/team.totalDeaths;
1152 var avgpDisplayStat = team.totalPDisplayStat == 0 ? '...' : (team.totalPDisplayStat/team.playersLoaded).toFixed(2);
1153
1154 //HTML for scoreboard foot
1155 //html += '<tfoot><tr class="as-scoreboard-foot"><td>'+ avgRank +'</td><td></td><td>'+ team.totalKills +'</td><td>' + team.totalDeaths + '</td><td>' + team.totalScore +'</td><td>' + avgKD.toFixed(2) + '</td><td>' + avgpDisplayStat + '</td></tr></tfoot>';
1156 html += '</table>'
1157 html += '</div>';
1158 }
1159
1160 this.drawRoundHeader(instance, s);
1161
1162 if($("#as-scoreboard-container").is(':visible')) {
1163 $("#as-scoreboard-container").html(html);
1164 } else { //Catch issue with navigation
1165 instance.unloadPlugin(instance);
1166 instance.handler(instance);
1167 }
1168 },
1169
1170 /*
1171 * This way is much better!
1172 *
1173 */
1174 drawVehiclesss: function(instance,scoreboardData) {
1175 instance.debug(instance, 0, 'Drawing VEHICLE scoreboard');
1176 var s = scoreboardData;
1177 var teams = instance.calculateTeamTotals(instance, s);
1178
1179 /*
1180 * Builds and returns the HTML for the vehicle role display
1181 *
1182 */
1183 function buildVehicleHtml(teams, vehicleData) {
1184 // First build an array of categories and the total number of kills
1185 var vehicleTypes = {}
1186
1187 for (var i = 0; i < vehicleData.length; i++) {
1188 var vehicles = vehicleData[i];
1189 for (var vehicleType in vehicles) {
1190 var vehicle = vehicles[vehicleType];
1191 if (vehicleTypes.hasOwnProperty(vehicleType)) {
1192 if (vehicle.totalKills > vehicleTypes[vehicleType].totalKills) {
1193 vehicleTypes[vehicleType].totalKills = vehicle.totalKills;
1194 }
1195 } else {
1196 vehicleTypes[vehicleType] = {
1197 totalKills: vehicle.totalKills
1198 }
1199 }
1200 }
1201 }
1202 console.log(vehicleTypes);
1203 }
1204
1205
1206 if (instance.storage('detailedVehicles')) {
1207 $("#as-scoreboard-container").html('<div class="as-loading-data"><div class="loader small"></div><p>Loading Vehicle Statistics...</p></div>');
1208 instance.calculateDetailedRoles(instance, s, function(vehicleData) {
1209 buildVehicleHtml(teams, vehicleData);
1210 });
1211 return;
1212 } else {
1213 var vehicleData = instance.calculateRoles(instance, s);
1214 return;
1215 }
1216 },
1217 /**
1218 * Draw the scoreboard organised by the number of kills in each vehicle type
1219 * @param instance Plugin instance
1220 * @param scoreboardData Scoreboard datat retrieved from the remote gameserver
1221 */
1222 drawVehicles: function(instance, scoreboardData) {
1223
1224 var _this = this;
1225 /*
1226 * Builds the HTML for the vehicle given a dataset of vehicle info
1227 */
1228 function getVehicleTable(teams, vehicleData) {
1229 var html = '';
1230
1231 for (var i = 0; i < teams.length; i++) {
1232 var team = teams[i];
1233 var teamVehicles = vehicleData[i];
1234
1235 html += '<div class="as-scoreboard-wrapper" teamId = "' + team.status.teamId + '">' +
1236 '<table class="table as-scoreboard-table" teamId="' + team.status.teamId + '">' +
1237 '<thead><tr class="as-scoreboard-details">' +
1238 '<td colspan="7">';
1239
1240 html += _this.getTeamHeader(instance, scoreboardData, team);
1241
1242 html += '</td></tr></thead><tbody>';
1243
1244 for (var categoryName in teamVehicles) {
1245 var vehicle = teamVehicles[categoryName];
1246
1247 var vehicleImageClass = instance.battlelog.getVehicleImage(vehicle.guid, 0);
1248 var vehicleImage = '<div class="as-table-role vehicle xsmall ' + vehicleImageClass + ' image"></div>';
1249
1250 var playersAdded = 0; // Keep track of the players added
1251 var vehiclePlayers = ''; // Store HTML for vehicle players
1252
1253 var ordered = instance.sortObject(vehicle.players, 'kills', 'desc');
1254
1255 console.log("Ordered:");
1256 console.log(ordered);
1257
1258 for (var j = 0; j < ordered.length; j++) {
1259 var personaId = ordered[j];
1260 var playerVehicleStats = vehicle.players[personaId];
1261 if (playerVehicleStats.kills < instance.storage('vehicleThreshold')) {
1262 continue;
1263 }
1264 playersAdded++;
1265 var player = playerVehicleStats.roundStats;
1266 var pRank = '<div class="bf4-rank rank small r' + player.rank + ' rank_icon" data-rank="' + player.rank + '"></div>';
1267 var pName = player.tag.length > 0 ? '[' + player.tag + ']' + player.name : player.name;
1268 var pKD = player.deaths == 0 ? player.kills : player.kills / player.deaths;
1269 var isFriend = false; //refactor
1270
1271 var hilightingType = '';
1272 if (instance.storage('hilightingEnabled')) {
1273 hilightingType = instance.lookup.hilightingClass(instance.storage('displayStat'), player.pDisplayStat);
1274 };
1275
1276 //Generate table row
1277 vehiclePlayers += '<tr class="as-player' + (player.personaId == instance.data.advancedViewPlayer ? ' as-advanced-stats-selected' : '') + (isFriend ? ' friend' : '') + (hilightingType ? ' ' + hilightingType : '') + '" personaid="' + player.personaId + '"><td>' + pRank + '</td><td>' + pName + '</td><td>' + player.kills + '</td><td>' + player.deaths + '</td><td>' + player.score + '</td><td>' + pKD.toFixed(2) + '</td><td>' + playerVehicleStats.kills + '</td></tr>';
1278 }
1279
1280 if (playersAdded) {
1281 html += '<tr><th class="as-team-vehicle-header" colspan="7">' + categoryName + vehicleImage + '</th></tr>';
1282 html += vehiclePlayers;
1283 }
1284
1285 }
1286 html += '</tbody></table></div>';
1287 }
1288
1289
1290
1291 instance.scoreboard.drawRoundHeader(instance, s);
1292
1293 if ($("#as-scoreboard-container").is(':visible')) {
1294 $("#as-scoreboard-container").html(html)
1295 } else { //Catch issue with navigation
1296 instance.unloadPlugin(instance);
1297 instance.handler(instance);
1298 }
1299
1300
1301 }
1302
1303 instance.debug(instance, 0, 'Drawing VEHICLE scoreboard');
1304 var s = scoreboardData
1305 var teams = instance.calculateTeamTotals(instance, scoreboardData);
1306
1307 if (instance.storage('detailedVehicles')) {
1308
1309 if (!$(".as-scoreboard-wrapper").is(':visible')) {
1310 $("#as-scoreboard-container").html('<div class="as-loading-data"><div class="loader small"></div><p>Loading Vehicle Statistics...</p></div>');
1311 }
1312
1313 instance.calculateDetailedRoles(instance, s, function(vehicleData) {
1314 getVehicleTable(teams, vehicleData);
1315
1316 });
1317 } else {
1318 var vehicleData = instance.calculateRoles(instance, s);
1319 getVehicleTable(teams, vehicleData);
1320 }
1321
1322
1323 },
1324 /*
1325 * Update the Advanced Scoreboard header to reflect time/map changes, etc
1326 *
1327 * @param instance Plugin instance
1328 * @param scoreboardData Scoreboard data retrieved from the remote gameserver
1329 */
1330 updateRoundHeader : function(instance, scoreboardData) {
1331 var s = scoreboardData;
1332 //The current map running on the server
1333 var currentMap = '<img class="current-map" src="//eaassets-a.akamaihd.net/bl-cdn/cdnprefix/9c0b010cd947f38bf5e87df5e82af64e0ffdc12fh/public/base/bf4/map_images/195x79/' + s.mapName.toLowerCase() + '.jpg"></img>' +
1334 '<div id="as-map-name">' + instance.data.gameServerWarsaw.mapLookup[s.mapName].label + '</div>' +
1335 '<div id="as-map-mode">' + instance.lookup.gameMode(s.gameMode) + '</div>';
1336 $("#as-scoreboard-mapinfo").html(currentMap);
1337
1338 //Calculate the round time remaining
1339 var totalRoundTime = 3600 * (s.defaultRoundTimeMultiplier/100);
1340 var expiredTime = s.roundTime;
1341 var secondsRemaining = totalRoundTime - expiredTime;
1342 var timeLeft = Math.floor(secondsRemaining/60) + 'M ' + (Math.round((secondsRemaining%60) * 100)/100) + 'S';
1343
1344 //Calculate the total players
1345 var totalPlayers = 0;
1346 for (var i = 0; i < s.teams.length; i++) {
1347 var team = s.teams[i];
1348 totalPlayers += team.players.length;
1349 }
1350
1351
1352 //Round properties and info
1353 var roundInfo = '<table class="as-round-properties">' +
1354 '<tr><td>Players</td><td>' + totalPlayers + '/' + s.maxPlayers + (s.queueingPlayers > 0 ? '[' + s.queueingPlayers + ']' : '') + '</td></tr>' +
1355 '<tr><td>Time Remaining</td><td>' + timeLeft + 'S</td></tr>' +
1356 '</table>';
1357 $("#as-scoreboard-round-properties").html(roundInfo);
1358 },
1359
1360 /*
1361 * Draw the scoreboard header
1362 *
1363 * @param instance Plugin instance
1364 * @param scoreboardData Scoreboard data retrieved from the remote gameserver
1365 */
1366
1367 drawRoundHeader: function(instance, scoreboardData) {
1368 var s = scoreboardData;
1369 var html = '';
1370
1371 //Calculate the total players on the server by counting both teams
1372
1373 var totalPlayers = 0;
1374 for (var i = 0; i < s.teams.length; i++) {
1375 var team = s.teams[i];
1376 totalPlayers += team.players.length;
1377 }
1378
1379 //Map information
1380 html += '<div id="as-round-map">' +
1381 '<img class="current-map" src="//eaassets-a.akamaihd.net/bl-cdn/cdnprefix/9c0b010cd947f38bf5e87df5e82af64e0ffdc12fh/public/base/bf4/map_images/195x79/' + s.mapName.toLowerCase() + '.jpg"</img>' +
1382 '<div id="as-round-map-title">' + instance.battlelog.getMapTitle(s.mapName) + '</div>' +
1383 '<div id="as-round-mode">' + instance.battlelog.getGameMode(s.gameMode) + '</div>' +
1384 '</div>';
1385
1386 //
1387
1388 var queueingPlayers = s.queueingPlayers ? '[' + s.queueingPlayers + ']' : '';
1389
1390 html += '<div id="as-round-properties">' +
1391 '<div><i class="players-icon"></i><span>Players</span><span>' + totalPlayers + '/' + s.maxPlayers + queueingPlayers + '</span></div>' +
1392 '</div>';
1393
1394
1395
1396
1397 $("#as-server-info").html(html);
1398
1399 }
1400 },
1401
1402 drawCharts : function(instance)
1403 {
1404 //instance.debug("Okay, drawing charts...");
1405
1406 //Create a div to hold the test chart
1407 if($("#as-scoreboard-container").is(':visible')) {
1408 $("#as-scoreboard-container").html('<div id="as-charting-container"></div>')
1409 } else { //Catch issue with navigation
1410 instance.unloadPlugin(instance);
1411 instance.handler(instance);
1412 }
1413
1414 //Put tracking data into array
1415 var chartData = [];
1416 for( var dataSet in instance.data.tracker.tickets )
1417 {
1418 var data = instance.data.tracker.tickets[dataSet];
1419 //instance.debug(data);
1420 chartData.push(data);
1421 }
1422 /*
1423 instance.data.currentChart = c3.generate({
1424 bindto: '#as-charting-container',
1425 data: {
1426 columns: chartData
1427 },
1428 type: 'spline'
1429 });
1430 */
1431 //instance.debug("Here's the player stats");
1432 //instance.debug(instance.playerStats);
1433
1434 //instance.debug("Here's the scoreboardData");
1435 //instance.debug(instance.data.latestScoreboardData);
1436
1437 //Okay get the latest scoreboarddata
1438
1439 var teamPlot = [];
1440 var teamNames = {};
1441
1442 for(var team in instance.data.latestScoreboardData.teams)
1443 {
1444 var teamObject = instance.data.latestScoreboardData.teams[team];
1445
1446 var teamName = instance.lookup.teamName(instance.data.latestScoreboardData.gameMode, teamObject.status);
1447
1448 var skillStats = [teamName + ' skill'];
1449 var kdStats = [teamName + ' kd'];
1450
1451 for (var player in teamObject.players)
1452 {
1453 //instance.debug("Looping player in sbdata players");
1454 var playerObject = teamObject.players[player];
1455 var personaId = playerObject.personaId;
1456
1457 skillStats.push(instance.playerStats[personaId].overviewStats.skill);
1458 kdStats.push(instance.playerStats[personaId].overviewStats.kdRatio);
1459 }
1460
1461 teamPlot.push(skillStats, kdStats);
1462 teamNames[teamName + " skill"] = teamName + ' kd';
1463 }
1464
1465
1466 //instance.debug(teamPlot);
1467
1468 instance.data.charts.skillDistribution = c3.generate({
1469 bindto: '#as-charting-container',
1470 data : {
1471 xs: teamNames,
1472 columns: teamPlot,
1473 type: 'scatter'
1474 },
1475 axis: {
1476 x: {
1477 label: 'KD Ratio',
1478 min: 0,
1479 tick: {
1480 fit: false,
1481 centered: true
1482 }
1483 },
1484 y: {
1485 label: 'Skill',
1486 min: 0,
1487 },
1488 },
1489 tooltip: {
1490 format: {
1491 title: function (d) { return 'Data' + teamNames[0]; },
1492 }
1493 },
1494 })
1495 },
1496
1497 /**
1498 * Refreshes all data and redraws scoreboard
1499 *
1500 * @param instance Plugin object instance
1501 *
1502 */
1503 updateAll : function(instance){
1504
1505 var serverInfo = instance.getServerAttributes(); // Server attributes
1506
1507 instance.queryServerScoreboard(serverInfo, function(queryResult)
1508 {
1509 if (queryResult.gameMode == 32) {
1510 queryResult.teams.pop();
1511 queryResult.teams.pop();
1512 }
1513
1514 // UI Indicator
1515
1516 $("#as-live-indicator").css({ "background-color": "green" });
1517 setTimeout(function() {$("#as-live-indicator").css
1518 $("#as-live-indicator").css({ "background-color": "#78c753" })
1519 }, 250);
1520
1521 //instance.debug(queryResult);
1522
1523 //Store the result of the query
1524 instance.data.latestScoreboardData = queryResult;
1525
1526 //Cache player statistics
1527 instance.updatePlayerStats(instance, queryResult);
1528
1529 //Update tracker
1530 instance.updateTracker(instance, queryResult, function(instance)
1531 {
1532 instance.updateCharts(instance);
1533 });
1534
1535 //Render the scoreboard with this data
1536 if( !instance.data.scoreboard.animationActive && instance.data.mode == 'scoreboard' )
1537 {
1538 if( instance.data.drawMode == "player" ) {
1539 instance.scoreboard.drawPlayers(instance, queryResult);
1540 }
1541 else if ( instance.data.drawMode == "squad" ) {
1542 instance.scoreboard.drawSquads(instance, queryResult); // Draw the scoreboard using the query result
1543 } else if ( instance.data.drawMode == "role" ) {
1544 instance.scoreboard.drawVehicles(instance, queryResult);
1545 }
1546 else if ( instance.data.drawMode == "charts" ) {
1547 instance.updateTracker(instance, queryResult, function(instance)
1548 {
1549 instance.updateCharts(instance);
1550 });
1551 }
1552 }
1553
1554
1555 });
1556 },
1557
1558 /**
1559 * Redraws HTML without refreshing data sources
1560 *
1561 * @param instance Plugin object instance
1562 *
1563 */
1564 updateHTML : function(instance) {
1565
1566 if(!instance.data.scoreboard.animationActive && instance.data.mode == 'scoreboard')
1567 {
1568 if(instance.data.drawMode == "player")
1569 {
1570 instance.scoreboard.drawPlayers(instance, instance.data.latestScoreboardData);
1571 } else if ( instance.data.drawMode == "role" ) {
1572 instance.scoreboard.drawVehicles(instance, instance.data.latestScoreboardData);
1573 } else if (instance.data.drawMode == "squad") {
1574 instance.scoreboard.drawSquads(instance, instance.data.latestScoreboardData); // Draw the scoreboard using the query result
1575 }
1576 }
1577 },
1578
1579 /**
1580 * Updates the tracking object with data from the server
1581 *
1582 * @param instance Plugin object instance
1583 * @param serverData Data from the server
1584 * @param callback Callback function
1585 */
1586 updateTracker : function(instance, serverData, callback)
1587 {
1588 for(i = 0; i < serverData.teams.length; i++)
1589 {
1590 var team = serverData.teams[i];
1591 var teamId = team.status.teamId;
1592 var tickets = team.status.tickets;
1593
1594 if( instance.data.tracker.tickets.hasOwnProperty(teamId) )
1595 {
1596 instance.data.tracker.tickets[teamId].push(tickets);
1597 }
1598 else
1599 {
1600 instance.data.tracker.tickets[teamId] = [instance.lookup.teamName(serverData.gameMode, team.status), tickets];
1601 }
1602 }
1603 //instance.debug(instance.data.tracker);
1604 callback(instance);
1605 },
1606
1607 /**
1608 * Updates the charts
1609 *
1610 * @param instance Plugin object instance
1611 */
1612
1613 updateCharts : function(instance)
1614 {
1615 if( instance.data.ticketsChart ) {
1616 var chartData = [];
1617 console.log("Here is instance.data.tracker.tickets");
1618 console.log(instance.data.tracker.tickets);
1619 for (var teamId in instance.data.tracker.tickets) {
1620 var team = instance.data.tracker.tickets[teamId];
1621 var teamName = team[0];
1622 var records = team.length - 1;
1623
1624 if (records <= 50) {
1625 chartData.push(team);
1626 } else {
1627 chartData.push([teamName].concat(team.slice(-50)));
1628 }
1629
1630 }
1631
1632
1633 instance.data.ticketsChart.load({
1634 columns : chartData
1635 });
1636 }
1637 },
1638
1639
1640 /**
1641 * Returns an object containing the team data for the round and the total stats for each team
1642 *
1643 * @param instance Plugin Object Instance
1644 * @param scoreboardData JSON Object containing the information received from the gameserver
1645 */
1646 calculateTeamTotals : function(instance, scoreboardData) {
1647 var s = scoreboardData;
1648 console.log("Gimme commander feature pls");
1649 console.log(s);
1650 var teams = [];
1651 for (var i = 0; i < s.teams.length; i++) {
1652 var team = s.teams[i];
1653 var players = team.players;
1654 var status = team.status;
1655
1656 // Object holding team specific statistics
1657 var teamObj = {
1658 'players': [],
1659 'status': team.status,
1660 'totalPlayers': 0,
1661 'totalRank': 0,
1662 'totalKills': 0,
1663 'totalDeaths': 0,
1664 'totalScore': 0,
1665 'playersLoaded': 0,
1666 'totalPDisplayStat': 0,
1667 'globalStats': {
1668 'totalKd': 0,
1669 'totalResetKd': 0,
1670 'totalKills': 0,
1671 'totalDeaths': 0,
1672 'totalSkill': 0,
1673 'totalScorePerMinute': 0,
1674 'totalKillsPerMinute': 0
1675 },
1676 'commander': false
1677 };
1678
1679 for (var j = 0; j < team.players.length; j++) {
1680 var player = team.players[j];
1681 // Object holding player specific statistics
1682 var playerObj = {
1683 'id': player.personaId,
1684 'tag': player.tag,
1685 'name': player.name,
1686 'rank': player.rank,
1687 'role': player.role,
1688 'squad': player.squad,
1689 'score': player.score,
1690 'kills': player.kills,
1691 'deaths': player.deaths,
1692 'kd': (player.deaths == 0) ? player.kills : player.kills / player.deaths,
1693 'globalStats': {
1694 'kd': 0,
1695 'resetKd': 0,
1696 'kills': 0,
1697 'deaths': 0,
1698 'skill': 0,
1699 'scorePerMinute': 0,
1700 'killsPerMinute': 0,
1701 },
1702 'statsLoaded': false,
1703 'pDisplayStat': 0
1704 }
1705
1706 // This player is a commander
1707 if (player.role == 2) {
1708 teamObj.commander = playerObj;
1709 } else {
1710 // Increment the team's statistics
1711 teamObj.totalRank += player.rank;
1712 teamObj.totalKills += player.kills;
1713 teamObj.totalDeaths += player.deaths;
1714 teamObj.totalScore += player.score;
1715 teamObj.totalPlayers++;
1716
1717 // Check that the player's global statistics have been fetched
1718 if (instance.playerStats.hasOwnProperty(player.personaId)) {
1719 playerObj.statsLoaded = true;
1720
1721 var pStats = instance.playerStats[player.personaId];
1722
1723 // Here is where we choose which stats to add to our totals
1724
1725 playerObj.globalStats.kills = pStats.overviewStats.kills;
1726 playerObj.globalStats.deaths = pStats.overviewStats.deaths;
1727 playerObj.globalStats.kd = pStats.overviewStats.deaths == 0 ? pStats.overviewStats.kills : (pStats.overviewStats.kills / pStats.overviewStats.deaths);
1728 playerObj.globalStats.resetKd = pStats.overviewStats.kdRatio;
1729 playerObj.globalStats.skill = pStats.overviewStats.skill;
1730 playerObj.globalStats.scorePerMinute = pStats.overviewStats.scorePerMinute;
1731 playerObj.globalStats.killsPerMinute = pStats.overviewStats.killsPerMinute;
1732
1733 var displayStat = instance.storage('displayStat');
1734 // Calculate the values/totals for the custom statistic column
1735 // (REFACTOR) (This logic can be done on the display side)
1736 switch (instance.storage('displayStat')) {
1737 case 'kdRatio':
1738 if (instance.storage('useResetKdr')) {
1739 playerObj.pDisplayStat = pStats.overviewStats.kdRatio;
1740 } else { //Calculate the real global KD Ratio using the player's total deaths/total kills
1741 playerObj.pDisplayStat = pStats.overviewStats.deaths == 0 ? pStats.overviewStats.kills : (pStats.overviewStats.kills / pStats.overviewStats.deaths);
1742 }
1743 break;
1744 case 'strength':
1745 //Placeholder formula for player strength
1746 var playerStrength = (((pStats.overviewStats.killsPerMinute * pStats.overviewStats.kdRatio) * 10) + ((pStats.overviewStats.skill * pStats.overviewStats.scorePerMinute) / 10000)) * 10;
1747 playerObj.pDisplayStat = Math.floor(playerStrength);
1748 break;
1749 default:
1750 playerObj.pDisplayStat = pStats.overviewStats[displayStat];
1751 break;
1752 }
1753 teamObj.totalPDisplayStat += playerObj.pDisplayStat;
1754
1755 //Add player's global stats to the total
1756 teamObj.globalStats.totalKills += playerObj.globalStats.kills;
1757 teamObj.globalStats.totalDeaths += playerObj.globalStats.deaths;
1758 teamObj.globalStats.totalKd += playerObj.globalStats.kd;
1759 teamObj.globalStats.totalResetKd += playerObj.globalStats.resetKd;
1760 teamObj.globalStats.totalSkill += playerObj.globalStats.skill;
1761 teamObj.globalStats.totalScorePerMinute += playerObj.globalStats.scorePerMinute;
1762 teamObj.globalStats.totalKillsPerMinute += playerObj.globalStats.killsPerMinute;
1763
1764 teamObj.playersLoaded++;
1765
1766 }
1767 teamObj.players.push(playerObj);
1768 }
1769
1770 }
1771 teams.push(teamObj);
1772 }
1773 return teams;
1774 },
1775
1776 /**
1777 * Returns an object detailing the role speciailizations of the team. This method polls the additional vehicle stats page to ensure all vehicle data is present.
1778 *
1779 * @param instance Plugin instance
1780 * @param scoreboardData JSON Object cotnaining the information received from the gameserver
1781 * @param callback Function executed on success (once all statistics are loaded)
1782 *
1783 */
1784 calculateDetailedRoles : function(instance, scoreboardData, callback) {
1785 var s = scoreboardData;
1786 instance.updatePlayerVehicleStats(instance, scoreboardData, function(vehicleData) {
1787 // We now have complete vehicle data for the entire team (accessible also in instance.playerVehicleStats)
1788 console.log(vehicleData);
1789 var teamVehicleStats = [];
1790 for (var teamId in s.teams) {
1791 var team = s.teams[teamId];
1792 var teamVehicles = {};
1793 for (var playerId in team.players) {
1794 var player = team.players[playerId];
1795 if (!instance.playerVehicleStats.hasOwnProperty(player.personaId)) {
1796 continue;
1797 }
1798 var playerStats = instance.playerStats[player.personaId];
1799 var playerVehicleStats = instance.playerVehicleStats[player.personaId].mainVehicleStats;
1800
1801 for (var vehicleId in playerVehicleStats) {
1802 var vehicle = playerVehicleStats[vehicleId];
1803
1804 if (vehicle.kills == 0) {
1805 continue;
1806 }
1807
1808 if (!teamVehicles.hasOwnProperty(vehicle.category)) {
1809 teamVehicles[vehicle.category] = {
1810 players: {},
1811 totalKills: 0,
1812 totalTime: 0,
1813 guid: vehicle.guid
1814 }
1815 }
1816
1817 if (!teamVehicles[vehicle.category].players.hasOwnProperty(player.personaId)) {
1818 var vehicleStats = {
1819 personaId: player.personaId,
1820 name: playerStats.name,
1821 vehiclesDestroyed: vehicle.destroyXinY,
1822 roundStats: player,
1823 kills: vehicle.kills,
1824 time: vehicle.timeIn,
1825 stars: vehicle.serviceStars
1826 }
1827 teamVehicles[vehicle.category].players[player.personaId] = vehicleStats
1828 } else {
1829 teamVehicles[vehicle.category].players[player.personaId].vehiclesDestroyed += vehicle.destroyXinY;
1830 teamVehicles[vehicle.category].players[player.personaId].kills += vehicle.kills;
1831 teamVehicles[vehicle.category].players[player.personaId].time += vehicle.timeIn;
1832 }
1833
1834 teamVehicles[vehicle.category].totalKills += vehicle.kills;
1835 teamVehicles[vehicle.category].totalTime += vehicle.timeIn;
1836 }
1837
1838 }
1839 // Cull any vehicle categories with less than 100 kills
1840
1841 for (var teamVehicleName in teamVehicles) {
1842 var teamVehicle = teamVehicles[teamVehicleName];
1843 if (teamVehicle.totalKills < 100) {
1844 delete teamVehicles[teamVehicleName];
1845 }
1846 }
1847 teamVehicleStats.push(teamVehicles);
1848 }
1849
1850 callback(teamVehicleStats);
1851 });
1852
1853
1854 },
1855
1856 /**
1857 * Returns an object detailing the role specializations of the team. i.e. top players by vehicle type/weapon type
1858 *
1859 * @param instance Plugin Object Instance
1860 * @param scoreboardData JSON Object cotnaining the information received from the gameserver
1861 */
1862 calculateRoles : function(instance, scoreboardData) {
1863
1864 var s = scoreboardData;
1865 var vehicleData = [];
1866 //instance.debug("Starting roles calc");
1867
1868 for (teamId in s.teams) {
1869 var team = s.teams[teamId];
1870 var hasCommander = false;
1871 var teamVehicles = {};
1872
1873 for (playerId in team.players) {
1874 var player = team.players[playerId];
1875 //Check that the player's stattistics are loaded
1876 if (!instance.playerStats.hasOwnProperty(player.personaId)) {
1877 continue;
1878 }
1879 var pStats = instance.playerStats[player.personaId];
1880 var playerVehicles = pStats.topVehicles;
1881 //Iterate the player's top vehicles and add them to the team's total
1882 for (var i = 0; i < playerVehicles.length; i++) {
1883 var vehicle = playerVehicles[i];
1884 //Limits the kills at 100 to prevent clutter
1885 if (vehicle.kills < 100) {
1886 continue;
1887 }
1888
1889 //If first instance of this vehicle category, create it
1890 if (!teamVehicles.hasOwnProperty(vehicle.category)) {
1891 teamVehicles[vehicle.category] = {
1892 players : {},
1893 totalKills : 0,
1894 totalTime : 0,
1895 guid : vehicle.guid
1896 };
1897 }
1898
1899 //Add player's stats to the total
1900 if (teamVehicles[vehicle.category].players.hasOwnProperty(player.personaId)) {
1901 var playerVehicleStats = teamVehicles[vehicle.category].players[player.personaId];
1902 playerVehicleStats.kills += vehicle.kills;
1903 playerVehicleStats.timeIn += vehicle.timeIn;
1904 playerVehicleStats.vehiclesDestroyed += vehicle.destroyXinY;
1905 } else {
1906 teamVehicles[vehicle.category].players[player.personaId] = {
1907 roundStats : player,
1908 kills : vehicle.kills,
1909 timeIn : vehicle.timeIn,
1910 vehiclesDestroyed : vehicle.destroyXinY
1911 };
1912 }
1913 }
1914
1915 }
1916 vehicleData.push(teamVehicles);
1917 }
1918 console.log(vehicleData);
1919 return vehicleData;
1920 },
1921
1922 //Updates the round header
1923 updateRoundHeader : function(instance, s)
1924 {
1925
1926 var totalPlayers = 0;
1927 console.log("updating header");
1928 console.log(s);
1929 for (var i = 0; i < s.teams.length; i++)
1930 {
1931 var team = s.teams[i];
1932 totalPlayers += team.players.length;
1933 }
1934 $("#as-server-players").html(totalPlayers + '/' + s.maxPlayers + (s.queueingPlayers > 0 ? '[' + s.queueingPlayers + ']' : ''));
1935 },
1936 /**
1937 * Creates HTML detailing expanded player statistics available in their overview stats.
1938 *
1939 * @param instance Plugin instance
1940 * @param personaId The Persona ID of the player
1941 * @param displayDefault If the HTML should be hidden by default
1942 */
1943 createAdvancedPlayerView : function(instance, personaId, displayDefault) {
1944 //If statistics have not been retrieved just return a basic loader
1945 if (!instance.playerStats.hasOwnProperty(personaId)) {
1946 var html = '<div class="loader small"></div></div></td></tr>'
1947
1948 return html;
1949 }
1950
1951 var playerStats = instance.playerStats[personaId];
1952
1953 var playerName = playerStats.name;
1954
1955 if (playerStats.activeEmblem) {
1956 var emblemPath = playerStats.activeEmblem.cdnUrl;
1957 emblemPath = emblemPath.replace('[SIZE]', '128');
1958 emblemPath = emblemPath.replace('[FORMAT]', 'png');
1959 }
1960
1961
1962 var html = '<tr class="as-scoreboard-advanced-stats-row"><td colspan="7">' +
1963 '<div class="as-advanced-player-view"' + (displayDefault ? '' : ' style="display:none"') + '>';
1964
1965 //Player name, gravatar, and emblem]
1966 html += '<div class="as-ao-header"><span id="as-ao-name">' + playerName + (playerStats.activeEmblem ? '</span><img class="as-ao-emblem" src="' + emblemPath + '"></img>' : '') +
1967 '<button id="as-ao-join" persona-id="' + personaId + '" class="as-ao-btn btn btn-primary">Join Player</button>' +
1968 '<button id="as-ao-radar" persona-id="' + personaId + '" class="as-ao-btn btn btn-primary">Add to Radar</button>' +
1969 '</div>';
1970
1971 //Table detailing the player's basic statistics, kills/deaths/etc
1972
1973 var secondsPlayed = playerStats.overviewStats.timePlayed;
1974 var minutesPlayed = secondsPlayed/60;
1975 var hoursPlayed = minutesPlayed/60;
1976
1977 var timePlayed = Math.floor(hoursPlayed) + 'H ' + Math.round((hoursPlayed - Math.floor(hoursPlayed)) * 60) + 'M';
1978
1979 html += '<table class="as-stats-overview">' +
1980 '<tr><th>Kills</th><th>Deaths</th><th>Skill</th><th>Accuracy</th></tr>' +
1981 '<tr>' +
1982 '<td>' + instance.commaFormat(playerStats.overviewStats.kills) + '</td>' +
1983 '<td>' + instance.commaFormat(playerStats.overviewStats.deaths) + '</td>' +
1984 '<td>' + playerStats.overviewStats.skill + '</td>' +
1985 '<td>' + playerStats.overviewStats.accuracy.toFixed(2) + '% </td>' +
1986 '</tr>' +
1987 '<tr><th>K/D</th><th>KPM</th><th>SPM</th><th>Time</th></tr>' +
1988 '<tr>' +
1989 '<td>' + (playerStats.overviewStats.kills/playerStats.overviewStats.deaths).toFixed(2) + '</td>' +
1990 '<td>' + playerStats.overviewStats.killsPerMinute + '</td>' +
1991 '<td>' + playerStats.overviewStats.scorePerMinute + '</td>' +
1992 '<td>' + timePlayed + '</td>' +
1993 '</tr>' +
1994 '</table>';
1995
1996 //Table detailing the player's top three vehicles
1997 html += '<table class="as-role-stats as-vehicle-stats">' +
1998 '<tr><th>Vehicle</th><th>Kills</th><th>KPM</th></tr>';
1999 for (var vehicleId in playerStats.topVehicles) {
2000 var vehicle = playerStats.topVehicles[vehicleId];
2001 var vehicleName = vehicle.slug;
2002 var vehicleKpm = vehicle.timeIn > 0 ? (vehicle.kills/(vehicle.timeIn/60)).toFixed(2) : '--';
2003 var vehicleImageClass = instance.battlelog.getVehicleImage(vehicle.guid, 0);
2004 html += '<tr><td>' + vehicleName.toUpperCase() + '<div class="as-table-role vehicle xsmall ' + vehicleImageClass + ' image"></div></td><td>' + instance.commaFormat(vehicle.kills) + '</td><td>' + vehicleKpm + '</tr>';
2005 }
2006 html += '</table>';
2007
2008 //The same for weapons
2009 html += '<table class="as-role-stats as-weapon-stats">' +
2010 '<tr><th>Weapon</th><th>Kills</th><th>Accuracy</th></tr>';
2011 for (var weaponId in playerStats.topWeapons) {
2012 var weapon = playerStats.topWeapons[weaponId];
2013 var weaponName = weapon.slug;
2014 var weaponImageClass = instance.battlelog.getWeaponImage(weapon.guid, 0);
2015 html += '<tr><td>' + weaponName.toUpperCase() + '<div class="as-table-role weapon xsmall ' + weaponImageClass + ' image"></div></td><td>' + instance.commaFormat(weapon.kills) + '</td><td>' + ((weapon.shotsHit / weapon.shotsFired) * 100).toFixed(2) + '%</tr>';
2016
2017 }
2018 html += '</table>'
2019 html += '</div></td></tr>';
2020
2021 return html;
2022
2023 //Top kits
2024 var topKits = '<table class="table as-advanced-overview-top-kits">' +
2025 '<tr><th><div class="kit-icon xsmall kit-1"></div></th><th><div class="kit-icon xsmall kit-2"></div></th><th><div class="kit-icon xsmall kit-32"></div></th><th><div class="kit-icon xsmall kit-8"></div></th></tr>' +
2026 '<tr><td>' + playerStats.overviewStats.serviceStars["1"] + '</td><td>' + playerStats.overviewStats.serviceStars["2"] + '</td><td>' + playerStats.overviewStats.serviceStars["32"] + '</td><td>' + playerStats.overviewStats.serviceStars["8"] + '</td>' +
2027 '</table>';
2028
2029
2030 //Stats overviewhttp://battlelog.battlefield.com/bf4/platoons/view/3353238464465530114/
2031
2032 console.log(playerStats);
2033 //Viewport
2034 //html += '<div class="as-ao-view">';
2035
2036
2037
2038 html += topKits;
2039
2040 html += '</div>'
2041
2042 //Overview Stats
2043
2044
2045 //Overview Table
2046
2047 html += '<div class="as-advanced-overview-top-roles">' +
2048 '<table class="table as-advanced-overview-top-vehicles">' +
2049 '<tr><th colspan="2">Vehicle</th><th>Kills</th><th>KPM</th></tr>';
2050
2051
2052 //Top vehicles
2053 $.each(playerStats.topVehicles, function (id, vehicle) {
2054
2055 //Get vehicle name for image
2056
2057 var vehicleDisplay = window.items.game_data.compact.vehicles[vehicle.guid];
2058 vehicleDisplay = vehicleDisplay.see[0];
2059
2060 var lineartSlug = window.items.game_data.compact.vehicles[vehicleDisplay];
2061 lineartSlug = lineartSlug.imageConfig.slug;
2062 console.log(vehicle);
2063
2064 html += '<tr><td>' + vehicle.slug.toUpperCase() + '</td><td><div class="vehicle xsmall ' + lineartSlug + ' image"></div></td><td>' + instance.commaFormat(vehicle.kills) + '</td><td>' + (vehicle.kills/(vehicle.timeIn/60)).toFixed(2) + '</td></tr>'
2065 });
2066
2067 html += '</table><table class="table as-advanced-overview-top-weapons">' +
2068 '<tr><th colspan="2">Weapon</th><th>Kills</th><th>Accuracy</th></tr>';
2069
2070 $.each(playerStats.topWeapons, function(id, weapon){
2071
2072 //Get vehicle name for image
2073
2074 var weaponDisplay = window.items.game_data.compact.weapons[weapon.guid];
2075 weaponDisplay = weaponDisplay.see[0];
2076
2077 var lineartSlug = window.items.game_data.compact.weapons[weaponDisplay];
2078 lineartSlug = lineartSlug.imageConfig.slug;
2079
2080 html += '<tr><td>' + weapon.slug.toUpperCase() + '</td><td><div class="weapon xsmall ' + lineartSlug + ' image"></div></td><td>' + instance.commaFormat(weapon.kills) + '</td><td>' + ((weapon.shotsHit / weapon.shotsFired) * 100).toFixed(2) + '%</td></tr>'
2081
2082 });
2083
2084 html += '</table></div>';
2085
2086
2087 html += '</div>'
2088
2089 //End of as-ao-stats
2090
2091
2092 //Get Dogtag Information
2093
2094 var dogtagBasic = playerStats.dogTagBasic.imageConfig.slug;
2095 var dogtagAdvanced = playerStats.dogTagAdvanced.imageConfig.slug;
2096
2097 html += '</div>';
2098
2099 return html;
2100
2101 },
2102
2103 //Draw the advanced statistics overview
2104
2105 drawAdvancedStats : function (instance, personaId) {
2106
2107 //The player's stats
2108 var player = instance.playerStats[personaId];
2109
2110
2111 var playerVehicleStats = {};
2112 instance.loadPlayerVehicleStats(personaId, function (data) {
2113 playerVehicleStats = data.data;
2114 });
2115
2116 var playerWeaponStats = {};
2117 instance.loadPlayerWeaponStats(personaId, function (data) {
2118 playerWeaponStats = data.data;
2119
2120 $("#as-container").fadeOut('slow', function () {
2121 var html = '<div id="as-stats-container">' + '<button class="as-stats-close">Back</button>';
2122
2123 html += '<h3>' + player.name + '</h3>';
2124
2125 html += '<div class="as-stats-selectors"><button class="btn as-stats-select-weapons">Weapons</button><button class="btn as-stats-select-vehicles">Vehicles</button></div>';
2126
2127 //Container to list possible cheating flags
2128
2129 html += '<div class="as-stats-overview">'
2130
2131 html += '<div class="as-stats-weapons">' +
2132 '<table class="table as-stats-weapons-table">';
2133
2134 html += '<tr><th>Weapon</th><th>Kills</th><th>Accuracy</th><th>HSKR</th><th>KPM</th></tr>';
2135
2136 for (var i = 0; i < playerWeaponStats.mainWeaponStats.length; i++) {
2137
2138 if (weapon.kills > 100) {
2139 var weaponDisplay = window.items.game_data.compact.weapons[weapon.guid];
2140 weaponDisplay = weaponDisplay.see[0];
2141 var lineartSlug = window.items.game_data.compact.weapons[weaponDisplay];
2142 lineartSlug = lineartSlug.imageConfig.slug;
2143
2144 var w_accuracy = 0;
2145 if (weapon.shotsFired > 0 && weapon.shotsHit > 0) {
2146 w_accuracy = (weapon.shotsHit / weapon.shotsFired) * 100;
2147 }
2148
2149 var w_kpm = 0;
2150 if (weapon.kills > 0 && weapon.timeEquipped > 0) {
2151 w_kpm = (weapon.kills / (weapon.timeEquipped / 60));
2152 }
2153
2154 var w_hskr = 0;
2155 if (weapon.kills > 0 && weapon.headshots > 0) {
2156 w_hskr = ((weapon.headshots / weapon.kills) * 100);
2157 }
2158
2159 html += '<tr class="as-stats-weapon"><td><div class="weapon xsmall ' + lineartSlug + ' image"></div><div>' + weapon.slug.toUpperCase() + '</div></td><td>' + weapon.kills + '</td><td>' + w_accuracy.toFixed(2) + '%</td><td>' + w_hskr.toFixed(2) + '%</td><td>' + w_kpm.toFixed(2) + '</td></tr>';
2160 console.log(weapon.slug);
2161 console.log(weapon);
2162 }
2163 }
2164 html += '</table></div><div class="as-stats-vehicles" style="display: none;"><table class="table as-stats-vehicles-table">';
2165 html += '<tr><th>Vehicle</th><th>Kills</th><th>Vehicles Destroyed</th><th>KPM</th><th>Time</th></tr>';
2166 console.log(playerVehicleStats);
2167
2168 for (var i = 0; i < playerVehicleStats.mainVehicleStats.length; i++) {
2169 var vehicle = playerVehicleStats.mainVehicleStats[i];
2170
2171 if (vehicle.kills > 100) {
2172 var vehicleDisplay = window.items.game_data.compact.vehicles[vehicle.guid];
2173 vehicleDisplay = vehicleDisplay.see[0];
2174 var lineartSlug = window.items.game_data.compact.vehicles[vehicleDisplay];
2175 lineartSlug = lineartSlug.imageConfig.slug;
2176
2177 var v_vehiclesDestroyed = 0;
2178 if (vehicle.destroyXinY > 0) {
2179 v_vehiclesDestroyed = vehicle.destroyXinY;
2180 }
2181
2182 var v_kpm = 0;
2183 if (vehicle.timeIn > 0 && vehicle.kills > 0)
2184 {
2185 v_kpm = (vehicle.kills / (vehicle.timeIn / 60));
2186 }
2187
2188 var v_time = (vehicle.timeIn / 60).toFixed(2);
2189
2190
2191
2192 html += '<tr class="as-stats-vehicle"><td><div class="vehicle xsmall ' + lineartSlug + ' image"></div><div>' + vehicle.slug.toUpperCase() + '</div></td><td>' + vehicle.kills + '</td><td>' + v_vehiclesDestroyed + '</td><td>' + v_kpm.toFixed(2) + '%</td><td>' + v_time + '</td></tr>';
2193
2194 }
2195 }
2196
2197 html += '</table></div></div>';
2198
2199 $("#serverbrowser-page").after(html);
2200 console.log(playerWeaponStats);
2201 console.log(playerVehicleStats);
2202 });
2203
2204 });
2205
2206
2207
2208 },
2209
2210 /*
2211 * Builds HTML for the runtime options
2212 *
2213 * @param instance Plugin instance
2214 *
2215 */
2216 getRuntimeOptions : function(instance) {
2217 var selection = {
2218 'kdRatio': 'KD (G)',
2219 'strength': 'Strength',
2220 'skill': 'Skill'
2221 }
2222
2223 var html = '<select id="as-select-stat">'
2224
2225 for (var stat in selection) {
2226 var value = stat;
2227 var name = selection[stat];
2228
2229 html += '<option value="' + value + '">' + name + '</option>';
2230 }
2231
2232 html += '</select>';
2233
2234 return html;
2235 },
2236
2237 drawSettings : function(instance) {
2238 var html = '<div id="as-settings-container"><header class="as-settings-header"><h1>' + instance.t("settings-title") + '</h1></header>' +
2239 '<div id="as-settings-options">';
2240
2241 //Get the settings
2242 console.log(instance.storage('hilightingEnabled'));
2243 var hilightingEnabled = instance.storage('hilightingEnabled') ? (instance.storage('hilightingEnabled') == true ? true : false) : false;
2244
2245
2246 /** Check box for the live update **/
2247
2248 html += '<table class="as-settings-table">' +
2249 '<tr><td class="as-settings-table-header" colspan="3">General</td></tr>' +
2250 '<tr><th>Live Scoreboard</th><td>' +
2251 '<div class="switch-container pull-right clearfix">' +
2252 '<div class="switch pull-left">' +
2253 '<input type="checkbox" id="as-enable-live" name="as-enable-live" value="' + instance.storage('liveEnabled').toString() + '" ' + (instance.storage('liveEnabled') == true ? 'checked' : '') + '>' +
2254 '<div class="handle"></div>' +
2255 '</div>' +
2256 '</div></td>' +
2257 '<td class="option-description">Determines whether or not the scoreboard automatically updates as the game progresses</td>' +
2258 '</tr>' +
2259 '<tr><th>Hilighting</th><td>' +
2260 '<div class="switch-container pull-right clearfix">' +
2261 '<div class="switch pull-left">' +
2262 '<input type="checkbox" id="as-enable-hilighting" name="as-enable-hilighting" value="' + hilightingEnabled.toString() + '" ' + (hilightingEnabled == true ? 'checked' : '') + '>' +
2263 '<div class="handle"></div>' +
2264 '</div>' +
2265 '</div></td>' +
2266 '<td class="option-description">Enables hilighting based off the strength of player statistics</td>' +
2267 '</tr>' +
2268 '<tr><th>Polling Rate (ms)</th><td><input id="as-polling-rate" type="number" name="as-polling-rate" value="5000"></td>' +
2269 '<td class="option-description">The frequency the scoreboard queries the gameserver for information. 5000ms is the default</td>' +
2270 '</tr>' +
2271 '<tr><th>Debug Information</h><td>' +
2272 '<div class="switch-container pull-right clearfix">' +
2273 '<div class="switch pull-left">' +
2274 '<input type="checkbox" id="as-enable-debugging" name="as-enable-debugging" value="' + instance.storage('debuggingEnabled').toString() + '" ' + (instance.storage('debuggingEnabled') == true ? 'checked' : '') + '>' +
2275 '<div class="handle"></div>' +
2276 '</div>' +
2277 '</div></td>' +
2278 '<td class="option-description">Enable debugging window</td>' +
2279 '</tr>' +
2280 '<tr><td class="as-settings-table-header" colspan="3">Statistics</td></tr>' +
2281 '<tr><th>Detailed Vehicle Overview</th><td>' +
2282 '<div class="switch-container pull-right clearfix">' +
2283 '<div class="switch pull-left">' +
2284 '<input type="checkbox" id="as-detailed-vehicles" name="as-detailed-vehicles" value="' + instance.storage('detailedVehicles').toString() + '" ' + (instance.storage('detailedVehicles') == true ? 'checked' : '') + '>' +
2285 '<div class="handle"></div>' +
2286 '</div>' +
2287 '</div></td>' +
2288 '<td class="option-description">Gives a more complete view of vehicle stats. <br> Warning: Enabling this option will increase the number of requests sent to Battlelog</td>' +
2289 '</tr>' +
2290 '<tr><th>Vehicle Kill Threshold</th>' +
2291 '<td><input type="number" name="as-vehicle-threshold" id="as-vehicle-threshold" value="' + instance.storage('vehicleThreshold').toString() + '" /></td>' +
2292 '<td class="option-description">The number of kills a player must have in a given vehicle to appear in the vehicle overview.</td>' +
2293 '</tr>';
2294
2295
2296 html += '</table>';
2297
2298 /** Input field for polling rate **/
2299
2300 /** Check box for hilighting **/
2301
2302
2303 $('#as-scoreboard-container').html(html);
2304 },
2305 /**
2306 * Draws settings HTML
2307 *
2308 */
2309 renderSettings : function(instance){
2310 var html = '';
2311 html += '<div id="advs_scoreboard_settings">';
2312 html += '<h4 class="advs_title">' + instance.t("settings-title") + '</h4>';
2313
2314 /** Check box for live update **/
2315
2316 html += '<div class="as-settings-option"><label class="as-settings-label" for="as-enable-live">Live Scoreboard</label>';
2317
2318 html += '<input id="as-enable-live" type="checkbox" name="as-enable-live" value="'+ instance.storage('liveEnabled').toString() +'" '+ (instance.storage('liveEnabled') == true ? 'checked' : '') + '>';
2319
2320 html += '</div>';
2321
2322 /** Check box for hilighting **/
2323
2324 html += '<div class="as-settings-option"><label class="as-settings-label" for="as-enable-hilighting">Enable Hilighting</label>';
2325
2326 html += '<input id="as-enable-hilighting" type="checkbox" name="as-enable-hilighting" value="'+ instance.storage('hilightingEnabled').toString() +'" '+ (instance.storage('hilightingEnabled') == true ? 'checked' : '') + '>';
2327
2328 html += '</div>';
2329
2330 /** Check box for hilighting friends **/
2331
2332 html += '<div class="as-settings-option"><label class="as-settings-label" for="as-enable-friend-hilighting">Hilight Friends</label>';
2333
2334 html += '<input id="as-enable-friend-hilighting" type="checkbox" name="as-enable-friend-hilighting" value="'+ instance.storage('hilightFriends').toString() +'" '+ (instance.storage('hilightFriends') == true ? 'checked' : '') + '>';
2335
2336 html += '</div>';
2337
2338
2339 /** **/
2340
2341 html += '<div class="as-about" style="font-size: 10px;"><p>Advanced Scoreboard 0.1.1 Beta</p><p>Developed by Cr1N</p></div>';
2342
2343 html += '</div>';
2344
2345 $("#content").append(html);
2346 },
2347
2348 drawRoundInfo : function(instance) {
2349 var serverHeader = '<div id="as-scoreboard-roundinfo">';
2350
2351 serverHeader += '<div id="as-scoreboard-mapinfo"></div>';
2352
2353 serverHeader += '<div id="as-scoreboard-round-properties">' +
2354 '<div><span>Players : </span><span id="as-server-players"></span></div>' +
2355 '<div><span>Time : </span><span id="as-server-time-remaining"></span></div>' +
2356 '</div>';
2357
2358
2359 serverHeader += '<div id="as-scoreboard-options">' +
2360 '<div class="as-sort-option"><label class="as-settings-label" for="as-select-display-stat">Show: </label><select id="as-select-display-stat">';
2361
2362 var existingDisplayStat = instance.storage('displayStat');
2363 var customStats = ['skill', 'kdRatio', 'kills', 'deaths', 'strength'];
2364
2365 for(var i = 0; i < customStats.length; i++)
2366 {
2367 if(customStats[i] !== existingDisplayStat) {
2368 serverHeader += '<option value="' + customStats[i] + '">' + instance.lookup.displayStat(customStats[i]) + '</option>';
2369 } else {
2370 serverHeader += '<option value="' + customStats[i] + '" selected>' + instance.lookup.displayStat(customStats[i]) + '</option>';
2371 }
2372 }
2373 serverHeader += '</select></div></div>';
2374
2375
2376
2377
2378 return serverHeader;
2379 },
2380
2381 drawSelectors : function(instance) {
2382
2383 var selectors = [
2384 { htmlId: 'as-show-players', htmlText: 'Show Players', drawMode: 'player' },
2385 { htmlId: 'as-show-squads', htmlText: 'Show Squads', drawMode: 'squad' },
2386 { htmlId: 'as-show-roles', htmlText: 'Show Vehicles', drawMode: 'role' },
2387 { htmlId: 'as-show-charts', htmlText: 'Show Charts', drawMode: 'charts' },
2388 ];
2389
2390 selectorHtml = '<div id="as-scoreboard-selectors">';
2391 for (var i = 0; i < selectors.length; i++) {
2392 var selector = selectors[i];
2393 selectorHtml += '<button class="btn view-selector ' + (instance.data.drawMode === selector.drawMode ? 'btn-primary' : '') + '" id="' + selector.htmlId + '">' + selector.htmlText + '</button>';
2394 }
2395
2396 selectorHtml += '<button class="btn view-selector" id="as-settings">Settings</button>';
2397 selectorHtml += '</div>';
2398
2399 return selectorHtml;
2400
2401 },
2402
2403 /**
2404 * Simple debugging
2405 *
2406 * @param instance Plugin instance
2407 * @param type Type of debug information, message, error, information
2408 * @param msg The debug message or array to display
2409 */
2410 debug : function(instance, type, msg) {
2411 //Only output if debugging is expressly enabled
2412 if (instance.storage('debuggingEnabled')) {
2413 //Ensure debugging window is present
2414 var debugWindow = $("#as-debug-output");
2415 console.log("Debugging event fired");
2416 console.log(debugWindow);
2417
2418 //Get time
2419
2420 var currentDate = new Date();
2421 var currentTime = ('0'+currentDate.getHours()).slice(-2) + ':' + ('0'+currentDate.getMinutes()).slice(-2) + ':' + ('0'+currentDate.getSeconds()).slice(-2);
2422
2423 switch (type) {
2424 case 0:
2425 debugWindow.append('<p class="as-debug-information"><span class="as-debug-time">[' + currentTime + ']</span><span>' + msg + '</span></p>');
2426 console.log('[AdvancedScoreboard][Info] - ' + msg);
2427 break;
2428 case 1:
2429 debugWindow.append('<p class="as-debug-success"><span class="as-debug-time">[' + currentTime + ']</span><span>' + msg + '</span></p>');
2430 console.log('[AdvancedScoreboard][Success] - ' + msg, 'color: green');
2431 break;
2432 case 2:
2433 debugWindow.append('<p class="as-debug-error"><span class="as-debug-time">[' + currentTime + ']</span><span>' + msg + '</span></p>');
2434 console.log('[AdvancedScoreboard][Error] - ' + msg, 'color: red');
2435 break;
2436 default:
2437 debugWindow.append('<p class="as-debug-information"><span class="as-debug-time">[' + currentTime + ']</span><span>' + msg + '</span></p>');
2438 console.log('[AdvancedScoreboard][Success] - ' + msg, 'color: red');
2439 }
2440 }
2441 },
2442
2443
2444 /**
2445 * Returns JSON object containing server attributes extracted from the DOM
2446 *
2447 * @return JSON object containing server data
2448 */
2449 getServerAttributes : function() {
2450
2451 var $joinMpServerButton = $("#server-page-join-buttons");
2452
2453 var server = {
2454 ip: $joinMpServerButton.data("ip"),
2455 gameId: $joinMpServerButton.attr("data-gameid"),
2456 port: $joinMpServerButton.data("port"),
2457 game: $joinMpServerButton.data("game"),
2458 guid: $joinMpServerButton.data("guid")
2459 };
2460
2461 return server;
2462 },
2463
2464 /**
2465 * Returns scoreboard data from the game server
2466 *
2467 * @callback callback Callback function
2468 * @param serverInfo Server information in JSON format
2469 *
2470 */
2471 queryServerScoreboard : function(serverInfo, callback) {
2472
2473 launcher.queryServer(serverInfo, function(queryInfo) {
2474 if (!queryInfo) {
2475 instance.debug(instance, 2, 'Could not obtain information from the server')
2476 } else {
2477 if(queryInfo.status == "OK") {
2478 callback(queryInfo.result)
2479 } else {
2480 $("#as-scoreboard-container").html('<div class="as-scoreboard-roundfinished">Round is over. Waiting for next round to start...</div>');
2481 console.log("Round has not started");
2482 }
2483 }
2484 });
2485
2486
2487 },
2488 /**
2489 *
2490 *
2491 */
2492
2493 updatePlayerVehicleStats: function(instance, scoreboardData, callback) {
2494 var s = scoreboardData;
2495 var toLoad = []
2496
2497 for (teamId in s.teams) {
2498 var team = s.teams[teamId];
2499 for (playerId in team.players) {
2500 var player = team.players[playerId];
2501 if (!instance.playerVehicleStats.hasOwnProperty(player.personaId)) {
2502 toLoad.push(player.personaId);
2503 }
2504 }
2505 }
2506
2507 if (toLoad.length == 0) {
2508 callback(instance.playerVehicleStats);
2509 return;
2510 }
2511
2512 var playersLoaded = 0;
2513 // Get the statistics for the individual players
2514 for (var i = 0; i < toLoad.length; i++) {
2515 var personaId = toLoad[i];
2516 instance.loadPlayerVehicleStats(personaId, function(data) {
2517 var vehicleStats = data.data;
2518 instance.playerVehicleStats[vehicleStats.personaId] = vehicleStats;
2519 playersLoaded += 1;
2520 if (playersLoaded == toLoad.length) {
2521 callback(instance.playerVehicleStats);
2522 }
2523 })
2524 }
2525
2526 },
2527
2528 /**
2529 * Checks for players who don't't have their statistics cached and fetches them
2530 *
2531 * @param scoreboardData Scoreboard data from the game server
2532 * @param instance Plugin instance
2533 *
2534 */
2535 updatePlayerStats: function (instance, scoreboardData) {
2536 var updatePlayers = [];
2537 var toLoad = 0;
2538 var loaded = 0;
2539 //For each team
2540
2541 $.each(scoreboardData.teams, function (teamID, team) {
2542 $.each(team.players, function (playerId, player) {
2543 if (!instance.playerStats.hasOwnProperty(player.personaId)) {
2544 toLoad++;
2545 }
2546 });
2547 });
2548 $.each(scoreboardData.teams, function (teamID, team)
2549 {
2550 //For each player in the team
2551 $.each(team.players, function (playerID, player)
2552 {
2553 //Only load the statistics if they are not already present in the database
2554 if (!instance.playerStats.hasOwnProperty(player.personaId)) {
2555 var playerName = player.tag ? '[' + player.tag + ']' + player.name : player.name;
2556 instance.loadPlayerStats(player.personaId, playerName, function (overviewStats, playerName) {
2557 if (overviewStats.data.statsTemplate == 'profile.warsawoverviewpopulate') {
2558 instance.playerStats[player.personaId] = overviewStats.data;
2559 instance.playerStats[player.personaId]["name"] = playerName;
2560 loaded++;
2561 if (loaded == toLoad) {
2562 instance.updateHTML(instance);
2563 }
2564 } else { //Stats are down, hacky rework for now
2565 console.log(overviewStats);
2566 toLoad--;
2567 }
2568 })
2569 }
2570 });
2571
2572 })
2573 },
2574
2575
2576 /**
2577 * Return player status
2578 *
2579 * @callback callback Callback function
2580 * @param personaId Persona ID of the player to be queried
2581 *
2582 */
2583 loadPlayerStats: function (personaId, playerName, callback) {
2584 $.ajax({
2585 url: "https://battlelog.battlefield.com/bf4/warsawoverviewpopulate/" + personaId + "/1/",
2586 type: 'GET',
2587 async: true,
2588 cache: false,
2589 timeout: 30000,
2590 success: function(data) {
2591 callback(data, playerName);
2592 }
2593 });
2594 },
2595
2596 /**
2597 * Returns a players weapon stats
2598 *
2599 * @callback callback Callback function
2600 * @param personaId Persona ID of the player to fetch
2601 */
2602 loadPlayerWeaponStats: function (personaId, callback) {
2603 $.ajax({
2604 url: "https://battlelog.battlefield.com/bf4/warsawWeaponsPopulateStats/" + personaId + "/1/stats/",
2605 type: 'GET',
2606 async: true,
2607 cache: false,
2608 timeout: 30000,
2609 success: function(data) {
2610 callback(data);
2611 }
2612 });
2613 },
2614
2615 /**
2616 * Returns a players vehicle stats stats
2617 *
2618 * @callback callback Callback function
2619 * @param personaId Persona ID of the player to fetch
2620 */
2621 loadPlayerVehicleStats : function(personaId, callback) {
2622 $.ajax({
2623 url: "https://battlelog.battlefield.com/bf4/warsawvehiclesPopulateStats/" + personaId + "/1/stats/",
2624 type: 'GET',
2625 async: true,
2626 cache: false,
2627 timeout: 30000,
2628 success: function (data) {
2629 callback(data);
2630 }
2631 });
2632 },
2633
2634
2635
2636
2637 lookup : {
2638 displayStat : function(displayStatValue){
2639
2640 var displayStatsLookup = {
2641 'skill' : 'Skill',
2642 'kdRatio' : 'K/D (G)',
2643 'kills' : 'Kills',
2644 'deaths': 'Deaths',
2645 'strength' : 'Strength'
2646 };
2647
2648 return displayStatsLookup[displayStatValue];
2649
2650 },
2651
2652 teamName : function(gameMode, status) {
2653
2654 if (gameMode == 2) {
2655 if (status.teamType == 'attackers') {
2656 return 'ATK';
2657 } else {
2658 return 'DEF';
2659 }
2660 } else {
2661 var factions = ["US", "RU", "CN"];
2662
2663 return factions[status.faction];
2664 }
2665
2666 },
2667
2668 teamType : function(teamId) {
2669
2670 if(teamId == 1) {
2671 var type = 'home'
2672 } else {
2673 var type = 'away';
2674 }
2675
2676 return type;
2677 },
2678
2679 teamFlag : function(teamName)
2680 {
2681 var urlPrefix = "https://eaassets-a.akamaihd.net/bl-cdn/cdnprefix/2e8fa20e7dba3f4aecb727fc8dcb902f1efef569b/public/common/flags/";
2682 if (teamName == "US" || teamName == "RU" || teamName == "CN") {
2683 return urlPrefix + teamName.toLowerCase() + '.gif';
2684 } else {
2685 return false
2686 }
2687 },
2688
2689 squadName : function(squadId) {
2690 var squads = ["No Squad", "Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot", "Golf", "Hotel", "India", "Juliett", "Kilo", "Lima", "Mike", "Oscar", "Lima"];
2691 return squads[squadId];
2692 },
2693 gameMode: function (mode)
2694 {
2695 var gameModes = { 2: "Rush", 64: "Conquest Large" }
2696 if( gameModes.hasOwnProperty(mode) ) {
2697 return gameModes[mode];
2698 } else {
2699 return "Unknown Gamemode";
2700 }
2701 },
2702 /*
2703 * Match the players stat to a hilighting class based on a defined statistic
2704 *
2705 */
2706 hilightingClass : function (displayStat, pDisplayStat) {
2707 if (displayStat == 'kdRatio') {
2708 if (pDisplayStat < 1) {
2709 hilightingType = 'low';
2710 } else if (pDisplayStat < 2) {
2711 hilightingType = 'average';
2712 } else if (pDisplayStat < 3) {
2713 hilightingType = 'good';
2714 } else if (pDisplayStat < 4) {
2715 hilightingType = 'high';
2716 } else if (pDisplayStat < 5) {
2717 hilightingType = 'v-high';
2718 } else if (pDisplayStat >= 5) {
2719 hilightingType = 'pro';
2720 }
2721 } else if (displayStat == 'skill') {
2722 if (pDisplayStat < 200) {
2723 hilightingType = 'low';
2724 } else if (pDisplayStat < 300) {
2725 hilightingType = 'average';
2726 } else if (pDisplayStat < 400) {
2727 hilightingType = 'good';
2728 } else if (pDisplayStat < 550) {
2729 hilightingType = 'high';
2730 } else if (pDisplayStat >= 550) {
2731 hilightingType = 'v-high';
2732 }
2733 }
2734 else if (displayStat == 'strength') {
2735 if (pDisplayStat < 200) {
2736 hilightingType = 'low';
2737 } else if (pDisplayStat < 300) {
2738 hilightingType = 'average';
2739 } else if (pDisplayStat < 400) {
2740 hilightingType = 'good';
2741 } else if (pDisplayStat < 550) {
2742 hilightingType = 'high';
2743 } else if (pDisplayStat >= 550) {
2744 hilightingType = 'v-high';
2745 }
2746 }
2747
2748 return hilightingType;
2749 }
2750
2751 },
2752
2753 /*
2754 * Sorts an object by the value of a given field. Returns an ordered array of keys to iterate the object.
2755 *
2756 * @param object Object to sort
2757 * @param field The field to sort by
2758 * @param order The order to sort by. Either asc or desc
2759 */
2760 sortObject: function(object, field, order) {
2761
2762 var keys = [];
2763
2764 for (var key in object) {
2765 keys.push(key);
2766 }
2767
2768 keys.sort(function(a, b) {
2769 if (object[a][field] < object[b][field])
2770 return order == 'desc' ? 1 : -1;
2771 if (object[a][field] > object[b][field])
2772 return order == 'desc' ? -1 : 1;
2773 return 0;
2774 });
2775
2776 return keys;
2777 },
2778
2779 commaFormat : function(number)
2780 {
2781 return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
2782 },
2783
2784 /**
2785 *
2786 * Unloads plugin by clearing variables and intervals
2787 *
2788 * @param instance Plugin Instance
2789 */
2790
2791 unloadPlugin : function(instance) {
2792
2793 //Clear interval
2794 if(instance.ticker.isActive) {
2795
2796 instance.ticker.stop(instance);
2797
2798 }
2799
2800 //instance.data.latestScoreboardData = {};
2801 instance.data.advancedViewPlayer = 0;
2802 instance.data.pluginLoaded = false;
2803 instance.data.currentChart = false;
2804 for(var tracked in instance.data.tracker)
2805 {
2806 instance.data.tracker[tracked] = {};
2807 }
2808 }
2809
2810
2811
2812
2813
2814});
2815/**
2816* True Player Counts - Shows the true player count on the server (Not the ones in queue/cheating with the bots).
2817*
2818* Used I-MrFixIt-I's Friends Highlighter as a base.
2819*
2820* @author xfileFIN
2821* @version 2.1
2822* @url https://getbblog.com
2823*/
2824
2825/*************/
2826/* Changelog */
2827/*************/
2828/*
2829Version: 2.1
2830- Fix: Stop excessive request flooding (hopefully :))
2831- Fix: Fix match info and scoreboard on battlelog (Thanks DICE for breaking them). And thanks PolloLoco for pointing out that https works even though http doesn't
2832Version: 2.0
2833- Change: Fetch data from another place
2834Version: 1.4
2835- Fix: Made ajax request async so it won't hang the whole site when the request doesn't work
2836Version: 1.3
2837- Added: Color coding on low, mid, high difference of the player count shown/the actual ones playing.
2838- Added: Option to remove spectators/commanders if there are none. This is to trim down the view.
2839Version: 1.1
2840- Fixed a bug that prevented automatic loading on page load (Worked from the Editor but not when uploaded).
2841Version: 1.0
2842- Initial release
2843*/
2844
2845
2846var instanssi;
2847
2848// initialize your plugin
2849BBLog.handle("add.plugin", {
2850
2851 /**
2852 * The unique, lowercase id of my plugin
2853 * Allowed chars: 0-9, a-z, -
2854 */
2855 id: "xfilefin-true-playercounts",
2856
2857 /**
2858 * The name of my plugin, used to show config values in bblog options
2859 * Could also be translated with the translation key "plugin.name" (optional)
2860 *
2861 * @type String
2862 */
2863 name: "True Player Counts",
2864
2865 /**
2866 * Some translations for this plugins
2867 * For every config flag must exist a corresponding EN translation
2868 * otherwise the plugin will no be loaded
2869 *
2870 * @type Object
2871 */
2872 translations: {
2873 "en": {
2874 "use.true-playercounts": "Use True Player Counts",
2875 "use.trim-view": "Trim Spectator/Commander",
2876 "change-color-high": "Change color (High)",
2877 "choose-color-high": "Choose a color of your choice. Example: #ff0000",
2878 "change-color-mid": "Change color (Mid)",
2879 "choose-color-mid": "Choose a color of your choice. Example: #99b839",
2880 "change-color-low": "Change color (Low)",
2881 "choose-color-low": "Choose a color of your choice. Example: #39b54a"
2882 },
2883 "de": {
2884 "use.true-playercounts": "Use True Player Counts",
2885 "use.trim-view": "Trim Spectator/Commander",
2886 "change-color-high": "Farbe ändern (High)",
2887 "choose-color-high": "Wähle eine Farbe deiner Wahl. Beispiel: #ff0000",
2888 "change-color-mid": "Farbe ändern (Mid)",
2889 "choose-color-mid": "Wähle eine Farbe deiner Wahl. Beispiel: #99b839",
2890 "change-color-low": "Farbe ändern (Low)",
2891 "choose-color-low": "Wähle eine Farbe deiner Wahl. Beispiel: #39b54a"
2892 }
2893 },
2894
2895 stdColorHigh: "#ff0000",
2896 stdColorMid: "#99b839",
2897 stdColorLow: "#39b54a",
2898
2899 /**
2900 * Configuration Options that appears in the BBLog Menu
2901 * Every option must be an object with properties as shown bellow
2902 * Properties available:
2903 * key : The name for your config flag - The user can toggle this option
2904 * and you can retreive the users choice with instance instance.storage(YOUR_KEY_NAME) (0 or 1 will be returned)
2905 * init : Can be 0 or 1 - Represent the initial status of the option when the user load the plugin for the first time
2906 * If you want that this option is enabled on first load (opt-out) than set it to 1, otherwise to 0 (opt-in)
2907 * handler(optional): When set as a function this config entry turns into a button (like the plugins button you see in the bblog menu)
2908 * The function well be executed when the user clicks the button
2909 */
2910 configFlags: [
2911 { "key": "use.true-playercounts", "init": 1 },
2912 { "key": "use.trim-view", "init": 0 },
2913 {
2914 "key": "change-color-high", "init": 0, "handler": function (instance) {
2915 var color = prompt(instance.t("choose-color-high"));
2916 if (color.charAt(0) != "#") {
2917 color = + "#";
2918 }
2919
2920 var isHexValue = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(color);
2921 if (isHexValue) {
2922 instance.storage("colorHigh", color);
2923 }
2924 }
2925 },
2926 {
2927 "key": "change-color-mid", "init": 0, "handler": function (instance) {
2928 var color = prompt(instance.t("choose-color-mid"));
2929 if (color.charAt(0) != "#") {
2930 color = + "#";
2931 }
2932
2933 var isHexValue = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(color);
2934 if (isHexValue) {
2935 instance.storage("colorMid", color);
2936 }
2937 }
2938 },
2939 {
2940 "key": "change-color-low", "init": 0, "handler": function (instance) {
2941 var color = prompt(instance.t("choose-color-low"));
2942 if (color.charAt(0) != "#") {
2943 color = + "#";
2944 }
2945
2946 var isHexValue = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(color);
2947 if (isHexValue) {
2948 instance.storage("colorLow", color);
2949 }
2950 }
2951 }
2952 ],
2953
2954 /**
2955 * A handler that be fired immediately (only once) after the plugin is loaded into bblog
2956 *
2957 * @param object instance The instance of your plugin which is the whole plugin object
2958 * Always use "instance" to access any plugin related function, not use "this" because it's not working properly
2959 * For example: If you add a new function to your addon, always pass the "instance" object
2960 */
2961 init: function (instance) {
2962 // some log to the console to show you how the things work
2963 /*console.log(
2964 "plugin."+instance.id+".init"
2965 );*/
2966 instanssi = instance;
2967 },
2968
2969 /**
2970 * A trigger that fires everytime when the dom is changing but at max only once each 200ms (5x per second) to prevent too much calls in a short time
2971 * Example Case: If 10 DOM changes happen in a period of 100ms than this function will only been called 200ms after the last of this 10 DOM changes
2972 * This make sure that all actions in battlelog been finished before this function been called
2973 * This is how BBLog track Battlelog for any change, like url, content or anything
2974 *
2975 * @param object instance The instance of your plugin which is the whole plugin object
2976 * Always use "instance" to access any plugin related function, not use "this" because it's not working properly
2977 * For example: If you add a new function to your addon, always pass the "instance" object
2978 */
2979 domchange: function (instance) {
2980 instanssi = instance;
2981
2982 S.globalContext.staticContext.keeperQueryEndpoint = "https://keeper.battlelog.com"
2983 },
2984});
2985
2986$( document ).ready(function() {
2987 S.globalContext.staticContext.keeperQueryEndpoint = "https://keeper.battlelog.com"
2988});
2989
2990// Create a closure
2991(function () {
2992 // Your base, I'm in it!
2993 var originalAddClassMethod = jQuery.fn.addClass;
2994
2995 jQuery.fn.addClass = function () {
2996 if(jQuery.inArray("loading-info", arguments) !== -1){
2997 if (this.hasClass("bblog-serverbrowser-filters")) {
2998 this.removeClass("bblog-serverbrowser-filters");
2999 }
3000 }
3001 if(jQuery.inArray("bblog-serverbrowser-filters", arguments) !== -1){
3002 if (!this.hasClass("bblog-serverbrowser-filters")) {
3003 doTheMagic(this);
3004 }
3005 }
3006
3007 // Execute the original method.
3008 var result = originalAddClassMethod.apply(this, arguments);
3009
3010 // trigger a custom event
3011 jQuery(this).trigger('cssClassChanged');
3012
3013 // return the original result
3014 return result;
3015 }
3016})();
3017
3018function doTheMagic(row){
3019 if (!instanssi.storage("use.true-playercounts")) {
3020 return;
3021 }
3022
3023 if (BBLog.cache("mode") != "bf4" || !serverbrowserwarsaw || !serverbrowserwarsaw.table) {
3024 return;
3025 }
3026
3027 var data = $(row).data("server");
3028 if (!data) return true;
3029
3030 // True player count
3031 var url = "http://battlelog.battlefield.com/bf4/servers/show/pc/" + data.guid + "?json=1";
3032
3033 var $serverRow = $(row);
3034 function showTrueCounts(response) {
3035 if (response.type == "success" && response.message.SERVER_INFO && response.message.SERVER_PLAYERS) {
3036 //console.log("Current: " + response.message.SERVER_INFO.slots[2].max + "/" + response.message.SERVER_PLAYERS.length);
3037 var slotData = response.message.SERVER_INFO.slots;
3038 var totalPlayers = response.message.SERVER_PLAYERS.length;
3039
3040 if (slotData[2]) {
3041 if (!$serverRow.find(".bblog-slots.trueplayercount").length) {
3042 if ($serverRow.find(".bblog-slots.commander").length) {
3043 $serverRow.find(".bblog-slots.commander").before('<div class="bblog-slots trueplayercount">' + totalPlayers + "/" + slotData[2].max + '</div>');
3044 }
3045 else if ($serverRow.find(".bblog-slots.spectator").length) {
3046 $serverRow.find(".bblog-slots.spectator").before('<div class="bblog-slots trueplayercount">' + totalPlayers + "/" + slotData[2].max + '</div>');
3047 }
3048 else {
3049 $serverRow.find("td.players").append('<div class="bblog-slots trueplayercount">' + totalPlayers + "/" + slotData[2].max + '</div>');
3050 }
3051 }
3052 else{
3053 $serverRow.find(".bblog-slots.trueplayercount").html('<div class="bblog-slots trueplayercount">' + totalPlayers + "/" + slotData[2].max + '</div>');
3054 }
3055 var serverplayers = $serverRow.find(".bblog-slots.trueplayercount");
3056
3057 var difference = Math.abs(slotData[2].current - totalPlayers);
3058 if (difference <= 2) {
3059 if (instanssi.storage("change-color-low")) {
3060 var color = instanssi.storage("colorLow");
3061 if (color !== null) {
3062 $(serverplayers).css("color", color);
3063 }
3064 else {
3065 $(serverplayers).css("color", instanssi.stdColorLow);
3066 }
3067 }
3068 else {
3069 $(serverplayers).css("color", instanssi.stdColorLow);
3070 }
3071 }
3072 else if (difference <= 5) {
3073 if (instanssi.storage("change-color-mid")) {
3074 var color = instanssi.storage("colorMid");
3075 if (color !== null) {
3076 $(serverplayers).css("color", color);
3077 }
3078 else {
3079 $(serverplayers).css("color", instanssi.stdColorMid);
3080 }
3081 }
3082 else {
3083 $(serverplayers).css("color", instanssi.stdColorMid);
3084 }
3085 }
3086 else {
3087 if (instanssi.storage("change-color-high")) {
3088 var color = instanssi.storage("colorHigh");
3089 if (color !== null) {
3090 $(serverplayers).css("color", color);
3091 }
3092 else {
3093 $(serverplayers).css("color", instanssi.stdColorHigh);
3094 }
3095 }
3096 else {
3097 $(serverplayers).css("color", instanssi.stdColorHigh);
3098 }
3099 }
3100 $(serverplayers).css("font-size", "12px");
3101 }
3102
3103 // Remove the unneeded nodes to make the view a bit nicer/cleaner
3104 if (instanssi.storage("use.trim-view")) {
3105 if (slotData[4] && $serverRow.find(".bblog-slots.commander").length && slotData[4].current <= 0) {
3106 $serverRow.find(".bblog-slots.commander").css("display", "none");
3107 }
3108 if (slotData[8] && $serverRow.find(".bblog-slots.spectator").length && slotData[8].current <= 0) {
3109 $serverRow.find(".bblog-slots.spectator").css("display", "none");
3110 }
3111 }
3112 }
3113 }
3114
3115 // Fetch the current data
3116 $.ajax({
3117 async: true,
3118 url: url,
3119 error: function () {
3120 //console.log("Fetching: " + url + " timed out.");
3121 },
3122 success: function (result) {
3123 //console.log(result);
3124 if (result) {
3125 showTrueCounts(result);
3126 }
3127 },
3128 timeout: 5000 // sets timeout to 5 seconds
3129 });
3130}