// ARTIFACT /////////////////////////////////////////////////////// // added as part of the ingress #13magnus in november 2013, artifacts // are additional game elements overlayed on the intel map // currently there are only jarvis-related entities // - shards: move between portals (along links) each hour. more than one can be at a portal // - targets: specific portals - one per team // the artifact data includes details for the specific portals, so can be useful // 2014-02-06: intel site updates hint at new 'amar artifacts', likely following the same system as above window.artifact = function() {} window.artifact.setup = function() { artifact.REFRESH_JITTER = 2*60; // 2 minute random period so not all users refresh at once artifact.REFRESH_SUCCESS = 60*60; // 60 minutes on success artifact.REFRESH_FAILURE = 2*60; // 2 minute retry on failure artifact.idle = false; artifact.clearData(); addResumeFunction(artifact.idleResume); // move the initial data request onto a very short timer. prevents thrown exceptions causing IITC boot failures setTimeout (artifact.requestData, 1); artifact._layer = new L.LayerGroup(); addLayerGroup ('Artifacts', artifact._layer, true); $('#toolbox').append(' Artifacts'); } window.artifact.requestData = function() { if (isIdle()) { artifact.idle = true; } else { window.postAjax('getArtifactPortals', {}, artifact.handleSuccess, artifact.handleError); } } window.artifact.idleResume = function() { if (artifact.idle) { artifact.idle = false; artifact.requestData(); } } window.artifact.handleSuccess = function(data) { artifact.processData (data); // start the next refresh at a multiple of REFRESH_SUCCESS seconds, plus a random REFRESH_JITTER amount to prevent excessive server hits at one time var now = Date.now(); var nextTime = Math.ceil(now/(artifact.REFRESH_SUCCESS*1000))*(artifact.REFRESH_SUCCESS*1000) + Math.floor(Math.random()*artifact.REFRESH_JITTER*1000); setTimeout (artifact.requestData, nextTime - now); } window.artifact.handleFailure = function(data) { // no useful data on failure - do nothing setTimeout (artifact.requestData, artifact.REFRESH_FAILURE*1000); } window.artifact.processData = function(data) { if (data.error || !data.result) { console.warn('Failed to find result in getArtifactPortals response'); return; } var oldArtifacts = artifact.entities; artifact.clearData(); artifact.processResult(data.result); runHooks('artifactsUpdated', {old: oldArtifacts, 'new': artifact.entities}); // redraw the artifact layer artifact.updateLayer(); } window.artifact.clearData = function() { artifact.portalInfo = {}; artifact.artifactTypes = {}; artifact.entities = []; } 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); if (!data.artifactBrief) { // 2/12/2017 - Shard removed from a portal leaves it in artifact results but has no artifactBrief continue; } // 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... } for(var type in data.artifactBrief.fragment) { if (!artifact.artifactTypes[type]) artifact.artifactTypes[type] = {}; if (!artifact.portalInfo[guid][type]) artifact.portalInfo[guid][type] = {}; artifact.portalInfo[guid][type].fragments = true; //as we no longer have a list of the fragments there } // 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() { return Object.keys(artifact.artifactTypes); } window.artifact.isArtifact = function(type) { return type in artifact.artifactTypes; } // used to render portals that would otherwise be below the visible level window.artifact.getArtifactEntities = function() { return artifact.entities; } window.artifact.getInterestingPortals = function() { return Object.keys(artifact.portalInfo); } // quick test for portal being relevant to artifacts - of any type window.artifact.isInterestingPortal = function(guid) { return guid in artifact.portalInfo; } // get the artifact data for a specified artifact id (e.g. 'jarvis'), if it exists - otherwise returns something 'false'y window.artifact.getPortalData = function(guid,artifactId) { return artifact.portalInfo[guid] && artifact.portalInfo[guid][artifactId]; } window.artifact.updateLayer = function() { artifact._layer.clearLayers(); $.each(artifact.portalInfo, function(guid,data) { var latlng = L.latLng ([data._data.latE6/1E6, data._data.lngE6/1E6]); $.each(data, function(type,detail) { // we'll construct the URL form the type - stock seems to do that now 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); } }); //end $.each(data, function(type,detail) }); //end $.each(artifact.portalInfo, function(guid,data) } window.artifact.showArtifactList = function() { var html = ''; if (Object.keys(artifact.artifactTypes).length == 0) { html += 'No artifacts at this time'; } var first = true; $.each(artifact.artifactTypes, function(type,type2) { // 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; html += '
'+name+'
'; html += ''; html += ''; var tableRows = []; $.each(artifact.portalInfo, function(guid, data) { if (type in data) { // this portal has data for this artifact type - add it to the table var onclick = 'zoomToAndShowPortal(\''+guid+'\',['+data._data.latE6/1E6+','+data._data.lngE6/1E6+'])'; var row = ''; row += ''; // sort by target portals first, then by portal GUID var sortVal = (data[type].target !== undefined ? 'A' : 'Z') + guid; tableRows.push ( [sortVal, row] ); } }); // check for no rows, and add a note to the table instead if (tableRows.length == 0) { html += ''; } // sort the rows tableRows.sort(function(a,b) { if (a[0] == b[0]) return 0; else if (a[0] < b[0]) return -1; else return 1; }); // and add them to the table html += tableRows.map(function(a){return a[1];}).join(''); html += '
PortalDetails
'+escapeHtmlSpecialChars(data._data.title)+''; 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 !== undefined) { row += '
'; } var fragmentName = 'shard'; // row += ''+fragmentName+': #'+data[type].fragments.join(', #')+' '; row += ''+fragmentName+': yes '; } row += '
No portals at this time
'; }); 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, width: 400, position: {my: 'right center', at: 'center-60 center', of: window, collision: 'fit'} }); }