s aren't needed
if(!isSmartphone()) {
// chat window area
$(window.map._controlCorners['bottomleft']).append(
$('
').width(708).height(108).addClass('leaflet-control').css({'pointer-events': 'none', 'margin': '0'}));
}
var addLayers = {};
var hiddenLayer = [];
portalsFactionLayers = [];
var portalsLayers = [];
for(var i = 0; i <= 8; i++) {
portalsFactionLayers[i] = [L.layerGroup(), L.layerGroup(), L.layerGroup()];
portalsLayers[i] = L.layerGroup(portalsFactionLayers[i]);
map.addLayer(portalsLayers[i]);
var t = (i === 0 ? 'Unclaimed/Placeholder' : 'Level ' + i) + ' Portals';
addLayers[t] = portalsLayers[i];
// Store it in hiddenLayer to remove later
if(!isLayerGroupDisplayed(t, true)) hiddenLayer.push(portalsLayers[i]);
}
fieldsFactionLayers = [L.layerGroup(), L.layerGroup(), L.layerGroup()];
var fieldsLayer = L.layerGroup(fieldsFactionLayers);
map.addLayer(fieldsLayer, true);
addLayers['Fields'] = fieldsLayer;
// Store it in hiddenLayer to remove later
if(!isLayerGroupDisplayed('Fields', true)) hiddenLayer.push(fieldsLayer);
linksFactionLayers = [L.layerGroup(), L.layerGroup(), L.layerGroup()];
var linksLayer = L.layerGroup(linksFactionLayers);
map.addLayer(linksLayer, true);
addLayers['Links'] = linksLayer;
// Store it in hiddenLayer to remove later
if(!isLayerGroupDisplayed('Links', true)) hiddenLayer.push(linksLayer);
// faction-specific layers
// these layers don't actually contain any data. instead, every time they're added/removed from the map,
// the matching sub-layers within the above portals/fields/links are added/removed from their parent with
// the below 'onoverlayadd/onoverlayremove' events
var factionLayers = [L.layerGroup(), L.layerGroup(), L.layerGroup()];
for (var fac in factionLayers) {
map.addLayer (factionLayers[fac]);
}
var setFactionLayersState = function(fac,enabled) {
if (enabled) {
if (!fieldsLayer.hasLayer(fieldsFactionLayers[fac])) fieldsLayer.addLayer (fieldsFactionLayers[fac]);
if (!linksLayer.hasLayer(linksFactionLayers[fac])) linksLayer.addLayer (linksFactionLayers[fac]);
for (var lvl in portalsLayers) {
if (!portalsLayers[lvl].hasLayer(portalsFactionLayers[lvl][fac])) portalsLayers[lvl].addLayer (portalsFactionLayers[lvl][fac]);
}
} else {
if (fieldsLayer.hasLayer(fieldsFactionLayers[fac])) fieldsLayer.removeLayer (fieldsFactionLayers[fac]);
if (linksLayer.hasLayer(linksFactionLayers[fac])) linksLayer.removeLayer (linksFactionLayers[fac]);
for (var lvl in portalsLayers) {
if (portalsLayers[lvl].hasLayer(portalsFactionLayers[lvl][fac])) portalsLayers[lvl].removeLayer (portalsFactionLayers[lvl][fac]);
}
}
}
// to avoid any favouritism, we'll put the player's own faction layer first
if (PLAYER.team == 'RESISTANCE') {
addLayers['Resistance'] = factionLayers[TEAM_RES];
addLayers['Enlightened'] = factionLayers[TEAM_ENL];
} else {
addLayers['Enlightened'] = factionLayers[TEAM_ENL];
addLayers['Resistance'] = factionLayers[TEAM_RES];
}
if (!isLayerGroupDisplayed('Resistance', true)) hiddenLayer.push (factionLayers[TEAM_RES]);
if (!isLayerGroupDisplayed('Enlightened', true)) hiddenLayer.push (factionLayers[TEAM_ENL]);
setFactionLayersState (TEAM_NONE, true);
setFactionLayersState (TEAM_RES, isLayerGroupDisplayed('Resistance', true));
setFactionLayersState (TEAM_ENL, isLayerGroupDisplayed('Enlightened', true));
// NOTE: these events are fired by the layer chooser, so won't happen until that's created and added to the map
window.map.on('overlayadd overlayremove', function(e) {
var displayed = (e.type == 'overlayadd');
switch (e.name) {
case 'Resistance':
setFactionLayersState (TEAM_RES, displayed);
break;
case 'Enlightened':
setFactionLayersState (TEAM_ENL, displayed);
break;
}
});
var baseLayers = createDefaultBaseMapLayers();
window.layerChooser = new L.Control.Layers(baseLayers, addLayers);
// Remove the hidden layer after layerChooser built, to avoid messing up ordering of layers
$.each(hiddenLayer, function(ind, layer){
map.removeLayer(layer);
// as users often become confused if they accidentally switch a standard layer off, display a warning in this case
$('#portaldetails').html('
'
+'
Warning: some of the standard layers are turned off. Some portals/links/fields will not be visible.
'
+'
Enable standard layers'
+'
');
$('#enable_standard_layers').on('click', function() {
$.each(addLayers, function(ind, layer) {
if (!map.hasLayer(layer)) map.addLayer(layer);
});
$('#portaldetails').html('');
});
});
map.addControl(window.layerChooser);
map.attributionControl.setPrefix('');
// listen for changes and store them in cookies
map.on('moveend', window.storeMapPosition);
map.on('moveend', function(e) {
// two limits on map position
// we wrap longitude (the L.LatLng 'wrap' method) - so we don't find ourselves looking beyond +-180 degrees
// then latitude is clamped with the clampLatLng function (to the 85 deg north/south limits)
var newPos = clampLatLng(map.getCenter().wrap());
if (!map.getCenter().equals(newPos)) {
map.panTo(newPos,{animate:false})
}
});
// map update status handling & update map hooks
// ensures order of calls
map.on('movestart', function() { window.mapRunsUserAction = true; window.requests.abort(); window.startRefreshTimeout(-1); });
map.on('moveend', function() { window.mapRunsUserAction = false; window.startRefreshTimeout(ON_MOVE_REFRESH*1000); });
map.on('zoomend', function() { window.layerChooserSetDisabledStates(); });
window.layerChooserSetDisabledStates();
// on zoomend, check to see the zoom level is an int, and reset the view if not
// (there's a bug on mobile where zoom levels sometimes end up as fractional levels. this causes the base map to be invisible)
map.on('zoomend', function() {
var z = map.getZoom();
if (z != parseInt(z))
{
console.warn('Non-integer zoom level at zoomend: '+z+' - trying to fix...');
map.setZoom(parseInt(z), {animate:false});
}
});
// set a 'moveend' handler for the map to clear idle state. e.g. after mobile 'my location' is used.
// possibly some cases when resizing desktop browser too
map.on('moveend', idleReset);
window.addResumeFunction(function() { window.startRefreshTimeout(ON_MOVE_REFRESH*1000); });
// create the map data requester
window.mapDataRequest = new MapDataRequest();
window.mapDataRequest.start();
// start the refresh process with a small timeout, so the first data request happens quickly
// (the code originally called the request function directly, and triggered a normal delay for the next refresh.
// however, the moveend/zoomend gets triggered on map load, causing a duplicate refresh. this helps prevent that
window.startRefreshTimeout(ON_MOVE_REFRESH*1000);
};
//adds a base layer to the map. done separately from the above, so that plugins that add base layers can be the default
window.setMapBaseLayer = function() {
//create a map name -> layer mapping - depends on internals of L.Control.Layers
var nameToLayer = {};
var firstLayer = null;
for (i in window.layerChooser._layers) {
var obj = window.layerChooser._layers[i];
if (!obj.overlay) {
nameToLayer[obj.name] = obj.layer;
if (!firstLayer) firstLayer = obj.layer;
}
}
var baseLayer = nameToLayer[localStorage['iitc-base-map']] || firstLayer;
map.addLayer(baseLayer);
// now we have a base layer we can set the map position
// (setting an initial position, before a base layer is added, causes issues with leaflet)
var pos = getPosition();
map.setView (pos.center, pos.zoom, {reset:true});
//event to track layer changes and store the name
map.on('baselayerchange', function(info) {
for(i in window.layerChooser._layers) {
var obj = window.layerChooser._layers[i];
if (info.layer === obj.layer) {
localStorage['iitc-base-map'] = obj.name;
break;
}
}
//also, leaflet no longer ensures the base layer zoom is suitable for the map (a bug? feature change?), so do so here
map.setZoom(map.getZoom());
});
}
// 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() {
// stock site updated to supply the actual player level, AP requirements and XM capacity values
var level = PLAYER.verified_level;
PLAYER.level = level; //for historical reasons IITC expects PLAYER.level to contain the current player level
var n = window.PLAYER.nickname;
PLAYER.nickMatcher = new RegExp('\\b('+n+')\\b', 'ig');
var ap = parseInt(PLAYER.ap);
var thisLvlAp = parseInt(PLAYER.min_ap_for_current_level);
var nextLvlAp = parseInt(PLAYER.min_ap_for_next_level);
if (nextLvlAp) {
var lvlUpAp = digits(nextLvlAp-ap);
var lvlApProg = Math.round((ap-thisLvlAp)/(nextLvlAp-thisLvlAp)*100);
} // else zero nextLvlAp - so at maximum level(?)
var xmMax = parseInt(PLAYER.xm_capacity);
var xmRatio = Math.round(PLAYER.energy/xmMax*100);
var cls = PLAYER.team === 'RESISTANCE' ? 'res' : 'enl';
var t = 'Level:\t' + level + '\n'
+ 'XM:\t' + PLAYER.energy + ' / ' + xmMax + '\n'
+ 'AP:\t' + digits(ap) + '\n'
+ (nextLvlAp > 0 ? 'level up in:\t' + lvlUpAp + ' AP' : 'Maximul level reached(!)')
+ '\n\Invites:\t'+PLAYER.available_invites
+ '\n\nNote: your player stats can only be updated by a full reload (F5)';
$('#playerstat').html(''
+ '
'+level+' '
+ ''
+ '
'+PLAYER.nickname+''
+ '
sign out'
+ '
'
+ '
'
+ 'XM: '+xmRatio+'%'
+ '' + (nextLvlAp > 0 ? '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');
}
$('.ui-tooltip').remove();
});
}
window.setupTooltips = function(element) {
element = element || $(document);
element.tooltip({
// disable show/hide animation
show: { effect: 'none', duration: 0, delay: 350 },
hide: false,
open: function(event, ui) {
// ensure all other tooltips are closed
$(".ui-tooltip").not(ui.tooltip).remove();
},
content: function() {
var title = $(this).attr('title');
return window.convertTextToTableMagic(title);
}
});
if(!window.tooltipClearerHasBeenSetup) {
window.tooltipClearerHasBeenSetup = true;
$(document).on('click', '.ui-tooltip', function() { $(this).remove(); });
}
}
window.setupTaphold = function() {
@@INCLUDERAW:external/taphold.js@@
}
window.setupQRLoadLib = function() {
@@INCLUDERAW:external/jquery.qrcode.min.js@@
}
window.setupLayerChooserApi = function() {
// hide layer chooser if booted with the iitcm android app
if (typeof android !== 'undefined' && android && android.setLayers) {
$('.leaflet-control-layers').hide();
}
//hook some additional code into the LayerControl so it's easy for the mobile app to interface with it
//WARNING: does depend on internals of the L.Control.Layers code
window.layerChooser.getLayers = function() {
var baseLayers = new Array();
var overlayLayers = new Array();
for (i in this._layers) {
var obj = this._layers[i];
var layerActive = window.map.hasLayer(obj.layer);
var info = {
layerId: L.stamp(obj.layer),
name: obj.name,
active: layerActive
}
if (obj.overlay) {
overlayLayers.push(info);
} else {
baseLayers.push(info);
}
}
var overlayLayersJSON = JSON.stringify(overlayLayers);
var baseLayersJSON = JSON.stringify(baseLayers);
if (typeof android !== 'undefined' && android && android.setLayers) {
if(this.androidTimer) clearTimeout(this.androidTimer);
this.androidTimer = setTimeout(function() {
this.androidTimer = null;
android.setLayers(baseLayersJSON, overlayLayersJSON);
}, 1000);
}
return {
baseLayers: baseLayers,
overlayLayers: overlayLayers
}
}
window.layerChooser.showLayer = function(id,show) {
if (show === undefined) show = true;
obj = this._layers[id];
if (!obj) return false;
if(show) {
if (!this._map.hasLayer(obj.layer)) {
//the layer to show is not currently active
this._map.addLayer(obj.layer);
//if it's a base layer, remove any others
if (!obj.overlay) {
for(i in this._layers) {
if (i != id) {
var other = this._layers[i];
if (!other.overlay && this._map.hasLayer(other.layer)) this._map.removeLayer(other.layer);
}
}
}
}
} else {
if (this._map.hasLayer(obj.layer)) {
this._map.removeLayer(obj.layer);
}
}
//below logic based on code in L.Control.Layers _onInputClick
if(!obj.overlay) {
this._map.setZoom(this._map.getZoom());
this._map.fire('baselayerchange', {layer: obj.layer});
}
return true;
};
var _update = window.layerChooser._update;
window.layerChooser._update = function() {
// update layer menu in IITCm
try {
if(typeof android != 'undefined')
window.layerChooser.getLayers();
} catch(e) {
console.error(e);
}
// call through
return _update.apply(this, arguments);
}
// as this setupLayerChooserApi function is called after the layer menu is populated, we need to also get they layers once
// so they're passed through to the android app
try {
if(typeof android != 'undefined')
window.layerChooser.getLayers();
} catch(e) {
console.error(e);
}
}
// BOOTING ///////////////////////////////////////////////////////////
function boot() {
if(!isSmartphone()) // TODO remove completely?
window.debug.console.overwriteNativeIfRequired();
console.log('loading done, booting. Built: @@BUILDDATE@@');
if(window.deviceID) console.log('Your device ID: ' + window.deviceID);
window.runOnSmartphonesBeforeBoot();
var iconDefImage = '@@INCLUDEIMAGE:images/marker-icon.png@@';
var iconDefRetImage = '@@INCLUDEIMAGE:images/marker-icon-2x.png@@';
L.Icon.Default = L.Icon.extend({options: {
iconUrl: iconDefImage,
iconRetinaUrl: iconDefRetImage,
iconSize: new L.Point(25, 41),
iconAnchor: new L.Point(12, 41),
popupAnchor: new L.Point(1, -34),
}});
window.extractFromStock();
window.setupIdle();
window.setupTaphold();
window.setupStyles();
window.setupDialogs();
window.setupDataTileParams();
window.setupMap();
window.setupOMS();
window.search.setup();
window.setupRedeem();
window.setupLargeImagePreview();
window.setupSidebarToggle();
window.updateGameScore();
window.artifact.setup();
window.ornaments.setup();
window.setupPlayerStat();
window.setupTooltips();
window.chat.setup();
window.portalDetail.setup();
window.setupQRLoadLib();
window.setupLayerChooserSelectOne();
window.setupLayerChooserStatusRecorder();
// read here ONCE, so the URL is only evaluated one time after the
// necessary data has been loaded.
urlPortalLL = getURLParam('pll');
if(urlPortalLL) {
urlPortalLL = urlPortalLL.split(",");
urlPortalLL = [parseFloat(urlPortalLL[0]) || 0.0, parseFloat(urlPortalLL[1]) || 0.0];
}
urlPortal = getURLParam('pguid');
$('#sidebar').show();
if(window.bootPlugins) {
// check to see if a known 'bad' plugin is installed. If so, alert the user, and don't boot any plugins
var badPlugins = {
'arc': 'Contains hidden code to report private data to a 3rd party server:
details here',
};
// remove entries from badPlugins which are not installed
$.each(badPlugins, function(name,desc) {
if (!(window.plugin && window.plugin[name])) {
// not detected: delete from the list
delete badPlugins[name];
}
});
// if any entries remain in the list, report this to the user and don't boot ANY plugins
// (why not any? it's tricky to know which of the plugin boot entries were safe/unsafe)
if (Object.keys(badPlugins).length > 0) {
var warning = 'One or more known unsafe plugins were detected. For your safety, IITC has disabled all plugins.
';
$.each(badPlugins,function(name,desc) {
warning += '- '+name+': '+desc+'
';
});
warning += '
Please uninstall the problem plugins and reload the page. See this FAQ entry for help.
Note: It is tricky for IITC to safely disable just problem plugins
';
dialog({
title: 'Plugin Warning',
html: warning,
width: 400
});
} else {
// no known unsafe plugins detected - boot all plugins
$.each(window.bootPlugins, function(ind, ref) {
try {
ref();
} catch(err) {
console.error("error starting plugin: index "+ind+", error: "+err);
debugger;
}
});
}
}
window.setMapBaseLayer();
window.setupLayerChooserApi();
window.runOnSmartphonesAfterBoot();
// workaround for #129. Not sure why this is required.
// setTimeout('window.map.invalidateSize(false);', 500);
window.iitcLoaded = true;
window.runHooks('iitcLoaded');
if (typeof android !== 'undefined' && android && android.bootFinished) {
android.bootFinished();
}
}
@@INCLUDERAW:external/load.js@@
try { console.log('Loading included JS now'); } catch(e) {}
@@INCLUDERAW:external/leaflet-src.js@@
@@INCLUDERAW:external/L.Geodesic.js@@
// modified version of https://github.com/shramov/leaflet-plugins. Also
// contains the default Ingress map style.
@@INCLUDERAW:external/Google.js@@
@@INCLUDERAW:external/autolink.js@@
@@INCLUDERAW:external/oms.min.js@@
try { console.log('done loading included JS'); } catch(e) {}
//note: no protocol - so uses http or https as used on the current page
var JQUERY = '//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js';
var JQUERYUI = '//ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js';
// after all scripts have loaded, boot the actual app
load(JQUERY).then(JQUERYUI).thenRun(boot);