The old system was getting too complicated to add new features to. The new one should be easier to understand and will be explained below. This patch should fix #140 and fix #16. There are several places where new data requests may come from: - chat.needMoreMessages checks if we are scrolled too far up or there is no scrollbar. It then requests new messages. - scrolling up - switching the chat tab via chat.chooser - auto refresh - once on boot The latter two always request both types of chat. Faction and public are handled the same way. Public is later split into categories. I will explain the faction chat here because it’s easier. It starts in chat.requestFaction. This will initialize the query to the server for data. The exact query is determined by chat. genPostData. There can only be one faction chat request at a time. Clearing on map move is handled automatically in code/requests.js. Let’s assume the data query is successful. It is now passed to chat.handleFaction which runs some basic checks before the data is processed and stored in chat._factionData. This is done by chat. writeDataToHash. The hash keys are the message GUIDs to avoid duplicates. The hash values are an array of timestamp, processed message and other meta data. There is only one way to render faction chat, so data is directly handed to chat.renderFaction which immediately hands it to chat .renderData. renderData discards the GUIDs and sorts the values by their timestamp. It then iterates over the data to insert date break lines and finally renders the data in the DOM. It also does some magic to keep the correct scrolling position. Chat data will be cleared on map move or zoom. However, the display is not refreshed, so the old, invalid chat data is still displayed. Only once the data query is finished the data is rendered. The upside is that small map operations within your local area appear to never lose their chat data. Downside is that there’s invalid chat for some time when changing cities.
210 lines
9.1 KiB
JavaScript
210 lines
9.1 KiB
JavaScript
// ==UserScript==
|
||
// @id ingress-intel-total-conversion@breunigs
|
||
// @name intel map total conversion
|
||
// @version 0.51-@@BUILDDATE@@
|
||
// @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 = ''
|
||
//~ + '<link rel="stylesheet" type="text/css" href="http://0.0.0.0:8000/style.css"/>'
|
||
+ '<title>Ingress Intel Map</title>'
|
||
+ '<link rel="stylesheet" type="text/css" href="http://breunigs.github.com/ingress-intel-total-conversion/style.css?@@BUILDDATE@@"/>'
|
||
+ '<link rel="stylesheet" type="text/css" href="http://cdn.leafletjs.com/leaflet-0.5/leaflet.css"/>'
|
||
+ '<link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Coda"/>';
|
||
|
||
document.getElementsByTagName('body')[0].innerHTML = ''
|
||
+ '<div id="map">Loading, please wait</div>'
|
||
+ '<div id="chatcontrols" style="display:none">'
|
||
+ ' <a><span class="toggle expand"></span></a>'
|
||
+ '<a>full</a><a>compact</a><a>public</a><a class="active">faction</a>'
|
||
+ '</div>'
|
||
+ '<div id="chat" style="display:none">'
|
||
+ ' <div id="chatfaction"></div>'
|
||
+ ' <div id="chatpublic"></div>'
|
||
+ ' <div id="chatcompact"></div>'
|
||
+ ' <div id="chatfull"></div>'
|
||
+ '</div>'
|
||
+ '<form id="chatinput" style="display:none"><time></time><span>tell faction:</span><input type="text"/></form>'
|
||
+ '<a id="sidebartoggle"><span class="toggle close"></span></a>'
|
||
+ '<div id="scrollwrapper">' // enable scrolling for small screens
|
||
+ ' <div id="sidebar" style="display: none">'
|
||
+ ' <div id="playerstat">t</div>'
|
||
+ ' <div id="gamestat"> loading global control stats</div>'
|
||
+ ' <input id="geosearch" placeholder="Search location…" type="text"/>'
|
||
+ ' <div id="portaldetails"></div>'
|
||
+ ' <input id="redeem" placeholder="Redeem code…" type="text"/>'
|
||
+ ' <div id="toolbox"><a onmouseover="setPermaLink(this)">permalink</a></div>'
|
||
+ ' </div>'
|
||
+ '</div>'
|
||
+ '<div id="updatestatus"></div>';
|
||
|
||
// 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;
|
||
// how many pixels to the top before requesting new data
|
||
var CHAT_REQUEST_SCROLL_TOP = 200;
|
||
|
||
// 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() {};
|
||
|
||
|
||
@@INJECTHERE@@
|
||
|
||
|
||
} // 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);
|