diff --git a/leaflet_google.js b/leaflet_google.js deleted file mode 100644 index 37d6dd8c..00000000 --- a/leaflet_google.js +++ /dev/null @@ -1,152 +0,0 @@ -/* - * L.TileLayer is used for standard xyz-numbered tile layers. - */ -L.Google = L.Class.extend({ - includes: L.Mixin.Events, - - options: { - minZoom: 0, - maxZoom: 18, - tileSize: 256, - subdomains: 'abc', - errorTileUrl: '', - attribution: '', - opacity: 1, - continuousWorld: false, - noWrap: false, - }, - - // Possible types: SATELLITE, ROADMAP, HYBRID, INGRESS - initialize: function(type, options, styles) { - L.Util.setOptions(this, options); - if(type === 'INGRESS') { - type = 'ROADMAP'; - this._styles = [{featureType:"all", elementType:"all", stylers:[{visibility:"on"}, {hue:"#0091ff"}, {invert_lightness:true}]}, {featureType:"water", elementType:"all", stylers:[{visibility:"on"}, {hue:"#005eff"}, {invert_lightness:true}]}, {featureType:"poi", stylers:[{visibility:"off"}]}, {featureType:"transit", elementType:"all", stylers:[{visibility:"off"}]}]; - } else { - this._styles = null; - } - this._type = google.maps.MapTypeId[type || 'SATELLITE']; - }, - - onAdd: function(map, insertAtTheBottom) { - this._map = map; - this._insertAtTheBottom = insertAtTheBottom; - - // create a container div for tiles - this._initContainer(); - this._initMapObject(); - - // set up events - map.on('viewreset', this._resetCallback, this); - - this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this); - map.on('move', this._update, this); - //map.on('moveend', this._update, this); - - this._reset(); - this._update(); - }, - - onRemove: function(map) { - this._map._container.removeChild(this._container); - //this._container = null; - - this._map.off('viewreset', this._resetCallback, this); - - this._map.off('move', this._update, this); - //this._map.off('moveend', this._update, this); - }, - - getAttribution: function() { - return this.options.attribution; - }, - - setOpacity: function(opacity) { - this.options.opacity = opacity; - if (opacity < 1) { - L.DomUtil.setOpacity(this._container, opacity); - } - }, - - _initContainer: function() { - var tilePane = this._map._container - first = tilePane.firstChild; - - if (!this._container) { - this._container = L.DomUtil.create('div', 'leaflet-google-layer leaflet-top leaflet-left'); - this._container.id = "_GMapContainer"; - } - - if (true) { - tilePane.insertBefore(this._container, first); - - this.setOpacity(this.options.opacity); - var size = this._map.getSize(); - this._container.style.width = size.x + 'px'; - this._container.style.height = size.y + 'px'; - } - }, - - _initMapObject: function() { - this._google_center = new google.maps.LatLng(0, 0); - var map = new google.maps.Map(this._container, { - center: this._google_center, - zoom: 0, - styles: this._styles, - mapTypeId: this._type, - disableDefaultUI: true, - keyboardShortcuts: false, - draggable: false, - disableDoubleClickZoom: true, - scrollwheel: false, - streetViewControl: false - }); - - var _this = this; - this._reposition = google.maps.event.addListenerOnce(map, "center_changed", - function() { _this.onReposition(); }); - - map.backgroundColor = '#ff0000'; - this._google = map; - }, - - _resetCallback: function(e) { - this._reset(e.hard); - }, - - _reset: function(clearOldContainer) { - this._initContainer(); - }, - - _update: function() { - this._resize(); - - var bounds = this._map.getBounds(); - var ne = bounds.getNorthEast(); - var sw = bounds.getSouthWest(); - var google_bounds = new google.maps.LatLngBounds( - new google.maps.LatLng(sw.lat, sw.lng), - new google.maps.LatLng(ne.lat, ne.lng) - ); - var center = this._map.getCenter(); - var _center = new google.maps.LatLng(center.lat, center.lng); - - this._google.setCenter(_center); - this._google.setZoom(this._map.getZoom()); - //this._google.fitBounds(google_bounds); - }, - - _resize: function() { - var size = this._map.getSize(); - if (this._container.style.width == size.x && - this._container.style.height == size.y) - return; - this._container.style.width = size.x + 'px'; - this._container.style.height = size.y + 'px'; - google.maps.event.trigger(this._google, "resize"); - }, - - onReposition: function() { - //google.maps.event.trigger(this._google, "resize"); - } -}); diff --git a/total-conversion-build.user.js b/total-conversion-build.user.js deleted file mode 100644 index e56a990d..00000000 --- a/total-conversion-build.user.js +++ /dev/null @@ -1,2723 +0,0 @@ -// ==UserScript== -// @id ingress-intel-total-conversion@breunigs -// @name intel map total conversion -// @version 0.51-2013-02-11-193316 -// @namespace https://github.com/breunigs/ingress-intel-total-conversion -// @updateURL https://raw.github.com/breunigs/ingress-intel-total-conversion/gh-pages/dist/total-conversion-build.user.js -// @downloadURL https://raw.github.com/breunigs/ingress-intel-total-conversion/gh-pages/dist/total-conversion-build.user.js -// @description total conversion for the ingress intel map. -// @include http://www.ingress.com/intel* -// @match http://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.'); - -// 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();', 10*1000); - throw('Page doesn’t have player data, but you are logged in. Reloading in 10s.'); - } - // 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' - + '' - + '' - + ''; - -document.getElementsByTagName('body')[0].innerHTML = '' - + '
Loading, please wait
' - + '' - + '' - + '' - + '' - + '
' // enable scrolling for small screens - + ' ' - + '
' - + '
'; - -// 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 //////////////////////////////////////////////////// -var REFRESH = 30; // refresh view every 30s (base time) -var ZOOM_LEVEL_ADJ = 5; // add 5 seconds per zoom level -var REFRESH_GAME_SCORE = 5*60; // refresh game score every 5 minutes -var MAX_IDLE_TIME = 4; // stop updating map after 4min idling -var PRECACHE_PLAYER_NAMES_ZOOM = 17; // zoom level to start pre-resolving player names -var HIDDEN_SCROLLBAR_ASSUMED_WIDTH = 20; -var 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. -var 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. -var VIEWPORT_PAD_RATIO = 0.3; - -// how many items to request each query -var CHAT_PUBLIC_ITEMS = 200 -var CHAT_FACTION_ITEMS = 50 - -// Leaflet will get very slow for MANY items. It’s better to display -// only some instead of crashing the browser. -var MAX_DRAWN_PORTALS = 1000; -var MAX_DRAWN_LINKS = 400; -var MAX_DRAWN_FIELDS = 200; - - -var COLOR_SELECTED_PORTAL = '#f00'; -var COLORS = ['#FFCE00', '#0088FF', '#03FE03']; // none, res, enl -var COLORS_LVL = ['#000', '#FECE5A', '#FFA630', '#FF7315', '#E40000', '#FD2992', '#EB26CD', '#C124E0', '#9627F4']; -var COLORS_MOD = {VERY_RARE: '#F78AF6', RARE: '#AD8AFF', COMMON: '#84FBBD'}; - - -// 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) -var ACCESS_INDICATOR_COLOR = 'orange'; -var RANGE_INDICATOR_COLOR = 'red'; - -// INGRESS CONSTANTS ///////////////////////////////////////////////// -// http://decodeingress.me/2012/11/18/ingress-portal-levels-and-link-range/ -var RESO_NRG = [0, 1000, 1500, 2000, 2500, 3000, 4000, 5000, 6000]; -var MAX_XM_PER_LEVEL = [0, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]; -var MIN_AP_FOR_LEVEL = [0, 10000, 30000, 70000, 150000, 300000, 600000, 1200000]; -var HACK_RANGE = 40; // in meters, max. distance from portal to be able to access it -var OCTANTS = ['E', 'NE', 'N', 'NW', 'W', 'SW', 'S', 'SE']; -var DEFAULT_PORTAL_IMG = 'http://commondatastorage.googleapis.com/ingress/img/default-portal-image.png'; -var DESTROY_RESONATOR = 75; //AP for destroying portal -var DESTROY_LINK = 187; //AP for destroying link -var DESTROY_FIELD = 750; //AP for destroying field - -// OTHER MORE-OR-LESS CONSTANTS ////////////////////////////////////// -var NOMINATIM = 'http://nominatim.openstreetmap.org/search?format=json&limit=1&q='; -var DEG2RAD = Math.PI / 180; -var TEAM_NONE = 0, TEAM_RES = 1, TEAM_ENL = 2; -var TEAM_TO_CSS = ['none', 'res', 'enl']; -var TYPE_UNKNOWN = 0, TYPE_PORTAL = 1, TYPE_LINK = 2, TYPE_FIELD = 3, TYPE_PLAYER = 4, TYPE_CHAT = 5, TYPE_RESONATOR = 6; -// make PLAYER variable available in site context -var PLAYER = window.PLAYER; -var CHAT_SHRINKED = 60; - -// Minimum zoom level resonator will display -var RESONATOR_DISPLAY_ZOOM_LEVEL = 17; - -// Constants for resonator positioning -var SLOT_TO_LAT = [0, Math.sqrt(2)/2, 1, Math.sqrt(2)/2, 0, -Math.sqrt(2)/2, -1, -Math.sqrt(2)/2]; -var SLOT_TO_LNG = [1, Math.sqrt(2)/2, 0, -Math.sqrt(2)/2, -1, -Math.sqrt(2)/2, 0, Math.sqrt(2)/2]; -var EARTH_RADIUS=6378137; - -// 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() {}; - - - - -// 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” os -// 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. - -window._hooks = {} -window.VALID_HOOKS = ['portalAdded']; - -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); -} - - - -// MAP DATA ////////////////////////////////////////////////////////// -// these functions handle how and which entities are displayed on the -// map. They also keep them up to date, unless interrupted by user -// action. - - -// requests map data for current viewport. For details on how this -// works, refer to the description in “MAP DATA REQUEST CALCULATORS” -window.requestData = function() { - console.log('refreshing data'); - requests.abort(); - cleanUp(); - - var magic = convertCenterLat(map.getCenter().lat); - var R = calculateR(magic); - - var bounds = map.getBounds(); - // convert to point values - topRight = convertLatLngToPoint(bounds.getNorthEast(), magic, R); - bottomLeft = convertLatLngToPoint(bounds.getSouthWest() , magic, R); - // how many quadrants intersect the current view? - quadsX = Math.abs(bottomLeft.x - topRight.x); - quadsY = Math.abs(bottomLeft.y - topRight.y); - - // will group requests by second-last quad-key quadrant - tiles = {}; - - // walk in x-direction, starts right goes left - for(var i = 0; i <= quadsX; i++) { - var x = Math.abs(topRight.x - i); - var qk = pointToQuadKey(x, topRight.y); - var bnds = convertPointToLatLng(x, topRight.y, magic, R); - if(!tiles[qk.slice(0, -1)]) tiles[qk.slice(0, -1)] = []; - tiles[qk.slice(0, -1)].push(generateBoundsParams(qk, bnds)); - - // walk in y-direction, starts top, goes down - for(var j = 1; j <= quadsY; j++) { - var qk = pointToQuadKey(x, topRight.y + j); - var bnds = convertPointToLatLng(x, topRight.y + j, magic, R); - if(!tiles[qk.slice(0, -1)]) tiles[qk.slice(0, -1)] = []; - tiles[qk.slice(0, -1)].push(generateBoundsParams(qk, bnds)); - } - } - - // finally send ajax requests - $.each(tiles, function(ind, tls) { - data = { minLevelOfDetail: -1 }; - data.boundsParamsList = tls; - window.requests.add(window.postAjax('getThinnedEntitiesV2', data, window.handleDataResponse)); - }); -} - -// works on map data response and ensures entities are drawn/updated. -window.handleDataResponse = function(data, textStatus, jqXHR) { - // remove from active ajax queries list - if(!data || !data.result) { - window.failedRequestCount++; - console.warn(data); - return; - } - - var portalUpdateAvailable = false; - var portalInUrlAvailable = false; - var m = data.result.map; - // defer rendering of portals because there is no z-index in SVG. - // this means that what’s rendered last ends up on top. While the - // portals can be brought to front, this costs extra time. They need - // to be in the foreground, or they cannot be clicked. See - // https://github.com/Leaflet/Leaflet/issues/185 - var ppp = []; - var p2f = {}; - $.each(m, function(qk, val) { - $.each(val.deletedGameEntityGuids, function(ind, guid) { - if(getTypeByGuid(guid) === TYPE_FIELD && window.fields[guid] !== undefined) { - $.each(window.fields[guid].options.vertices, function(ind, vertex) { - if(window.portals[vertex.guid] === undefined) return true; - fieldArray = window.portals[vertex.guid].options.portalV2.linkedFields; - fieldArray.splice($.inArray(guid, fieldArray), 1); - }); - } - window.removeByGuid(guid); - }); - - $.each(val.gameEntities, function(ind, ent) { - // ent = [GUID, id(?), details] - // format for links: { controllingTeam, creator, edge } - // format for portals: { controllingTeam, turret } - - if(ent[2].turret !== undefined) { - if(selectedPortal == ent[0]) portalUpdateAvailable = true; - if(urlPortal && ent[0] == urlPortal) portalInUrlAvailable = true; - - var latlng = [ent[2].locationE6.latE6/1E6, ent[2].locationE6.lngE6/1E6]; - if(!window.getPaddedBounds().contains(latlng) - && selectedPortal != ent[0] - && urlPortal != ent[0] - ) return; - - - - ppp.push(ent); // delay portal render - } else if(ent[2].edge !== undefined) { - renderLink(ent); - } else if(ent[2].capturedRegion !== undefined) { - $.each(ent[2].capturedRegion, function(ind, vertex) { - if(p2f[vertex.guid] === undefined) - p2f[vertex.guid] = new Array(); - p2f[vertex.guid].push(ent[0]); - }); - renderField(ent); - } else { - throw('Unknown entity: ' + JSON.stringify(ent)); - } - }); - }); - - $.each(ppp, function(ind, portal) { - if(portal[2].portalV2['linkedFields'] === undefined) { - portal[2].portalV2['linkedFields'] = []; - } - if(p2f[portal[0]] !== undefined) { - $.merge(p2f[portal[0]], portal[2].portalV2['linkedFields']); - portal[2].portalV2['linkedFields'] = uniqueArray(p2f[portal[0]]); - } - }); - - $.each(ppp, function(ind, portal) { renderPortal(portal); }); - if(portals[selectedPortal]) { - try { - portals[selectedPortal].bringToFront(); - } catch(e) { /* portal is now visible, catch Leaflet error */ } - } - - if(portalInUrlAvailable) { - renderPortalDetails(urlPortal); - urlPortal = null; // select it only once - } - - if(portalUpdateAvailable) renderPortalDetails(selectedPortal); - resolvePlayerNames(); -} - -// removes entities that are still handled by Leaflet, although they -// do not intersect the current viewport. -window.cleanUp = function() { - var cnt = [0,0,0]; - var b = getPaddedBounds(); - var minlvl = getMinPortalLevel(); - for(var i = 0; i < portalsLayers.length; i++) { - // i is also the portal level - portalsLayers[i].eachLayer(function(item) { - var itemGuid = item.options.guid; - // check if 'item' is a portal - if(getTypeByGuid(itemGuid) != TYPE_PORTAL) return true; - // portal must be in bounds and have a high enough level. Also don’t - // remove if it is selected. - if(itemGuid == window.selectedPortal || - (b.contains(item.getLatLng()) && i >= minlvl)) return true; - cnt[0]++; - portalsLayers[i].removeLayer(item); - }); - } - linksLayer.eachLayer(function(link) { - if(b.intersects(link.getBounds())) return; - cnt[1]++; - linksLayer.removeLayer(link); - }); - fieldsLayer.eachLayer(function(field) { - if(b.intersects(field.getBounds())) return; - cnt[2]++; - fieldsLayer.removeLayer(field); - }); - console.log('removed out-of-bounds: '+cnt[0]+' portals, '+cnt[1]+' links, '+cnt[2]+' fields'); -} - - -// removes given entity from map -window.removeByGuid = function(guid) { - switch(getTypeByGuid(guid)) { - case TYPE_PORTAL: - if(!window.portals[guid]) return; - var p = window.portals[guid]; - for(var i = 0; i < portalsLayers.length; i++) - portalsLayers[i].removeLayer(p); - break; - case TYPE_LINK: - if(!window.links[guid]) return; - linksLayer.removeLayer(window.links[guid]); - break; - case TYPE_FIELD: - if(!window.fields[guid]) return; - fieldsLayer.removeLayer(window.fields[guid]); - break; - case TYPE_RESONATOR: - if(!window.resonators[guid]) return; - var r = window.resonators[guid]; - for(var i = 1; i < portalsLayers.length; i++) - portalsLayers[i].removeLayer(r); - break; - default: - console.warn('unknown GUID type: ' + guid); - //window.debug.printStackTrace(); - } -} - - - -// renders a portal on the map from the given entity -window.renderPortal = function(ent) { - if(Object.keys(portals).length >= MAX_DRAWN_PORTALS && ent[0] != selectedPortal) - return removeByGuid(ent[0]); - - // hide low level portals on low zooms - var portalLevel = getPortalLevel(ent[2]); - if(portalLevel < getMinPortalLevel() && ent[0] != selectedPortal) - return removeByGuid(ent[0]); - - var team = getTeam(ent[2]); - - // do nothing if portal did not change - var layerGroup = portalsLayers[parseInt(portalLevel)]; - var old = findEntityInLeaflet(layerGroup, window.portals, ent[0]); - if(old) { - var oo = old.options; - var u = oo.team !== team; - u = u || oo.level !== portalLevel; - // nothing for the portal changed, so don’t update. Let resonators - // manage themselves if they want to be updated. - if(!u) return renderResonators(ent); - removeByGuid(ent[0]); - } - - // there were changes, remove old portal - removeByGuid(ent[0]); - - var latlng = [ent[2].locationE6.latE6/1E6, ent[2].locationE6.lngE6/1E6]; - - // pre-loads player names for high zoom levels - loadPlayerNamesForPortal(ent[2]); - - - var lvWeight = Math.max(2, portalLevel / 1.5); - var lvRadius = Math.max(portalLevel + 3, 5); - - var p = L.circleMarker(latlng, { - radius: lvRadius, - color: ent[0] == selectedPortal ? COLOR_SELECTED_PORTAL : COLORS[team], - opacity: 1, - weight: lvWeight, - fillColor: COLORS[team], - fillOpacity: 0.5, - clickable: true, - level: portalLevel, - team: team, - details: ent[2], - guid: ent[0]}); - - p.on('remove', function() { - var portalGuid = this.options.guid - - // remove attached resonators, skip if - // all resonators have already removed by zooming - if(isResonatorsShow()) { - for(var i = 0; i <= 7; i++) - removeByGuid(portalResonatorGuid(portalGuid,i)); - } - delete window.portals[portalGuid]; - if(window.selectedPortal === portalGuid) { - window.unselectOldPortal(); - window.map.removeLayer(window.portalAccessIndicator); - window.portalAccessIndicator = null; - } - }); - - p.on('add', function() { - // enable for debugging - if(window.portals[this.options.guid]) throw('duplicate portal detected'); - window.portals[this.options.guid] = this; - // handles the case where a selected portal gets removed from the - // map by hiding all portals with said level - if(window.selectedPortal != this.options.guid) - window.portalResetColor(this); - }); - - p.on('click', function() { window.renderPortalDetails(ent[0]); }); - p.on('dblclick', function() { - window.renderPortalDetails(ent[0]); - window.map.setView(latlng, 17); - }); - - window.renderResonators(ent); - - window.runHooks('portalAdded', {portal: p}); - - // portalLevel contains a float, need to round down - p.addTo(layerGroup); -} - -window.renderResonators = function(ent) { - var portalLevel = getPortalLevel(ent[2]); - if(portalLevel < getMinPortalLevel() && ent[0] != selectedPortal) return; - - if(!isResonatorsShow()) return; - - for(var i=0; i < ent[2].resonatorArray.resonators.length; i++) { - var rdata = ent[2].resonatorArray.resonators[i]; - - if(rdata == null) continue; - - if(window.resonators[portalResonatorGuid(ent[0],i)]) continue; - - // offset in meters - var dn = rdata.distanceToPortal*SLOT_TO_LAT[rdata.slot]; - var de = rdata.distanceToPortal*SLOT_TO_LNG[rdata.slot]; - - // Coordinate offset in radians - var dLat = dn/EARTH_RADIUS; - var dLon = de/(EARTH_RADIUS*Math.cos(Math.PI/180*(ent[2].locationE6.latE6/1E6))); - - // OffsetPosition, decimal degrees - var lat0 = ent[2].locationE6.latE6/1E6 + dLat * 180/Math.PI; - var lon0 = ent[2].locationE6.lngE6/1E6 + dLon * 180/Math.PI; - var Rlatlng = [lat0, lon0]; - var r = L.circleMarker(Rlatlng, { - radius: 3, - // #AAAAAA outline seems easier to see the fill opacity - color: '#AAAAAA', - opacity: 1, - weight: 1, - fillColor: COLORS_LVL[rdata.level], - fillOpacity: rdata.energyTotal/RESO_NRG[rdata.level], - clickable: false, - level: rdata.level, - details: rdata, - pDetails: ent[2], - guid: portalResonatorGuid(ent[0],i) }); - - r.on('remove', function() { delete window.resonators[this.options.guid]; }); - r.on('add', function() { window.resonators[this.options.guid] = this; }); - - r.addTo(portalsLayers[parseInt(portalLevel)]); - } -} - -// append portal guid with -resonator-[slot] to get guid for resonators -window.portalResonatorGuid = function(portalGuid, slot) { - return portalGuid + '-resonator-' + slot; -} - -window.isResonatorsShow = function() { - return map.getZoom() >= RESONATOR_DISPLAY_ZOOM_LEVEL; -} - -window.portalResetColor = function(portal) { - portal.setStyle({color: portal.options.fillColor}); -} - -// renders a link on the map from the given entity -window.renderLink = function(ent) { - if(Object.keys(links).length >= MAX_DRAWN_LINKS) - return removeByGuid(ent[0]); - - // assume that links never change. If they do, they will have a - // different ID. - if(findEntityInLeaflet(linksLayer, links, ent[0])) return; - - var team = getTeam(ent[2]); - var edge = ent[2].edge; - var latlngs = [ - [edge.originPortalLocation.latE6/1E6, edge.originPortalLocation.lngE6/1E6], - [edge.destinationPortalLocation.latE6/1E6, edge.destinationPortalLocation.lngE6/1E6] - ]; - var poly = L.polyline(latlngs, { - color: COLORS[team], - opacity: 1, - weight:2, - clickable: false, - guid: ent[0], - smoothFactor: 10 - }); - - if(!getPaddedBounds().intersects(poly.getBounds())) return; - - poly.on('remove', function() { delete window.links[this.options.guid]; }); - poly.on('add', function() { - // enable for debugging - if(window.links[this.options.guid]) throw('duplicate link detected'); - window.links[this.options.guid] = this; - this.bringToBack(); - }); - poly.addTo(linksLayer); -} - -// renders a field on the map from a given entity -window.renderField = function(ent) { - if(Object.keys(fields).length >= MAX_DRAWN_FIELDS) - return window.removeByGuid(ent[0]); - - // assume that fields never change. If they do, they will have a - // different ID. - if(findEntityInLeaflet(fieldsLayer, fields, ent[0])) return; - - var team = getTeam(ent[2]); - var reg = ent[2].capturedRegion; - var latlngs = [ - [reg.vertexA.location.latE6/1E6, reg.vertexA.location.lngE6/1E6], - [reg.vertexB.location.latE6/1E6, reg.vertexB.location.lngE6/1E6], - [reg.vertexC.location.latE6/1E6, reg.vertexC.location.lngE6/1E6] - ]; - var poly = L.polygon(latlngs, { - fillColor: COLORS[team], - fillOpacity: 0.25, - stroke: false, - clickable: false, - smoothFactor: 10, - vertices: ent[2].capturedRegion, - lastUpdate: ent[1], - guid: ent[0]}); - - if(!getPaddedBounds().intersects(poly.getBounds())) return; - - poly.on('remove', function() { delete window.fields[this.options.guid]; }); - poly.on('add', function() { - // enable for debugging - if(window.fields[this.options.guid]) console.warn('duplicate field detected'); - window.fields[this.options.guid] = this; - this.bringToBack(); - }); - poly.addTo(fieldsLayer); -} - - -// looks for the GUID in either the layerGroup or entityHash, depending -// on which is faster. Will either return the Leaflet entity or null, if -// it does not exist. -// For example, to find a field use the function like this: -// field = findEntityInLeaflet(fieldsLayer, fields, 'asdasdasd'); -window.findEntityInLeaflet = function(layerGroup, entityHash, guid) { - // fast way - if(map.hasLayer(layerGroup)) return entityHash[guid] || null; - - // slow way in case the layer is currently hidden - var ent = null; - layerGroup.eachLayer(function(entity) { - if(entity.options.guid !== guid) return true; - ent = entity; - return false; - }); - return ent; -} - - - -// REQUEST HANDLING ////////////////////////////////////////////////// -// note: only meant for portal/links/fields request, everything else -// does not count towards “loading” - -window.activeRequests = []; -window.failedRequestCount = 0; - -window.requests = function() {} - -window.requests.add = function(ajax) { - window.activeRequests.push(ajax); - renderUpdateStatus(); -} - -window.requests.remove = function(ajax) { - window.activeRequests.splice(window.activeRequests.indexOf(ajax), 1); - renderUpdateStatus(); -} - -window.requests.abort = function() { - $.each(window.activeRequests, function(ind, actReq) { - if(actReq) actReq.abort(); - }); - - window.activeRequests = []; - window.failedRequestCount = 0; - window.chat._requestOldPublicRunning = false; - window.chat._requestNewPublicRunning = false; - window.chat._requestOldFactionRunning = false; - window.chat._requestNewFactionRunning = false; - - renderUpdateStatus(); -} - -// gives user feedback about pending operations. Draws current status -// to website. Updates info in layer chooser. -window.renderUpdateStatus = function() { - var t = 'map status: '; - if(mapRunsUserAction) - t += 'paused during interaction'; - else if(isIdle()) - t += 'Idle, not updating.'; - else if(window.activeRequests.length > 0) - t += window.activeRequests.length + ' requests running.'; - else - t += 'Up to date.'; - - if(renderLimitReached()) - t += ' RENDER LIMIT ' - - if(window.failedRequestCount > 0) - t += ' ' + window.failedRequestCount + ' failed.' - - t += '
('; - var minlvl = getMinPortalLevel(); - if(minlvl === 0) - t += 'loading all portals'; - else - t+= 'only loading portals with level '+minlvl+' and up'; - t += ')'; - - var portalSelection = $('.leaflet-control-layers-overlays label'); - portalSelection.slice(0, minlvl+1).addClass('disabled').attr('title', 'Zoom in to show those.'); - portalSelection.slice(minlvl, 8).removeClass('disabled').attr('title', ''); - - - $('#updatestatus').html(t); -} - - -// sets the timer for the next auto refresh. Ensures only one timeout -// is queued. May be given 'override' in milliseconds if time should -// not be guessed automatically. Especially useful if a little delay -// is required, for example when zooming. -window.startRefreshTimeout = function(override) { - // may be required to remove 'paused during interaction' message in - // status bar - window.renderUpdateStatus(); - if(refreshTimeout) clearTimeout(refreshTimeout); - var t = 0; - if(override) { - t = override; - } else { - t = REFRESH*1000; - var adj = ZOOM_LEVEL_ADJ * (18 - window.map.getZoom()); - if(adj > 0) t += adj*1000; - } - var next = new Date(new Date().getTime() + t).toLocaleTimeString(); - console.log('planned refresh: ' + next); - refreshTimeout = setTimeout(window.requests._callOnRefreshFunctions, t); -} - -window.requests._onRefreshFunctions = []; -window.requests._callOnRefreshFunctions = function() { - startRefreshTimeout(); - - if(isIdle()) { - console.log('user has been idle for ' + idleTime + ' minutes. Skipping refresh.'); - renderUpdateStatus(); - return; - } - - console.log('refreshing'); - - $.each(window.requests._onRefreshFunctions, function(ind, f) { - f(); - }); -} - - -// add method here to be notified of auto-refreshes -window.requests.addRefreshFunction = function(f) { - window.requests._onRefreshFunctions.push(f); -} - - - - -// UTILS + MISC /////////////////////////////////////////////////////// - -// 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); }; - return $.ajax({ - url: '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')); - } - }); -} - -// 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()); -} - -window.reportPortalIssue = function(info) { - var t = 'Redirecting you to a Google Help Page. Once there, click on “Contact Us” in the upper right corner.\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'; -} - -window._storedPaddedBounds = undefined; -window.getPaddedBounds = function() { - if(_storedPaddedBounds === undefined) { - map.on('zoomstart zoomend movestart moveend', function() { - window._storedPaddedBounds = null; - }); - } - if(window._storedPaddedBounds) return window._storedPaddedBounds; - - var p = window.map.getBounds().pad(VIEWPORT_PAD_RATIO); - window._storedPaddedBounds = p; - return p; -} - -window.renderLimitReached = function() { - if(Object.keys(portals).length >= MAX_DRAWN_PORTALS) return true; - if(Object.keys(links).length >= MAX_DRAWN_LINKS) return true; - if(Object.keys(fields).length >= MAX_DRAWN_FIELDS) return true; - return false; -} - -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]; - return conv[z]; -} - -// 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(); - $(elm).attr('href', 'http://www.ingress.com/intel?' + qry); -} - -window.uniqueArray = function(arr) { - return $.grep(arr, function(v, i) { - return $.inArray(v, arr) === i; - }); -} - - - - -// 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.setupLargeImagePreview = function() { - $('#portaldetails').on('click', '.imgpreview img', function() { - var ex = $('#largepreview'); - if(ex.length > 0) { - ex.remove(); - return; - } - var img = $(this).parent().html(); - var w = $(this)[0].naturalWidth/2; - var h = $(this)[0].naturalHeight/2; - var c = $('#portaldetails').attr('class'); - $('body').append( - '
' + img + '
' - ); - $('#largepreview').click(function() { $(this).remove() }); - $('#largepreview img').attr('title', ''); - }); -} - - -window.setupStyles = function() { - $('head').append(''); -} - -window.setupMap = function() { - $('#map').text(''); - - var osmOpt = {attribution: 'Map data © OpenStreetMap contributors', maxZoom: 18}; - var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', osmOpt); - - var cmOpt = {attribution: 'Map data © OpenStreetMap contributors, Imagery © CloudMade', maxZoom: 18}; - var cmMin = new L.TileLayer('http://{s}.tile.cloudmade.com/654cef5fd49a432ab81267e200ecc502/22677/256/{z}/{x}/{y}.png', cmOpt); - var cmMid = new L.TileLayer('http://{s}.tile.cloudmade.com/654cef5fd49a432ab81267e200ecc502/999/256/{z}/{x}/{y}.png', cmOpt); - - var views = [cmMid, cmMin, osm, new L.Google('INGRESS'), new L.Google('ROADMAP'), - new L.Google('SATELLITE'), new L.Google('HYBRID')]; - - - window.map = new L.Map('map', $.extend(getPosition(), - {zoomControl: !(localStorage['iitc.zoom.buttons'] === 'false')} - )); - - try { - map.addLayer(views[readCookie('ingress.intelmap.type')]); - } catch(e) { map.addLayer(views[0]); } - - var addLayers = {}; - - portalsLayers = []; - for(var i = 0; i <= 8; i++) { - portalsLayers[i] = L.layerGroup([]); - map.addLayer(portalsLayers[i]); - var t = (i === 0 ? 'Unclaimed' : 'Level ' + i) + ' Portals'; - addLayers[t] = portalsLayers[i]; - } - - fieldsLayer = L.layerGroup([]); - map.addLayer(fieldsLayer, true); - addLayers['Fields'] = fieldsLayer; - - linksLayer = L.layerGroup([]); - map.addLayer(linksLayer, true); - addLayers['Links'] = linksLayer; - - map.addControl(new L.Control.Layers({ - 'OSM Cloudmade Midnight': views[0], - 'OSM Cloudmade Minimal': views[1], - 'OSM Mapnik': views[2], - 'Google Roads Ingress Style': views[3], - 'Google Roads': views[4], - 'Google Satellite': views[5], - 'Google Hybrid': views[6] - }, addLayers)); - map.attributionControl.setPrefix(''); - // listen for changes and store them in cookies - map.on('moveend', window.storeMapPosition); - map.on('zoomend', function() { - window.storeMapPosition; - - // remove all resonators if zoom out to < RESONATOR_DISPLAY_ZOOM_LEVEL - if(isResonatorsShow()) return; - for(var i = 1; i < portalsLayers.length; i++) { - portalsLayers[i].eachLayer(function(item) { - var itemGuid = item.options.guid; - // check if 'item' is a resonator - if(getTypeByGuid(itemGuid) != TYPE_RESONATOR) return true; - portalsLayers[i].removeLayer(item); - }); - } - - console.log('Remove all resonators'); - }); - $("[name='leaflet-base-layers']").change(function () { - writeCookie('ingress.intelmap.type', $(this).parent().index()); - }); - - // map update status handling - map.on('movestart zoomstart', function() { window.mapRunsUserAction = true }); - map.on('moveend zoomend', function() { window.mapRunsUserAction = false }); - - // update map hooks - map.on('movestart zoomstart', window.requests.abort); - map.on('moveend zoomend', function() { window.startRefreshTimeout(500) }); - - // run once on init - window.requestData(); - window.startRefreshTimeout(); - - window.addResumeFunction(window.requestData); - window.requests.addRefreshFunction(window.requestData); -}; - -// renders player details into the website. Since the player info is -// included as inline script in the original site, the data is static -// and cannot be updated. -window.setupPlayerStat = function() { - var level; - var ap = parseInt(PLAYER.ap); - for(level = 0; level < MIN_AP_FOR_LEVEL.length; level++) { - if(ap < MIN_AP_FOR_LEVEL[level]) break; - } - - var thisLvlAp = MIN_AP_FOR_LEVEL[level-1]; - var nextLvlAp = MIN_AP_FOR_LEVEL[level] || ap; - var lvlUpAp = digits(nextLvlAp-ap); - var lvlApProg = Math.round((ap-thisLvlAp)/(nextLvlAp-thisLvlAp)*100); - - - var xmMax = MAX_XM_PER_LEVEL[level]; - var xmRatio = Math.round(PLAYER.energy/xmMax*100); - - var cls = PLAYER.team === 'ALIENS' ? 'enl' : 'res'; - - - var t = 'Level:\t\t' + level + '\n' - + 'XM:\t\t\t' + PLAYER.energy + ' / ' + xmMax + '\n' - + 'AP:\t\t\t' + digits(ap) + '\n' - + (level < 8 ? 'level up in:\t' + lvlUpAp + ' AP' : 'Congrats! (neeeeerd)') - + '\n\Invites:\t\t'+PLAYER.available_invites; - + '\n\nNote: your player stats can only be updated by a full reload (F5)'; - - $('#playerstat').html('' - + '

'+level+' ' - + ''+PLAYER.nickname+'' - + '
' - + 'XM: '+xmRatio+'%' - + '' + (level < 8 ? 'level: '+lvlApProg+'%' : 'max level') + '' - + '
' - + '

' - ); -} - -window.setupSidebarToggle = function() { - $('#sidebartoggle').on('click', function() { - var toggle = $('#sidebartoggle'); - var sidebar = $('#scrollwrapper'); - if(sidebar.is(':visible')) { - sidebar.hide().css('z-index', 1); - $('.leaflet-right').css('margin-right','0'); - toggle.html(''); - toggle.css('right', '0'); - } else { - sidebar.css('z-index', 1001).show(); - $('.leaflet-right').css('margin-right', SIDEBAR_WIDTH+1+'px'); - toggle.html(''); - toggle.css('right', SIDEBAR_WIDTH+1+'px'); - } - }); -} - - -// BOOTING /////////////////////////////////////////////////////////// - -function boot() { - console.log('loading done, booting'); - window.setupStyles(); - window.setupMap(); - window.setupGeosearch(); - window.setupRedeem(); - window.setupLargeImagePreview(); - window.setupSidebarToggle(); - window.updateGameScore(); - window.setupPlayerStat(); - window.chat.setup(); - // read here ONCE, so the URL is only evaluated one time after the - // necessary data has been loaded. - urlPortal = getURLParam('pguid'); - - // load only once - var n = window.PLAYER['nickname']; - window.PLAYER['nickMatcher'] = new RegExp('\\b('+n+')\\b', 'ig'); - - $('#sidebar').show(); - - if(window.bootPlugins) - $.each(window.bootPlugins, function(ind, ref) { ref(); }); - - window.iitcLoaded = true; -} - -// this is the minified load.js script that allows us to easily load -// further javascript files async as well as in order. -// https://github.com/chriso/load.js -// Copyright (c) 2010 Chris O'Hara . MIT Licensed -function asyncLoadScript(a){return function(b,c){var d=document.createElement("script");d.type="text/javascript",d.src=a,d.onload=b,d.onerror=c,d.onreadystatechange=function(){var a=this.readyState;if(a==="loaded"||a==="complete")d.onreadystatechange=null,b()},head.insertBefore(d,head.firstChild)}}(function(a){a=a||{};var b={},c,d;c=function(a,d,e){var f=a.halt=!1;a.error=function(a){throw a},a.next=function(c){c&&(f=!1);if(!a.halt&&d&&d.length){var e=d.shift(),g=e.shift();f=!0;try{b[g].apply(a,[e,e.length,g])}catch(h){a.error(h)}}return a};for(var g in b){if(typeof a[g]=="function")continue;(function(e){a[e]=function(){var g=Array.prototype.slice.call(arguments);if(e==="onError"){if(d)return b.onError.apply(a,[g,g.length]),a;var h={};return b.onError.apply(h,[g,g.length]),c(h,null,"onError")}return g.unshift(e),d?(a.then=a[e],d.push(g),f?a:a.next()):c({},[g],e)}})(g)}return e&&(a.then=a[e]),a.call=function(b,c){c.unshift(b),d.unshift(c),a.next(!0)},a.next()},d=a.addMethod=function(d){var e=Array.prototype.slice.call(arguments),f=e.pop();for(var g=0,h=e.length;g= 15) - r.shift(); -} - -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 = window.chat._lastNicksForAutocomplete; - list = list[1].concat(list[0]); - - 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._oldFactionTimestamp = -1; -window.chat._newFactionTimestamp = -1; -window.chat._oldPublicTimestamp = -1; -window.chat._newPublicTimestamp = -1; - -window.chat.getOldestTimestamp = function(public) { - return chat['_old'+(public ? 'Public' : 'Faction')+'Timestamp']; -} - -window.chat.getNewestTimestamp = function(public) { - return chat['_new'+(public ? 'Public' : 'Faction')+'Timestamp']; -} - -window.chat.clearIfRequired = function(elm) { - if(!elm.data('needsClearing')) return; - elm.data('ignoreNextScroll', true).data('needsClearing', false).html(''); -} - -window.chat._oldBBox = null; -window.chat.genPostData = function(public, getOlderMsgs) { - if(typeof public !== '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._displayedFactionGuids = []; - chat._displayedPublicGuids = []; - chat._displayedPlayerActionTime = {}; - chat._oldFactionTimestamp = -1; - chat._newFactionTimestamp = -1; - chat._oldPublicTimestamp = -1; - chat._newPublicTimestamp = -1; - } - chat._oldBBox = bbs; - - var ne = b.getNorthEast(); - var sw = b.getSouthWest(); - var data = { - desiredNumItems: public ? CHAT_PUBLIC_ITEMS : CHAT_FACTION_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: !public - } - - if(getOlderMsgs) { - // ask for older chat when scrolling up - data = $.extend(data, {maxTimestampMs: chat.getOldestTimestamp(public)}); - } else { - // ask for newer chat - var min = chat.getNewestTimestamp(public); - // 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; -} - - - -// -// requesting faction -// - -window.chat._requestOldFactionRunning = false; -window.chat.requestOldFaction = function(isRetry) { - if(chat._requestOldFactionRunning) return; - if(isIdle()) return renderUpdateStatus(); - chat._requestOldFactionRunning = true; - - var d = chat.genPostData(false, true); - var r = window.postAjax( - 'getPaginatedPlextsV2', - d, - chat.handleOldFaction, - isRetry - ? function() { window.chat._requestOldFactionRunning = false; } - : function() { window.chat.requestOldFaction(true) } - ); - - requests.add(r); -} - -window.chat._requestNewFactionRunning = false; -window.chat.requestNewFaction = function(isRetry) { - if(chat._requestNewFactionRunning) return; - if(window.isIdle()) return renderUpdateStatus(); - chat._requestNewFactionRunning = true; - - var d = chat.genPostData(false, false); - var r = window.postAjax( - 'getPaginatedPlextsV2', - d, - chat.handleNewFaction, - isRetry - ? function() { window.chat._requestNewFactionRunning = false; } - : function() { window.chat.requestNewFaction(true) } - ); - - requests.add(r); -} - - -// -// handle faction -// - -window.chat.handleOldFaction = function(data, textStatus, jqXHR) { - chat._requestOldFactionRunning = false; - chat.handleFaction(data, textStatus, jqXHR, true); -} - -window.chat.handleNewFaction = function(data, textStatus, jqXHR) { - chat._requestNewFactionRunning = false; - chat.handleFaction(data, textStatus, jqXHR, false); -} - - - -window.chat._displayedFactionGuids = []; -window.chat.handleFaction = function(data, textStatus, jqXHR, isOldMsgs) { - if(!data || !data.result) { - window.failedRequestCount++; - return console.warn('faction chat error. Waiting for next auto-refresh.'); - } - - var c = $('#chatfaction'); - chat.clearIfRequired(c); - - if(data.result.length === 0) return; - - chat._newFactionTimestamp = data.result[0][1]; - chat._oldFactionTimestamp = data.result[data.result.length-1][1]; - - var scrollBefore = scrollBottom(c); - chat.renderPlayerMsgsTo(true, data, isOldMsgs, chat._displayedFactionGuids); - chat.keepScrollPosition(c, scrollBefore, isOldMsgs); - - if(data.result.length >= CHAT_FACTION_ITEMS) chat.needMoreMessages(); -} - - - - -// -// requesting public -// - -window.chat._requestOldPublicRunning = false; -window.chat.requestOldPublic = function(isRetry) { - if(chat._requestOldPublicRunning) return; - if(isIdle()) return renderUpdateStatus(); - chat._requestOldPublicRunning = true; - - var d = chat.genPostData(true, true); - var r = window.postAjax( - 'getPaginatedPlextsV2', - d, - chat.handleOldPublic, - isRetry - ? function() { window.chat._requestOldPublicRunning = false; } - : function() { window.chat.requestOldPublic(true) } - ); - - requests.add(r); -} - -window.chat._requestNewPublicRunning = false; -window.chat.requestNewPublic = function(isRetry) { - if(chat._requestNewPublicRunning) return; - if(window.isIdle()) return renderUpdateStatus(); - chat._requestNewPublicRunning = true; - - var d = chat.genPostData(true, false); - var r = window.postAjax( - 'getPaginatedPlextsV2', - d, - chat.handleNewPublic, - isRetry - ? function() { window.chat._requestNewPublicRunning = false; } - : function() { window.chat.requestNewPublic(true) } - ); - - requests.add(r); -} - - -// -// handle public -// - - -window.chat.handleOldPublic = function(data, textStatus, jqXHR) { - chat._requestOldPublicRunning = false; - chat.handlePublic(data, textStatus, jqXHR, true); -} - -window.chat.handleNewPublic = function(data, textStatus, jqXHR) { - chat._requestNewPublicRunning = false; - chat.handlePublic(data, textStatus, jqXHR, false); -} - -window.chat._displayedPublicGuids = []; -window.chat._displayedPlayerActionTime = {}; -window.chat.handlePublic = function(data, textStatus, jqXHR, isOldMsgs) { - if(!data || !data.result) { - window.failedRequestCount++; - return console.warn('public chat error. Waiting for next auto-refresh.'); - } - - var ca = $('#chatautomated'); - var cp = $('#chatpublic'); - chat.clearIfRequired(ca); - chat.clearIfRequired(cp); - - if(data.result.length === 0) return; - - chat._newPublicTimestamp = data.result[0][1]; - chat._oldPublicTimestamp = data.result[data.result.length-1][1]; - - - var scrollBefore = scrollBottom(ca); - chat.handlePublicAutomated(data); - chat.keepScrollPosition(ca, scrollBefore, isOldMsgs); - - - var scrollBefore = scrollBottom(cp); - chat.renderPlayerMsgsTo(false, data, isOldMsgs, chat._displayedPublicGuids); - chat.keepScrollPosition(cp, scrollBefore, isOldMsgs); - - if(data.result.length >= CHAT_PUBLIC_ITEMS) chat.needMoreMessages(); -} - - -window.chat.handlePublicAutomated = function(data) { - $.each(data.result, function(ind, json) { // newest first! - var time = json[1]; - - // ignore player messages - var t = json[2].plext.plextType; - if(t !== 'SYSTEM_BROADCAST' && t !== 'SYSTEM_NARROWCAST') return true; - - var tmpmsg = '', nick = null, pguid, team; - - // each automated message is composed of many text chunks. loop - // over them to gather all necessary data. - $.each(json[2].plext.markup, function(ind, part) { - switch(part[0]) { - case 'PLAYER': - pguid = part[1].guid; - var lastAction = window.chat._displayedPlayerActionTime[pguid]; - // ignore older messages about player - if(lastAction && lastAction[0] > time) return false; - - nick = part[1].plain; - team = part[1].team === 'ALIENS' ? TEAM_ENL : TEAM_RES; - window.setPlayerName(pguid, nick); // free nick name resolves - if(ind > 0) tmpmsg += nick; // don’t repeat nick directly - break; - - case 'TEXT': - tmpmsg += part[1].plain; - break; - - case 'PORTAL': - var latlng = [part[1].latE6/1E6, part[1].lngE6/1E6]; - var js = 'window.zoomToAndShowPortal(\''+part[1].guid+'\', ['+latlng[0]+', '+latlng[1]+'])'; - tmpmsg += ''+part[1].name+''; - break; - } - }); - - // nick will only be set if we don’t have any info about that - // player yet. - if(nick) { - tmpmsg = chat.renderMsg(tmpmsg, nick, time, team); - window.chat._displayedPlayerActionTime[pguid] = [time, tmpmsg]; - }; - }); - - if(chat.getActive() === 'automated') - window.chat.renderAutomatedMsgsTo(); -} - -window.chat.renderAutomatedMsgsTo = function() { - var x = window.chat._displayedPlayerActionTime; - // we don’t care about the GUIDs anymore - var vals = $.map(x, function(v, k) { return [v]; }); - // sort them old to new - vals = vals.sort(function(a, b) { return a[0]-b[0]; }); - - var prevTime = null; - var msgs = $.map(vals, function(v) { - var nowTime = new Date(v[0]).toLocaleDateString(); - if(prevTime && prevTime !== nowTime) - var val = chat.renderDivider(nowTime) + v[1]; - else - var val = v[1]; - - prevTime = nowTime; - return val; - }).join('\n'); - - $('#chatautomated').html(msgs); -} - - - - -// -// common -// - - -window.chat.renderPlayerMsgsTo = function(isFaction, data, isOldMsgs, dupCheckArr) { - var msgs = ''; - var prevTime = null; - - $.each(data.result.reverse(), function(ind, json) { // oldest first! - if(json[2].plext.plextType !== 'PLAYER_GENERATED') return true; - - // avoid duplicates - if(dupCheckArr.indexOf(json[0]) !== -1) return true; - dupCheckArr.push(json[0]); - - var time = json[1]; - var team = json[2].plext.team === 'ALIENS' ? TEAM_ENL : TEAM_RES; - var msg, nick, pguid; - $.each(json[2].plext.markup, function(ind, markup) { - if(markup[0] === 'SENDER') { - nick = markup[1].plain.slice(0, -2); // cut “: ” at end - pguid = markup[1].guid; - window.setPlayerName(pguid, nick); // free nick name resolves - if(!isOldMsgs) window.chat.addNickForAutocomplete(nick, isFaction); - } - - if(markup[0] === 'TEXT') { - msg = markup[1].plain.autoLink(); - msg = msg.replace(window.PLAYER['nickMatcher'], '$1'); - } - - if(!isFaction && markup[0] === 'SECURE') { - nick = null; - return false; // aka break - } - }); - - if(!nick) return true; // aka next - - var nowTime = new Date(time).toLocaleDateString(); - if(prevTime && prevTime !== nowTime) - msgs += chat.renderDivider(nowTime); - - msgs += chat.renderMsg(msg, nick, time, team); - prevTime = nowTime; - }); - - var addTo = isFaction ? $('#chatfaction') : $('#chatpublic'); - - // if there is a change of day between two requests, handle the - // divider insertion here. - if(isOldMsgs) { - var ts = addTo.find('time:first').data('timestamp'); - var nextTime = new Date(ts).toLocaleDateString(); - if(prevTime && prevTime !== nextTime && ts) - msgs += chat.renderDivider(nextTime); - } - - if(isOldMsgs) - addTo.prepend(msgs); - else - addTo.append(msgs); -} - - -window.chat.renderDivider = function(text) { - return '─ '+text+' ────────────────────────────────────────────────────────────────────────────'; -} - - -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"' : ''; - return '

'+t+' <'+nick+'> '+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.requestNewFaction(); - chat.requestNewPublic(); -} - - -// checks if there are enough messages in the selected chat tab and -// loads more if not. -window.chat.needMoreMessages = function() { - var activeChat = $('#chat > :visible'); - if(scrollBottom(activeChat) !== 0 || activeChat.scrollTop() !== 0) return; - console.log('no scrollbar in active chat, requesting more msgs'); - if($('#chatcontrols a:last.active').length) - chat.requestOldFaction(); - else - chat.requestOldPublic(); -} - - -window.chat.chooser = function(event) { - var t = $(event.target); - var tt = t.text(); - var span = $('#chatinput span'); - - $('#chatcontrols .active').removeClass('active'); - t.addClass('active'); - - $('#chat > div').hide(); - - var elm; - - switch(tt) { - case 'faction': - span.css('color', ''); - span.text('tell faction:'); - elm = $('#chatfaction'); - break; - - case 'public': - span.css('cssText', 'color: red !important'); - span.text('broadcast:'); - elm = $('#chatpublic'); - break; - - case 'automated': - span.css('cssText', 'color: #bbb !important'); - span.text('tell Jarvis:'); - chat.renderAutomatedMsgsTo(); - elm = $('#chatautomated'); - break; - } - - elm.show(); - 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:not(:first)').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() < 200) chat.requestOldFaction(); - if(scrollBottom(t) === 0) chat.requestNewFaction(); - }); - - $('#chatpublic, #chatautomated').scroll(function() { - var t = $(this); - if(t.data('ignoreNextScroll')) return t.data('ignoreNextScroll', false); - if(t.scrollTop() < 200) chat.requestOldPublic(); - if(scrollBottom(t) === 0) chat.requestNewPublic(); - }); - - chat.request(); - window.addResumeFunction(chat.request); - window.requests.addRefreshFunction(chat.request); - - var cls = PLAYER.team === 'ALIENS' ? 'enl' : 'res'; - $('#chatinput span').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) { - chat.postMsg(); - event.preventDefault(); - }); -} - - -window.chat.postMsg = function() { - var c = chat.getActive(); - if(c === 'automated') 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; - - var public = c === 'public'; - var latlng = map.getCenter(); - - var data = {message: msg, - latE6: Math.round(latlng.lat*1E6), - lngE6: Math.round(latlng.lng*1E6), - factionOnly: !public}; - - window.postAjax('sendPlext', data, - function() { if(public) chat.requestNewPublic(); else chat.requestNewFaction(); }, - function() { - alert('Your message could not be delivered. You can copy&' + - 'paste it here and try again if you want:\n\n'+msg); - } - ); - - $('#chatinput input').val(''); -} - - - -// 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 ['⌀ res 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, 3, 4, 5, 1, 0, 7, 6], function(ind, slot) { - var isLeft = slot >= 2 && slot <= 5; - var reso = d.resonatorArray.resonators[slot]; - if(!reso) { - resoDetails += renderResonatorDetails(slot, 0, 0, null, null, isLeft); - 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 += renderResonatorDetails(slot, l, v, dist, nick, isLeft); - }); - return 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, isLeft) { - if(level === 0) { - var meter = ''; - } else { - var max = RESO_NRG[level]; - var fillGrade = nrg/max*100; - - var inf = 'energy:\t\t' + nrg + ' / ' + max + ' (' + Math.round(fillGrade) + '%)\n' - + 'level:\t\t' + level + '\n' - + 'distance:\t' + dist + 'm\n' - + 'owner:\t\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 + ''; - } - var cls = isLeft ? 'left' : 'right'; - var text = ''+(nick||'')+''; - return (isLeft ? text+meter : meter+text) + '
'; -} - -// calculate AP gain from destroying portal -// so far it counts only resonators + links -window.getDestroyAP = function(d) { - var resoCount = 0; - - $.each(d.resonatorArray.resonators, function(ind, reso) { - if(!reso) return true; - resoCount += 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 sum = resoAp + linkAp + fieldAp; - - function tt(text) { - var t = 'Destroy:\n'; - t += resoCount + '×\tResonators\t= ' + digits(resoAp) + '\n'; - t += linkCount + '×\tLinks\t\t= ' + digits(linkAp) + '\n'; - t += fieldCount + '×\tFields\t\t= ' + digits(fieldAp) + '\n'; - t += 'Sum: ' + digits(sum) + ' AP'; - return '' + digits(text) + ''; - } - - return [tt('AP Gain'), tt(sum)]; -} - - - -// GAME STATUS /////////////////////////////////////////////////////// -// MindUnit display -window.updateGameScore = function(data) { - if(!data) { - window.postAjax('getGameScore', {}, window.updateGameScore); - return; - } - - var r = parseInt(data.result.resistanceScore), e = parseInt(data.result.alienScore); - var s = r+e; - var rp = r/s*100, ep = e/s*100; - r = digits(r), e = digits(e); - var rs = ''+Math.round(rp)+'% '; - var es = ' '+Math.round(ep)+'%'; - $('#gamestat').html(rs+es).one('click', function() { window.updateGameScore() }); - // help cursor via “#gamestat span” - $('#gamestat').attr('title', 'Resistance:\t\t'+r+' MindUnits\nEnlightenment:\t'+e+' MindUnits'); - - window.setTimeout('window.updateGameScore', REFRESH_GAME_SCORE*1000); -} - - - - -// 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) - }; -} - - - - -// ENTITY DETAILS TOOLS ////////////////////////////////////////////// -// hand any of these functions the details-hash of an entity (i.e. -// portal, link, field) and they will return useful data. - - -// given the entity detail data, returns the team the entity belongs -// to. Uses TEAM_* enum values. -window.getTeam = function(details) { - var team = TEAM_NONE; - if(details.controllingTeam.team === 'ALIENS') team = TEAM_ENL; - if(details.controllingTeam.team === 'RESISTANCE') team = TEAM_RES; - return team; -} - - -// IDLE HANDLING ///////////////////////////////////////////////////// - -window.idleTime = 0; // in minutes - -setInterval('window.idleTime += 1', 60*1000); -var idleReset = function () { - // update immediately when the user comes back - if(isIdle()) { - window.idleTime = 0; - $.each(window._onResumeFunctions, function(ind, f) { - f(); - }); - } - window.idleTime = 0; -}; -$('body').mousemove(idleReset).keypress(idleReset); - -window.isIdle = function() { - return window.idleTime >= MAX_IDLE_TIME; -} - -window._onResumeFunctions = []; - -// add your function here if you want to be notified when the user -// resumes from being idle -window.addResumeFunction = function(f) { - window._onResumeFunctions.push(f); -} - - - -// LOCATION HANDLING ///////////////////////////////////////////////// -// i.e. setting initial position and storing new position after moving - -// retrieves current position from map and stores it cookies -window.storeMapPosition = function() { - var m = window.map.getCenter(); - writeCookie('ingress.intelmap.lat', m['lat']); - writeCookie('ingress.intelmap.lng', m['lng']); - writeCookie('ingress.intelmap.zoom', window.map.getZoom()); -} - -// either retrieves the last shown position from a cookie, from the -// URL or if neither is present, via Geolocation. If that fails, it -// returns a map that shows the whole world. -window.getPosition = function() { - if(getURLParam('latE6') && getURLParam('lngE6')) { - console.log("mappos: reading URL params"); - var lat = parseInt(getURLParam('latE6'))/1E6 || 0.0; - var lng = parseInt(getURLParam('lngE6'))/1E6 || 0.0; - // google seems to zoom in far more than leaflet - var z = parseInt(getURLParam('z'))+1 || 17; - return {center: new L.LatLng(lat, lng), zoom: z > 18 ? 18 : z}; - } - - if(readCookie('ingress.intelmap.lat') && readCookie('ingress.intelmap.lng')) { - console.log("mappos: reading cookies"); - var lat = parseFloat(readCookie('ingress.intelmap.lat')) || 0.0; - var lng = parseFloat(readCookie('ingress.intelmap.lng')) || 0.0; - var z = parseInt(readCookie('ingress.intelmap.zoom')) || 17; - return {center: new L.LatLng(lat, lng), zoom: z > 18 ? 18 : z}; - } - - setTimeout("window.map.locate({setView : true, maxZoom: 13});", 50); - - return {center: new L.LatLng(0.0, 0.0), zoom: 1}; -} - - - -// PORTAL DETAILS MAIN /////////////////////////////////////////////// -// main code block that renders the portal details in the sidebar and -// methods that highlight the portal in the map view. - -window.renderPortalDetails = function(guid) { - var d = window.portals[guid].options.details; - if(!d) { - unselectOldPortal(); - urlPortal = guid; - return; - } - - var update = selectPortal(guid); - - // collect some random data that’s not worth to put in an own method - var links = {incoming: 0, outgoing: 0}; - if(d.portalV2.linkedEdges) $.each(d.portalV2.linkedEdges, function(ind, link) { - links[link.isOrigin ? 'outgoing' : 'incoming']++; - }); - function linkExpl(t) { return ''+t+''; } - var linksText = [linkExpl('links'), linkExpl(' ↳ ' + links.incoming+'  •  '+links.outgoing+' ↴')]; - - var player = d.captured && d.captured.capturingPlayerId - ? getPlayerName(d.captured.capturingPlayerId) - : null; - var playerText = player ? ['owner', player] : null; - - var time = d.captured ? unixTimeToString(d.captured.capturedTime) : null; - var sinceText = time ? ['since', time] : null; - - var linkedFields = ['fields', d.portalV2.linkedFields.length]; - - // collect and html-ify random data - var randDetails = [playerText, sinceText, getRangeText(d), getEnergyText(d), linksText, getAvgResoDistText(d), linkedFields, getDestroyAP(d)]; - randDetails = randDetails.map(function(detail) { - if(!detail) return ''; - detail = ''; - return detail; - }).join('\n'); - - // replacing causes flicker, so if the selected portal does not - // change, only update the data points that are likely to change. - if(update) { - console.log('Updating portal details'); - $('#level').text(Math.floor(getPortalLevel(d))); - $('.mods').html(getModDetails(d)); - $('#randdetails').html(randDetails); - $('#resodetails').html(getResonatorDetails(d)); - $('#portaldetails').attr('class', TEAM_TO_CSS[getTeam(d)]); - } else { - console.log('exchanging portal details'); - 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 = 'http://ingress.com/intel?latE6='+lat+'&lngE6='+lng+'&z=17&pguid='+guid; - - $('#portaldetails') - .attr('class', TEAM_TO_CSS[getTeam(d)]) - .html('' - + '

'+d.portalV2.descriptiveText.TITLE+'

' - // help cursor via “.imgpreview img” - + '
' - + ''+Math.floor(getPortalLevel(d))+'' - + '
'+getModDetails(d)+'
' - + '
'+randDetails+'
' - + '
'+getResonatorDetails(d)+'
' - + '
' - + '' - + '' - + '
' - ); - } - - // try to resolve names that were required for above functions, but - // weren’t available yet. - resolvePlayerNames(); -} - -// 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]) - portals[guid].bringToFront().setStyle({color: COLOR_SELECTED_PORTAL}); - - return update; -} - - -window.unselectOldPortal = function() { - var oldPortal = portals[selectedPortal]; - if(oldPortal) - oldPortal.setStyle({color: oldPortal.options.fillColor}); - selectedPortal = null; - $('#portaldetails').html(''); -} - - - - -// 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 = 'The passcode cannot be redeemed.'; - } - alert("Error: " + data.error + "\n" + error); - } else if (data.result) { - var res_level = 0, res_count = 0; - var xmp_level = 0, xmp_count = 0; - var shield_rarity = '', shield_count = 0; - - // This assumes that each passcode gives only one type of resonator/XMP/shield. - // This may break at some point, depending on changes to passcode functionality. - for (var i in data.result.inventoryAward) { - var acquired = data.result.inventoryAward[i][2]; - if (acquired.modResource) { - if (acquired.modResource.resourceType === 'RES_SHIELD') { - shield_rarity = acquired.modResource.rarity.split('_').map(function (i) {return i[0]}).join(''); - shield_count++; - } - } else if (acquired.resourceWithLevels) { - if (acquired.resourceWithLevels.resourceType === 'EMITTER_A') { - res_level = acquired.resourceWithLevels.level; - res_count++; - } else if (acquired.resourceWithLevels.resourceType === 'EMP_BURSTER') { - xmp_level = acquired.resourceWithLevels.level; - xmp_count++; - } - } - } - - alert("Passcode redeemed!\n" + [data.result.apAward + 'AP', data.result.xmAward + 'XM', res_count + 'xL' + res_level + ' RES', xmp_count + 'xL' + xmp_level + ' XMP', shield_count + 'x' + shield_rarity + ' SHIELD'].join('/')); - } -} - -window.setupRedeem = function() { - $("#redeem").keypress(function(e) { - if((e.keyCode ? e.keyCode : e.which) != 13) return; - var data = {passcode: $(this).val()}; - window.postAjax('redeemReward', data, window.handleRedeemResponse, - function() { alert('HTTP request failed. Try again?'); }); - }); -} - - -// 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)+'}'; -} - -// 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) { - 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); - }); -} - - - -// 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); -} - -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(); -} - - - -// 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); - }); - e.preventDefault(); - }); -} - - - - -// 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 sum/resos; -} - - - - - -} // end of wrapper - -// inject code into site context -var script = document.createElement('script'); -script.appendChild(document.createTextNode('('+ wrapper +')();')); -(document.body || document.head || document.documentElement).appendChild(script);