diff --git a/dist/total-conversion-build.user.js b/dist/total-conversion-build.user.js
deleted file mode 100644
index ded0b2c0..00000000
--- a/dist/total-conversion-build.user.js
+++ /dev/null
@@ -1,3976 +0,0 @@
-// ==UserScript==
-// @id ingress-intel-total-conversion@jonatkins
-// @name intel map total conversion
-// @version 0.9.0-2013-03-16-200744-jonatkins
-// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
-// @updateURL http://iitc.jonatkins.com/dist/total-conversion-build.user.js
-// @downloadURL http://iitc.jonatkins.com/dist/total-conversion-build.user.js
-// @description total conversion for the ingress intel map. (jonatkins branch)
-// @include http://www.ingress.com/intel*
-// @include https://www.ingress.com/intel*
-// @match http://www.ingress.com/intel*
-// @match https://www.ingress.com/intel*
-// ==/UserScript==
-
-
-// REPLACE ORIG SITE ///////////////////////////////////////////////////
-if(document.getElementsByTagName('html')[0].getAttribute('itemscope') != null)
- throw('Ingress Intel Website is down, not a userscript issue.');
-window.iitcBuildDate = '2013-03-16-200744';
-
-// disable vanilla JS
-window.onload = function() {};
-
-
-// rescue user data from original page
-var scr = document.getElementsByTagName('script');
-for(var x in scr) {
- var s = scr[x];
- if(s.src) continue;
- if(s.type !== 'text/javascript') continue;
- var d = s.innerHTML.split('\n');
- break;
-}
-
-
-if(!d) {
- // page doesn’t have a script tag with player information.
- if(document.getElementById('header_email')) {
- // however, we are logged in.
- setTimeout('location.reload();', 3*1000);
- throw('Page doesn’t have player data, but you are logged in. Reloading in 3s.');
- }
- // FIXME: handle nia takedown in progress
- throw('Couldn’t retrieve player data. Are you logged in?');
-}
-
-
-for(var i = 0; i < d.length; i++) {
- if(!d[i].match('var PLAYER = ')) continue;
- eval(d[i].match(/^var /, 'window.'));
- break;
-}
-// player information is now available in a hash like this:
-// window.PLAYER = {"ap": "123", "energy": 123, "available_invites": 123, "nickname": "somenick", "team": "ALIENS||RESISTANCE"};
-
-// remove complete page. We only wanted the user-data and the page’s
-// security context so we can access the API easily. Setup as much as
-// possible without requiring scripts.
-document.getElementsByTagName('head')[0].innerHTML = ''
- + '
Ingress Intel Map'
- + ''
- + ''
- // this navigator check is also used in code/smartphone.js
- + (navigator.userAgent.match(/Android.*Mobile/)
- ? ''
- : '')
- + '';
-
-document.getElementsByTagName('body')[0].innerHTML = ''
- + '
'
- + ''
- + '';
-
-// putting everything in a wrapper function that in turn is placed in a
-// script tag on the website allows us to execute in the site’s context
-// instead of in the Greasemonkey/Extension/etc. context.
-function wrapper() {
-
-// LEAFLET PREFER CANVAS ///////////////////////////////////////////////
-// Set to true if Leaflet should draw things using Canvas instead of SVG
-// Disabled for now because it has several bugs: flickering, constant
-// CPU usage and it continuously fires the moveend event.
-L_PREFER_CANVAS = false;
-
-// CONFIG OPTIONS ////////////////////////////////////////////////////
-window.REFRESH = 30; // refresh view every 30s (base time)
-window.ZOOM_LEVEL_ADJ = 5; // add 5 seconds per zoom level
-window.REFRESH_GAME_SCORE = 5*60; // refresh game score every 5 minutes
-window.MAX_IDLE_TIME = 4; // stop updating map after 4min idling
-window.PRECACHE_PLAYER_NAMES_ZOOM = 17; // zoom level to start pre-resolving player names
-window.HIDDEN_SCROLLBAR_ASSUMED_WIDTH = 20;
-window.SIDEBAR_WIDTH = 300;
-// chat messages are requested for the visible viewport. On high zoom
-// levels this gets pretty pointless, so request messages in at least a
-// X km radius.
-window.CHAT_MIN_RANGE = 6;
-// this controls how far data is being drawn outside the viewport. Set
-// it 0 to only draw entities that intersect the current view. A value
-// of one will render an area twice the size of the viewport (or some-
-// thing like that, Leaflet doc isn’t too specific). Setting it too low
-// makes the missing data on move/zoom out more obvious. Setting it too
-// high causes too many items to be drawn, making drag&drop sluggish.
-window.VIEWPORT_PAD_RATIO = 0.3;
-
-// how many items to request each query
-window.CHAT_PUBLIC_ITEMS = 200;
-window.CHAT_FACTION_ITEMS = 50;
-// how many pixels to the top before requesting new data
-window.CHAT_REQUEST_SCROLL_TOP = 200;
-window.CHAT_SHRINKED = 60;
-
-// Leaflet will get very slow for MANY items. It’s better to display
-// only some instead of crashing the browser.
-window.MAX_DRAWN_PORTALS = 1000;
-window.MAX_DRAWN_LINKS = 400;
-window.MAX_DRAWN_FIELDS = 200;
-// Minimum zoom level resonator will display
-window.RESONATOR_DISPLAY_ZOOM_LEVEL = 17;
-// Minimum area to zoom ratio that field MU's will display
-window.FIELD_MU_DISPLAY_AREA_ZOOM_RATIO = 0.001;
-// Point tolerance for displaying MU's
-window.FIELD_MU_DISPLAY_POINT_TOLERANCE = 60
-
-window.COLOR_SELECTED_PORTAL = '#f00';
-window.COLORS = ['#FFCE00', '#0088FF', '#03DC03']; // none, res, enl
-window.COLORS_LVL = ['#000', '#FECE5A', '#FFA630', '#FF7315', '#E40000', '#FD2992', '#EB26CD', '#C124E0', '#9627F4'];
-window.COLORS_MOD = {VERY_RARE: '#F78AF6', RARE: '#AD8AFF', COMMON: '#84FBBD'};
-
-window.OPTIONS_RESONATOR_SELECTED = { color: '#fff', weight: 2, radius: 4};
-window.OPTIONS_RESONATOR_NON_SELECTED = { color: '#aaa', weight: 1, radius: 3};
-
-window.OPTIONS_RESONATOR_LINE_SELECTED = {opacity: 0.7, weight: 3};
-window.OPTIONS_RESONATOR_LINE_NON_SELECTED = {opacity: 0.25, weight: 2};
-
-// circles around a selected portal that show from where you can hack
-// it and how far the portal reaches (i.e. how far links may be made
-// from this portal)
-window.ACCESS_INDICATOR_COLOR = 'orange';
-window.RANGE_INDICATOR_COLOR = 'red'
-
-// by how much pixels should the portal range be expanded on mobile
-// devices. This should make clicking them easier.
-window.PORTAL_RADIUS_ENLARGE_MOBILE = 5;
-
-
-window.DEFAULT_PORTAL_IMG = 'https://commondatastorage.googleapis.com/ingress/img/default-portal-image.png';
-window.NOMINATIM = 'http://nominatim.openstreetmap.org/search?format=json&limit=1&q=';
-
-// INGRESS CONSTANTS /////////////////////////////////////////////////
-// http://decodeingress.me/2012/11/18/ingress-portal-levels-and-link-range/
-window.RESO_NRG = [0, 1000, 1500, 2000, 2500, 3000, 4000, 5000, 6000];
-window.MAX_XM_PER_LEVEL = [0, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000];
-window.MIN_AP_FOR_LEVEL = [0, 10000, 30000, 70000, 150000, 300000, 600000, 1200000];
-window.HACK_RANGE = 40; // in meters, max. distance from portal to be able to access it
-window.OCTANTS = ['E', 'NE', 'N', 'NW', 'W', 'SW', 'S', 'SE'];
-window.DESTROY_RESONATOR = 75; //AP for destroying portal
-window.DESTROY_LINK = 187; //AP for destroying link
-window.DESTROY_FIELD = 750; //AP for destroying field
-window.CAPTURE_PORTAL = 500; //AP for capturing a portal
-window.DEPLOY_RESONATOR = 125; //AP for deploying a resonator
-window.COMPLETION_BONUS = 250; //AP for deploying all resonators on portal
-window.UPGRADE_ANOTHERS_RESONATOR = 65; //AP for upgrading another's resonator
-window.MAX_PORTAL_LEVEL = 8;
-window.MAX_RESO_PER_PLAYER = [0, 8, 4, 4, 4, 2, 2, 1, 1];
-
-// OTHER MORE-OR-LESS CONSTANTS //////////////////////////////////////
-window.TEAM_NONE = 0;
-window.TEAM_RES = 1;
-window.TEAM_ENL = 2;
-window.TEAM_TO_CSS = ['none', 'res', 'enl'];
-window.TYPE_UNKNOWN = 0;
-window.TYPE_PORTAL = 1;
-window.TYPE_LINK = 2;
-window.TYPE_FIELD = 3;
-window.TYPE_PLAYER = 4;
-window.TYPE_CHAT = 5;
-window.TYPE_RESONATOR = 6;
-
-window.SLOT_TO_LAT = [0, Math.sqrt(2)/2, 1, Math.sqrt(2)/2, 0, -Math.sqrt(2)/2, -1, -Math.sqrt(2)/2];
-window.SLOT_TO_LNG = [1, Math.sqrt(2)/2, 0, -Math.sqrt(2)/2, -1, -Math.sqrt(2)/2, 0, Math.sqrt(2)/2];
-window.EARTH_RADIUS=6378137;
-window.DEG2RAD = Math.PI / 180;
-
-// STORAGE ///////////////////////////////////////////////////////////
-// global variables used for storage. Most likely READ ONLY. Proper
-// way would be to encapsulate them in an anonymous function and write
-// getters/setters, but if you are careful enough, this works.
-var refreshTimeout;
-var urlPortal = null;
-window.playersToResolve = [];
-window.playersInResolving = [];
-window.selectedPortal = null;
-window.portalRangeIndicator = null;
-window.portalAccessIndicator = null;
-window.mapRunsUserAction = false;
-var portalsLayers, linksLayer, fieldsLayer;
-
-// contain references to all entities shown on the map. These are
-// automatically kept in sync with the items on *sLayer, so never ever
-// write to them.
-window.portals = {};
-window.links = {};
-window.fields = {};
-window.resonators = {};
-
-// plugin framework. Plugins may load earlier than iitc, so don’t
-// overwrite data
-if(typeof window.plugin !== 'function') window.plugin = function() {};
-
-
-
-
-
-
-// PORTAL DETAILS TOOLS //////////////////////////////////////////////
-// hand any of these functions the details-hash of a portal, and they
-// will return useful, but raw data.
-
-// returns a float. Displayed portal level is always rounded down from
-// that value.
-window.getPortalLevel = function(d) {
- var lvl = 0;
- var hasReso = false;
- $.each(d.resonatorArray.resonators, function(ind, reso) {
- if(!reso) return true;
- lvl += parseInt(reso.level);
- hasReso = true;
- });
- return hasReso ? Math.max(1, lvl/8) : 0;
-}
-
-window.getTotalPortalEnergy = function(d) {
- var nrg = 0;
- $.each(d.resonatorArray.resonators, function(ind, reso) {
- if(!reso) return true;
- var level = parseInt(reso.level);
- var max = RESO_NRG[level];
- nrg += max;
- });
- return nrg;
-}
-
-// For backwards compatibility
-window.getPortalEnergy = window.getTotalPortalEnergy;
-
-window.getCurrentPortalEnergy = function(d) {
- var nrg = 0;
- $.each(d.resonatorArray.resonators, function(ind, reso) {
- if(!reso) return true;
- nrg += parseInt(reso.energyTotal);
- });
- return nrg;
-}
-
-window.getPortalRange = function(d) {
- // formula by the great gals and guys at
- // http://decodeingress.me/2012/11/18/ingress-portal-levels-and-link-range/
-
- var lvl = 0;
- var resoMissing = false;
- $.each(d.resonatorArray.resonators, function(ind, reso) {
- if(!reso) {
- resoMissing = true;
- return false;
- }
- lvl += parseInt(reso.level);
- });
- if(resoMissing) return 0;
- return 160*Math.pow(getPortalLevel(d), 4);
-}
-
-window.getAvgResoDist = function(d) {
- var sum = 0, resos = 0;
- $.each(d.resonatorArray.resonators, function(ind, reso) {
- if(!reso) return true;
- sum += parseInt(reso.distanceToPortal);
- resos++;
- });
- return resos ? sum/resos : 0;
-}
-
-window.getAttackApGain = function(d) {
- var resoCount = 0;
- var maxResonators = MAX_RESO_PER_PLAYER.slice(0);
- var curResonators = [ 0, 0, 0, 0, 0, 0, 0, 0, 0];
-
- for(var n = PLAYER.level + 1; n < 9; n++) {
- maxResonators[n] = 0;
- }
- $.each(d.resonatorArray.resonators, function(ind, reso) {
- if(!reso)
- return true;
- resoCount += 1;
- if(reso.ownerGuid === PLAYER.guid) {
- maxResonators[parseInt(reso.level)] -= 1;
- } else {
- curResonators[parseInt(reso.level)] += 1;
- }
- });
-
- var linkCount = d.portalV2.linkedEdges ? d.portalV2.linkedEdges.length : 0;
- var fieldCount = d.portalV2.linkedFields ? d.portalV2.linkedFields.length : 0;
-
- var resoAp = resoCount * DESTROY_RESONATOR;
- var linkAp = linkCount * DESTROY_LINK;
- var fieldAp = fieldCount * DESTROY_FIELD;
- var destroyAp = resoAp + linkAp + fieldAp;
- var captureAp = CAPTURE_PORTAL + 8 * DEPLOY_RESONATOR + COMPLETION_BONUS;
- var enemyAp = destroyAp + captureAp;
- var deployCount = 8 - resoCount;
- var completionAp = (deployCount > 0) ? COMPLETION_BONUS : 0;
- var upgradeCount = 0;
- var upgradeAvailable = maxResonators[8];
- for(var n = 7; n >= 0; n--) {
- upgradeCount += curResonators[n];
- if(upgradeAvailable < upgradeCount) {
- upgradeCount -= (upgradeCount - upgradeAvailable);
- }
- upgradeAvailable += maxResonators[n];
- }
- var friendlyAp = deployCount * DEPLOY_RESONATOR + upgradeCount * UPGRADE_ANOTHERS_RESONATOR + completionAp;
- return {
- friendlyAp: friendlyAp,
- deployCount: deployCount,
- upgradeCount: upgradeCount,
- enemyAp: enemyAp,
- destroyAp: destroyAp,
- resoAp: resoAp,
- captureAp: captureAp
- };
-}
-
-
-
-// PORTAL RENDER LIMIT HANDLER ///////////////////////////////////////
-// Functions to handle hiding low level portal when portal render
-// limit is reached.
-//
-// On initialization, previous minLevel will preserve to previousMinLevel
-// with zoom level difference.
-//
-// After initialized and reset in window.requestData(), "processPortals"
-// intercept all portals data in "handleDataResponse". Put the count of
-// new portals to newPortalsPerLevel[portal level]. And split portals
-// into two parts base on previousMinLevel. Portals with level >=
-// previousMinLevel will return as result and continue to render.
-// Others will save to portalsPreviousMinLevel. If there is no more
-// active request of map data, portals will not split and
-// portalsPreviousMinLevel will add back to result and render base on
-// current minLevel.
-//
-// "handleFailRequest" is added to handle the case when the last request
-// failed and "processPortals" didn't get called. It will get
-// portalsPreviousMinLevel base on current minLevel and render them.
-//
-// "getMinLevel" will be called by "getMinPortalLevel" in utils_misc.js
-// to determine min portal level to draw on map.
-//
-// "getMinLevel" will return minLevel and call "setMinLevel" if
-// minLevel hasn't set yet.
-//
-// In "setMinLevel", it will loop through all portal level from
-// high to low, and sum total portal count (old + new) to check
-// minLevel.
-//
-// In each call of window.handleDataResponse(), it will call
-// "resetCounting" to reset previous response data. But minLevel
-// is preserved and only replaced when render limit reached in
-// higher level, until next window.requestData() called and reset.
-//
-
-window.portalRenderLimit = function() {}
-
-window.portalRenderLimit.initialized = false;
-window.portalRenderLimit.minLevelSet = false;
-window.portalRenderLimit.minLevel = -1;
-window.portalRenderLimit.previousMinLevel = -1;
-window.portalRenderLimit.previousZoomLevel;
-window.portalRenderLimit.newPortalsPerLevel = new Array(MAX_PORTAL_LEVEL + 1);
-window.portalRenderLimit.portalsPreviousMinLevel = new Array(MAX_PORTAL_LEVEL + 1);
-
-window.portalRenderLimit.init = function () {
- var currentZoomLevel = map.getZoom();
- portalRenderLimit.previousZoomLevel = portalRenderLimit.previousZoomLevel || currentZoomLevel;
-
- // If there is a minLevel set in previous run, calculate previousMinLevel with it.
- if(portalRenderLimit.minLevelSet) {
- var zoomDiff = currentZoomLevel - portalRenderLimit.previousZoomLevel;
- portalRenderLimit.previousMinLevel = Math.max(portalRenderLimit.minLevel - zoomDiff, -1);
- portalRenderLimit.previousMinLevel = Math.min(portalRenderLimit.previousMinLevel, MAX_PORTAL_LEVEL);
- }
-
- portalRenderLimit.previousZoomLevel = currentZoomLevel;
-
- portalRenderLimit.initialized = true;
- portalRenderLimit.minLevel = -1;
- portalRenderLimit.resetCounting();
- portalRenderLimit.resetPortalsPreviousMinLevel();
-}
-
-window.portalRenderLimit.resetCounting = function() {
- portalRenderLimit.minLevelSet = false;
- for(var i = 0; i <= MAX_PORTAL_LEVEL; i++) {
- portalRenderLimit.newPortalsPerLevel[i] = 0;
- }
-}
-
-window.portalRenderLimit.resetPortalsPreviousMinLevel = function() {
- for(var i = 0; i <= MAX_PORTAL_LEVEL; i++) {
- portalRenderLimit.portalsPreviousMinLevel[i] = new Array();
- }
-}
-
-window.portalRenderLimit.splitOrMergeLowLevelPortals = function(originPortals) {
- portalRenderLimit.resetCounting();
- portalRenderLimit.countingPortals(originPortals);
-
- var resultPortals = requests.isLastRequest('getThinnedEntitiesV2')
- ? portalRenderLimit.mergeLowLevelPortals(originPortals)
- : portalRenderLimit.splitLowLevelPortals(originPortals);
-
- return resultPortals;
-}
-
-window.portalRenderLimit.countingPortals = function(portals) {
- $.each(portals, function(ind, portal) {
- var portalGuid = portal[0];
- var portalLevel = parseInt(getPortalLevel(portal[2]));
- var layerGroup = portalsLayers[portalLevel];
-
- if(findEntityInLeaflet(layerGroup, window.portals, portalGuid)) return true;
-
- portalRenderLimit.newPortalsPerLevel[portalLevel]++;
- });
-}
-
-window.portalRenderLimit.splitLowLevelPortals = function(portals) {
- var resultPortals = new Array();
- $.each(portals, function(ind, portal) {
- var portalLevel = parseInt(getPortalLevel(portal[2]));
- if(portalLevel < portalRenderLimit.previousMinLevel) {
- portalRenderLimit.portalsPreviousMinLevel[portalLevel].push(portal);
- }else{
- resultPortals.push(portal);
- }
- });
- return resultPortals;
-}
-
-window.portalRenderLimit.mergeLowLevelPortals = function(appendTo) {
- var resultPortals = appendTo ? appendTo : new Array();
- for(var i = portalRenderLimit.getMinLevel();
- i < portalRenderLimit.previousMinLevel;
- i++) {
- $.merge(resultPortals, portalRenderLimit.portalsPreviousMinLevel[i]);
- }
-
- // Reset portalsPreviousMinLevel, ensure they return only once
- portalRenderLimit.resetPortalsPreviousMinLevel();
- return resultPortals;
-}
-
-window.portalRenderLimit.getMinLevel = function() {
- if(!portalRenderLimit.initialized) return -1;
- if(!portalRenderLimit.minLevelSet) portalRenderLimit.setMinLevel();
- return portalRenderLimit.minLevel;
-}
-
-window.portalRenderLimit.setMinLevel = function() {
- var totalPortalsCount = 0;
- var newMinLevel = MAX_PORTAL_LEVEL + 1;
-
- // Find the min portal level under render limit
- while(newMinLevel > 0) {
- var oldPortalCount = layerGroupLength(portalsLayers[newMinLevel - 1]);
- var newPortalCount = portalRenderLimit.newPortalsPerLevel[newMinLevel - 1];
- totalPortalsCount += oldPortalCount + newPortalCount;
- if(totalPortalsCount >= MAX_DRAWN_PORTALS)
- break;
- newMinLevel--;
- }
-
- // If render limit reached at max portal level, still let portal at max level render
- newMinLevel = Math.min(newMinLevel, MAX_PORTAL_LEVEL);
-
- portalRenderLimit.minLevel = Math.max(newMinLevel, portalRenderLimit.minLevel);
- portalRenderLimit.minLevelSet = true;
-}
-
-
-// PLUGIN HOOKS ////////////////////////////////////////////////////////
-// Plugins may listen to any number of events by specifying the name of
-// the event to listen to and handing a function that should be exe-
-// cuted when an event occurs. Callbacks will receive additional data
-// the event created as their first parameter. The value is always a
-// hash that contains more details.
-//
-// For example, this line will listen for portals to be added and print
-// the data generated by the event to the console:
-// window.addHook('portalAdded', function(data) { console.log(data) });
-//
-// Boot hook: booting is handled differently because IITC may not yet
-// be available. Have a look at the plugins in plugins/. All
-// code before “// PLUGIN START” and after “// PLUGIN END” is
-// required to successfully boot the plugin.
-//
-// Here’s more specific information about each event:
-// portalAdded: called when a portal has been received and is about to
-// be added to its layer group. Note that this does NOT
-// mean it is already visible or will be, shortly after.
-// If a portal is added to a hidden layer it may never be
-// shown at all. Injection point is in
-// code/map_data.js#renderPortal near the end. Will hand
-// the Leaflet CircleMarker for the portal in "portal" var.
-// portalDetailsUpdated: fired after the details in the sidebar have
-// been (re-)rendered Provides data about the portal that
-// has been selected.
-// publicChatDataAvailable: this hook runs after data for any of the
-// public chats has been received and processed, but not
-// yet been displayed. The data hash contains both the un-
-// processed raw ajax response as well as the processed
-// chat data that is going to be used for display.
-// factionChatDataAvailable: this hook runs after data for the faction
-// chat has been received and processed, but not yet been
-// displayed. The data hash contains both the unprocessed
-// raw ajax response as well as the processed chat data
-// that is going to be used for display.
-// portalDataLoaded: callback is passed the argument of
-// {portals : [portal, portal, ...]} where "portal" is the
-// data element and not the leaflet object. "portal" is an
-// array [GUID, time, details]. Plugin can manipulate the
-// array to change order or add additional values to the
-// details of a portal.
-// beforePortalReRender: the callback argument is
-// {portal: ent[2], oldPortal : d, reRender : false}.
-// The callback needs to update the value of reRender to
-// true if the plugin has a reason to have the portal
-// redrawn. It is called early on in the
-// code/map_data.js#renderPortal as long as there was an
-// old portal for the guid.
-// checkRenderLimit: callback is passed the argument of
-// {reached : false} to indicate that the renderlimit is reached
-// set reached to true.
-// requestFinished: called after each request finished. Argument is
-// {success: boolean} indicated the request success or fail.
-
-
-
-window._hooks = {}
-window.VALID_HOOKS = ['portalAdded', 'portalDetailsUpdated',
- 'publicChatDataAvailable', 'factionChatDataAvailable', 'portalDataLoaded',
- 'beforePortalReRender', 'checkRenderLimit', 'requestFinished'];
-
-window.runHooks = function(event, data) {
- if(VALID_HOOKS.indexOf(event) === -1) throw('Unknown event type: ' + event);
-
- if(!_hooks[event]) return;
- $.each(_hooks[event], function(ind, callback) {
- callback(data);
- });
-}
-
-
-window.addHook = function(event, callback) {
- if(VALID_HOOKS.indexOf(event) === -1) throw('Unknown event type: ' + event);
- if(typeof callback !== 'function') throw('Callback must be a function.');
-
- if(!_hooks[event])
- _hooks[event] = [callback];
- else
- _hooks[event].push(callback);
-}
-
-
-
-// DEBUGGING TOOLS ///////////////////////////////////////////////////
-// meant to be used from browser debugger tools and the like.
-
-window.debug = function() {}
-
-window.debug.renderDetails = function() {
- console.log('portals: ' + Object.keys(portals).length);
- console.log('links: ' + Object.keys(links).length);
- console.log('fields: ' + Object.keys(fields).length);
-}
-
-window.debug.printStackTrace = function() {
- var e = new Error('dummy');
- console.log(e.stack);
- return e.stack;
-}
-
-window.debug.clearPortals = function() {
- for(var i = 0; i < portalsLayers.length; i++)
- portalsLayers[i].clearLayers();
-}
-
-window.debug.clearLinks = function() {
- linksLayer.clearLayers();
-}
-
-window.debug.clearFields = function() {
- fieldsLayer.clearLayers();
-}
-
-window.debug.getFields = function() {
- return fields;
-}
-
-window.debug.forceSync = function() {
- localStorage.clear();
- window.playersToResolve = [];
- window.playersInResolving = [];
- debug.clearFields();
- debug.clearLinks();
- debug.clearPortals();
- updateGameScore();
- requestData();
-}
-
-window.debug.console = function() {
- $('#debugconsole').text();
-}
-
-window.debug.console.create = function() {
- if($('#debugconsole').length) return;
- $('#chatcontrols').append('debug');
- $('#chatcontrols a:last').click(function() {
- $('#chatinput mark').css('cssText', 'color: #bbb !important').text('debug:');
- $('#chat > div').hide();
- $('#debugconsole').show();
- $('#chatcontrols .active').removeClass('active');
- $(this).addClass('active');
- });
- $('#chat').append('
');
-}
-
-window.debug.console.renderLine = function(text, errorType) {
- debug.console.create();
- switch(errorType) {
- case 'error': var color = '#FF424D'; break;
- case 'warning': var color = '#FFDE42'; break;
- default: var color = '#eee';
- }
- if(typeof text !== 'string' && typeof text !== 'number') {
- var cache = [];
- text = JSON.stringify(text, function(key, value) {
- if(typeof value === 'object' && value !== null) {
- if(cache.indexOf(value) !== -1) {
- // Circular reference found, discard key
- return;
- }
- // Store value in our collection
- cache.push(value);
- }
- return value;
- });
- cache = null;
- }
- var d = new Date();
- var ta = d.toLocaleTimeString(); // print line instead maybe?
- var tb = d.toLocaleString();
- var t = '';
- var s = 'style="color:'+color+'"';
- var l = '
'+t+'
'+errorType+'
'+text+'
';
- $('#debugconsole table').prepend(l);
-}
-
-window.debug.console.log = function(text) {
- debug.console.renderLine(text, 'notice');
-}
-
-window.debug.console.warn = function(text) {
- debug.console.renderLine(text, 'warning');
-}
-
-window.debug.console.error = function(text) {
- debug.console.renderLine(text, 'error');
-}
-
-window.debug.console.overwriteNative = function() {
- window.debug.console.create();
- window.console = function() {}
- window.console.log = window.debug.console.log;
- window.console.warn = window.debug.console.warn;
- window.console.error = window.debug.console.error;
-}
-
-window.debug.console.overwriteNativeIfRequired = function() {
- if(!window.console || L.Browser.mobile)
- window.debug.console.overwriteNative();
-}
-
-
-// PORTAL DETAILS DISPLAY ////////////////////////////////////////////
-// hand any of these functions the details-hash of a portal, and they
-// will return pretty, displayable HTML or parts thereof.
-
-// returns displayable text+link about portal range
-window.getRangeText = function(d) {
- var range = getPortalRange(d);
- return ['range',
- ''
- + (range > 1000
- ? Math.round(range/1000) + ' km'
- : Math.round(range) + ' m')
- + ''];
-}
-
-// generates description text from details for portal
-window.getPortalDescriptionFromDetails = function(details) {
- var descObj = details.portalV2.descriptiveText;
- // FIXME: also get real description?
- var desc = descObj.TITLE + '\n' + descObj.ADDRESS;
- if(descObj.ATTRIBUTION)
- desc += '\nby '+descObj.ATTRIBUTION+' ('+descObj.ATTRIBUTION_LINK+')';
- return desc;
-}
-
-
-// given portal details, returns html code to display mod details.
-window.getModDetails = function(d) {
- var mods = [];
- var modsTitle = [];
- var modsColor = [];
- $.each(d.portalV2.linkedModArray, function(ind, mod) {
- if(!mod) {
- mods.push('');
- modsTitle.push('');
- modsColor.push('#000');
- } else if(mod.type === 'RES_SHIELD') {
-
- var title = mod.rarity.capitalize() + ' ' + mod.displayName + '\n';
- title += 'Installed by: '+ getPlayerName(mod.installingUser);
-
- title += '\nStats:';
- for (var key in mod.stats) {
- if (!mod.stats.hasOwnProperty(key)) continue;
- title += '\n+' + mod.stats[key] + ' ' + key.capitalize();
- }
-
- mods.push(mod.rarity.capitalize().replace('_', ' ') + ' ' + mod.displayName);
- modsTitle.push(title);
- modsColor.push(COLORS_MOD[mod.rarity]);
- } else {
- mods.push(mod.type);
- modsTitle.push('Unknown mod. No further details available.');
- modsColor.push('#FFF');
- }
- });
-
- var t = ''+mods[0]+''
- + ''+mods[1]+''
- + ''+mods[2]+''
- + ''+mods[3]+''
-
- return t;
-}
-
-window.getEnergyText = function(d) {
- var currentNrg = getCurrentPortalEnergy(d);
- var totalNrg = getTotalPortalEnergy(d);
- var inf = currentNrg + ' / ' + totalNrg;
- var fill = prettyEnergy(currentNrg) + ' / ' + prettyEnergy(totalNrg)
- return ['energy', '' + fill + ''];
-}
-
-window.getAvgResoDistText = function(d) {
- var avgDist = Math.round(10*getAvgResoDist(d))/10;
- return ['reso dist', avgDist + ' m'];
-}
-
-window.getResonatorDetails = function(d) {
- var resoDetails = [];
- // octant=slot: 0=E, 1=NE, 2=N, 3=NW, 4=W, 5=SW, 6=S, SE=7
- // resos in the display should be ordered like this:
- // N NE Since the view is displayed in columns, they
- // NW E need to be ordered like this: N, NW, W, SW, NE,
- // W SE E, SE, S, i.e. 2 3 4 5 1 0 7 6
- // SW S
-
- $.each([2, 1, 3, 0, 4, 7, 5, 6], function(ind, slot) {
- var reso = d.resonatorArray.resonators[slot];
- if(!reso) {
- resoDetails.push(renderResonatorDetails(slot, 0, 0, null, null));
- return true;
- }
-
- var l = parseInt(reso.level);
- var v = parseInt(reso.energyTotal);
- var nick = window.getPlayerName(reso.ownerGuid);
- var dist = reso.distanceToPortal;
- // if array order and slot order drift apart, at least the octant
- // naming will still be correct.
- slot = parseInt(reso.slot);
-
- resoDetails.push(renderResonatorDetails(slot, l, v, dist, nick));
- });
- return genFourColumnTable(resoDetails);
-}
-
-// helper function that renders the HTML for a given resonator. Does
-// not work with raw details-hash. Needs digested infos instead:
-// slot: which slot this resonator occupies. Starts with 0 (east) and
-// rotates clockwise. So, last one is 7 (southeast).
-window.renderResonatorDetails = function(slot, level, nrg, dist, nick) {
- if(level === 0) {
- var meter = '';
- } else {
- var max = RESO_NRG[level];
- var fillGrade = nrg/max*100;
-
- var inf = 'energy:\t' + nrg + ' / ' + max + ' (' + Math.round(fillGrade) + '%)\n'
- + 'level:\t' + level + '\n'
- + 'distance:\t' + dist + 'm\n'
- + 'owner:\t' + nick + '\n'
- + 'octant:\t' + OCTANTS[slot];
-
- var style = 'width:'+fillGrade+'%; background:'+COLORS_LVL[level]+';';
-
- var color = (level < 3 ? "#9900FF" : "#FFFFFF");
-
- var lbar = ' ' + level + ' ';
-
- var fill = '';
-
- var meter = '' + fill + lbar + '';
- }
- nick = nick ? ''+nick+'' : null;
- return [meter, nick || ''];
-}
-
-// calculate AP gain from destroying portal and then capturing it by deploying resonators
-window.getAttackApGainText = function(d) {
- var breakdown = getAttackApGain(d);
- var totalGain = breakdown.enemyAp;
-
- function tt(text) {
- var t = '';
- if (PLAYER.team == d.controllingTeam.team) {
- totalGain = breakdown.friendlyAp;
- t += 'Friendly AP:\t' + breakdown.friendlyAp + '\n';
- t += ' Deploy ' + breakdown.deployCount + ', ';
- t += 'Upgrade ' + breakdown.upgradeCount + '\n';
- t += '\n';
- }
- t += 'Enemy AP:\t' + breakdown.enemyAp + '\n';
- t += ' Destroy AP:\t' + breakdown.destroyAp + '\n';
- t += ' Capture AP:\t' + breakdown.captureAp + '\n';
- return '' + digits(text) + '';
- }
-
- return [tt('AP Gain'), tt(totalGain)];
-}
-
-
-
-
-// UTILS + MISC ///////////////////////////////////////////////////////
-
-window.layerGroupLength = function(layerGroup) {
- var layersCount = 0;
- var layers = layerGroup._layers;
- if (layers)
- layersCount = Object.keys(layers).length;
- return layersCount;
-}
-
-// retrieves parameter from the URL?query=string.
-window.getURLParam = function(param) {
- var v = document.URL;
- var i = v.indexOf(param);
- if(i <= -1) return '';
- v = v.substr(i);
- i = v.indexOf("&");
- if(i >= 0) v = v.substr(0, i);
- return v.replace(param+"=","");
-}
-
-// read cookie by name.
-// http://stackoverflow.com/a/5639455/1684530 by cwolves
-var cookies;
-window.readCookie = function(name,c,C,i){
- if(cookies) return cookies[name];
- c = document.cookie.split('; ');
- cookies = {};
- for(i=c.length-1; i>=0; i--){
- C = c[i].split('=');
- cookies[C[0]] = unescape(C[1]);
- }
- return cookies[name];
-}
-
-window.writeCookie = function(name, val) {
- document.cookie = name + "=" + val + '; expires=Thu, 31 Dec 2020 23:59:59 GMT; path=/';
-}
-
-// add thousand separators to given number.
-// http://stackoverflow.com/a/1990590/1684530 by Doug Neiner.
-window.digits = function(d) {
- return (d+"").replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1 ");
-}
-
-// posts AJAX request to Ingress API.
-// action: last part of the actual URL, the rpc/dashboard. is
-// added automatically
-// data: JSON data to post. method will be derived automatically from
-// action, but may be overridden. Expects to be given Hash.
-// Strings are not supported.
-// success: method to call on success. See jQuery API docs for avail-
-// able arguments: http://api.jquery.com/jQuery.ajax/
-// error: see above. Additionally it is logged if the request failed.
-window.postAjax = function(action, data, success, error) {
- data = JSON.stringify($.extend({method: 'dashboard.'+action}, data));
- var remove = function(data, textStatus, jqXHR) { window.requests.remove(jqXHR); };
- var errCnt = function(jqXHR) { window.failedRequestCount++; window.requests.remove(jqXHR); };
- var result = $.ajax({
- // use full URL to avoid issues depending on how people set their
- // slash. See:
- // https://github.com/breunigs/ingress-intel-total-conversion/issues/56
- url: 'https://www.ingress.com/rpc/dashboard.'+action,
- type: 'POST',
- data: data,
- dataType: 'json',
- success: [remove, success],
- error: error ? [errCnt, error] : errCnt,
- contentType: 'application/json; charset=utf-8',
- beforeSend: function(req) {
- req.setRequestHeader('X-CSRFToken', readCookie('csrftoken'));
- }
- });
- result.action = action;
- return result;
-}
-
-// converts unix timestamps to HH:mm:ss format if it was today;
-// otherwise it returns YYYY-MM-DD
-window.unixTimeToString = function(time, full) {
- if(!time) return null;
- var d = new Date(typeof time === 'string' ? parseInt(time) : time);
- var time = d.toLocaleTimeString();
- var date = d.getFullYear()+'-'+(d.getMonth()+1)+'-'+d.getDate();
- if(typeof full !== 'undefined' && full) return date + ' ' + time;
- if(d.toDateString() == new Date().toDateString())
- return time;
- else
- return date;
-}
-
-window.unixTimeToHHmm = function(time) {
- if(!time) return null;
- var d = new Date(typeof time === 'string' ? parseInt(time) : time);
- var h = '' + d.getHours(); h = h.length === 1 ? '0' + h : h;
- var s = '' + d.getMinutes(); s = s.length === 1 ? '0' + s : s;
- return h + ':' + s;
-}
-
-window.rangeLinkClick = function() {
- if(window.portalRangeIndicator)
- window.map.fitBounds(window.portalRangeIndicator.getBounds());
- if(window.isSmartphone)
- window.smartphone.mapButton.click();
-}
-
-window.showPortalPosLinks = function(lat, lng) {
- if (typeof android !== 'undefined' && android && android.intentPosLink) {
- android.intentPosLink('https://maps.google.com/?q='+lat+','+lng);
- } else {
- var qrcode = '';
- var script = '';
- var gmaps = 'gmaps';
- var osm = 'OSM';
- alert('
' + qrcode + script + gmaps + ' ' + osm + '
');
- }
-}
-
-window.androidCopy = function(text) {
- if(typeof android === 'undefined' || !android || !android.copy)
- return true; // i.e. execute other actions
- else
- android.copy(text);
- return false;
-}
-
-window.reportPortalIssue = function(info) {
- var t = 'Redirecting you to a Google Help Page.\n\nThe text box contains all necessary information. Press CTRL+C to copy it.';
- var d = window.portals[window.selectedPortal].options.details;
-
- var info = 'Your Nick: ' + PLAYER.nickname + ' '
- + 'Portal: ' + d.portalV2.descriptiveText.TITLE + ' '
- + 'Location: ' + d.portalV2.descriptiveText.ADDRESS
- +' (lat ' + (d.locationE6.latE6/1E6) + '; lng ' + (d.locationE6.lngE6/1E6) + ')';
-
- //codename, approx addr, portalname
- if(prompt(t, info) !== null)
- location.href = 'https://support.google.com/ingress?hl=en&contact=1';
-}
-
-window._storedPaddedBounds = undefined;
-window.getPaddedBounds = function() {
- if(_storedPaddedBounds === undefined) {
- map.on('zoomstart zoomend movestart moveend', function() {
- window._storedPaddedBounds = null;
- });
- }
- if(renderLimitReached(0.7)) return window.map.getBounds();
- if(window._storedPaddedBounds) return window._storedPaddedBounds;
-
- var p = window.map.getBounds().pad(VIEWPORT_PAD_RATIO);
- window._storedPaddedBounds = p;
- return p;
-}
-
-// returns true if the render limit has been reached. The default ratio
-// is 1, which means it will tell you if there are more items drawn than
-// acceptable. A value of 0.9 will tell you if 90% of the amount of
-// acceptable entities have been drawn. You can use this to heuristi-
-// cally detect if the render limit will be hit.
-window.renderLimitReached = function(ratio) {
- ratio = ratio || 1;
- if(Object.keys(portals).length*ratio >= MAX_DRAWN_PORTALS) return true;
- if(Object.keys(links).length*ratio >= MAX_DRAWN_LINKS) return true;
- if(Object.keys(fields).length*ratio >= MAX_DRAWN_FIELDS) return true;
- var param = { 'reached': false };
- window.runHooks('checkRenderLimit', param);
- return param.reached;
-}
-
-window.getMinPortalLevel = function() {
- var z = map.getZoom();
- if(z >= 16) return 0;
- var conv = ['impossible', 8,7,7,6,6,5,5,4,4,3,3,2,2,1,1];
- var minLevelByRenderLimit = portalRenderLimit.getMinLevel();
- var result = minLevelByRenderLimit > conv[z]
- ? minLevelByRenderLimit
- : conv[z];
- return result;
-}
-
-// returns number of pixels left to scroll down before reaching the
-// bottom. Works similar to the native scrollTop function.
-window.scrollBottom = function(elm) {
- if(typeof elm === 'string') elm = $(elm);
- return elm.get(0).scrollHeight - elm.innerHeight() - elm.scrollTop();
-}
-
-window.zoomToAndShowPortal = function(guid, latlng) {
- map.setView(latlng, 17);
- // if the data is available, render it immediately. Otherwise defer
- // until it becomes available.
- if(window.portals[guid])
- renderPortalDetails(guid);
- else
- urlPortal = guid;
-}
-
-// translates guids to entity types
-window.getTypeByGuid = function(guid) {
- // portals end in “.11” or “.12“, links in “.9", fields in “.b”
- // .11 == portals
- // .12 == portals
- // .9 == links
- // .b == fields
- // .c == player/creator
- // .d == chat messages
- //
- // others, not used in web:
- // .5 == resources (burster/resonator)
- // .6 == XM
- // .4 == media items, maybe all droppped resources (?)
- // resonator guid is [portal guid]-resonator-[slot]
- switch(guid.slice(33)) {
- case '11':
- case '12':
- return TYPE_PORTAL;
-
- case '9':
- return TYPE_LINK;
-
- case 'b':
- return TYPE_FIELD;
-
- case 'c':
- return TYPE_PLAYER;
-
- case 'd':
- return TYPE_CHAT;
-
- default:
- if(guid.slice(-11,-2) == 'resonator') return TYPE_RESONATOR;
- return TYPE_UNKNOWN;
- }
-}
-
-String.prototype.capitalize = function() {
- return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
-}
-
-// http://stackoverflow.com/a/646643/1684530 by Bergi and CMS
-if (typeof String.prototype.startsWith !== 'function') {
- String.prototype.startsWith = function (str){
- return this.slice(0, str.length) === str;
- };
-}
-
-window.prettyEnergy = function(nrg) {
- return nrg> 1000 ? Math.round(nrg/1000) + ' k': nrg;
-}
-
-window.setPermaLink = function(elm) {
- var c = map.getCenter();
- var lat = Math.round(c.lat*1E6);
- var lng = Math.round(c.lng*1E6);
- var qry = 'latE6='+lat+'&lngE6='+lng+'&z=' + (map.getZoom()-1);
- $(elm).attr('href', 'https://www.ingress.com/intel?' + qry);
-}
-
-window.uniqueArray = function(arr) {
- return $.grep(arr, function(v, i) {
- return $.inArray(v, arr) === i;
- });
-}
-
-window.genFourColumnTable = function(blocks) {
- var t = $.map(blocks, function(detail, index) {
- if(!detail) return '';
- if(index % 2 === 0)
- return '
'+detail[1]+'
'+detail[0]+'
';
- else
- return '
'+detail[0]+'
'+detail[1]+'
';
- }).join('');
- if(t.length % 2 === 1) t + '
';
- return t;
-}
-
-
-// converts given text with newlines (\n) and tabs (\t) to a HTML
-// table automatically.
-window.convertTextToTableMagic = function(text) {
- // check if it should be converted to a table
- if(!text.match(/\t/)) return text.replace(/\n/g, ' ');
-
- var data = [];
- var columnCount = 0;
-
- // parse data
- var rows = text.split('\n');
- $.each(rows, function(i, row) {
- data[i] = row.split('\t');
- if(data[i].length > columnCount) columnCount = data[i].length;
- });
-
- // build the table
- var table = '
';
- return table;
-}
-
-// Given 3 sets of points in an array[3]{lat, lng} returns the area of the triangle
-window.calcTriArea = function(p) {
- return Math.abs((p[0].lat*(p[1].lng-p[2].lng)+p[1].lat*(p[2].lng-p[0].lng)+p[2].lat*(p[0].lng-p[1].lng))/2);
-}
-
-
-
-// SETUP /////////////////////////////////////////////////////////////
-// these functions set up specific areas after the boot function
-// created a basic framework. All of these functions should only ever
-// be run once.
-
-
-window.setupBackButton = function() {
- var c = window.isSmartphone()
- ? window.smartphone.mapButton
- : $('#chatcontrols a.active');
-
- window.setupBackButton._actions = [c.get(0)];
- $('#chatcontrols a').click(function() {
- // ignore shrink button
- if($(this).hasClass('toggle')) return;
- window.setupBackButton._actions.push(this);
- window.setupBackButton._actions = window.setupBackButton._actions.slice(-2);
- });
-
- window.goBack = function() {
- var a = window.setupBackButton._actions[0];
- if(!a) return;
- $(a).click();
- window.setupBackButton._actions = [a];
- }
-}
-
-
-
-
-window.setupLargeImagePreview = function() {
- $('#portaldetails').on('click', '.imgpreview', function() {
- var ex = $('#largepreview');
- if(ex.length > 0) {
- ex.remove();
- return;
- }
- var img = $(this).find('img')[0];
- var w = img.naturalWidth/2;
- var h = img.naturalHeight/2;
- var c = $('#portaldetails').attr('class');
- $('body').append(
- '
';
-
- setPortalIndicators(d);
- var img = d.imageByUrl && d.imageByUrl.imageUrl
- ? d.imageByUrl.imageUrl
- : DEFAULT_PORTAL_IMG;
-
- var lat = d.locationE6.latE6;
- var lng = d.locationE6.lngE6;
- var perma = 'https://ingress.com/intel?latE6='+lat+'&lngE6='+lng+'&z=17&pguid='+guid;
- var imgTitle = 'title="'+getPortalDescriptionFromDetails(d)+'\n\nClick to show full image."';
- var poslinks = 'window.showPortalPosLinks('+lat/1E6+','+lng/1E6+')';
- var postcard = 'Send in a postcard. Will put it online after receiving. Address:\\n\\nStefan Breunig\\nINF 305 – R045\\n69120 Heidelberg\\nGermany';
-
- $('#portaldetails')
- .attr('class', TEAM_TO_CSS[getTeam(d)])
- .html(''
- + '
'
- );
-
- // try to resolve names that were required for above functions, but
- // weren’t available yet.
- resolvePlayerNames();
-
- runHooks('portalDetailsUpdated', {portalDetails: d});
-}
-
-// draws link-range and hack-range circles around the portal with the
-// given details.
-window.setPortalIndicators = function(d) {
- if(portalRangeIndicator) map.removeLayer(portalRangeIndicator);
- var range = getPortalRange(d);
- var coord = [d.locationE6.latE6/1E6, d.locationE6.lngE6/1E6];
- portalRangeIndicator = (range > 0
- ? L.circle(coord, range, { fill: false, color: RANGE_INDICATOR_COLOR, weight: 3, clickable: false })
- : L.circle(coord, range, { fill: false, stroke: false, clickable: false })
- ).addTo(map);
- if(!portalAccessIndicator)
- portalAccessIndicator = L.circle(coord, HACK_RANGE,
- { fill: false, color: ACCESS_INDICATOR_COLOR, weight: 2, clickable: false }
- ).addTo(map);
- else
- portalAccessIndicator.setLatLng(coord);
-
-}
-
-// highlights portal with given GUID. Automatically clears highlights
-// on old selection. Returns false if the selected portal changed.
-// Returns true if it’s still the same portal that just needs an
-// update.
-window.selectPortal = function(guid) {
- var update = selectedPortal === guid;
- var oldPortal = portals[selectedPortal];
- if(!update && oldPortal) portalResetColor(oldPortal);
-
- selectedPortal = guid;
-
- if(portals[guid]) {
- resonatorsSetSelectStyle(guid);
- portals[guid].bringToFront().setStyle({color: COLOR_SELECTED_PORTAL});
- }
-
- return update;
-}
-
-
-window.unselectOldPortal = function() {
- var oldPortal = portals[selectedPortal];
- if(oldPortal) portalResetColor(oldPortal);
- selectedPortal = null;
- $('#portaldetails').html('');
-}
-
-
-
-// GEOSEARCH /////////////////////////////////////////////////////////
-
-window.setupGeosearch = function() {
- $('#geosearch').keypress(function(e) {
- if((e.keyCode ? e.keyCode : e.which) != 13) return;
- $.getJSON(NOMINATIM + encodeURIComponent($(this).val()), function(data) {
- if(!data || !data[0]) return;
- var b = data[0].boundingbox;
- if(!b) return;
- var southWest = new L.LatLng(b[0], b[2]),
- northEast = new L.LatLng(b[1], b[3]),
- bounds = new L.LatLngBounds(southWest, northEast);
- window.map.fitBounds(bounds);
- if(window.isSmartphone()) window.smartphone.mapButton.click();
- });
- e.preventDefault();
- });
-}
-
-
-
-
-// MAP DATA REQUEST CALCULATORS //////////////////////////////////////
-// Ingress Intel splits up requests for map data (portals, links,
-// fields) into tiles. To get data for the current viewport (i.e. what
-// is currently visible) it first calculates which tiles intersect.
-// For all those tiles, it then calculates the lat/lng bounds of that
-// tile and a quadkey. Both the bounds and the quadkey are “somewhat”
-// required to get complete data. No idea how the projection between
-// lat/lng and tiles works.
-// What follows now are functions that allow conversion between tiles
-// and lat/lng as well as calculating the quad key. The variable names
-// may be misleading.
-// The minified source for this code was in gen_dashboard.js after the
-// “// input 89” line (alternatively: the class was called “Xe”).
-
-window.convertCenterLat = function(centerLat) {
- return Math.round(256 * 0.9999 * Math.abs(1 / Math.cos(centerLat * DEG2RAD)));
-}
-
-window.calculateR = function(convCenterLat) {
- return 1 << window.map.getZoom() - (convCenterLat / 256 - 1);
-}
-
-window.convertLatLngToPoint = function(latlng, magic, R) {
- var x = (magic/2 + latlng.lng * magic / 360)*R;
- var l = Math.sin(latlng.lat * DEG2RAD);
- var y = (magic/2 + 0.5*Math.log((1+l)/(1-l)) * -(magic / (2*Math.PI)))*R;
- return {x: Math.floor(x/magic), y: Math.floor(y/magic)};
-}
-
-window.convertPointToLatLng = function(x, y, magic, R) {
- var e = {};
- e.sw = {
- // orig function put together from all over the place
- // lat: (2 * Math.atan(Math.exp((((y + 1) * magic / R) - (magic/ 2)) / (-1*(magic / (2 * Math.PI))))) - Math.PI / 2) / (Math.PI / 180),
- // shortened version by your favorite algebra program.
- lat: (360*Math.atan(Math.exp(Math.PI - 2*Math.PI*(y+1)/R)))/Math.PI - 90,
- lng: 360*x/R-180
- };
- e.ne = {
- //lat: (2 * Math.atan(Math.exp(((y * magic / R) - (magic/ 2)) / (-1*(magic / (2 * Math.PI))))) - Math.PI / 2) / (Math.PI / 180),
- lat: (360*Math.atan(Math.exp(Math.PI - 2*Math.PI*y/R)))/Math.PI - 90,
- lng: 360*(x+1)/R-180
- };
- return e;
-}
-
-// calculates the quad key for a given point. The point is not(!) in
-// lat/lng format.
-window.pointToQuadKey = function(x, y) {
- var quadkey = [];
- for(var c = window.map.getZoom(); c > 0; c--) {
- // +-------+ quadrants are probably ordered like this
- // | 0 | 1 |
- // |---|---|
- // | 2 | 3 |
- // |---|---|
- var quadrant = 0;
- var e = 1 << c - 1;
- (x & e) != 0 && quadrant++; // push right
- (y & e) != 0 && (quadrant++, quadrant++); // push down
- quadkey.push(quadrant);
- }
- return quadkey.join("");
-}
-
-// given quadkey and bounds, returns the format as required by the
-// Ingress API to request map data.
-window.generateBoundsParams = function(quadkey, bounds) {
- return {
- id: quadkey,
- qk: quadkey,
- minLatE6: Math.round(bounds.sw.lat * 1E6),
- minLngE6: Math.round(bounds.sw.lng * 1E6),
- maxLatE6: Math.round(bounds.ne.lat * 1E6),
- maxLngE6: Math.round(bounds.ne.lng * 1E6)
- };
-}
-
-
-// PLAYER NAMES //////////////////////////////////////////////////////
-// Player names are cached in local storage forever. There is no GUI
-// element from within the total conversion to clean them, but you
-// can run localStorage.clean() to reset it.
-
-
-// retrieves player name by GUID. If the name is not yet available, it
-// will be added to a global list of GUIDs that need to be resolved.
-// The resolve method is not called automatically.
-window.getPlayerName = function(guid) {
- if(localStorage[guid]) return localStorage[guid];
- // only add to queue if it isn’t already
- if(playersToResolve.indexOf(guid) === -1 && playersInResolving.indexOf(guid) === -1) {
- console.log('resolving player guid=' + guid);
- playersToResolve.push(guid);
- }
- return '{'+guid.slice(0, 12)+'}';
-}
-
-window.playerNameToGuid = function(playerName){
- var guid = null;
- $.each(Object.keys(localStorage), function(ind,key) {
- if(playerName === localStorage[key]) {
- guid = key;
- return false;
- }
- });
- return guid;
-}
-
-// resolves all player GUIDs that have been added to the list. Reruns
-// renderPortalDetails when finished, so that then-unresolved names
-// get replaced by their correct versions.
-window.resolvePlayerNames = function() {
- if(window.playersToResolve.length === 0) return;
- var p = window.playersToResolve;
- var d = {guids: p};
- playersInResolving = window.playersInResolving.concat(p);
- playersToResolve = [];
- postAjax('getPlayersByGuids', d, function(dat) {
- $.each(dat.result, function(ind, player) {
- window.setPlayerName(player.guid, player.nickname);
- // remove from array
- window.playersInResolving.splice(window.playersInResolving.indexOf(player.guid), 1);
- });
- if(window.selectedPortal)
- window.renderPortalDetails(window.selectedPortal);
- },
- function() {
- // append failed resolves to the list again
- console.warn('resolving player guids failed: ' + p.join(', '));
- window.playersToResolve.concat(p);
- });
-}
-
-
-window.setPlayerName = function(guid, nick) {
- if($.trim(('' + nick)).slice(0, 5) === '{"L":' && !window.alertFor37WasShown) {
- window.alertFor37WasShown = true;
- alert('You have run into bug #37. Please help me solve it!\nCopy and paste this text and post it here:\nhttps://github.com/breunigs/ingress-intel-total-conversion/issues/37\nIf copy & pasting doesn’t work, make a screenshot instead.\n\n\n' + window.debug.printStackTrace() + '\n\n\n' + JSON.stringify(nick));
- }
- localStorage[guid] = nick;
-}
-
-
-window.loadPlayerNamesForPortal = function(portal_details) {
- if(map.getZoom() < PRECACHE_PLAYER_NAMES_ZOOM) return;
- var e = portal_details;
-
- if(e.captured && e.captured.capturingPlayerId)
- getPlayerName(e.captured.capturingPlayerId);
-
- if(!e.resonatorArray || !e.resonatorArray.resonators) return;
-
- $.each(e.resonatorArray.resonators, function(ind, reso) {
- if(reso) getPlayerName(reso.ownerGuid);
- });
-}
-
-
-window.chat = function() {};
-
-window.chat.handleTabCompletion = function() {
- var el = $('#chatinput input');
- var curPos = el.get(0).selectionStart;
- var text = el.val();
- var word = text.slice(0, curPos).replace(/.*\b([a-z0-9-_])/, '$1').toLowerCase();
-
- var list = $('#chat > div:visible mark');
- list = list.map(function(ind, mark) { return $(mark).text(); } );
- list = uniqueArray(list);
-
- var nick = null;
- for(var i = 0; i < list.length; i++) {
- if(!list[i].toLowerCase().startsWith(word)) continue;
- if(nick && nick !== list[i]) {
- console.log('More than one nick matches, aborting. ('+list[i]+' vs '+nick+')');
- return;
- }
- nick = list[i];
- }
- if(!nick) {
- console.log('No matches for ' + word);
- return;
- }
-
- var posStart = curPos - word.length;
- var newText = text.substring(0, posStart);
- newText += nick + (posStart === 0 ? ': ' : ' ');
- newText += text.substring(curPos);
- el.val(newText);
-}
-
-//
-// timestamp and clear management
-//
-
-window.chat.getTimestamps = function(isFaction) {
- var storage = isFaction ? chat._factionData : chat._publicData;
- return $.map(storage, function(v, k) { return [v[0]]; });
-}
-
-window.chat.getOldestTimestamp = function(isFaction) {
- var t = Math.min.apply(null, chat.getTimestamps(isFaction));
- return t === Infinity ? -1 : t;
-}
-
-window.chat.getNewestTimestamp = function(isFaction) {
- var t = Math.max.apply(null, chat.getTimestamps(isFaction));
- return t === -1*Infinity ? -1 : t;
-}
-
-window.chat._oldBBox = null;
-window.chat.genPostData = function(isFaction, getOlderMsgs) {
- if(typeof isFaction !== 'boolean') throw('Need to know if public or faction chat.');
-
- chat._localRangeCircle.setLatLng(map.getCenter());
- var b = map.getBounds().extend(chat._localRangeCircle.getBounds());
- var ne = b.getNorthEast();
- var sw = b.getSouthWest();
-
- // round bounds in order to ignore rounding errors
- var bbs = $.map([ne.lat, ne.lng, sw.lat, sw.lng], function(x) { return Math.round(x*1E4) }).join();
- if(chat._oldBBox && chat._oldBBox !== bbs) {
- $('#chat > div').data('needsClearing', true);
- console.log('Bounding Box changed, chat will be cleared (old: '+chat._oldBBox+' ; new: '+bbs+' )');
- // need to reset these flags now because clearing will only occur
- // after the request is finished – i.e. there would be one almost
- // useless request.
- chat._factionData = {};
- chat._publicData = {};
- }
- chat._oldBBox = bbs;
-
- var ne = b.getNorthEast();
- var sw = b.getSouthWest();
- var data = {
- desiredNumItems: isFaction ? CHAT_FACTION_ITEMS : CHAT_PUBLIC_ITEMS ,
- minLatE6: Math.round(sw.lat*1E6),
- minLngE6: Math.round(sw.lng*1E6),
- maxLatE6: Math.round(ne.lat*1E6),
- maxLngE6: Math.round(ne.lng*1E6),
- minTimestampMs: -1,
- maxTimestampMs: -1,
- factionOnly: isFaction
- }
-
- if(getOlderMsgs) {
- // ask for older chat when scrolling up
- data = $.extend(data, {maxTimestampMs: chat.getOldestTimestamp(isFaction)});
- } else {
- // ask for newer chat
- var min = chat.getNewestTimestamp(isFaction);
- // the inital request will have both timestamp values set to -1,
- // thus we receive the newest desiredNumItems. After that, we will
- // only receive messages with a timestamp greater or equal to min
- // above.
- // After resuming from idle, there might be more new messages than
- // desiredNumItems. So on the first request, we are not really up to
- // date. We will eventually catch up, as long as there are less new
- // messages than desiredNumItems per each refresh cycle.
- // A proper solution would be to query until no more new results are
- // returned. Another way would be to set desiredNumItems to a very
- // large number so we really get all new messages since the last
- // request. Setting desiredNumItems to -1 does unfortunately not
- // work.
- // Currently this edge case is not handled. Let’s see if this is a
- // problem in crowded areas.
- $.extend(data, {minTimestampMs: min});
- }
- return data;
-}
-
-
-
-//
-// faction
-//
-
-window.chat._requestFactionRunning = false;
-window.chat.requestFaction = function(getOlderMsgs, isRetry) {
- if(chat._requestFactionRunning && !isRetry) return;
- if(isIdle()) return renderUpdateStatus();
- chat._requestFactionRunning = true;
-
- var d = chat.genPostData(true, getOlderMsgs);
- var r = window.postAjax(
- 'getPaginatedPlextsV2',
- d,
- chat.handleFaction,
- isRetry
- ? function() { window.chat._requestFactionRunning = false; }
- : function() { window.chat.requestFaction(getOlderMsgs, true) }
- );
-
- requests.add(r);
-}
-
-
-window.chat._factionData = {};
-window.chat.handleFaction = function(data, textStatus, jqXHR) {
- chat._requestFactionRunning = false;
-
- if(!data || !data.result) {
- window.failedRequestCount++;
- return console.warn('faction chat error. Waiting for next auto-refresh.');
- }
-
- if(data.result.length === 0) return;
-
- var old = chat.getOldestTimestamp(true);
- chat.writeDataToHash(data, chat._factionData, false);
- var oldMsgsWereAdded = old !== chat.getOldestTimestamp(true);
-
- runHooks('factionChatDataAvailable', {raw: data, processed: chat._factionData});
-
- window.chat.renderFaction(oldMsgsWereAdded);
-
- if(data.result.length >= CHAT_FACTION_ITEMS) chat.needMoreMessages();
-}
-
-window.chat.renderFaction = function(oldMsgsWereAdded) {
- chat.renderData(chat._factionData, 'chatfaction', oldMsgsWereAdded);
-}
-
-
-//
-// public
-//
-
-window.chat._requestPublicRunning = false;
-window.chat.requestPublic = function(getOlderMsgs, isRetry) {
- if(chat._requestPublicRunning && !isRetry) return;
- if(isIdle()) return renderUpdateStatus();
- chat._requestPublicRunning = true;
-
- var d = chat.genPostData(false, getOlderMsgs);
- var r = window.postAjax(
- 'getPaginatedPlextsV2',
- d,
- chat.handlePublic,
- isRetry
- ? function() { window.chat._requestPublicRunning = false; }
- : function() { window.chat.requestPublic(getOlderMsgs, true) }
- );
-
- requests.add(r);
-}
-
-window.chat._publicData = {};
-window.chat.handlePublic = function(data, textStatus, jqXHR) {
- chat._requestPublicRunning = false;
-
- if(!data || !data.result) {
- window.failedRequestCount++;
- return console.warn('public chat error. Waiting for next auto-refresh.');
- }
-
- if(data.result.length === 0) return;
-
- var old = chat.getOldestTimestamp(false);
- chat.writeDataToHash(data, chat._publicData, true);
- var oldMsgsWereAdded = old !== chat.getOldestTimestamp(false);
-
- runHooks('publicChatDataAvailable', {raw: data, processed: chat._publicData});
-
- switch(chat.getActive()) {
- case 'public': window.chat.renderPublic(oldMsgsWereAdded); break;
- case 'compact': window.chat.renderCompact(oldMsgsWereAdded); break;
- case 'full': window.chat.renderFull(oldMsgsWereAdded); break;
- }
-
- if(data.result.length >= CHAT_PUBLIC_ITEMS) chat.needMoreMessages();
-}
-
-window.chat.renderPublic = function(oldMsgsWereAdded) {
- // only keep player data
- var data = $.map(chat._publicData, function(entry) {
- if(!entry[1]) return [entry];
- });
- chat.renderData(data, 'chatpublic', oldMsgsWereAdded);
-}
-
-window.chat.renderCompact = function(oldMsgsWereAdded) {
- var data = {};
- $.each(chat._publicData, function(guid, entry) {
- // skip player msgs
- if(!entry[1]) return true;
- var pguid = entry[3];
- // ignore if player has newer data
- if(data[pguid] && data[pguid][0] > entry[0]) return true;
- data[pguid] = entry;
- });
- // data keys are now player guids instead of message guids. However,
- // it is all the same to renderData.
- chat.renderData(data, 'chatcompact', oldMsgsWereAdded);
-}
-
-window.chat.renderFull = function(oldMsgsWereAdded) {
- // only keep automatically generated data
- var data = $.map(chat._publicData, function(entry) {
- if(entry[1]) return [entry];
- });
- chat.renderData(data, 'chatfull', oldMsgsWereAdded);
-}
-
-
-//
-// common
-//
-
-window.chat.writeDataToHash = function(newData, storageHash, skipSecureMsgs) {
- $.each(newData.result, function(ind, json) {
- // avoid duplicates
- if(json[0] in storageHash) return true;
-
- var skipThisEntry = false;
-
- var time = json[1];
- var team = json[2].plext.team === 'ALIENS' ? TEAM_ENL : TEAM_RES;
- var auto = json[2].plext.plextType !== 'PLAYER_GENERATED';
- var msg = '', nick = '', pguid;
- $.each(json[2].plext.markup, function(ind, markup) {
- switch(markup[0]) {
- case 'SENDER': // user generated messages
- nick = markup[1].plain.slice(0, -2); // cut “: ” at end
- pguid = markup[1].guid;
- break;
-
- case 'PLAYER': // automatically generated messages
- pguid = markup[1].guid;
- nick = markup[1].plain;
- team = markup[1].team === 'ALIENS' ? TEAM_ENL : TEAM_RES;
- if(ind > 0) msg += nick; // don’t repeat nick directly
- break;
-
- case 'TEXT':
- var tmp = $('').text(markup[1].plain).html().autoLink();
- msg += tmp.replace(window.PLAYER['nickMatcher'], '$1');
- break;
-
- case 'PORTAL':
- var latlng = [markup[1].latE6/1E6, markup[1].lngE6/1E6];
- var perma = 'https://ingress.com/intel?latE6='+markup[1].latE6+'&lngE6='+markup[1].lngE6+'&z=17&pguid='+markup[1].guid;
- var js = 'window.zoomToAndShowPortal(\''+markup[1].guid+'\', ['+latlng[0]+', '+latlng[1]+']);return false';
-
- msg += ''
- + window.chat.getChatPortalName(markup[1])
- + '';
- break;
-
- case 'SECURE':
- if(skipSecureMsgs) {
- skipThisEntry = true;
- return false; // breaks $.each
- }
- }
- });
- if(skipThisEntry) return true;
-
- // format: timestamp, autogenerated, HTML message, player guid
- storageHash[json[0]] = [json[1], auto, chat.renderMsg(msg, nick, time, team), pguid];
-
- window.setPlayerName(pguid, nick); // free nick name resolves
- });
-}
-
-// Override portal names that are used over and over, such as 'US Post Office'
-window.chat.getChatPortalName = function(markup) {
- var name = markup.name;
- if(name === 'US Post Office') {
- var address = markup.address.split(',');
- name = 'USPS: ' + address[0];
- }
- return name;
-}
-
-// renders data from the data-hash to the element defined by the given
-// ID. Set 3rd argument to true if it is likely that old data has been
-// added. Latter is only required for scrolling.
-window.chat.renderData = function(data, element, likelyWereOldMsgs) {
- var elm = $('#'+element);
- if(elm.is(':hidden')) return;
-
- // discard guids and sort old to new
- var vals = $.map(data, function(v, k) { return [v]; });
- vals = vals.sort(function(a, b) { return a[0]-b[0]; });
-
- // render to string with date separators inserted
- var msgs = '';
- var prevTime = null;
- $.each(vals, function(ind, msg) {
- var nextTime = new Date(msg[0]).toLocaleDateString();
- if(prevTime && prevTime !== nextTime)
- msgs += chat.renderDivider(nextTime);
- msgs += msg[2];
- prevTime = nextTime;
- });
-
- var scrollBefore = scrollBottom(elm);
- elm.html('
';
-}
-
-
-window.chat.renderMsg = function(msg, nick, time, team) {
- var ta = unixTimeToHHmm(time);
- var tb = unixTimeToString(time, true);
- // help cursor via “#chat time”
- var t = '';
- var s = 'style="color:'+COLORS[team]+'"';
- var title = nick.length >= 8 ? 'title="'+nick+'" class="help"' : '';
- var i = ['<', '>'];
- return '
'+t+'
'+i[0]+''+nick+''+i[1]+'
'+msg+'
';
-}
-
-
-
-window.chat.getActive = function() {
- return $('#chatcontrols .active').text();
-}
-
-
-window.chat.toggle = function() {
- var c = $('#chat, #chatcontrols');
- if(c.hasClass('expand')) {
- $('#chatcontrols a:first').html('');
- c.removeClass('expand');
- var div = $('#chat > div:visible');
- div.data('ignoreNextScroll', true);
- div.scrollTop(99999999); // scroll to bottom
- $('.leaflet-control').css('margin-left', '13px');
- } else {
- $('#chatcontrols a:first').html('');
- c.addClass('expand');
- $('.leaflet-control').css('margin-left', '720px');
- chat.needMoreMessages();
- }
-}
-
-
-window.chat.request = function() {
- console.log('refreshing chat');
- chat.requestFaction(false);
- chat.requestPublic(false);
-}
-
-
-// checks if there are enough messages in the selected chat tab and
-// loads more if not.
-window.chat.needMoreMessages = function() {
- var activeTab = chat.getActive();
- if(activeTab === 'debug') return;
-
- var activeChat = $('#chat > :visible');
- if(activeChat.length === 0) return;
-
- var hasScrollbar = scrollBottom(activeChat) !== 0 || activeChat.scrollTop() !== 0;
- var nearTop = activeChat.scrollTop() <= CHAT_REQUEST_SCROLL_TOP;
- if(hasScrollbar && !nearTop) return;
-
- console.log('No scrollbar or near top in active chat. Requesting more data.');
-
- if(activeTab === 'faction')
- chat.requestFaction(true);
- else
- chat.requestPublic(true);
-}
-
-
-window.chat.chooser = function(event) {
- var t = $(event.target);
- var tt = t.text();
-
- var mark = $('#chatinput mark');
- var input = $('#chatinput input');
-
- $('#chatcontrols .active').removeClass('active');
- t.addClass('active');
-
- $('#chat > div').hide();
-
- var elm;
-
- switch(tt) {
- case 'faction':
- input.css('color', '');
- mark.css('color', '');
- mark.text('tell faction:');
- break;
-
- case 'public':
- input.css('cssText', 'color: red !important');
- mark.css('cssText', 'color: red !important');
- mark.text('broadcast:');
- break;
-
- case 'compact':
- case 'full':
- mark.css('cssText', 'color: #bbb !important');
- input.css('cssText', 'color: #bbb !important');
- mark.text('tell Jarvis:');
- break;
-
- default:
- throw('chat.chooser was asked to handle unknown button: ' + tt);
- }
-
- var elm = $('#chat' + tt);
- elm.show();
- eval('chat.render' + tt.capitalize() + '(false);');
- if(elm.data('needsScrollTop')) {
- elm.data('ignoreNextScroll', true);
- elm.scrollTop(elm.data('needsScrollTop'));
- elm.data('needsScrollTop', null);
- }
-
- chat.needMoreMessages();
-}
-
-
-// contains the logic to keep the correct scroll position.
-window.chat.keepScrollPosition = function(box, scrollBefore, isOldMsgs) {
- // If scrolled down completely, keep it that way so new messages can
- // be seen easily. If scrolled up, only need to fix scroll position
- // when old messages are added. New messages added at the bottom don’t
- // change the view and enabling this would make the chat scroll down
- // for every added message, even if the user wants to read old stuff.
-
- if(box.is(':hidden') && !isOldMsgs) {
- box.data('needsScrollTop', 99999999);
- return;
- }
-
- if(scrollBefore === 0 || isOldMsgs) {
- box.data('ignoreNextScroll', true);
- box.scrollTop(box.scrollTop() + (scrollBottom(box)-scrollBefore));
- }
-}
-
-
-
-
-//
-// setup
-//
-
-window.chat.setup = function() {
- window.chat._localRangeCircle = L.circle(map.getCenter(), CHAT_MIN_RANGE*1000);
-
- $('#chatcontrols, #chat, #chatinput').show();
-
- $('#chatcontrols a:first').click(window.chat.toggle);
- $('#chatcontrols a').each(function(ind, elm) {
- if($.inArray($(elm).text(), ['full', 'compact', 'public', 'faction']) !== -1)
- $(elm).click(window.chat.chooser);
- });
-
-
- $('#chatinput').click(function() {
- $('#chatinput input').focus();
- });
-
- window.chat.setupTime();
- window.chat.setupPosting();
-
- $('#chatfaction').scroll(function() {
- var t = $(this);
- if(t.data('ignoreNextScroll')) return t.data('ignoreNextScroll', false);
- if(t.scrollTop() < CHAT_REQUEST_SCROLL_TOP) chat.requestFaction(true);
- if(scrollBottom(t) === 0) chat.requestFaction(false);
- });
-
- $('#chatpublic, #chatfull, #chatcompact').scroll(function() {
- var t = $(this);
- if(t.data('ignoreNextScroll')) return t.data('ignoreNextScroll', false);
- if(t.scrollTop() < CHAT_REQUEST_SCROLL_TOP) chat.requestPublic(true);
- if(scrollBottom(t) === 0) chat.requestPublic(false);
- });
-
- chat.request();
- window.addResumeFunction(chat.request);
- window.requests.addRefreshFunction(chat.request);
-
- var cls = PLAYER.team === 'ALIENS' ? 'enl' : 'res';
- $('#chatinput mark').addClass(cls)
-}
-
-
-window.chat.setupTime = function() {
- var inputTime = $('#chatinput time');
- var updateTime = function() {
- if(window.isIdle()) return;
- var d = new Date();
- var h = d.getHours() + ''; if(h.length === 1) h = '0' + h;
- var m = d.getMinutes() + ''; if(m.length === 1) m = '0' + m;
- inputTime.text(h+':'+m);
- // update ON the minute (1ms after)
- setTimeout(updateTime, (60 - d.getSeconds()) * 1000 + 1);
- };
- updateTime();
- window.addResumeFunction(updateTime);
-}
-
-
-//
-// posting
-//
-
-
-window.chat.setupPosting = function() {
- $('#chatinput input').keydown(function(event) {
- try {
- var kc = (event.keyCode ? event.keyCode : event.which);
- if(kc === 13) { // enter
- chat.postMsg();
- event.preventDefault();
- } else if (kc === 9) { // tab
- event.preventDefault();
- window.chat.handleTabCompletion();
- }
- } catch(error) {
- console.log(error);
- debug.printStackTrace();
- }
- });
-
- $('#chatinput').submit(function(event) {
- event.preventDefault();
- chat.postMsg();
- });
-}
-
-
-window.chat.postMsg = function() {
- var c = chat.getActive();
- if(c === 'full' || c === 'compact')
- return alert('Jarvis: A strange game. The only winning move is not to play. How about a nice game of chess?');
-
- var msg = $.trim($('#chatinput input').val());
- if(!msg || msg === '') return;
-
- if(c === 'debug') return new Function (msg)();
-
- var publik = c === 'public';
- var latlng = map.getCenter();
-
- var data = {message: msg,
- latE6: Math.round(latlng.lat*1E6),
- lngE6: Math.round(latlng.lng*1E6),
- factionOnly: !publik};
-
- var errMsg = 'Your message could not be delivered. You can copy&' +
- 'paste it here and try again if you want:\n\n' + msg;
-
- window.postAjax('sendPlext', data,
- function(response) {
- if(response.error) alert(errMsg);
- if(publik) chat.requestPublic(false); else chat.requestFaction(false); },
- function() {
- alert(errMsg);
- }
- );
-
- $('#chatinput input').val('');
-}
-
-
-
-
-// REDEEMING /////////////////////////////////////////////////////////
-
-window.handleRedeemResponse = function(data, textStatus, jqXHR) {
- if(data.error) {
- var error = '';
- if(data.error === 'ALREADY_REDEEMED') {
- error = 'The passcode has already been redeemed.';
- } else if(data.error === 'ALREADY_REDEEMED_BY_PLAYER') {
- error = 'You have already redeemed this passcode.';
- } else if(data.error === 'INVALID_PASSCODE') {
- error = 'This passcode is invalid.';
- } else {
- error = 'There was a problem redeeming the passcode. Try again?';
- }
- alert('' + data.error + '\n' + error);
- } else if(data.result) {
- var tblResult = $('