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