diff --git a/code/artifact.js b/code/artifact.js index f1e5b874..28b61b3a 100644 --- a/code/artifact.js +++ b/code/artifact.js @@ -35,7 +35,7 @@ window.artifact.requestData = function() { if (isIdle()) { artifact.idle = true; } else { - window.postAjax('artifacts', {}, artifact.handleSuccess, artifact.handleError); + window.postAjax('getArtifactPortals', {}, artifact.handleSuccess, artifact.handleError); } } @@ -65,34 +65,16 @@ window.artifact.handleFailure = function(data) { window.artifact.processData = function(data) { - if (data.error || !data.artifacts) { - console.warn('Failed to find artifacts in artifact response'); + if (data.error || !data.result) { + console.warn('Failed to find result in getArtifactPortals response'); return; } + var oldArtifacts = artifact.entities; artifact.clearData(); - $.each (data.artifacts, function(i,artData) { - // if we have no descriptions for a type, we don't know about it - if (!artifact.getArtifactDescriptions(artData.artifactId)) { - // jarvis and amar artifacts - fragmentInfos and targetInfos - // (future types? completely unknown at this time!) - console.warn('Note: unknown artifactId '+artData.artifactId+' - guessing how to handle it'); - } - - artifact.artifactTypes[artData.artifactId] = artData.artifactId; - - if (artData.fragmentInfos) { - artifact.processFragmentInfos (artData.artifactId, artData.fragmentInfos); - } - - if (artData.targetInfos) { - artifact.processTargetInfos (artData.artifactId, artData.targetInfos); - } - - // other data in future? completely unknown! - }); - + artifact.processResult(data.result); + runHooks('artifactsUpdated', {old: oldArtifacts, 'new': artifact.entities}); // redraw the artifact layer artifact.updateLayer(); @@ -101,38 +83,52 @@ window.artifact.processData = function(data) { window.artifact.clearData = function() { - artifact.portalInfo = {}; artifact.artifactTypes = {}; + + artifact.entities = []; } -window.artifact.processFragmentInfos = function (id, fragments) { - $.each(fragments, function(i, fragment) { - if (!artifact.portalInfo[fragment.portalGuid]) { - artifact.portalInfo[fragment.portalGuid] = { _entityData: fragment.portalInfo }; + +window.artifact.processResult = function (portals) { + // portals is an object, keyed from the portal GUID, containing the portal entity array + + for (var guid in portals) { + var ent = portals[guid]; + var data = decodeArray.portalSummary(ent); + + // we no longer know the faction for the target portals, and we don't know which fragment numbers are at the portals + // all we know, from the portal summary data, for each type of artifact, is that each artifact portal is + // - a target portal or not - no idea for which faction + // - has one (or more) fragments, or not + + if (!artifact.portalInfo[guid]) artifact.portalInfo[guid] = {}; + + // store the decoded data - needed for lat/lng for layer markers + artifact.portalInfo[guid]._data = data; + + for(var type in data.artifactBrief.target) { + if (!artifact.artifactTypes[type]) artifact.artifactTypes[type] = {}; + + if (!artifact.portalInfo[guid][type]) artifact.portalInfo[guid][type] = {}; + + artifact.portalInfo[guid][type].target = TEAM_NONE; // as we no longer know the team... } - if (!artifact.portalInfo[fragment.portalGuid][id]) artifact.portalInfo[fragment.portalGuid][id] = {}; + for(var type in data.artifactBrief.fragment) { + if (!artifact.artifactTypes[type]) artifact.artifactTypes[type] = {}; - if (!artifact.portalInfo[fragment.portalGuid][id].fragments) artifact.portalInfo[fragment.portalGuid][id].fragments = []; + if (!artifact.portalInfo[guid][type]) artifact.portalInfo[guid][type] = {}; - $.each(fragment.fragments, function(i,f) { - artifact.portalInfo[fragment.portalGuid][id].fragments.push(f); - }); - - }); -} - -window.artifact.processTargetInfos = function (id, targets) { - $.each(targets, function(i, target) { - if (!artifact.portalInfo[target.portalGuid]) { - artifact.portalInfo[target.portalGuid] = { _entityData: target.portalInfo }; + artifact.portalInfo[guid][type].fragments = true; //as we no longer have a list of the fragments there } - if (!artifact.portalInfo[target.portalGuid][id]) artifact.portalInfo[target.portalGuid][id] = {}; - artifact.portalInfo[target.portalGuid][id].target = target.team === 'RESISTANCE' ? TEAM_RES : TEAM_ENL; - }); + // let's pre-generate the entities needed to render the map - array of [guid, timestamp, ent_array] + artifact.entities.push ( [guid, data.timestamp, ent] ); + + } + } window.artifact.getArtifactTypes = function() { @@ -143,30 +139,9 @@ window.artifact.isArtifact = function(type) { return type in artifact.artifactTypes; } -window.artifact.getArtifactDescriptions = function(type) { - var descriptions = { - 'jarvis': { 'title': "Jarvis Shards", 'fragmentName': "shards" }, - 'amar': { 'title': "Amar Artifacts", 'fragmentName': "artifacts" }, - 'helios': { 'title': "Helios Artifacts", 'fragmentName': "artifacts" }, - 'shonin': { 'title': "Sh\u014Dnin Shards", 'fragmentName': "shards" }, - 'lightman': { 'title': "Lightman Shards", 'fragmentName': "shards" }, - }; - - return descriptions[type]; -} - // used to render portals that would otherwise be below the visible level window.artifact.getArtifactEntities = function() { - var entities = []; - - // create fake entities from the artifact data - $.each (artifact.portalInfo, function(guid,data) { - var timestamp = 0; // we don't have a valid timestamp - so let's use 0 - var ent = [ guid, timestamp, data._entityData ]; - entities.push(ent); - }); - - return entities; + return artifact.entities; } window.artifact.getInterestingPortals = function() { @@ -187,99 +162,53 @@ window.artifact.updateLayer = function() { artifact._layer.clearLayers(); $.each(artifact.portalInfo, function(guid,data) { - var latlng = L.latLng ([data._entityData[2]/1E6, data._entityData[3]/1E6]); + var latlng = L.latLng ([data._data.latE6/1E6, data._data.lngE6/1E6]); - // jarvis shard icon - var iconUrl = undefined; - var iconSize = 0; - var opacity = 1.0; + $.each(data, function(type,detail) { - // redundant as of 2014-02-05 - jarvis shards removed - if (data.jarvis) { - if (data.jarvis.target) { - // target portal - show the target marker. use the count of fragments at the target to pick the right icon - it has segments that fill up + // we'll construct the URL form the type - stock seems to do that now - var count = data.jarvis.fragments ? data.jarvis.fragments.length : 0; + var iconUrl; + if (data[type].target !== undefined) { + // target portal + var iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/'+type+'_shard_target.png' + var iconSize = 100/2; + var opacity = 1.0; + + var icon = L.icon({ + iconUrl: iconUrl, + iconSize: [iconSize,iconSize], + iconAnchor: [iconSize/2,iconSize/2], + className: 'no-pointer-events' // the clickable: false below still blocks events going through to the svg underneath + }); + + var marker = L.marker (latlng, {icon: icon, clickable: false, keyboard: false, opacity: opacity }); + + artifact._layer.addLayer(marker); + + } else if (data[type].fragments) { + // fragment(s) at portal + + var iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/'+type+'_shard.png' + var iconSize = 60/2; + var opacity = 0.6; + + var icon = L.icon({ + iconUrl: iconUrl, + iconSize: [iconSize,iconSize], + iconAnchor: [iconSize/2,iconSize/2], + className: 'no-pointer-events' // the clickable: false below still blocks events going through to the svg underneath + }); + + var marker = L.marker (latlng, {icon: icon, clickable: false, keyboard: false, opacity: opacity }); + + artifact._layer.addLayer(marker); - iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/jarvis_shard_target_'+count+'.png'; - iconSize = 100/2; // 100 pixels - half that size works better - } else if (data.jarvis.fragments) { - iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/jarvis_shard.png'; - iconSize = 60/2; // 60 pixels - half that size works better - opacity = 0.6; // these often hide portals - let's make them semi transparent } - } - // 2014-02-06: a guess at whats needed for the new artifacts - if (data.amar) { - if (data.amar.target) { - // target portal - show the target marker. use the count of fragments at the target to pick the right icon - it has segments that fill up + }); //end $.each(data, function(type,detail) - var count = data.amar.fragments ? data.amar.fragments.length : 0; - - iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/amar_shard_target_'+count+'.png'; - iconSize = 100/2; // 100 pixels - half that size works better - } else if (data.amar.fragments) { - iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/amar_shard.png'; - iconSize = 60/2; // 60 pixels - half that size works better - opacity = 0.6; // these often hide portals - let's make them semi transparent - } - } - - // 2014-08-09 - helios artifacts. original guess was slightly wrong - if (data.helios) { - if (data.helios.target) { - // target portal - show the target marker. helios target marker doesn't fill like the earlier jarvis/amar targets - iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/helios_shard_target.png'; - iconSize = 100/2; // 100 pixels - half that size works better - } else if (data.helios.fragments) { - iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/helios_shard.png'; - iconSize = 60/2; // 60 pixels - half that size works better - opacity = 0.6; // these often hide portals - let's make them semi transparent - } - } - - // 2015-03-05 - shonin shards - if (data.shonin) { - if (data.shonin.target) { - // target portal - show the target marker. - iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/shonin_shard_target.png'; - iconSize = 100/2; // 100 pixels - half that size works better - } else if (data.shonin.fragments) { - iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/shonin_shard.png'; - iconSize = 60/2; // 60 pixels - half that size works better - opacity = 0.6; // these often hide portals - let's make them semi transparent - } - } - - // 2015-04-22 - lightman fragments (guessed) - if (data.lightman) { - if (data.lightman.target) { - // target portal - show the target marker. - iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/lightman_shard_target.png'; - iconSize = 100/2; // 100 pixels - half that size works better - } else if (data.lightman.fragments) { - iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/lightman_shard.png'; - iconSize = 60/2; // 60 pixels - half that size works better - opacity = 0.6; // these often hide portals - let's make them semi transparent - } - } - - if (iconUrl) { - var icon = L.icon({ - iconUrl: iconUrl, - iconSize: [iconSize,iconSize], - iconAnchor: [iconSize/2,iconSize/2], - className: 'no-pointer-events' // the clickable: false below still blocks events going through to the svg underneath - }); - - var marker = L.marker (latlng, {icon: icon, clickable: false, keyboard: false, opacity: opacity }); - - artifact._layer.addLayer(marker); - } else { - console.warn('Oops! no URL for artifact portal icon?!'); - } - }); + }); //end $.each(artifact.portalInfo, function(guid,data) } @@ -293,9 +222,9 @@ window.artifact.showArtifactList = function() { var first = true; $.each(artifact.artifactTypes, function(type,type2) { - var description = artifact.getArtifactDescriptions(type); - - var name = description ? description.title : ('unknown artifact type: '+type); + // no nice way to convert the Niantic internal name into the correct display name + // (we do get the description string once a portal with that shard type is selected - could cache that somewhere?) + var name = type.capitalize() + ' shards'; if (!first) html += '
'; first = false; @@ -310,29 +239,33 @@ window.artifact.showArtifactList = function() { if (type in data) { // this portal has data for this artifact type - add it to the table - var sortVal = 0; - - var onclick = 'zoomToAndShowPortal(\''+guid+'\',['+data._entityData[2]/1E6+','+data._entityData[3]/1E6+'])'; - var row = ''+escapeHtmlSpecialChars(data._entityData[8])+''; + var onclick = 'zoomToAndShowPortal(\''+guid+'\',['+data._data.latE6/1E6+','+data._data.lngE6/1E6+'])'; + var row = ''+escapeHtmlSpecialChars(data._data.title)+''; row += ''; - if (data[type].target) { - row += ''+(data[type].target==TEAM_RES?'Resistance':'Enlightened')+' target '; - sortVal = 100000+data[type].target; + if (data[type].target !== undefined) { + if (data[type].target == TEAM_NONE) { + row += 'Target Portal '; + } else { + row += ''+(data[type].target==TEAM_RES?'Resistance':'Enlightened')+' target '; + } } if (data[type].fragments) { - if (data[type].target) { + if (data[type].target !== undefined) { row += '
'; } - var fragmentName = description ? description.fragmentName : 'fragment'; - row += ''+fragmentName+': #'+data[type].fragments.join(', #')+' '; - sortVal = Math.min.apply(null, data[type].fragments); // use min shard number at portal as sort key + var fragmentName = 'shard'; +// row += ''+fragmentName+': #'+data[type].fragments.join(', #')+' '; + row += ''+fragmentName+': yes '; } row += ''; + // sort by target portals first, then by portal GUID + var sortVal = (data[type].target !== undefined ? 'A' : 'Z') + guid; + tableRows.push ( [sortVal, row] ); } }); @@ -344,7 +277,9 @@ window.artifact.showArtifactList = function() { // sort the rows tableRows.sort(function(a,b) { - return a[0]-b[0]; + if (a[0] == b[0]) return 0; + else if (a[0] < b[0]) return -1; + else return 1; }); // and add them to the table @@ -355,6 +290,13 @@ window.artifact.showArtifactList = function() { }); + html += "
" + + "

In Summer 2015, Niantic changed the data format for artifact portals. We no longer know:

" + + "" + + "

You can select a portal and the detailed data contains the list of shard numbers, but there's still no" + + " more information on targets.

"; + dialog({ title: 'Artifacts', html: html, diff --git a/code/boot.js b/code/boot.js index 4ebce81f..905182cb 100644 --- a/code/boot.js +++ b/code/boot.js @@ -519,7 +519,11 @@ window.setupLayerChooserApi = function() { var baseLayersJSON = JSON.stringify(baseLayers); if (typeof android !== 'undefined' && android && android.setLayers) { - android.setLayers(baseLayersJSON, overlayLayersJSON); + if(this.androidTimer) clearTimeout(this.androidTimer); + this.androidTimer = setTimeout(function() { + this.androidTimer = null; + android.setLayers(baseLayersJSON, overlayLayersJSON); + }, 1000); } return { @@ -561,6 +565,27 @@ window.setupLayerChooserApi = function() { } 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); } } @@ -586,7 +611,6 @@ function boot() { }}); window.extractFromStock(); - window.iitc_bg.init(); //NOTE: needs to be early (before any requests sent), but after extractFromStock() window.setupIdle(); window.setupTaphold(); window.setupStyles(); diff --git a/code/botguard_interface.js b/code/botguard_interface.js deleted file mode 100644 index e960723b..00000000 --- a/code/botguard_interface.js +++ /dev/null @@ -1,326 +0,0 @@ -// interface to the use of the google 'botguard' javascript added to the intel site - - -iitc_bg = Object(); - -iitc_bg.DISABLED = false; //if set, botguard is disabld. no b/c params sent in requests, no processing of responses - -iitc_bg.init = function() { - if (iitc_bg.DISABLED) return; - -// stock site - 'ad.e()' constructor -//function Ad() { -// this.Eb = {}; // a map, indexed by 'group-[ab]-actions', each entry containing an array of 'yd' objects (simple object, with 'gf' and 'cb' members). a queue of data to process? -// this.Oa = {}; // a map, indexed by 'group-[ab]-actions', each entry containing an object with an 'invoke' method -// this.Zc = {}; // a map, indexed by group, witn constructors for botguard -// this.eb = ""; // the 'key' - B -// this.Kh = e; // e is defined in the main web page as "var e = function(w) {eval(w);};" -//} - - var botguard_eval = e; - - iitc_bg.data_queue = {}; //.lb - indexed by group - iitc_bg.instance_queue = {}; //.ya - indexed by group - iitc_bg.key = ""; //.cb - iitc_bg.botguard = {}; //.qc - indexed by key - iitc_bg.evalFunc = botguard_eval; - -// stock site code -//Ad.prototype.U = function(a, b) { -// Bd(this, a); -// for (var c in b) if ("group-b-actions" == c || "group-a-actions" == c) { -// for (var d = 0; d < b[c].length; ++d) Dd(this, c, new Ed(b[c][d], a)); -// Fd(this, c); -// } -//}; -// and.. Ed - a simple object that holds it's parameters and the timestamp it was created -//function Ed(a, b) { -// var c = w(); -// this.mg = a; -// this.eb = b; -// this.Ki = c; -//} - - - // to initialise, we need four things - // B - a key(?). set in the main web page HTML, name isn't changed on site updates - // CS - initialisation data for botguard - again in the main page, again name is constant - - - var botguard_key = B; - var botguard_data = CS; - - iitc_bg.process_key(botguard_key); - - for (var group in botguard_data) { - // TODO? filter this loop by group-[ab]-actions only? the stock site does, but this seems unnecessary - - // the stock site has code to create the emtpy arrays with the group index as and when needed - // however, it makes more sense to do it here just once, rather than testing every time - iitc_bg.data_queue[group] = []; - iitc_bg.instance_queue[group] = []; - - for (var i=0; i < botguard_data[group].length; i++) { - iitc_bg.push_queue(group, botguard_data[group][i], botguard_key); - } - - iitc_bg.process_queue(group); - } -}; - -//TODO: better name - will do for now... -iitc_bg.push_queue = function(group, data, key) { -//stock site code -//a=='this', b==group-[ab]-actions, c==object with .mg==data, .eb==key, .Ki==current timestamp -//function Dd(a, b, c) { -// var d = c.eb && a.Zc[c.eb]; -// if ("dkxm" == c.mg || d) a.Eb[b] || (a.Eb[b] = []), a.Eb[b].push(c); -//} - - // Niantic have changed how the server returns data items to the client a few times, which cause - // bad non-string items to come into here. this breaks things badly - if (typeof data !== "string") throw "Error: iitc_bg.process_queue got dodgy data - expected a string"; - - var botguard_instance = iitc_bg.key && iitc_bg.botguard[iitc_bg.key]; - - if (data == "dkxm" || botguard_instance) { - // NOTE: we pre-create empty per-group arrays on init - iitc_bg.data_queue[group].push( {data: data, key: key, time: Date.now()} ); - } -}; - - -// called both on initialisation and on processing responses from the server -// -iitc_bg.process_key = function(key,serverEval) { - if (iitc_bg.DISABLED) return; - - -// stock site code -//function Bd(a, b, c) { -// if (a.Zc[b]) a.eb = b; else { -// var d = !0; -// if (c) try { -// a.Kh(c); -// } catch (f) { -// d = !1; -// } -// d && (a.Zc[b] = botguard.bg, a.eb = b); -// } -//} - - if (iitc_bg.botguard[key]) { - iitc_bg.key = key; - } else { - var noCatch = true; - - if (serverEval) { - // server wants us to eval some code! risky, and impossible to be certain we can do it safely - // seems to always be the same javascript as already found in the web page source. - try { - console.warn('botguard: Server-generated javascript eval requested:\n'+serverEval); - iitc_bg.evalFunc(serverEval); - console.log('botguard: Server-generated javascript ran OK'); - } catch(e) { - console.warn('botguard: Server-generated javascript - threw an exception'); - console.warn(e); - noCatch = false; - } - } - if (noCatch) { - iitc_bg.botguard[key] = botguard.bg; - iitc_bg.key = key; - } - } -}; - - -//convert a method name to the group-[ab]-actions value, or return 'undefined' for no group -//NOTE: the stock code separates the 'in any group' and 'which group' test, but it's cleaner to combine them -//UPDATE: the 'not in any group' case was removed from the stock site logic -iitc_bg.get_method_group = function(method) { -//stock site -//function Cd(a) { -// return -1 != ig.indexOf(a) ? "group-a-actions" : "group-b-actions"; -//} - - if (window.niantic_params.botguard_method_group_flag[method] === undefined) { - throw 'Error: method '+method+' not found in the botguard_method_group_flag object'; - } - - if (window.niantic_params.botguard_method_group_flag[method]) { - return "ingress-a-actions"; - } else { - return "ingress-b-actions"; - } -}; - - - - -// returns the extra parameters to add to any JSON request -iitc_bg.extra_request_params = function(method) { - if (iitc_bg.DISABLED) return {}; - - - var extra = {}; - extra.b = iitc_bg.key; - extra.c = iitc_bg.get_request_data(method); - - return extra; -}; - -iitc_bg.get_request_data = function(method) { -//function Id(a, b) { -// var c = "mxkd", d = Cd(b); -// a.Oa[d] && 0 < a.Oa[d].length && a.Oa[d].shift().invoke(function(a) { -// c = a; -// }); -// Fd(a, d); -// return c; -//} - - - var group = iitc_bg.get_method_group(method); - if (!group) { - return ""; - } - - // this seems to be some kind of 'flag' string - and is either "mxkd" or "dkxm". it can be returned from the - // server, so we stick with the same string rather than something more sensibly named - var data = "mxkd"; - - if (iitc_bg.instance_queue[group] && iitc_bg.instance_queue[group].length > 0) { - var instance = iitc_bg.instance_queue[group].shift(); - instance.invoke(function(a) { data=a; }); - }; - - iitc_bg.process_queue(group); - - if (data.indexOf('undefined is not a function') != -1) { - // there's been cases of something going iffy in the botguard code, or IITC's interface to it. in this case, - // instead of the correct encoded string, the data contains an error message along the lines of - // "E:undefined is not a function:TypeError: undefined is not a function"[...] - // in this case, better to just stop with some kind of error than send the string to the server - debugger; - throw ('Error: iitc_bg.get_request_data got bad data - cannot safely continue'); - } - - return data; -}; - -// stock site - 'dummy' botguard object -//function Sf() {} -//Sf.prototype.invoke = function(a) { -// a("dkxm"); -//}; - -iitc_bg.dummy_botguard = function() {}; -iitc_bg.dummy_botguard.prototype.invoke = function(callback) { - callback("dkxm"); -}; - - -iitc_bg.process_queue = function(group) { -//stock site -//function Fd(a, b) { -// if (a.Eb[b]) for (; 0 < a.Eb[b].length; ) { -// var c = a.Eb[b].shift(), d = c.mg, f = c.Ki + 717e4, g; -// "dkxm" == d ? g = new jg : w() < f && (g = new a.Zc[c.eb](d)); -// if (g) { -// c = a; -// d = b; -// c.Oa[d] || (c.Oa[d] = []); -// c.Oa[d].push(g); -// break; -// } -// } -//} - -// processes an entry in the queue for the specified group - - while (iitc_bg.data_queue[group] && iitc_bg.data_queue[group].length > 0) { - var item = iitc_bg.data_queue[group].shift(); - var obj = undefined; - - if (item.data == "dkxm") { - obj = new iitc_bg.dummy_botguard; - } else if (Date.now() < item.time + 7170000) { - obj = new iitc_bg.botguard[item.key](item.data); - } - - // note: we pre-create empty per-group arrays on init - if (obj) { - iitc_bg.instance_queue[group].push(obj); - break; - } - } - -}; - - -iitc_bg.process_response_params = function(method,data) { - if (iitc_bg.DISABLED) { - // the rest of IITC won't expect these strange a/b/c params in the response data - // (e.g. it's pointless to keep in the cache, etc), so clear them - delete data.a; - delete data.b; - delete data.c; - return; - } - -// stock site: response processing -//yd.prototype.vi = function(a, b) { -// var c = b.target; -// if (cd(c)) { -// this.Ib.reset(); -// var d = a.hg, f = JSON.parse(ed(c)); -// if (f.c && 0 < f.c.length) { -// var g = Ad.e(), h = a.getMethodName(), l = f.a, m = f.b, r = f.c[0]; -// m && l && Bd(g, m, l); -// h = Cd(h); -// if (r && m && 0 < r.length) for (l = 0; l < r.length; ++l) Dd(g, h, new Ed(r[l], m)); -// g.Oa[h] && 0 != g.Oa[h].length || Fd(g, h); -// } -// "error" in f && "out of date" == f.error ? (d = rd.e(), Gd(!1), d.ie = !0, Hd("Please refresh for the latest version.")) : "error" in f && "RETRY" == f.error ? (this.Da.ja(1, a), td(this.Ib)) : n.setTimeout(pa(d, f), 0); -// } else this.Ib.Ec = !0, d = a.cg, ha(d) && (f = { -// error: dd(c) || "unknown", -// respStatus: c.getStatus() -// }, n.setTimeout(pa(d, f), 0)); -// d = this.Te; -// d.Aa.remove(c) && d.zc(c); -//}; - - - if (data.c && data.c.length > 0) { - - if (data.b && data.a) { - // in this case, we *EVAL* the 'data.a' string from the server! - // however, it's not a case that's been ever triggered in normal use, as far as I know - iitc_bg.process_key(data.b, data.a); - } - - var group = iitc_bg.get_method_group(method); - -//NOTE: I missed a change here a while ago. originally data.c was a single-level array of items to push on the queue, -//but now it's a two-dimensional array, and it's only index zero that's used! - var data_items = data.c[0]; - - if (data_items && data.b && data_items.length > 0) { - for (var i=0; i'); var dialog = $(jqID).dialog($.extend(true, { @@ -140,7 +145,8 @@ window.dialog = function(options) { var button = dialog.find('.ui-dialog-titlebar-button-collapse'); // Slide toggle - $(selector).slideToggle({duration: window.DIALOG_SLIDE_DURATION}); + $(this).css('height', ''); + $(selector).slideToggle({duration: window.DIALOG_SLIDE_DURATION, complete: sizeFix}); if(collapsed) { $(button).removeClass('ui-dialog-titlebar-button-collapse-collapsed'); @@ -214,6 +220,8 @@ window.dialog = function(options) { } }, options)); + dialog.on('dialogdragstop dialogresizestop', sizeFix); + // Set HTML and IDs dialog.html(html); dialog.data('id', id); diff --git a/code/entity_decode.js b/code/entity_decode.js index 76fdd26f..5a6398f4 100644 --- a/code/entity_decode.js +++ b/code/entity_decode.js @@ -25,17 +25,59 @@ energy: arr[2], }; } + function parseArtifactBrief(arr) { + if (arr === null) return null; + + // array index 0 is for fragments at the portal. index 1 is for target portals + // each of those is two dimensional - not sure why. part of this is to allow for multiple types of artifacts, + // with their own targets, active at once - but one level for the array is enough for that + + // making a guess - first level is for different artifact types, second index would allow for + // extra data for that artifact type + + function decodeArtifactArray(arr) { + var result = {}; + for (var i=0; i= 15 && topObject.length <= 18) { + if (topObject.length >= 12 && topObject.length <= 18) { // a reasonable array length for tile parameters // need to find two types: // a. portal level limits. decreasing numbers, starting at 8 @@ -93,7 +74,8 @@ window.extractFromStock = function() { } } // end if (topObject[0] == 8) - if (topObject[topObject.length-1] == 36000 || topObject[topObject.length-1] == 18000 || topObject[topObject.length-1] == 9000) { + // 2015-06-25 - changed to top value of 64000, then to 32000 - allow for them to restore it just in case + if (topObject[topObject.length-1] >= 9000 && topObject[topObject.length-1] <= 64000) { var increasing = true; for (var i=1; i topObject[i]) { @@ -114,25 +96,11 @@ window.extractFromStock = function() { } - // finding the required method names for the botguard interface code - if(topObject && typeof topObject == "object" && Object.getPrototypeOf(topObject) == requestPrototype) { - var methodKey = Object - .keys(topObject) - .filter(function(key) { return typeof key == "string"; })[0]; - - for(var secLevel in topObject) { - if(typeof topObject[secLevel] == "boolean") { - window.niantic_params.botguard_method_group_flag[topObject[methodKey]] = topObject[secLevel]; - } - } - } - - } } - if (niantic_params.CURRENT_VERSION === undefined || Object.keys(window.niantic_params.botguard_method_group_flag).length == 0) { + if (niantic_params.CURRENT_VERSION === undefined) { dialog({ title: 'IITC Broken', html: '

IITC failed to extract the required parameters from the intel site

' diff --git a/code/hooks.js b/code/hooks.js index 2fd82477..9629647e 100644 --- a/code/hooks.js +++ b/code/hooks.js @@ -18,6 +18,7 @@ // portalSelected: called when portal on map is selected/unselected. // Provide guid of selected and unselected portal. // mapDataRefreshStart: called when we start refreshing map data +// mapDataEntityInject: called just as we start to render data. has callback to inject cached entities into the map render // mapDataRefreshEnd: called when we complete the map data load // portalAdded: called when a portal has been received and is about to // be added to its layer group. Note that this does NOT @@ -51,11 +52,13 @@ // this only selects the current chat pane; on mobile, it // also switches between map, info and other panes defined // by plugins +// artifactsUpdated: called when the set of artifacts (including targets) +// has changed. Parameters names are old, new. window._hooks = {} window.VALID_HOOKS = [ - 'portalSelected', 'portalDetailsUpdated', - 'mapDataRefreshStart', 'mapDataRefreshEnd', + 'portalSelected', 'portalDetailsUpdated', 'artifactsUpdated', + 'mapDataRefreshStart', 'mapDataEntityInject', 'mapDataRefreshEnd', 'portalAdded', 'linkAdded', 'fieldAdded', 'publicChatDataAvailable', 'factionChatDataAvailable', 'requestFinished', 'nicknameClicked', @@ -103,3 +106,16 @@ window.addHook = function(event, callback) { else _hooks[event].push(callback); } + +// callback must the SAME function to be unregistered. +window.removeHook = function(event, callback) { + if (typeof callback !== 'function') throw('Callback must be a function.'); + + if (_hooks[event]) { + var index = _hooks[event].indexOf(callback); + if(index == -1) + console.warn('Callback wasn\'t registered for this event.'); + else + _hooks[event].splice(index, 1); + } +} diff --git a/code/map_data_calc_tools.js b/code/map_data_calc_tools.js index 34636500..54736bb2 100755 --- a/code/map_data_calc_tools.js +++ b/code/map_data_calc_tools.js @@ -12,12 +12,18 @@ window.setupDataTileParams = function() { // default values - used to fall back to if we can't detect those used in stock intel - var DEFAULT_ZOOM_TO_TILES_PER_EDGE = [256, 256, 256, 256, 512, 512, 512, 2048, 2048, 2048, 4096, 4096, 6500, 6500, 6500, 18e3, 18e3, 36e3]; - var DEFAULT_ZOOM_TO_LEVEL = [ 8, 8, 8, 8, 7, 7, 7, 6, 6, 5, 4, 4, 3, 2, 2, 1, 1 ]; + var DEFAULT_ZOOM_TO_TILES_PER_EDGE = [1,1,1,40,40,80,80,320,1000,2000,2000,4000,8000,16000,16000,32000]; + var DEFAULT_ZOOM_TO_LEVEL = [8,8,8,8,7,7,7,6,6,5,4,4,3,2,2,1,1]; + // stock intel doesn't have this array (they use a switch statement instead), but this is far neater + var DEFAULT_ZOOM_TO_LINK_LENGTH = [200000,200000,200000,200000,200000,60000,60000,10000,5000,2500,2500,800,300,0,0]; window.TILE_PARAMS = {}; + // not in stock to detect - we'll have to assume the above values... + window.TILE_PARAMS.ZOOM_TO_LINK_LENGTH = DEFAULT_ZOOM_TO_LINK_LENGTH; + + if (niantic_params.ZOOM_TO_LEVEL && niantic_params.TILES_PER_EDGE) { window.TILE_PARAMS.ZOOM_TO_LEVEL = niantic_params.ZOOM_TO_LEVEL; window.TILE_PARAMS.TILES_PER_EDGE = niantic_params.TILES_PER_EDGE; @@ -25,22 +31,56 @@ window.setupDataTileParams = function() { // lazy numerical array comparison if ( JSON.stringify(niantic_params.ZOOM_TO_LEVEL) != JSON.stringify(DEFAULT_ZOOM_TO_LEVEL)) { - console.warn('Tile parameter ZOOM_TO_LEVEL have changed in stock intel. Detectec correct values, but code should be updated'); + console.warn('Tile parameter ZOOM_TO_LEVEL have changed in stock intel. Detected correct values, but code should be updated'); debugger; } if ( JSON.stringify(niantic_params.TILES_PER_EDGE) != JSON.stringify(DEFAULT_ZOOM_TO_TILES_PER_EDGE)) { - console.warn('Tile parameter ZOOM_TO_LEVEL have changed in stock intel. Detectec correct values, but code should be updated'); + console.warn('Tile parameter TILES_PER_EDGE have changed in stock intel. Detected correct values, but code should be updated'); debugger; } } else { - console.warn('Failed to detect both ZOOM_TO_LEVEL and TILES_PER_EDGE in the stock intel site - using internal defaults'); - debugger; + dialog({ + title: 'IITC Warning', + html: "

IITC failed to detect the ZOOM_TO_LEVEL and/or TILES_PER_EDGE settings from the stock intel site.

" + +"

IITC is now using fallback default values. However, if detection has failed it's likely the values have changed." + +" IITC may not load the map if these default values are wrong.

", + }); window.TILE_PARAMS.ZOOM_TO_LEVEL = DEFAULT_ZOOM_TO_LEVEL; window.TILE_PARAMS.TILES_PER_EDGE = DEFAULT_ZOOM_TO_TILES_PER_EDGE; } + // 2015-07-01: niantic added code to the stock site that overrides the min zoom level for unclaimed portals to 15 and above + // instead of updating the zoom-to-level array. makes no sense really.... + // we'll just chop off the array at that point, so the code defaults to level 0 (unclaimed) everywhere... + window.TILE_PARAMS.ZOOM_TO_LEVEL = window.TILE_PARAMS.ZOOM_TO_LEVEL.slice(0,15); + +} + + +window.debugMapZoomParameters = function() { + + //for debug purposes, log the tile params used for each zoom level + console.log('DEBUG: Map Zoom Parameters'); + var doneZooms = {}; + for (var z=MIN_ZOOM; z<=21; z++) { + var ourZoom = getDataZoomForMapZoom(z); + console.log('DEBUG: map zoom '+z+': IITC requests '+ourZoom+(ourZoom!=z?' instead':'')); + if (!doneZooms[ourZoom]) { + var params = getMapZoomTileParameters(ourZoom); + var msg = 'DEBUG: data zoom '+ourZoom; + if (params.hasPortals) { + msg += ' has portals, L'+params.level+'+'; + } else { + msg += ' NO portals (was L'+params.level+'+)'; + } + msg += ', minLinkLength='+params.minLinkLength; + msg += ', tiles per edge='+params.tilesPerEdge; + console.log(msg); + doneZooms[ourZoom] = true; + } + } } @@ -54,12 +94,12 @@ window.getMapZoomTileParameters = function(zoom) { var level = window.TILE_PARAMS.ZOOM_TO_LEVEL[zoom] || 0; // default to level 0 (all portals) if not in array - if (window.CONFIG_ZOOM_SHOW_LESS_PORTALS_ZOOMED_OUT) { - if (level <= 7 && level >= 4) { - // reduce portal detail level by one - helps reduce clutter - level = level+1; - } - } +// if (window.CONFIG_ZOOM_SHOW_LESS_PORTALS_ZOOMED_OUT) { +// if (level <= 7 && level >= 4) { +// // reduce portal detail level by one - helps reduce clutter +// level = level+1; +// } +// } var maxTilesPerEdge = window.TILE_PARAMS.TILES_PER_EDGE[window.TILE_PARAMS.TILES_PER_EDGE.length-1]; @@ -67,6 +107,8 @@ window.getMapZoomTileParameters = function(zoom) { level: level, maxLevel: window.TILE_PARAMS.ZOOM_TO_LEVEL[zoom] || 0, // for reference, for log purposes, etc tilesPerEdge: window.TILE_PARAMS.TILES_PER_EDGE[zoom] || maxTilesPerEdge, + minLinkLength: window.TILE_PARAMS.ZOOM_TO_LINK_LENGTH[zoom] || 0, + hasPortals: zoom >= window.TILE_PARAMS.ZOOM_TO_LINK_LENGTH.length, // no portals returned at all when link length limits things zoom: zoom // include the zoom level, for reference }; } @@ -83,16 +125,6 @@ window.getDataZoomForMapZoom = function(zoom) { zoom = 21; } - if (window.CONFIG_ZOOM_SHOW_MORE_PORTALS) { - // slightly unfriendly to the servers, requesting more, but smaller, tiles, for the 'unclaimed' level of detail - // however, server load issues are all related to the map area in view, with no real issues related to detail level - // therefore, I believel we can get away with these smaller tiles for one or two further zoom levels without issues - - if (zoom == 16) { - zoom = 17; - } - } - if (!window.CONFIG_ZOOM_DEFAULT_DETAIL_LEVEL) { @@ -104,7 +136,11 @@ window.getDataZoomForMapZoom = function(zoom) { while (zoom > MIN_ZOOM) { var newTileParams = getMapZoomTileParameters(zoom-1); - if (newTileParams.tilesPerEdge != origTileParams.tilesPerEdge || newTileParams.level != origTileParams.level) { + + if ( newTileParams.tilesPerEdge != origTileParams.tilesPerEdge + || newTileParams.hasPortals != origTileParams.hasPortals + || newTileParams.level*newTileParams.hasPortals != origTileParams.level*origTileParams.hasPortals // multiply by 'hasPortals' bool - so comparison does not matter when no portals available + ) { // switching to zoom-1 would result in a different detail level - so we abort changing things break; } else { diff --git a/code/map_data_render.js b/code/map_data_render.js index a113620b..d3811a15 100644 --- a/code/map_data_render.js +++ b/code/map_data_render.js @@ -4,7 +4,6 @@ window.Render = function() { - this.portalMarkerScale = undefined; } @@ -25,7 +24,7 @@ window.Render.prototype.startRenderPass = function(level,bounds) { // this will just avoid a few entity removals at start of render when they'll just be added again var paddedBounds = bounds.pad(0.1); - this.clearPortalsBelowLevelOrOutsideBounds(level,paddedBounds); + this.clearPortalsOutsideBounds(paddedBounds); this.clearLinksOutsideBounds(paddedBounds); this.clearFieldsOutsideBounds(paddedBounds); @@ -34,12 +33,12 @@ window.Render.prototype.startRenderPass = function(level,bounds) { this.rescalePortalMarkers(); } -window.Render.prototype.clearPortalsBelowLevelOrOutsideBounds = function(level,bounds) { +window.Render.prototype.clearPortalsOutsideBounds = function(bounds) { var count = 0; for (var guid in window.portals) { var p = portals[guid]; - // clear portals below specified level - unless it's the selected portal, or it's relevant to artifacts - if ((parseInt(p.options.level) < level || !bounds.contains(p.getLatLng())) && guid !== selectedPortal && !artifact.isInterestingPortal(guid)) { + // clear portals outside visible bounds - unless it's the selected portal, or it's relevant to artifacts + if (!bounds.contains(p.getLatLng()) && guid !== selectedPortal && !artifact.isInterestingPortal(guid)) { this.deletePortalEntity(guid); count++; } @@ -110,7 +109,7 @@ window.Render.prototype.processDeletedGameEntityGuids = function(deleted) { } -window.Render.prototype.processGameEntities = function(entities,ignoreLevel) { +window.Render.prototype.processGameEntities = function(entities) { // we loop through the entities three times - for fields, links and portals separately // this is a reasonably efficient work-around for leafletjs limitations on svg render order @@ -131,27 +130,13 @@ window.Render.prototype.processGameEntities = function(entities,ignoreLevel) { } } - // 2015-03-12 - Niantic have been returning all mission portals to the client, ignoring portal level - // and density filtering usually in use. this makes things unusable when viewing the global view, so we - // filter these out - var minLevel = ignoreLevel ? 0 : this.level; - var ignoredCount = 0; - for (var i in entities) { var ent = entities[i]; if (ent[2][0] == 'p' && !(ent[0] in this.deletedGuid)) { - var portalLevel = ent[2][1] == 'N' ? 0 : parseInt(ent[2][4]); - if (portalLevel >= minLevel) { - this.createPortalEntity(ent); - } else { - ignoredCount++; - } - + this.createPortalEntity(ent); } } - - if (ignoredCount) console.log('Render: ignored '+ignoredCount+' portals below the level requested from the server'); } @@ -263,6 +248,39 @@ window.Render.prototype.deleteFieldEntity = function(guid) { } +window.Render.prototype.createPlaceholderPortalEntity = function(guid,latE6,lngE6,team) { + // intel no longer returns portals at anything but the closest zoom + // stock intel creates 'placeholder' portals from the data in links/fields - IITC needs to do the same + // we only have the portal guid, lat/lng coords, and the faction - no other data + // having the guid, at least, allows the portal details to be loaded once it's selected. however, + // no highlighters, portal level numbers, portal names, useful counts of portals, etc are possible + + + var ent = [ + guid, //ent[0] = guid + 0, //ent[1] = timestamp - zero will mean any other source of portal data will have a higher timestamp + //ent[2] = an array with the entity data + [ 'p', //0 - a portal + team, //1 - team + latE6, //2 - lat + lngE6 //3 - lng + ] + ]; + + // placeholder portals don't have a useful timestamp value - so the standard code that checks for updated + // portal details doesn't apply + // so, check that the basic details are valid and delete the existing portal if out of date + if (guid in window.portals) { + var p = window.portals[guid]; + if (team != p.options.data.team || latE6 != p.options.data.latE6 || lngE6 != p.options.data.lngE6) { + // team or location have changed - delete existing portal + this.deletePortalEntity(guid); + } + } + + this.createPortalEntity(ent); + +} window.Render.prototype.createPortalEntity = function(ent) { @@ -288,7 +306,7 @@ window.Render.prototype.createPortalEntity = function(ent) { this.deletePortalEntity(ent[0]); } - var portalLevel = parseInt(ent[2][4]); + var portalLevel = parseInt(ent[2][4])||0; var team = teamStringToId(ent[2][1]); // the data returns unclaimed portals as level 1 - but IITC wants them treated as level 0 if (team == TEAM_NONE) portalLevel = 0; @@ -350,6 +368,18 @@ window.Render.prototype.createPortalEntity = function(ent) { window.Render.prototype.createFieldEntity = function(ent) { this.seenFieldsGuid[ent[0]] = true; // flag we've seen it + var data = { +// type: ent[2][0], + team: ent[2][1], + points: ent[2][2].map(function(arr) { return {guid: arr[0], latE6: arr[1], lngE6: arr[2] }; }) + }; + + //create placeholder portals for field corners. we already do links, but there are the odd case where this is useful + for (var i=0; i<3; i++) { + var p=data.points[i]; + this.createPlaceholderPortalEntity(p.guid, p.latE6, p.lngE6, data.team); + } + // check if entity already exists if(ent[0] in window.fields) { // yes. in theory, we should never get updated data for an existing field. they're created, and they're destroyed - never changed @@ -364,12 +394,6 @@ window.Render.prototype.createFieldEntity = function(ent) { this.deleteFieldEntity(ent[0]); // option 2, for now } - var data = { -// type: ent[2][0], - team: ent[2][1], - points: ent[2][2].map(function(arr) { return {guid: arr[0], latE6: arr[1], lngE6: arr[2] }; }) - }; - var team = teamStringToId(ent[2][1]); var latlngs = [ L.latLng(data.points[0].latE6/1E6, data.points[0].lngE6/1E6), @@ -399,8 +423,31 @@ window.Render.prototype.createFieldEntity = function(ent) { } window.Render.prototype.createLinkEntity = function(ent,faked) { + // Niantic have been faking link entities, based on data from fields + // these faked links are sent along with the real portal links, causing duplicates + // the faked ones all have longer GUIDs, based on the field GUID (with _ab, _ac, _bc appended) + var fakedLink = new RegExp("^[0-9a-f]{32}\.b_[ab][bc]$"); //field GUIDs always end with ".b" - faked links append the edge identifier + if (fakedLink.test(ent[0])) return; + + this.seenLinksGuid[ent[0]] = true; // flag we've seen it + var data = { // TODO add other properties and check correction direction +// type: ent[2][0], + team: ent[2][1], + oGuid: ent[2][2], + oLatE6: ent[2][3], + oLngE6: ent[2][4], + dGuid: ent[2][5], + dLatE6: ent[2][6], + dLngE6: ent[2][7] + }; + + // create placeholder entities for link start and end points (before checking if the link itself already exists + this.createPlaceholderPortalEntity(data.oGuid, data.oLatE6, data.oLngE6, data.team); + this.createPlaceholderPortalEntity(data.dGuid, data.dLatE6, data.dLngE6, data.team); + + // check if entity already exists if (ent[0] in window.links) { // yes. now, as sometimes links are 'faked', they have incomplete data. if the data we have is better, replace the data @@ -415,17 +462,6 @@ window.Render.prototype.createLinkEntity = function(ent,faked) { this.deleteLinkEntity(ent[0]); // option 2 - for now } - var data = { // TODO add other properties and check correction direction -// type: ent[2][0], - team: ent[2][1], - oGuid: ent[2][2], - oLatE6: ent[2][3], - oLngE6: ent[2][4], - dGuid: ent[2][5], - dLatE6: ent[2][6], - dLngE6: ent[2][7] - }; - var team = teamStringToId(ent[2][1]); var latlngs = [ L.latLng(data.oLatE6/1E6, data.oLngE6/1E6), @@ -469,12 +505,12 @@ window.Render.prototype.rescalePortalMarkers = function() { // add the portal to the visible map layer window.Render.prototype.addPortalToMapLayer = function(portal) { - portalsFactionLayers[parseInt(portal.options.level)][portal.options.team].addLayer(portal); + portalsFactionLayers[parseInt(portal.options.level)||0][portal.options.team].addLayer(portal); } window.Render.prototype.removePortalFromMapLayer = function(portal) { //remove it from the portalsLevels layer - portalsFactionLayers[parseInt(portal.options.level)][portal.options.team].removeLayer(portal); + portalsFactionLayers[parseInt(portal.options.level)||0][portal.options.team].removeLayer(portal); } diff --git a/code/map_data_request.js b/code/map_data_request.js index 12ad93c8..514eabd8 100644 --- a/code/map_data_request.js +++ b/code/map_data_request.js @@ -57,7 +57,7 @@ window.MapDataRequest = function() { // render queue // number of items to process in each render pass. there are pros and cons to smaller and larger values // (however, if using leaflet canvas rendering, it makes sense to push as much as possible through every time) - this.RENDER_BATCH_SIZE = L.Path.CANVAS ? 1E9 : 500; + this.RENDER_BATCH_SIZE = L.Path.CANVAS ? 1E9 : 1500; // delay before repeating the render loop. this gives a better chance for user interaction this.RENDER_PAUSE = (typeof android === 'undefined') ? 0.1 : 0.2; //100ms desktop, 200ms mobile @@ -69,6 +69,13 @@ window.MapDataRequest = function() { // ensure we have some initial map status this.setStatus ('startup', undefined, -1); + + // add a portalDetailLoaded hook, so we can use the exteneed details to update portals on the map + var _this = this; + addHook('portalDetailLoaded',function(data){ + _this.render.processGameEntities([data.ent]); + }); + } @@ -232,8 +239,11 @@ window.MapDataRequest.prototype.refresh = function() { this.render.startRenderPass(tileParams.level, dataBounds); + var _render = this.render; + window.runHooks ('mapDataEntityInject', {callback: function(ents) { _render.processGameEntities(ents);}}); - this.render.processGameEntities(artifact.getArtifactEntities(),true); + + this.render.processGameEntities(artifact.getArtifactEntities()); var logMessage = 'requesting data tiles at zoom '+dataZoom; if (tileParams.level != tileParams.maxLevel) { diff --git a/code/ornaments.js b/code/ornaments.js index efcbdad0..e5b35633 100644 --- a/code/ornaments.js +++ b/code/ornaments.js @@ -33,16 +33,18 @@ window.ornaments.addPortal = function(portal) { var size = window.ornaments.OVERLAY_SIZE; var latlng = portal.getLatLng(); - window.ornaments._portals[guid] = portal.options.data.ornaments.map(function(ornament) { - var icon = L.icon({ - iconUrl: "//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/"+ornament+".png", - iconSize: [size, size], - iconAnchor: [size/2,size/2], - className: 'no-pointer-events' // the clickable: false below still blocks events going through to the svg underneath - }); + if (portal.options.data.ornaments) { + window.ornaments._portals[guid] = portal.options.data.ornaments.map(function(ornament) { + var icon = L.icon({ + iconUrl: "//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/"+ornament+".png", + iconSize: [size, size], + iconAnchor: [size/2,size/2], + className: 'no-pointer-events' // the clickable: false below still blocks events going through to the svg underneath + }); - return L.marker(latlng, {icon: icon, clickable: false, keyboard: false, opacity: window.ornaments.OVERLAY_OPACITY }).addTo(window.ornaments._layer); - }); + return L.marker(latlng, {icon: icon, clickable: false, keyboard: false, opacity: window.ornaments.OVERLAY_OPACITY }).addTo(window.ornaments._layer); + }); + } } window.ornaments.removePortal = function(portal) { diff --git a/code/portal_detail.js b/code/portal_detail.js index 8e8f80a7..55400098 100644 --- a/code/portal_detail.js +++ b/code/portal_detail.js @@ -37,8 +37,12 @@ var handleResponse = function(guid, data, success) { } if (success) { + var dict = decodeArray.portalDetail(data.result); + // entity format, as used in map data + var ent = [guid,dict.timestamp,data.result]; + cache.store(guid,dict); //FIXME..? better way of handling sidebar refreshing... @@ -47,7 +51,7 @@ var handleResponse = function(guid, data, success) { renderPortalDetails(guid); } - window.runHooks ('portalDetailLoaded', {guid:guid, success:success, details:dict}); + window.runHooks ('portalDetailLoaded', {guid:guid, success:success, details:dict, ent:ent}); } else { if (data && data.error == "RETRY") { diff --git a/code/portal_detail_display.js b/code/portal_detail_display.js index 3c51cb80..dc4793df 100644 --- a/code/portal_detail_display.js +++ b/code/portal_detail_display.js @@ -41,46 +41,13 @@ window.renderPortalDetails = function(guid) { var img = fixPortalImageUrl(details ? details.image : data.image); - var title = data.title; + var title = (details && details.title) || (data && data.title) || '(untitled)'; var lat = data.latE6/1E6; var lng = data.lngE6/1E6; - var imgTitle = details ? getPortalDescriptionFromDetails(details) : data.title; - imgTitle += '\n\nClick to show full image.'; - var portalDetailObj = details ? window.getPortalDescriptionFromDetailsExtended(details) : undefined; + var imgTitle = title+'\n\nClick to show full image.'; - var portalDetailedDescription = ''; - - if(portalDetailObj) { - portalDetailedDescription = ''; - - // TODO (once the data supports it) - portals can have multiple photos. display all, with navigation between them - // (at this time the data isn't returned from the server - although a count of images IS returned!) - - if(portalDetailObj.submitter.name.length > 0) { - if(portalDetailObj.submitter.team) { - submitterSpan = ''; - } else { - submitterSpan = ''; - } - portalDetailedDescription += ''; - } - if(portalDetailObj.submitter.link.length > 0) { - portalDetailedDescription += ''; - } - - if(portalDetailObj.description) { - portalDetailedDescription += ''; - } -// if(d.descriptiveText.map.ADDRESS) { -// portalDetailedDescription += ''; -// } - - portalDetailedDescription += '
Photo by:' + submitterSpan - + escapeHtmlSpecialChars(portalDetailObj.submitter.name) + ''+(portalDetailObj.submitter.voteCount !== undefined ? ' (' + portalDetailObj.submitter.voteCount + ' votes)' : '')+'
Photo from:' + escapeHtmlSpecialChars(portalDetailObj.submitter.link) + '
Description:' + escapeHtmlSpecialChars(portalDetailObj.description) + '
Address:' + escapeHtmlSpecialChars(d.descriptiveText.map.ADDRESS) + '
'; - } // portal level. start with basic data - then extend with fractional info in tooltip if available var levelInt = (teamStringToId(data.team) == TEAM_NONE) ? 0 : data.level; @@ -127,7 +94,7 @@ window.renderPortalDetails = function(guid) { .html('') //to ensure it's clear .attr('class', TEAM_TO_CSS[teamStringToId(data.team)]) .append( - $('

').attr({class:'title'}).text(data.title), + $('

').attr({class:'title'}).text(title), $('').attr({ class: 'close', @@ -141,7 +108,6 @@ window.renderPortalDetails = function(guid) { .attr({class:'imgpreview', title:imgTitle, style:"background-image: url('"+img+"')"}) .append( $('').attr({id:'level', title: levelDetails}).text(levelInt), - $('
').attr({class:'portalDetails'}).html(portalDetailedDescription), $('').attr({class:'hide', src:img}) ), @@ -169,11 +135,15 @@ window.getPortalMiscDetails = function(guid,d) { // collect some random data that’s not worth to put in an own method var linkInfo = getPortalLinks(guid); + var maxOutgoing = getMaxOutgoingLinks(d); var linkCount = linkInfo.in.length + linkInfo.out.length; var links = {incoming: linkInfo.in.length, outgoing: linkInfo.out.length}; - function linkExpl(t) { return ''+t+''; } - var linksText = [linkExpl('links'), linkExpl(links.outgoing+' out / '+links.incoming+' in')]; + var title = 'at most ' + maxOutgoing + ' outgoing links\n' + + links.outgoing + ' links out\n' + + links.incoming + ' links in\n' + + '(' + (links.outgoing+links.incoming) + ' total)' + var linksText = ['links', links.outgoing+' out / '+links.incoming+' in', title]; var player = d.owner ? '' + d.owner + '' @@ -214,32 +184,23 @@ window.getPortalMiscDetails = function(guid,d) { 'force amplifier', '×'+attackValues.force_amplifier]); - // artifact details - - // 2014-02-06: stock site changed from supporting 'jarvis shards' to 'amar artifacts'(?) - so let's see what we can do to be generic... - $.each(artifact.getArtifactTypes(),function(index,type) { - var artdata = artifact.getPortalData (guid, type); - if (artdata) { - var details = artifact.getArtifactDescriptions(type); - if (details) { - // the genFourColumnTable function below doesn't handle cases where one column is null and the other isn't - so default to *something* in both columns - var target = ['',''], shards = [details.fragmentName,'(none)']; - if (artdata.target) { - target = ['target', ''+(artdata.target==TEAM_RES?'Resistance':'Enlightened')+'']; - } - if (artdata.fragments) { - shards = [details.fragmentName, '#'+artdata.fragments.join(', #')]; - } - - randDetailsData.push (target, shards); - } else { - console.warn('Unknown artifact type '+type+': no names, so cannot display'); - } - } - }); - randDetails = '' + genFourColumnTable(randDetailsData) + '
'; + + // artifacts - tacked on after (but not as part of) the 'randdetails' table + // instead of using the existing columns.... + + if (d.artifactBrief && d.artifactBrief.target && Object.keys(d.artifactBrief.target).length > 0) { + var targets = Object.keys(d.artifactBrief.target); +//currently (2015-07-10) we no longer know the team each target portal is for - so we'll just show the artifact type(s) + randDetails += '
Target portal: '+targets.map(function(x) { return x.capitalize(); }).join(', ')+'
'; + } + + // shards - taken directly from the portal details + if (d.artifactDetail) { + randDetails += '
Shards: '+d.artifactDetail.displayName+' #'+d.artifactDetail.fragments.join(', ')+'
'; + } + } return randDetails; diff --git a/code/portal_detail_display_tools.js b/code/portal_detail_display_tools.js index abd54a50..3df146c8 100644 --- a/code/portal_detail_display_tools.js +++ b/code/portal_detail_display_tools.js @@ -12,67 +12,15 @@ window.getRangeText = function(d) { if(!range.isLinkable) title += '\nPortal is missing resonators,\nno new links can be made'; - return ['range', + return ['range', '' + + '>' + (range.range > 1000 ? Math.floor(range.range/1000) + ' km' : Math.floor(range.range) + ' m') - + '']; -} - -// generates description text from details for portal -window.getPortalDescriptionFromDetails = function(details) { - return details.title || '(untitled)'; - -// var descObj = details.descriptiveText.map; -// // FIXME: also get real description? -// var desc = descObj.TITLE; -// if(descObj.ADDRESS) -// desc += '\n' + descObj.ADDRESS; -//// if(descObj.ATTRIBUTION) -//// desc += '\nby '+descObj.ATTRIBUTION+' ('+descObj.ATTRIBUTION_LINK+')'; -// return desc; -} - -// Grabs more info, including the submitter name for the current main -// portal image -window.getPortalDescriptionFromDetailsExtended = function(details) { - var descObj = details.title; - var photoStreamObj = details.photoStreamInfo; - - var submitterObj = new Object(); - submitterObj.type = ""; - submitterObj.name = ""; - submitterObj.team = ""; - submitterObj.link = ""; - submitterObj.voteCount = undefined; - - if(photoStreamObj && photoStreamObj.hasOwnProperty("coverPhoto") && photoStreamObj.coverPhoto.hasOwnProperty("attributionMarkup")) { - submitterObj.name = "Unknown"; - - var attribution = photoStreamObj.coverPhoto.attributionMarkup; - submitterObj.type = attribution[0]; - if(attribution[1].hasOwnProperty("plain")) - submitterObj.name = attribution[1].plain; - if(attribution[1].hasOwnProperty("team")) - submitterObj.team = attribution[1].team; - if(attribution[1].hasOwnProperty("attributionLink")) - submitterObj.link = attribution[1].attributionLink; - if(photoStreamObj.coverPhoto.hasOwnProperty("voteCount")) - submitterObj.voteCount = photoStreamObj.coverPhoto.voteCount; - } - - - var portalDetails = { - title: descObj.TITLE, - description: descObj.DESCRIPTION, - address: descObj.ADDRESS, - submitter: submitterObj - }; - - return portalDetails; + + '', + title]; } @@ -115,6 +63,7 @@ window.getModDetails = function(d) { else if (key === 'ATTACK_FREQUENCY') val = (val/1000) +'x'; // 2000 = 2x else if (key === 'FORCE_AMPLIFIER') val = (val/1000) +'x'; // 2000 = 2x else if (key === 'LINK_RANGE_MULTIPLIER') val = (val/1000) +'x'; // 2000 = 2x + else if (key === 'LINK_DEFENSE_BOOST') val = (val/1000) +'x'; // 1500 = 1.5x else if (key === 'REMOVAL_STICKINESS' && val > 100) val = (val/10000)+'%'; // an educated guess // else display unmodified. correct for shield mitigation and multihack - unknown for future/other mods @@ -150,9 +99,9 @@ window.getModDetails = function(d) { window.getEnergyText = function(d) { var currentNrg = getCurrentPortalEnergy(d); var totalNrg = getTotalPortalEnergy(d); - var inf = currentNrg + ' / ' + totalNrg; + var title = currentNrg + ' / ' + totalNrg; var fill = prettyEnergy(currentNrg) + ' / ' + prettyEnergy(totalNrg) - return ['energy', '' + fill + '']; + return ['energy', fill, title]; } @@ -236,22 +185,19 @@ window.getAttackApGainText = function(d,fieldCount,linkCount) { var breakdown = getAttackApGain(d,fieldCount,linkCount); var totalGain = breakdown.enemyAp; - function tt(text) { - var t = ''; - if (teamStringToId(PLAYER.team) == teamStringToId(d.team)) { - totalGain = breakdown.friendlyAp; - t += 'Friendly AP:\t' + breakdown.friendlyAp + '\n'; - t += ' Deploy ' + breakdown.deployCount + ', '; - t += 'Upgrade ' + breakdown.upgradeCount + '\n'; - t += '\n'; - } - t += 'Enemy AP:\t' + breakdown.enemyAp + '\n'; - t += ' Destroy AP:\t' + breakdown.destroyAp + '\n'; - t += ' Capture AP:\t' + breakdown.captureAp + '\n'; - return '' + text + ''; + var t = ''; + if (teamStringToId(PLAYER.team) == teamStringToId(d.team)) { + totalGain = breakdown.friendlyAp; + t += 'Friendly AP:\t' + breakdown.friendlyAp + '\n'; + t += ' Deploy ' + breakdown.deployCount + ', '; + t += 'Upgrade ' + breakdown.upgradeCount + '\n'; + t += '\n'; } + t += 'Enemy AP:\t' + breakdown.enemyAp + '\n'; + t += ' Destroy AP:\t' + breakdown.destroyAp + '\n'; + t += ' Capture AP:\t' + breakdown.captureAp + '\n'; - return [tt('AP Gain'), tt(digits(totalGain))]; + return ['AP Gain', digits(totalGain), t]; } @@ -260,16 +206,12 @@ window.getHackDetailsText = function(d) { var shortHackInfo = hackDetails.hacks+' @ '+formatInterval(hackDetails.cooldown); - function tt(text) { - var t = 'Hacks available every 4 hours\n'; - t += 'Hack count:\t'+hackDetails.hacks+'\n'; - t += 'Cooldown time:\t'+formatInterval(hackDetails.cooldown)+'\n'; - t += 'Burnout time:\t'+formatInterval(hackDetails.burnout)+'\n'; + var title = 'Hacks available every 4 hours\n' + + 'Hack count:\t'+hackDetails.hacks+'\n' + + 'Cooldown time:\t'+formatInterval(hackDetails.cooldown)+'\n' + + 'Burnout time:\t'+formatInterval(hackDetails.burnout); - return ''+text+''; - } - - return [tt('hacks'), tt(shortHackInfo)]; + return ['hacks', shortHackInfo, title]; } @@ -279,16 +221,12 @@ window.getMitigationText = function(d,linkCount) { var mitigationShort = mitigationDetails.total; if (mitigationDetails.excess) mitigationShort += ' (+'+mitigationDetails.excess+')'; - function tt(text) { - var t = 'Total shielding:\t'+(mitigationDetails.shields+mitigationDetails.links)+'\n' - + '- active:\t'+mitigationDetails.total+'\n' - + '- excess:\t'+mitigationDetails.excess+'\n' - + 'From\n' - + '- shields:\t'+mitigationDetails.shields+'\n' - + '- links:\t'+mitigationDetails.links; + var title = 'Total shielding:\t'+(mitigationDetails.shields+mitigationDetails.links)+'\n' + + '- active:\t'+mitigationDetails.total+'\n' + + '- excess:\t'+mitigationDetails.excess+'\n' + + 'From\n' + + '- shields:\t'+mitigationDetails.shields+'\n' + + '- links:\t'+mitigationDetails.links; - return ''+text+''; - } - - return [tt('shielding'), tt(mitigationShort)]; + return ['shielding', mitigationShort, title]; } diff --git a/code/portal_info.js b/code/portal_info.js index 663dab51..29d607c6 100644 --- a/code/portal_info.js +++ b/code/portal_info.js @@ -207,7 +207,8 @@ window.getPortalModsByType = function(d, type) { TURRET: 'HIT_BONUS', // and/or ATTACK_FREQUENCY?? HEATSINK: 'HACK_SPEED', MULTIHACK: 'BURNOUT_INSULATION', - LINK_AMPLIFIER: 'LINK_RANGE_MULTIPLIER' + LINK_AMPLIFIER: 'LINK_RANGE_MULTIPLIER', + ULTRA_LINK_AMP: 'OUTGOING_LINKS_BONUS', // and/or LINK_DEFENSE_BOOST?? }; var stat = typeToStat[type]; @@ -257,6 +258,17 @@ window.getPortalMitigationDetails = function(d,linkCount) { return mitigation; } +window.getMaxOutgoingLinks = function(d) { + var linkAmps = getPortalModsByType(d, 'ULTRA_LINK_AMP'); + + var links = 8; + + linkAmps.forEach(function(mod, i) { + links += parseInt(mod.stats.OUTGOING_LINKS_BONUS); + }); + + return links; +}; window.getPortalHackDetails = function(d) { diff --git a/code/portal_marker.js b/code/portal_marker.js index 749d5d30..b9bfb764 100644 --- a/code/portal_marker.js +++ b/code/portal_marker.js @@ -47,11 +47,18 @@ window.getMarkerStyleOptions = function(details) { var LEVEL_TO_WEIGHT = [2, 2, 2, 2, 2, 3, 3, 4, 4]; var LEVEL_TO_RADIUS = [7, 7, 7, 7, 8, 8, 9,10,11]; - var level = Math.floor(details.level); + var level = Math.floor(details.level||0); var lvlWeight = LEVEL_TO_WEIGHT[level] * Math.sqrt(scale); var lvlRadius = LEVEL_TO_RADIUS[level] * scale; + var dashArray = null; + // thinner and dashed outline for placeholder portals + if (details.team != TEAM_NONE && level==0) { + lvlWeight = 1; + dashArray = [1,2]; + } + var options = { radius: lvlRadius, stroke: true, @@ -61,7 +68,7 @@ window.getMarkerStyleOptions = function(details) { fill: true, fillColor: COLORS[details.team], fillOpacity: 0.5, - dashArray: null + dashArray: dashArray }; return options; diff --git a/code/search.js b/code/search.js index ca446fb6..120ce055 100644 --- a/code/search.js +++ b/code/search.js @@ -225,6 +225,8 @@ addHook('search', function(query) { $.each(portals, function(guid, portal) { var data = portal.options.data; + if(!data.title) return; + if(data.title.toLowerCase().indexOf(term) !== -1) { var team = portal.options.team; var color = team==TEAM_NONE ? '#CCC' : COLORS[team]; diff --git a/code/send_request.js b/code/send_request.js index f0da8098..be6d5712 100644 --- a/code/send_request.js +++ b/code/send_request.js @@ -34,7 +34,6 @@ window.postAjax = function(action, data, successCallback, errorCallback) { var onSuccess = function(data, textStatus, jqXHR) { window.requests.remove(jqXHR); - iitc_bg.process_response_params(action,data); // the Niantic server can return a HTTP success, but the JSON response contains an error. handle that sensibly if (data && data.error && data.error == 'out of date') { @@ -65,7 +64,7 @@ window.postAjax = function(action, data, successCallback, errorCallback) { } var versionStr = niantic_params.CURRENT_VERSION; - var post_data = JSON.stringify($.extend({}, data, {v: versionStr}, iitc_bg.extra_request_params(action))); + var post_data = JSON.stringify($.extend({}, data, {v: versionStr})); var result = $.ajax({ url: '/r/'+action, @@ -96,7 +95,7 @@ window.outOfDateUserPrompt = function() dialog({ title: 'Reload IITC', - html: '

IITC is using an outdated version code. This will happen when Niantic update the standard intel site.

' + html: '

IITC is using an outdated version code. This will happen when Niantic updates the standard intel site.

' +'

You need to reload the page to get the updated changes.

' +'

If you have just reloaded the page, then an old version of the standard site script is cached somewhere.' +'In this case, try clearing your cache, or waiting 15-30 minutes for the stale data to expire.

', diff --git a/code/status_bar.js b/code/status_bar.js index 8aef2bb3..97703792 100644 --- a/code/status_bar.js +++ b/code/status_bar.js @@ -7,15 +7,32 @@ window.renderUpdateStatusTimer_ = undefined; window.renderUpdateStatus = function() { var progress = 1; - // portal level display - var t = ''; - if(!window.isSmartphone()) // space is valuable - t += 'portals: '; - var minlvl = getMinPortalLevel(); - if(minlvl === 0) - t+= 'all'; - else - t+= 'L'+minlvl+(minlvl<8?'+':'') + ''; + // portal/limk level display + + var zoom = map.getZoom(); + zoom = getDataZoomForMapZoom(zoom); + var tileParams = getMapZoomTileParameters(zoom); + + var t = ''; + + if (tileParams.hasPortals) { + // zoom level includes portals (and also all links/fields) + if(!window.isSmartphone()) // space is valuable + t += 'portals: '; + if(tileParams.level === 0) + t += 'all'; + else + t += 'L'+tileParams.level+(tileParams.level<8?'+':'') + ''; + } else { + if(!window.isSmartphone()) // space is valuable + t += 'links: '; + + if (tileParams.minLinkLength > 0) + t += '>'+(tileParams.minLinkLength>1000?tileParams.minLinkLength/1000+'km':tileParams.minLinkLength+'m')+''; + else + t += 'all links'; + } + t +=''; diff --git a/code/utils_misc.js b/code/utils_misc.js index f2f09ccb..b3009a58 100644 --- a/code/utils_misc.js +++ b/code/utils_misc.js @@ -335,10 +335,11 @@ window.uniqueArray = function(arr) { window.genFourColumnTable = function(blocks) { var t = $.map(blocks, function(detail, index) { if(!detail) return ''; + var title = detail[2] ? ' title="'+escapeHtmlSpecialChars(detail[2]) + '"' : ''; if(index % 2 === 0) - return ''+detail[1]+''+detail[0]+''; + return ''+detail[1]+''+detail[0]+''; else - return ' '+detail[0]+''+detail[1]+''; + return ' '+detail[0]+''+detail[1]+''; }).join(''); if(t.length % 2 === 1) t + ''; return t; @@ -413,6 +414,16 @@ window.addLayerGroup = function(name, layerGroup, defaultDisplay) { layerChooser.addOverlay(layerGroup, name); } +window.removeLayerGroup = function(layerGroup) { + if(!layerChooser._layers[layerGroup._leaflet_id]) throw('Layer was not found'); + // removing the layer will set it's default visibility to false (store if layer gets added again) + var name = layerChooser._layers[layerGroup._leaflet_id].name; + var enabled = isLayerGroupDisplayed(name); + map.removeLayer(layerGroup); + layerChooser.removeLayer(layerGroup); + updateDisplayedLayerGroup(name, enabled); +}; + window.clampLat = function(lat) { // the map projection used does not handle above approx +- 85 degrees north/south of the equator if (lat > 85.051128) diff --git a/external/s2geometry.js b/external/s2geometry.js index 4f7976e7..d6b6841a 100644 --- a/external/s2geometry.js +++ b/external/s2geometry.js @@ -212,8 +212,6 @@ S2.S2Cell.FromLatLng = function(latLng,level) { var ij = STToIJ(st,level); return S2.S2Cell.FromFaceIJ (faceuv[0], ij, level); - - return result; }; S2.S2Cell.FromFaceIJ = function(face,ij,level) { diff --git a/main.js b/main.js index 1af57009..817dd6cb 100644 --- a/main.js +++ b/main.js @@ -1,7 +1,7 @@ // ==UserScript== // @id ingress-intel-total-conversion@jonatkins // @name IITC: Ingress intel map total conversion -// @version 0.22.4.@@DATETIMEVERSION@@ +// @version 0.25.2.@@DATETIMEVERSION@@ // @namespace https://github.com/jonatkins/ingress-intel-total-conversion // @updateURL @@UPDATEURL@@ // @downloadURL @@DOWNLOADURL@@ @@ -27,18 +27,11 @@ window.iitcBuildDate = '@@BUILDDATE@@'; window.onload = function() {}; document.body.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; -} +//originally code here parsed the