Ansolley

Plugin - Advanced Scoreboard HTTPS Fix

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