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