Created
December 24, 2021 03:18
-
-
Save fortepc/8d0b8d34b123cf0b0d51dfb567064560 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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