Skip to content

Instantly share code, notes, and snippets.

@fortepc
Created December 24, 2021 03:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fortepc/8d0b8d34b123cf0b0d51dfb567064560 to your computer and use it in GitHub Desktop.
Save fortepc/8d0b8d34b123cf0b0d51dfb567064560 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @id iitc-plugin-player-tracker-trail-counts@breunigs
// @name Enhanced Player Tracker
// @category Layer
// @version 0.11.21.20160229
// @description Based on the normal player-tracker plugin. Enhanced to show counts and list for the current view, counts for players at the same portal and watch of players, options to toggle trails and change colors
// @downloadURL https://www.martinezdelizarrondo.com/iitc/iitc-plugin-player-tracker-w-count-and-trails.user.js
// @include https://www.ingress.com/intel*
// @include http://www.ingress.com/intel*
// @match https://www.ingress.com/intel*
// @match http://www.ingress.com/intel*
// @grant none
// ==/UserScript==
/*
Overview: https://docs.google.com/document/d/1RMtUFwQUpLPzwANUP6-z9n8ium4hPjOjQf1cB-Yl0ek/edit?usp=sharing
@breunigs
Added count of players on the current view
0.11.2.20160110:
@AlfonsoML
Number of players on the icon. Click to show list and then again to view details.
Enable removing traces without reload
0.11.3.20160111
@AlfonsoML
Single layer for both factions, show in the icon the number of players of each faction
0.11.4.20160114
@AlfonsoML
Display list of players of each faction, with last time and location and sorting
Split list of multiple players @ same portal if it's greater than 5
0.11.5.20160114
@AlfonsoML
Update lists with new data
Start using ES6 "const" and "let"
0.11.6.20160115
@AlfonsoML
Highlight trails of the selected player
0.11.7.20160117
@AlfonsoML
Customization of trails colors.
Add "Tracked players": These ones aren't subject to the 3h. limit and their nicks are preserved on reload
Autoremove trails of the selected player on popup close
0.11.8.20160118
@AlfonsoML
Changed the colors of the icon to Top->down
Always split the players on a portal by its faction
Rename Watched to Tracked
0.11.9.20160119
@AlfonsoML
Configuration for min zoom level and number of tracked hours
0.11.10.20160119
@AlfonsoML
Removed "let" usage because current Firefox 43 doesn't support it
0.11.11.20160121
@AlfonsoML
Added option to show the speed from the previous location if it's farther than 400m.
0.11.12.20160123
@AlfonsoML
Show which column is used to sort player lists
Adjust title of the watch icon according to the watched status
Manage list of watched players
Option to display labels with the names (instead of waiting for the tooltip)
0.11.13.20160124
@AlfonsoML
Adjusted Watched icon colors and the colors in the history popup
Translations (EN and ES)
Copy to cliboard for list of players and player history
0.11.14.20160201
By InductiveKick: export date & time
Move LeafletLabel to a @require
0.11.15.20160202
Fixed compatibility with IITC Mobile (at least now it doesn't crash)
Fire hooks when opening the dialogs and popups
0.11.16.20160202
Fixed broken dialogs and popups due to hooks
Removed the @require for LeafletLabel
0.11.17.20160206
Removed LeafletLabel
Enabled display of labels on mobile IITC
0.11.18.20160207
Use inline div instead of alert for successful copy to clipboard
Allow to specify a set of bookmarked portals that should not display any player data on them
Show warning in the Settings dialog if the layer is disabled
0.11.19.20160212
Adjust excluded portals when bookmarks are changed. Cache excluded portals
Help link
0.11.20.20160213
Avoid computing the Popup data until they are shown
0.11.21.20160229
Compatibility with Safari by @arwyl (removal of ES6 const because Safari doesn't support it http://caniuse.com/#feat=const)
Future:
cluster markers
*/
/* globals $, GM_info, L, TEAM_TO_CSS, map, addHook, runHooks, pluginCreateHook, dialog */
/* globals getTeam, COLORS_LVL */
function wrapper(plugin_info) {
'use strict';
// ensure plugin framework is there, even if iitc is not yet loaded
if (typeof window.plugin !== 'function') window.plugin = function() {};
// PLUGIN START ////////////////////////////////////////////////////////
window.PLAYER_TRACKER_MIN_OPACITY = 0.3;
var defaultSettings = {
viewEnl: true,
viewRes: true,
trailsvisible: true,
sort: 'Player',
tracesColorEnl: '#FF00FD',
tracesColorRes: '#FF00FD',
tracesColorWatched: '#FF0000',
tracesColorHighlight: '#9627F4',
minZoom: 9,
maxTime: 3,
showSpeed: false,
labelsEnl: false,
labelsRes: false,
excludedBookmarkFolder: ''
};
var settings = defaultSettings;
var Key_Settings = 'plugin-playertracker-settings';
var Key_Watched = 'plugin-playertracker-watched';
//var Key_ExcludedPortals = 'plugin-playertracker-excluded-portals';
var enlIco = 'data:image/png;base64,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';
var resIco = 'data:image/png;base64,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';
var eyeIcon = '<svg class="tracker-eye" viewBox="0 0 24 24"><path d="M12 4.5C7 4.5 2.7 7.6 1 12c1.7 4.4 6 7.5 11 7.5s9.3-3.1 11-7.5c-1.7-4.4-6-7.5-11-7.5zM12 17c-2.8 0-5-2.2-5-5s2.2-5 5-5 5 2.2 5 5-2.2 5-5 5zm0-8c-1.7 0-3 1.3-3 3s1.3 3 3 3 3-1.3 3-3-1.3-3-3-3z"/></svg>';
var copyIcon = '<svg class="copy-icon" viewBox="0 0 24 24"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>';
var enlImg = createImgFromDataUri(enlIco);
var resImg = createImgFromDataUri(resIco);
// We keep all the events of the Watched players (just for this session), without the maxTime limit
var watchedPlayers = {};
var selectedPlayer;
var selectedPlayerTracks = [];
var excludedPortals = {};
var translations;
function ConfigureLanguage() {
var language = window.navigator.language;
var lang_es = {
allPlayers: 'Todos',
atPortal: 'En el portal',
availablePlayers: 'Jugadores en este �rea:',
copy: 'Copiar:',
copyHistory: 'Copiar el historial al portapapeles',
displayEstimatedSpeed: 'Mostrar velocidad estimada',
displayLabels: 'Mostrar etiquetas',
ENL: 'ENL',
Enlightened: 'Iluminados',
estimatedSpeed: 'Velocidad estimada',
guessedLevel: ', posible nivel: ',
help: 'Ayuda',
lastSeen: 'Visto hace',
layerTitle: 'Seguimiento de Jugadores',
minimumZoom: 'Zoom m�nimo (zoom actual: {0})',
minLevel: 'Nivel min. ',
noWatched: 'No tienes jugadores en seguimiento',
onlyThisArea: 'S�lo este �rea',
options: 'Opciones',
optionsTitle: 'Seguimiento de Jugadores',
player: 'Jugador',
previousLocations: 'Portales anteriores:',
RES: 'RES',
Resistance: 'Resistancia',
selected: 'Seleccionado',
showEnlPlayers: 'Mostrar Iluminados',
showResPlayers: 'Mostrar Resistencia',
showTrails: 'Mostrar el rastro de jugadores',
sortByThis: 'Click para ordenar',
sortedByThis: 'Ordenado por este campo',
stopWatching: 'Dejar de hacer seguimiento al jugador',
textCopied: 'Texto copiado al portapapeles',
textCopyFailed: 'No se ha copiado el texto',
thereAreNoENLIGHTENED : 'No hay Iluminados en la zona',
thereAreNoRESISTANCE : 'No hay Resistencia en la zona',
timeAgo: 'hace {0}',
trackedHours: 'Horas de an�lisis',
trailsColors: 'Colores de los rastros',
watchAnother: 'Seguir a otro jugador:',
watch: 'Seguir',
watched: 'En Seguim.',
watchedPlayers: 'En Seguimiento',
watchThis: 'Seguir al jugador',
zoomInToShow: 'Haz zoom para mostrar'
};
var lang_en = {
allPlayers: 'All players',
atPortal: 'At portal',
availablePlayers: 'Available players in this area:',
copy: 'Copy:',
copyHistory: 'Copy history to the clipboard',
displayEstimatedSpeed: 'Display estimated speed',
displayLabels: 'Display labels',
ENL: 'ENL',
Enlightened: 'Enlightened',
estimatedSpeed: 'Estimated speed',
guessedLevel: ', guessed level: ',
help: 'Help',
lastSeen: 'Last Seen',
layerTitle: 'Player Tracker',
minimumZoom: 'Minimum zoom (current zoom: {0})',
minLevel: 'Min level ',
noWatched: 'You have no watched players',
onlyThisArea: 'Only this area',
options: 'Tracker Opt',
optionsTitle: 'Enhanced Player Tracker options',
player: 'Player',
previousLocations: 'Previous locations:',
RES: 'RES',
Resistance: 'Resistance',
selected: 'Selected',
showEnlPlayers: 'Show ENL Players',
showResPlayers: 'Show RES Players',
showTrails: 'Make player trails visible',
sortByThis: 'Click to sort',
sortedByThis: 'Sorted by this field',
stopWatching: 'Stop Watching this player',
textCopied: 'Text copied to the clipboard',
textCopyFailed: 'Failed to copy the text',
thereAreNoENLIGHTENED : 'There are no Enlightened players around',
thereAreNoRESISTANCE : 'There are no Enlightened players around',
timeAgo: '{0} ago',
trackedHours: 'Tracked hours',
trailsColors: 'Trails colors',
watch: 'Watch',
watchAnother: 'Watch another player:',
watched: 'Watched',
watchedPlayers: 'Watched Players',
watchThis: 'Watch this player',
zoomInToShow: 'Zoom in to show those'
};
if (language.indexOf('es') === 0) {
translations = $.extend({}, lang_en, lang_es);
} else {
translations = lang_en;
}
}
function createImgFromDataUri(src) {
var img = document.createElement('img');
img.src = src;
img.width = 50;
img.height = 82;
return img;
}
// Create icon image based on the number of players of each team
function generateIco(nEnl, nRes) {
var canvas = document.createElement('canvas');
canvas.width = 50;
canvas.height = 82;
var total = nEnl + nRes;
if (total > 0) {
var ctx = canvas.getContext('2d');
ctx.drawImage(enlImg, 0, 0);
if (nRes > 0) {
var y = Math.round( canvas.height * nEnl / total );
ctx.drawImage(resImg, 0, y, canvas.width, canvas.height, 0, y, canvas.width, canvas.height);
}
ctx.textAlign = 'center';
if (nEnl === 0 || nRes === 0) {
ctx.font = '32px sans-serif';
ctx.fillStyle = '#FFF';
ctx.fillText(total, 25, 40);
} else {
ctx.font = '26px sans-serif';
ctx.strokeStyle = '#FFF';
ctx.fillStyle = '#FFF';
ctx.lineWidth = 1;
ctx.fillText(nEnl, 25, 28);
ctx.fillText(nRes, 25, 52);
}
}
var result = {};
result.retinaIco = canvas.toDataURL();
var canvasIco = document.createElement('canvas');
canvasIco.width = 25;
canvasIco.height = 41;
var ctxIco = canvasIco.getContext('2d');
ctxIco.drawImage(canvas, 0, 0, 25, 41);
result.ico = canvasIco.toDataURL();
return result;
}
// Get icon for the team and number
var iconsCache = {};
function getIcon(nEnl, nRes) {
var key = nEnl + 'x' + nRes;
if (iconsCache[key]) {
return iconsCache[key];
}
var icons = generateIco(nEnl, nRes);
var ico = new (L.Icon.Default.extend({ options: {
iconUrl: icons.ico,
iconRetinaUrl: icons.retinaIco
} }))();
iconsCache[key] = ico;
return ico;
}
// use own namespace for plugin
window.plugin.playerTracker = function() {};
var thisPlugin = window.plugin.playerTracker;
thisPlugin.setup = function() {
// add a custom hook to share its activity with other plugins
pluginCreateHook('pluginEnhancedPlayerTracker');
thisPlugin.stored = {};
ConfigureLanguage();
$('<style>').prop('type', 'text/css').html(
'#ptracker {' +
'color:#eee;' +
'font-size:95%;' +
'padding:4px 2px;' +
'}' +
'#ptracker p {' +
'margin:0;' +
'}' +
'.tracker-popup {' +
'color:#eee;' +
'}' +
'.tracker-popup a{' +
'color:#ffce00;' +
'}' +
'.tracker-list th {' +
'cursor: s-resize;' +
'}' +
'th.sorted {' +
'cursor:default;' +
'}' +
'th.sorted:after {' +
'content:"?";' +
'}' +
'th.sorted-rev:after {' +
'content:"?";' +
'}' +
'.tracker-eye {' +
'border: 1px solid #ffce00;' +
'border-radius: 3px;' +
'fill:#ffce00;' +
'height:16px;' +
'margin-left: 5px;' +
'stroke:none;' +
'transition: all 0.2s ease-out;' +
'vertical-align:text-bottom;' +
'width:16px;' +
'}' +
'.tracker-eye:hover{' +
'box-shadow: 0 5px 11px 0 rgba(128, 128, 128, 0.3), 0 4px 15px 0 rgba(128, 128, 128, 0.3);' +
'fill:#fff;' +
'}' +
'.tracker-watched .tracker-eye{' +
'background-color:#ffce00;' +
'fill: #333;' +
'}' +
'.tracker-watched .tracker-eye:hover{' +
'fill: #fff;' +
'}' +
'input[type="color"] {' +
'border:0;' +
'padding:0;' +
'}' +
'fieldset {' +
'margin: 10px 0;' +
'}' +
'.tracker-wrapper {' +
'display:flex;' +
'flex-wrap:wrap;' +
'}' +
'.tracker-wrapper br {' +
'display:none;' +
'}' +
'.tracker-wrapper input[type="color"] {' +
'display: block;' +
'}' +
'.tracker-wrapper label {' +
'flex:1;' +
'justify-content:space-around;' +
'}' +
'.copy-icon {' +
'fill:#ffce00;' +
'height:16px;' +
'margin-left: 5px;' +
'stroke:none;' +
'vertical-align:text-bottom;' +
'width:16px;' +
'}' +
'.player-tracker-label {' +
'background:rgba(235,235,235,.81);background-clip:padding-box;border-radius:5px;border: 1px solid rgba(0,0,0,.25); color:#333;display:block;font:12px/16px "Helvetica Neue",Arial,Helvetica,sans-serif;font-weight:700;padding:1px 6px;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:none;white-space:nowrap;' +
'box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);' +
'width:auto !important; height:auto !important; z-index:0 !important' +
'}' +
'.player-tracker-notification {' +
'background: rgba(255,255,255,0.9);' +
'border: 1px solid rgba(0,0,0,.25);' +
'box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);' +
'color: #333;' +
'display:none;' +
'font: 18px/20px "Helvetica Neue",Arial,Helvetica,sans-serif;' +
'left: 50%;' +
'padding: 3px 6px 2px;' +
'pointer-events: none;' +
'position: absolute;' +
'top: 10px;' +
'transform: translateX(-50%);' +
'user-select: none;' +
'z-index: 10000;' +
'}' +
'.warning {' +
'color: red;' +
'}'
).appendTo('head');
$('#sidebar').append('<div id="ptracker">' +
translations.availablePlayers +
'<div id="ptrackerTable"></div>' +
'<p><a onclick="window.plugin.playerTracker.showDialog(); return false;">' + translations.options + '</a>' +
' - <a onclick="window.plugin.playerTracker.showWatched(); return false;">' + translations.watchedPlayers + '</a>' +
'</p>' +
'<p>' + translations.copy + ' ' +
'<a onclick="window.plugin.playerTracker.exportPlayers(true); return false">' + copyIcon + ' ' + translations.allPlayers + '</a> ' +
'<a onclick="window.plugin.playerTracker.exportPlayers(false); return false">' + copyIcon + ' ' + translations.onlyThisArea + '</a> ' +
'</p>' +
'</div>');
$('body').append($('<div class="player-tracker-notification">' + translations.textCopied + '</div>'));
$('body').on('click', '.tracker-list th', changePlayerSort);
var obj = localStorage[ Key_Settings ];
if (obj) {
settings = $.extend({}, defaultSettings, JSON.parse(obj));
}
obj = localStorage[ Key_Watched ];
if (obj) {
watchedPlayers = JSON.parse(obj);
}
// // FIXME: use it with drawn tools ...
// obj = localStorage[ Key_ExcludedPortals ];
// if (obj) {
// excludedPortals = JSON.parse(obj);
// }
thisPlugin.drawnTraces = new L.LayerGroup();
window.addLayerGroup(translations.layerTitle, thisPlugin.drawnTraces, true);
map.on('layeradd',function(obj) {
if (obj.layer === thisPlugin.drawnTraces) {
obj.layer.eachLayer(function(marker) {
if (marker._icon) window.setupTooltips($(marker._icon));
});
}
});
thisPlugin.playerPopup = new L.Popup({ offset: L.point([ 1,-34 ]) });
thisPlugin.playerPopup.on('close', clearPlayerTracks);
addHook('publicChatDataAvailable', thisPlugin.handleData);
if (window.plugin.bookmarks) {
addHook('pluginBkmrksEdit', function() {
thisPlugin.initExclusions();
thisPlugin.redrawAll();
});
}
window.map.on('zoomend', thisPlugin.zoomListener);
thisPlugin.zoomListener();
thisPlugin.setupUserSearch();
thisPlugin.initExclusions();
};
// Max time that we track all normal players. Default: 3 hours
function getMaxTime() {
return settings.maxTime * 60 * 60 * 1000; // in milliseconds
}
function createOptionsCheckboxLine( div, option, title ) {
var label = div.appendChild(document.createElement('label'));
var input = label.appendChild(document.createElement('input'));
input.type = 'checkbox';
input.checked = settings[ option ];
label.appendChild(document.createTextNode( title ));
div.appendChild(document.createElement('br'));
input.addEventListener('click', function() {
settings[ option ] = input.checked;
localStorage[ Key_Settings ] = JSON.stringify(settings);
thisPlugin.redrawAll();
}, false);
}
function createOptionsColorLine( div, option, title ) {
var label = div.appendChild(document.createElement('label'));
label.appendChild(document.createTextNode( title + ' ' ));
var input = label.appendChild(document.createElement('input'));
input.type = 'color';
input.value = settings[ option ];
div.appendChild(document.createElement('br'));
input.addEventListener('change', function() {
settings[ option ] = input.value;
localStorage[ Key_Settings ] = JSON.stringify(settings);
thisPlugin.redrawAll();
}, false);
}
function createOptionsNumberLine( div, option, title, min, max ) {
var label = div.appendChild(document.createElement('label'));
label.appendChild(document.createTextNode( title + ' ' ));
var input = label.appendChild(document.createElement('input'));
input.type = 'number';
input.min = min;
input.max = max;
input.value = settings[ option ];
div.appendChild(document.createElement('br'));
input.addEventListener('change', function() {
settings[ option ] = input.value;
localStorage[ Key_Settings ] = JSON.stringify(settings);
thisPlugin.redrawAll();
}, false);
}
function createFieldset(container, title) {
var fieldset = container.appendChild(document.createElement('fieldset'));
var legend = fieldset.appendChild(document.createElement('legend'));
legend.appendChild(document.createTextNode( title ));
var div = fieldset.appendChild(document.createElement('div'));
div.className = 'tracker-wrapper';
return div;
}
function createOptionsExclusions(div) {
var title = 'Exclude portals that are in this bookmarks set:';
var option = 'excludedBookmarkFolder';
var label = div.appendChild(document.createElement('label'));
label.appendChild(document.createTextNode( title + ' ' ));
if (window.plugin.bookmarks) {
var select = label.appendChild(document.createElement('select'));
var value = settings[ option ];
var folders = window.plugin.bookmarks.bkmrksObj.portals;
var options = [];
options.push('<option value="">-None-</option>');
$.each(folders, function(id, folder) {
var opt = '<option value="' + id + '"' + ( id == value ? ' selected' : '') + '>' + folder.label + '</option>';
options.push( opt );
});
select.innerHTML = options.join( '' );
select.addEventListener('change', function() {
settings[ option ] = select.value;
localStorage[ Key_Settings ] = JSON.stringify(settings);
thisPlugin.initExclusions();
thisPlugin.redrawAll();
}, false);
} else {
label.appendChild(document.createTextNode( ' Error. Bookmarks plugin is not loaded.' ));
}
div.appendChild(document.createElement('br'));
}
thisPlugin.showDialog = function() {
// Stolen from Vashiru's Display options
var div = document.createElement('div');
//div.appendChild(document.createElement('h3')).appendChild(document.createTextNode('Tracker Options:'));
if (!map.hasLayer(thisPlugin.drawnTraces)) {
var warning = document.createElement('h3');
warning.appendChild(document.createTextNode('Warning: tracking layer is disabled'));
warning.className = 'warning';
div.appendChild(warning);
}
createOptionsCheckboxLine(div, 'viewEnl', translations.showEnlPlayers);
createOptionsCheckboxLine(div, 'viewRes', translations.showResPlayers);
createOptionsCheckboxLine(div, 'trailsvisible', translations.showTrails);
createOptionsExclusions(div);
var fieldSetColors = createFieldset(div, translations.trailsColors);
createOptionsColorLine(fieldSetColors, 'tracesColorEnl', translations.ENL);
createOptionsColorLine(fieldSetColors, 'tracesColorRes', translations.RES);
createOptionsColorLine(fieldSetColors, 'tracesColorWatched', translations.watched);
createOptionsColorLine(fieldSetColors, 'tracesColorHighlight', translations.selected);
createOptionsNumberLine(div, 'minZoom', translations.minimumZoom.replace('{0}', map.getZoom() ), 1, 20);
createOptionsNumberLine(div, 'maxTime', translations.trackedHours, 1, 48);
createOptionsCheckboxLine(div, 'showSpeed', translations.displayEstimatedSpeed);
var fieldSetLabels = createFieldset(div, translations.displayLabels);
createOptionsCheckboxLine(fieldSetLabels, 'labelsEnl', translations.ENL);
createOptionsCheckboxLine(fieldSetLabels, 'labelsRes', translations.RES);
var help = document.createElement('a');
help.innerHTML = '?';
help.title = translations.help;
help.target = '_blank';
help.href = 'https://docs.google.com/document/d/1RMtUFwQUpLPzwANUP6-z9n8ium4hPjOjQf1cB-Yl0ek/';
help.style.position = 'absolute';
help.style.top = '5px';
help.style.right = '5px';
help.style.fontSize = '150%';
div.appendChild(help);
dialog({
id: 'playertracker-options',
html: div,
width: '350px',
title: translations.optionsTitle
});
};
thisPlugin.showWatched = function() {
var div = document.createElement('div');
//div.appendChild(document.createElement('h3')).appendChild(document.createTextNode('Watched Players'));
// Avoid autofocus on the first portal link because it's ugly
// http://stackoverflow.com/a/10455573/250294
div.appendChild( $('<span style="position:absolute; left:-10000px"><input type="text"/></span>')[0]);
var players = [];
$.each( watchedPlayers, function(nick/*, emptyData*/) {
var playerData = thisPlugin.stored[ nick ];
var last;
if (playerData) {
var evtsLength = playerData.events.length;
last = playerData.events[evtsLength - 1];
players.push( generatePlayerSummaryObject(playerData, last) );
} else {
// It has been watched at another zone/date, we don't have current data, so show just a line to unwatch it
playerData = {
nick: nick
};
last = {
time:0
};
players.push( generatePlayerSummaryObject(playerData, last) );
}
});
if (players.length > 0) {
div.appendChild( formatPlayerTable( players, true )[0]);
} else {
div.appendChild(document.createElement('h3')).appendChild(document.createTextNode(translations.noWatched ));
}
var addWatched = document.createElement('form');
div.appendChild(addWatched);
addWatched.style.marginTop = '10px';
addWatched.appendChild(document.createElement('p')).appendChild(document.createTextNode( translations.watchAnother ));
var input = document.createElement('input');
input.type = 'search';
input.setAttribute('list', 'KnownPlayers');
addWatched.appendChild(input);
var submit = document.createElement('input');
submit.type = 'submit';
submit.value = translations.watch;
addWatched.appendChild(submit);
div.onsubmit = function() {
thisPlugin.toggleWatchPlayer( input.value, null);
return false;
};
// HTML autocomplete
var dataList = document.createElement('datalist');
dataList.id = 'KnownPlayers';
var options = [];
$.each(thisPlugin.stored, function(nick/*, playerData*/) {
if (!watchedPlayers[ nick ])
options.push( '<option value="' + nick + '"/>');
});
dataList.innerHTML = options.join('');
addWatched.appendChild(dataList);
var id = 'playertracker-watched-players';
var existing = $('#' + id);
if (existing.length > 0) {
existing.html('');
existing.append(div);
return;
}
runHooks('pluginEnhancedPlayerTracker', { event:'openDialog', id:id });
dialog({
id: id,
html: div,
width: '400px',
title: translations.watchedPlayers
});
};
function changePlayerSort(e) {
var newSort = e.target.getAttribute('data-sort');
if (settings.sort == newSort)
return;
settings.sort = newSort;
localStorage[ Key_Settings ] = JSON.stringify(settings);
// update displayed tables
var tables = document.querySelectorAll('.tracker-list tbody');
for (var i = 0; i < tables.length; i++) {
var table = tables[i];
var rows = [];
while (table.firstChild) {
var tr = table.removeChild(table.firstChild);
var sortData = JSON.parse(tr.getAttribute('data-sortData'));
sortData.tr = tr; // Add the row as the payload so it's sorted
rows.push( sortData );
}
rows = sortPlayers(rows);
for (var j = 0; j < rows.length; j++) {
table.appendChild( rows[ j ].tr );
}
}
var headers = document.querySelectorAll('.tracker-list thead th');
for (var k = 0; k < headers.length; k++) {
adjustSortingDisplay( headers[k] );
}
}
// Toggles extra tracking of a player
thisPlugin.toggleWatchPlayer = function( nick, a ) {
if (watchedPlayers[ nick ]) {
delete watchedPlayers[ nick ];
if (a) {
a.setAttribute('title', translations.watchThis);
a.classList.remove('tracker-watched');
}
} else {
watchedPlayers[ nick ] = true;
if (a) {
a.setAttribute('title', translations.stopWatching);
a.classList.add('tracker-watched');
}
}
localStorage[ Key_Watched ] = JSON.stringify( watchedPlayers );
thisPlugin.drawData();
};
function copyTextToClipboard(text) {
var textArea = document.createElement('textarea');
// Place in top-left corner of screen regardless of scroll position.
textArea.style.position = 'fixed';
textArea.style.top = 0;
textArea.style.left = 0;
textArea.style.width = '1em';
textArea.style.height = '1em';
textArea.style.padding = 0;
textArea.style.border = 0;
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
textArea.style.background = 'transparent';
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
var ok = document.execCommand('copy');
if (ok) {
$('.player-tracker-notification')
.fadeIn(400).delay(3000).fadeOut(400);
//alert( translations.textCopied );
} else {
alert( translations.textCopyFailed );
}
} catch (err) {
console.log('document.execCommand("copy") failed', err); // eslint-disable-line no-console
}
document.body.removeChild(textArea);
}
// Copies the history of a player to the clipboard
thisPlugin.copyPlayer = function( nick ) {
var playerData = thisPlugin.stored[ nick ];
if (!playerData) {
alert('Error: Unknown player "' + nick + '"');
return;
}
var text = [];
// Header: nick + team
text.push(playerData.nick + ' (' + playerData.team.substr(0,3) + ')');
var evtsLength = playerData.events.length;
for (var i = evtsLength - 1; i >= 0; i--) {
var line = [];
var ev = playerData.events[i];
line.push(window.unixTimeToDateTimeString(ev.time));
line.push(window.chat.getChatPortalName(ev));
var position = ev.latlngs[0];
var latlng = position.join(',');
line.push('https://www.google.com/maps/place/' + latlng);
text.push( line.join('\t') );
}
copyTextToClipboard(text.join('\r\n'));
};
thisPlugin.exportPlayers = function( all ) {
var text = [];
var bounds = map.getBounds();
var gllfe = thisPlugin.getLatLngFromEvent;
$.each(thisPlugin.stored, function(plrname, playerData) {
if (!playerData || playerData.events.length === 0) {
return true;
}
// if (playerData.team != team)
// return;
var evtsLength = playerData.events.length;
var last = playerData.events[evtsLength - 1];
if (!all && !bounds.contains(gllfe(last)))
return;
var line = [];
line.push(playerData.nick);
line.push(playerData.team.substr(0,3));
line.push(window.unixTimeToDateTimeString(last.time));
line.push(window.chat.getChatPortalName(last));
var position = last.latlngs[0];
var latlng = position.join(',');
line.push('https://www.google.com/maps/place/' + latlng);
text.push( line.join('\t') );
});
copyTextToClipboard(text.join('\r\n'));
};
thisPlugin.showFactionStats = function( team ) {
var div = document.createElement('div');
// Avoid autofocus on the first portal link because it's ugly
// http://stackoverflow.com/a/10455573/250294
div.appendChild( $('<span style="position:absolute; left:-10000px"><input type="text"/></span>')[0]);
var players = [];
var bounds = map.getBounds();
var gllfe = thisPlugin.getLatLngFromEvent;
$.each(thisPlugin.stored, function(plrname, playerData) {
if (!playerData || playerData.events.length === 0) {
return true;
}
if (playerData.team != team)
return;
var evtsLength = playerData.events.length;
var last = playerData.events[evtsLength - 1];
if (bounds.contains(gllfe(last))) {
players.push( generatePlayerSummaryObject(playerData, last) );
}
});
if (players.length > 0) {
//div.appendChild(document.createElement('h3')).appendChild(document.createTextNode('Players in this area:'));
div.appendChild( formatPlayerTable( players, true )[0]);
} else {
div.appendChild(document.createElement('h3')).appendChild(document.createTextNode( translations[ 'thereAreNo' + team ]));
}
var id = 'playertracker-stats-' + team;
var existing = $('#' + id);
if (existing.length > 0) {
existing.html('');
existing.append(div);
return;
}
runHooks('pluginEnhancedPlayerTracker', { event:'openDialog', id:id } );
dialog({
id: id,
html: div,
width: '400px',
title: 'Player Tracker: ' + team
});
};
function generatePlayerSummaryObject(playerData, last) {
var portal = thisPlugin.getPortalLink(last);
return {
nick: playerData.nick,
time: last.time,
portal: portal,
team : playerData.team,
sortnick: playerData.nick.toLowerCase(),
sortportal: portal.text().toLowerCase(),
speed: last.speed || ''
};
}
// Generates a th for a table with sorting attributes
function generateSortingHeader(sort, title) {
var th = $('<th>').
append(title);
th.attr('data-sort', sort);
adjustSortingDisplay( th[0] );
return th;
}
// Adjust the class and title of a header according to the current sort preferences
function adjustSortingDisplay(th) {
if (settings.sort == th.getAttribute( 'data-sort')) {
th.classList.add( 'sorted' );
th.setAttribute('title', translations.sortedByThis );
} else {
th.classList.remove( 'sorted' );
th.setAttribute('title', translations.sortByThis );
}
}
function formatPlayerTable( players, showPortal ) {
if (!players.length) {
return $('<span>'); // no data for this team
}
players = sortPlayers( players );
var hasGuessLevels = window.plugin.guessPlayerLevels !== undefined &&
window.plugin.guessPlayerLevels.fetchLevelDetailsByPlayer !== undefined;
var now = new Date().getTime();
var ago = thisPlugin.ago;
var table = $('<table class="tracker-list">');
var heading = $('<tr>')
.append(generateSortingHeader('Player', translations.player ));
if (hasGuessLevels) {
heading.append($('<td> </td>'));
}
if (showPortal) {
// Watched
heading.append($('<td> </td>'));
}
heading
.append(generateSortingHeader('Ago', translations.lastSeen ));
if (showPortal) {
heading
.append(generateSortingHeader('Portal', translations.atPortal ));
if (settings.showSpeed) {
heading
.append($('<td title="' + translations.estimatedSpeed + '"> </td>'));
}
}
$('<thead>')
.append(heading)
.appendTo(table);
for (var i = 0; i < players.length ; i++) {
var data = players[ i ];
var teamClass = '';
if (data.team == 'RESISTANCE')
teamClass = 'res';
if (data.team == 'ENLIGHTENED')
teamClass = 'enl';
var nick = $('<span>')
.addClass('nickname tracker-player ' + teamClass)
.css('font-weight', 'bold')
.text(data.nick);
var sortData = {
sortnick: data.sortnick,
time: data.time,
sortportal: data.sortportal
};
var tr = $('<tr>')
.append($('<td>')
.append(nick));
if (hasGuessLevels) {
var playerLevelDetails = window.plugin.guessPlayerLevels.fetchLevelDetailsByPlayer(data.nick);
tr.append($('<td>')
.append(getLevelHtml(playerLevelDetails.min)));
}
if (showPortal) {
var a = $('<a onclick="return window.plugin.playerTracker.toggleWatchPlayer(\'' + data.nick + '\', this); return false;" title="' + translations.watchThis + '">' + eyeIcon + '</a>');
if (watchedPlayers[ data.nick ] ) {
a.addClass('tracker-watched');
a.attr('title', translations.stopWatching );
}
tr.append($('<td>')
.append(a));
}
tr.append($('<td>')
.text(ago(data.time, now)));
if (showPortal) {
tr.append($('<td>')
.append(data.portal));
if (settings.showSpeed) {
tr.append($('<td>' + ( data.speed || '') + '</td>'));
}
}
tr.attr('data-sortData', JSON.stringify(sortData));
tr.appendTo(table);
}
return table;
}
function sortByPlayerName(a, b) {
return a.sortnick.localeCompare( b.sortnick );
}
function sortByPortalName(a, b) {
var compare = a.sortportal.localeCompare( b.sortportal );
if (compare !== 0)
return compare;
return sortByPlayerName(a, b);
}
function sortByAgo(a, b) {
// Reverse
var compare = b.time - a.time;
if (compare !== 0)
return compare;
return sortByPlayerName(a, b);
}
function sortPlayers(players) {
switch (settings.sort) {
case 'Portal':
return players.sort( sortByPortalName );
case 'Ago':
return players.sort( sortByAgo );
case 'Player':
/* falls through */
default:
return players.sort( sortByPlayerName );
}
}
thisPlugin.onClickListener = function(event) {
var marker = event.target;
var nick = marker.options.nick;
if (nick && !marker.options.desc) {
marker.options.desc = generatePopupHtmlPlayer( thisPlugin.stored[ nick ] );
}
if (marker.options.markerDataArray && !marker.options.desc) {
marker.options.desc = generatePopupHtmlMultiple( marker.options.markerDataArray );
}
if (marker.options.desc) {
thisPlugin.playerPopup.setContent(marker.options.desc);
thisPlugin.playerPopup.setLatLng(marker.options.latlng);
map.openPopup(thisPlugin.playerPopup);
runHooks('pluginEnhancedPlayerTracker', { event:'openPopup', popup:thisPlugin.playerPopup, nick: nick });
if (nick) {
selectedPlayer = nick;
highlightPlayerTracks();
}
}
};
// force close all open tooltips before markers are cleared
thisPlugin.closeIconTooltips = function() {
thisPlugin.drawnTraces.eachLayer(function(layer) {
// Avoid problems with the labels as the have no tooltip set
if (layer._icon && layer._icon.nodeName == 'DIV')
return;
if ($(layer._icon)) { $(layer._icon).tooltip('close');}
});
};
thisPlugin.zoomListener = function() {
var ctrl = $('.leaflet-control-layers-selector + span:contains("' + translations.layerTitle + '")').parent();
if (window.map.getZoom() < settings.minZoom) {
if (!window.isTouchDevice())
thisPlugin.closeIconTooltips();
thisPlugin.drawnTraces.clearLayers();
ctrl.addClass('disabled').attr('title', translations.zoomInToShow );
//note: zoomListener is also called at init time to set up things, so we only need to do this in here
window.chat.backgroundChannelData('plugin.playerTracker', 'all', false); //disable this plugin's interest in 'all' COMM
} else {
ctrl.removeClass('disabled').attr('title', '');
//note: zoomListener is also called at init time to set up things, so we only need to do this in here
window.chat.backgroundChannelData('plugin.playerTracker', 'all', true); //enable this plugin's interest in 'all' COMM
}
};
thisPlugin.getLimit = function() {
return new Date().getTime() - getMaxTime() ;
};
thisPlugin.discardOldData = function() {
var limit = thisPlugin.getLimit();
$.each(thisPlugin.stored, function(plrname, player) {
// Don't truncate for watched players
if (watchedPlayers[ plrname ])
return true;
var i;
var ev = player.events;
for (i = 0; i < ev.length; i++) {
if (ev[i].time >= limit) break;
}
if (i === 0) return true;
if (i === ev.length) return delete thisPlugin.stored[plrname];
thisPlugin.stored[plrname].events.splice(0, i);
});
};
thisPlugin.eventHasLatLng = function(ev, lat, lng) {
var hasLatLng = false;
$.each(ev.latlngs, function(ind, ll) {
if (ll[0] === lat && ll[1] === lng) {
hasLatLng = true;
return false;
}
});
return hasLatLng;
};
thisPlugin.processNewData = function(data) {
var limit = thisPlugin.getLimit();
var areThereWatchedPlayers = Object.keys( watchedPlayers ).length > 0;
$.each(data.result, function(ind, json) {
// skip old data
// Except for specially watched ones
if (json[1] < limit && !areThereWatchedPlayers) return true;
// find player and portal information
var plrname, lat, lng, id = null, name, address;
var skipThisMessage = false;
$.each(json[2].plext.markup, function(ind, markup) {
switch (markup[0]) {
case 'TEXT':
// Destroy link and field messages depend on where the link or
// field was originally created. Therefore it�s not clear which
// portal the player is at, so ignore it.
if (markup[1].plain.indexOf('destroyed the Link') !== -1 ||
markup[1].plain.indexOf('destroyed a Control Field') !== -1 ||
markup[1].plain.indexOf('Your Link') !== -1) {
skipThisMessage = true;
return false;
}
break;
case 'PLAYER':
plrname = markup[1].plain;
break;
case 'PORTAL':
// link messages are �player linked X to Y� and the player is at
// X.
lat = lat ? lat : markup[1].latE6 / 1E6;
lng = lng ? lng : markup[1].lngE6 / 1E6;
// no GUID in the data any more - but we need some unique string. use the latE6,lngE6
id = lat * 1E6 + ',' + lng * 1E6;
name = name ? name : markup[1].name;
address = address ? address : markup[1].address;
break;
}
});
// skip unusable events
if (!plrname || !lat || !lng || !id || skipThisMessage) return true;
// Now that we know the name, check again the time
if (json[1] < limit && !watchedPlayers[ plrname ]) return true;
var newEvent = {
latlngs: [ [ lat, lng ] ],
ids: [ id ],
time: json[1],
name: name,
address: address
};
var playerData = thisPlugin.stored[plrname];
// short-path if this is a new player
if (!playerData || playerData.events.length === 0) {
thisPlugin.stored[plrname] = {
nick: plrname,
team: json[2].plext.team,
events: [ newEvent ]
};
return true;
}
var evts = playerData.events;
// there�s some data already. Need to find correct place to insert.
var i;
for (i = 0; i < evts.length; i++) {
if (evts[i].time > json[1]) break;
}
var cmp = Math.max(i - 1, 0);
// so we have an event that happened at the same time. Most likely
// this is multiple resos destroyed at the same time.
if (evts[cmp].time === json[1]) {
evts[cmp].latlngs.push([ lat, lng ]);
evts[cmp].ids.push(id);
thisPlugin.stored[plrname].events = evts;
return true;
}
// the time changed. Is the player still at the same location?
// assume this is an older event at the same location. Then we need
// to look at the next item in the event list. If this event is the
// newest one, there may not be a newer event so check for that. If
// it really is an older event at the same location, then skip it.
if (evts[cmp + 1] && thisPlugin.eventHasLatLng(evts[cmp + 1], lat, lng))
return true;
// if this event is newer, need to look at the previous one
var sameLocation = thisPlugin.eventHasLatLng(evts[cmp], lat, lng);
// if it�s the same location, just update the timestamp. Otherwise
// push as new event.
if (sameLocation) {
evts[cmp].time = json[1];
} else {
// If this is not the last event, update the speed of the next one
if (i <= evts.length - 1) {
evts[ i ].speed = computeSpeed(newEvent, evts[ i ]);
}
// If it has a previous event, compute the current speed
if (i > 0) {
newEvent.speed = computeSpeed(evts[ i - 1 ], newEvent );
}
evts.splice(i, 0, newEvent);
}
// update player data
thisPlugin.stored[plrname].events = evts;
});
};
// computes an estimate about the speed
function computeSpeed(srcEvt, destEvt) {
var srcLatLng = thisPlugin.getLatLngFromEvent( srcEvt );
var destLatLng = thisPlugin.getLatLngFromEvent( destEvt );
var distance = srcLatLng.distanceTo( destLatLng );
// If it's less than 400 meters don't take it as a valid/interesting
// because it can be due to destroyed resonators in far away portals
// XMP8 radius: 168m
if (distance < 400)
return '';
var milliSeconds = destEvt.time - srcEvt.time;
//var speed = (distance / 1000) / ( milliSeconds / (60 * 60 * 1000) );
var speed = distance * 60 * 60 / milliSeconds;
return speed.toFixed(2) + 'Km/h.';
}
thisPlugin.getLatLngFromEvent = function(ev) {
//TODO? add weight to certain events, or otherwise prefer them, to give better locations?
var lats = 0;
var lngs = 0;
$.each(ev.latlngs, function(i, latlng) {
lats += latlng[0];
lngs += latlng[1];
});
return L.latLng(lats / ev.latlngs.length, lngs / ev.latlngs.length);
};
thisPlugin.ago = function(time, now) {
if (!time)
return '';
var s = (now - time) / 1000;
var h = Math.floor(s / 3600);
var m = Math.floor( s % 3600 / 60);
var returnVal = m + 'm';
if (h > 0) {
returnVal = h + 'h' + returnVal;
}
return returnVal;
};
// Basic hook to check if a player should be ignored in its current portal
// For example to ignore anomaly portals but keep checking outside the cluster
thisPlugin.showPlayerAtPortal = function( playerData, last ) {
var position = last.latlngs[0];
var latlng = position.join( ',' );
//findPortalGuidByPositionE6(data.portalLatE6, data.portalLngE6 )
if (excludedPortals[ latlng ])
return false;
return true;
};
// Takes care of the initialization of the excluded portals so they aren't computed continously
thisPlugin.initExclusions = function() {
excludedPortals = {};
if (window.plugin.bookmarks && settings.excludedBookmarkFolder) {
var bookmarked = window.plugin.bookmarks.bkmrksObj.portals[ settings.excludedBookmarkFolder ];
if (!bookmarked) {
console.log('Error, exclusion folder ' + settings.excludedBookmarkFolder + ' not found'); // eslint-disable-line no-console
return true;
}
$.each(bookmarked.bkmrk, function(id, portal) {
excludedPortals[ portal.latlng ] = true;
});
}
};
// Extracted so it's executed only when we have to show it, skip these processing otherwise
// Popup with the info about a player
function generatePopupHtmlPlayer(playerData) {
var evtsLength = playerData.events.length;
var last = playerData.events[evtsLength - 1];
var isWatched = watchedPlayers[ playerData.nick ];
var now = new Date().getTime();
var popup = $('<div>')
.addClass('tracker-popup');
$('<span>')
.addClass('nickname ' + (playerData.team === 'RESISTANCE' ? 'res' : 'enl'))
.css('font-weight', 'bold')
.text(playerData.nick)
.appendTo(popup);
var a = $('<a onclick="window.plugin.playerTracker.toggleWatchPlayer(\'' + playerData.nick + '\', this); return false;" title="' + translations.watchThis + '">' + eyeIcon + '</a>');
if (isWatched ) {
a.addClass('tracker-watched');
a.attr('title', translations.stopWatching);
}
a.appendTo(popup);
var copyLink = $('<a onclick="window.plugin.playerTracker.copyPlayer(\'' + playerData.nick + '\'); return false;" title="' + translations.copyHistory + '">' + copyIcon + '</a>');
copyLink.appendTo(popup);
if (window.plugin.guessPlayerLevels !== undefined &&
window.plugin.guessPlayerLevels.fetchLevelDetailsByPlayer !== undefined) {
var level = $('<span>')
.css({ 'font-weight': 'bold', 'margin-left': '10px' })
.appendTo(popup);
var playerLevelDetails = window.plugin.guessPlayerLevels.fetchLevelDetailsByPlayer(playerData.nick);
level
.text( translations.minLevel )
.append(getLevelHtml(playerLevelDetails.min));
if (playerLevelDetails.min != playerLevelDetails.guessed)
level
.append(document.createTextNode( translations.guessedLevel ))
.append(getLevelHtml(playerLevelDetails.guessed));
}
popup
.append('<br>')
.append(document.createTextNode(thisPlugin.ago(last.time, now)))
.append('<br>')
.append(thisPlugin.getPortalLink(last));
if (last.speed && settings.showSpeed) {
popup
.append(document.createTextNode(last.speed));
}
// show previous data in popup
if (evtsLength >= 2) {
popup
.append('<br>')
.append('<br>')
.append(document.createTextNode( translations.previousLocations ))
.append('<br>');
var table = $('<table class="tracker-locations">')
.appendTo(popup)
.css('border-spacing', '0');
for (var j = evtsLength - 2; j >= 0 && j >= evtsLength - 10; j--) {
var ev = playerData.events[j];
var row = $('<tr>')
.append($('<td>')
.text( translations.timeAgo.replace('{0}', thisPlugin.ago(ev.time, now) )))
.append($('<td>')
.append(thisPlugin.getPortalLink(ev)));
if (settings.showSpeed)
row.append( $('<td>' + (ev.speed || '') + '</td>') );
row.appendTo(table);
}
}
return popup[0];
}
thisPlugin.drawData = function() {
//var isTouchDev = window.isTouchDevice();
var gllfe = thisPlugin.getLatLngFromEvent;
var polyLineByAgeEnl = [ [], [], [], [] ];
var polyLineByAgeRes = [ [], [], [], [] ];
var polyLineByAgeWatched = [ [], [], [], [] ];
var split = getMaxTime() / 4;
var now = new Date().getTime();
var smurfs = 0, toads = 0;
var portalsObject = {};
//var ago = thisPlugin.ago;
var bounds = map.getBounds();
$.each(thisPlugin.stored, function(plrname, playerData) {
if (!playerData || playerData.events.length === 0) {
console.warn('broken player data for plrname=' + plrname); // eslint-disable-line no-console
return true;
}
var evtsLength = playerData.events.length;
var last = playerData.events[evtsLength - 1];
var latlng = gllfe(last);
if (bounds.contains(latlng)) {
if (playerData.team === 'RESISTANCE')
smurfs++;
else
toads++;
}
var isWatched = watchedPlayers[ playerData.nick ];
if (!isWatched) {
if (playerData.team === 'RESISTANCE' && !settings.viewRes)
return;
if (playerData.team === 'ENLIGHTENED' && !settings.viewEnl)
return;
}
// Skip portals that are ignored
if (!thisPlugin.showPlayerAtPortal(playerData, last)) {
return;
}
// Draw trails if user has it configured
if (settings.trailsvisible || isWatched) {
// gather line data and put them in buckets so we can color them by
// their age
for (var i = 1; i < evtsLength; i++) {
var p = playerData.events[i];
var ageBucket = Math.min(parseInt((now - p.time) / split), 4 - 1);
var line = [ gllfe(p), gllfe(playerData.events[i - 1]) ];
var polyArray = isWatched ? polyLineByAgeWatched : playerData.team == 'RESISTANCE' ? polyLineByAgeRes : polyLineByAgeEnl;
polyArray[ageBucket].push(line);
}
}
// tooltip for marker
var tooltip = playerData.nick + ', ' + translations.timeAgo.replace('{0}', thisPlugin.ago(last.time, now) );
// calculate the closest portal to the player
var eventPortal = [];
var closestPortal;
var mostPortals = 0;
$.each(last.ids, function(idx, id) {
if (eventPortal[id]) {
eventPortal[id]++;
} else {
eventPortal[id] = 1;
}
if (eventPortal[id] > mostPortals) {
mostPortals = eventPortal[id];
closestPortal = id;
}
});
var markerData = {
time : last.time,
latlng : latlng,
tooltip : tooltip,
playerData : playerData
};
// Group players by portal
if (!portalsObject[ closestPortal ])
portalsObject[ closestPortal ] = [];
portalsObject[ closestPortal ].push( markerData );
playerData.marker = {
options : {
latlng : markerData.latlng,
nick: playerData.nick
}
};
});
addPlayersToMap(portalsObject, now);
// draw the poly lines to the map
drawTracesToMap(polyLineByAgeEnl, settings.tracesColorEnl);
drawTracesToMap(polyLineByAgeRes, settings.tracesColorRes);
drawTracesToMap(polyLineByAgeWatched, settings.tracesColorWatched);
highlightPlayerTracks();
$('#ptrackerTable').html( '<table>' +
'<tr><td>' + translations.Enlightened + '</td><td style="text-align:right"><a href="#" onclick="window.plugin.playerTracker.showFactionStats(\'ENLIGHTENED\'); return false">' + toads + '</a></td>' +
'<td>' + translations.Resistance + '</td><td style="text-align:right"><a href="#" onclick="window.plugin.playerTracker.showFactionStats(\'RESISTANCE\'); return false">' + smurfs + '</a></td></tr>' +
'</table>' );
// Refresh dialogs if they are open
refreshStats('ENLIGHTENED');
refreshStats('RESISTANCE');
var watchedDialog = document.getElementById('dialog-playertracker-watched-players');
if (watchedDialog)
thisPlugin.showWatched();
};
function refreshStats(team) {
var existing = document.getElementById('dialog-playertracker-stats-' + team);
if (existing)
thisPlugin.showFactionStats(team);
}
// Generate the HTML for the Popup when there are multiple players
function generatePopupHtmlMultiple( markerDataArray ) {
var enlPlayers = [];
var resPlayers = [];
for (var i = 0; i < markerDataArray.length; i++) {
var markerData = markerDataArray[ i ];
var team = markerData.playerData.team;
var playerData = markerData.playerData;
var evtsLength = playerData.events.length;
var last = playerData.events[evtsLength - 1];
if (team == 'RESISTANCE') {
resPlayers.push( generatePlayerSummaryObject(playerData, last) );
}
if (team == 'ENLIGHTENED') {
enlPlayers.push( generatePlayerSummaryObject(playerData, last) );
}
}
var content = $('<div style="display:flex">')
.append( formatPlayerTable(enlPlayers, false) )
.append( formatPlayerTable(resPlayers, false) );
return content[0].outerHTML;
}
function addPlayersToMap(portalsObject, now) {
var isTouchDev = window.isTouchDevice();
$.each(portalsObject, function(j, markerDataArray) {
var enlCount = 0;
var resCount = 0;
var time;
var tooltip;
var team;
var markerData;
var nick = '';
var labelText = '';
if (markerDataArray.length == 1) {
markerData = markerDataArray[ 0 ];
time = markerData.time;
//desc = markerData.desc;
tooltip = markerData.tooltip;
team = markerData.playerData.team;
if (team == 'RESISTANCE') {
resCount++;
if (settings.labelsRes)
labelText = tooltip;
}
if (team == 'ENLIGHTENED') {
enlCount++;
if (settings.labelsEnl)
labelText = tooltip;
}
nick = markerData.playerData.nick;
} else {
time = 0;
tooltip = '';
for (var i = 0; i < markerDataArray.length; i++) {
markerData = markerDataArray[ i ];
team = markerData.playerData.team;
if (team == 'RESISTANCE') {
resCount++;
if (settings.labelsRes)
labelText += markerData.tooltip + '<br>';
}
if (team == 'ENLIGHTENED') {
enlCount++;
if (settings.labelsEnl)
labelText += markerData.tooltip + '<br>';
}
if ( markerData.time > time) {
time = markerData.time;
}
tooltip += markerData.tooltip + '\r\n';
}
tooltip = tooltip.substr(0, tooltip.length - 2);
if (labelText.length > 4)
labelText = labelText.substr(0, labelText.length - 4);
}
// the marker itself
var icon = getIcon(enlCount, resCount);
// marker opacity
var relOpacity = 1 - (now - time) / getMaxTime();
var absOpacity = window.PLAYER_TRACKER_MIN_OPACITY + (1 - window.PLAYER_TRACKER_MIN_OPACITY) * relOpacity;
// No tooltip for mobile
if (isTouchDev)
tooltip = '';
var m = L.marker(markerDataArray[0].latlng, { icon: icon, referenceToPortal: markerDataArray[0].closestPortal, opacity: absOpacity, title: tooltip,
latlng: markerDataArray[0].latlng, nick: nick, markerDataArray: markerDataArray });
m.addEventListener('click', thisPlugin.onClickListener);
m.addTo( thisPlugin.drawnTraces );
// labels:
if (labelText) {
var label = L.marker(markerDataArray[0].latlng, {
icon: L.divIcon({
className: 'player-tracker-label',
iconAnchor: [ -5, 25 ],
html: labelText
})
});
label.addTo( thisPlugin.drawnTraces );
}
if (tooltip) {
// ensure tooltips are closed, sometimes they linger
m.on('mouseout', function() { $(this._icon).tooltip('close'); });
// jQueryUI doesn�t automatically notice the new markers
window.setupTooltips($(m._icon));
}
});
}
function drawTracesToMap(polyLineByAge, color) {
$.each(polyLineByAge, function(i, polyLine) {
if (polyLine.length === 0) return true;
var opts = {
weight: 2 - 0.25 * i,
color: color,
clickable: false,
opacity: 1 - 0.2 * i,
dashArray: '5,8'
};
$.each(polyLine, function(ind, poly) {
L.polyline(poly, opts).addTo( thisPlugin.drawnTraces );
});
});
}
function highlightPlayerTracks() {
if (!selectedPlayer)
return;
var playerData = thisPlugin.stored[ selectedPlayer ];
if (!playerData)
return;
clearPlayerTracks();
var polyLineByAge = [ [], [], [], [] ];
var gllfe = thisPlugin.getLatLngFromEvent;
var split = getMaxTime() / 4;
var now = new Date().getTime();
var evtsLength = playerData.events.length;
// gather line data and put them in buckets so we can color them by their age
for (var i = 1; i < evtsLength; i++) {
var p = playerData.events[i];
var ageBucket = Math.min(parseInt((now - p.time) / split), 4 - 1);
var line = [ gllfe(p), gllfe(playerData.events[i - 1]) ];
polyLineByAge[ageBucket].push(line);
}
$.each(polyLineByAge, function(i, polyLine) {
if (polyLine.length === 0) return true;
var opts = {
weight: 3 - 0.2 * i,
color: settings.tracesColorHighlight,
clickable: false,
opacity: 1 - 0.1 * i,
dashArray: '4,6'
};
$.each(polyLine, function(ind, poly) {
var line = L.polyline(poly, opts);
line.addTo( thisPlugin.drawnTraces );
selectedPlayerTracks.push( line );
});
});
}
function clearPlayerTracks(ev) {
if (ev) {
selectedPlayer = '';
}
for (var i = 0; i < selectedPlayerTracks.length; i++) {
thisPlugin.drawnTraces.removeLayer( selectedPlayerTracks[ i ] );
}
selectedPlayerTracks = [];
}
function getLevelHtml(lvl) {
return $('<span>')
.css({
padding: '4px',
color: 'white',
backgroundColor: COLORS_LVL[lvl]
})
.text(lvl);
}
thisPlugin.getPortalLink = function(data) {
if (!data || !data.latlngs)
return $('<span>?</span>');
var position = data.latlngs[0];
var ll = position.join(',');
return $('<a>')
.addClass('text-overflow-ellipsis')
.css('max-width', '15em')
.text(window.chat.getChatPortalName(data))
.prop({
title: window.chat.getChatPortalName(data),
href: '/intel?ll=' + ll + '&pll=' + ll
})
.click(function(event) {
window.selectPortalByLatLng(position);
event.preventDefault();
return false;
})
.dblclick(function(event) {
map.setView(position, 17);
window.selectPortalByLatLng(position);
event.preventDefault();
return false;
});
};
thisPlugin.redrawAll = function() {
thisPlugin.drawnTraces.clearLayers();
thisPlugin.drawData();
};
thisPlugin.handleData = function(data) {
if (window.map.getZoom() < settings.minZoom) return;
thisPlugin.discardOldData();
thisPlugin.processNewData(data);
if (!window.isTouchDevice()) thisPlugin.closeIconTooltips();
thisPlugin.redrawAll();
};
thisPlugin.findUser = function(nick) {
nick = nick.toLowerCase();
var foundPlayerData = false;
$.each(thisPlugin.stored, function(plrname, playerData) {
if (playerData.nick.toLowerCase() === nick) {
foundPlayerData = playerData;
return false;
}
});
return foundPlayerData;
};
thisPlugin.findUserPosition = function(nick) {
var data = thisPlugin.findUser(nick);
if (!data) return false;
var last = data.events[data.events.length - 1];
return thisPlugin.getLatLngFromEvent(last);
};
thisPlugin.centerMapOnUser = function(nick) {
var data = thisPlugin.findUser(nick);
if (!data) return false;
var last = data.events[data.events.length - 1];
var position = thisPlugin.getLatLngFromEvent(last);
if (window.isSmartphone()) window.show('map');
window.map.setView(position, map.getZoom());
if (data.marker) {
thisPlugin.onClickListener({ target: data.marker });
}
return true;
};
thisPlugin.onNicknameClicked = function(info) {
if (info.event.ctrlKey || info.event.metaKey || info.event.target.classList.contains('tracker-player')) {
return !thisPlugin.centerMapOnUser(info.nickname);
}
return true; // don't interrupt hook
};
thisPlugin.onSearchResultSelected = function(result, event) {
event.stopPropagation(); // prevent chat from handling the click
if (window.isSmartphone()) window.show('map');
// if the user moved since the search was started, check if we have a new set of data
if (false === thisPlugin.centerMapOnUser(result.nickname))
map.setView(result.position);
if (event.type == 'dblclick')
map.setZoom(17);
return true;
};
thisPlugin.onSearch = function(query) {
var term = query.term.toLowerCase();
if (term.length && term[0] == '@') term = term.substr(1);
$.each(thisPlugin.stored, function(nick, data) {
if (nick.toLowerCase().indexOf(term) === -1) return;
var event = data.events[data.events.length - 1];
query.addResult({
title: '<mark class="nickname help ' + TEAM_TO_CSS[getTeam(data)] + '">' + nick + '</mark>',
nickname: nick,
description: data.team.substr(0,3) + ', last seen ' + window.unixTimeToDateTimeString(event.time),
position: thisPlugin.getLatLngFromEvent(event),
onSelected: thisPlugin.onSearchResultSelected
});
});
};
thisPlugin.setupUserSearch = function() {
addHook('nicknameClicked', thisPlugin.onNicknameClicked);
addHook('search', thisPlugin.onSearch);
};
var setup = thisPlugin.setup;
// PLUGIN END //////////////////////////////////////////////////////////
setup.info = plugin_info; //add the script info data to the function as a property
if (!window.bootPlugins) window.bootPlugins = [];
window.bootPlugins.push(setup);
// if IITC has already booted, immediately run the 'setup' function
if (window.iitcLoaded && typeof setup === 'function') setup();
} // wrapper end
// inject code into site context
var script = document.createElement('script');
var info = {};
if (typeof GM_info !== 'undefined' && GM_info && GM_info.script) info.script = { version: GM_info.script.version, name: GM_info.script.name, description: GM_info.script.description };
script.appendChild(document.createTextNode('(' + wrapper + ')(' + JSON.stringify(info) + ');'));
(document.body || document.head || document.documentElement).appendChild(script);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment