diff --git a/code/artifact.js b/code/artifact.js new file mode 100644 index 00000000..b64f7bf0 --- /dev/null +++ b/code/artifact.js @@ -0,0 +1,254 @@ +// 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 + + +window.artifact = function() {} + +window.artifact.setup = function() { + artifact.REFRESH_SUCCESS = 15*60; // 15 minutes on success + artifact.REFRESH_FAILURE = 2*60; // 2 minute retry on failure + + artifact.idle = false; + artifact.clearData(); + + addResumeFunction(artifact.idleResume); + + artifact.requestData(); + + artifact._layer = new L.LayerGroup(); + addLayerGroup ('Artifacts (Jarvis shards)', artifact._layer, true); + + $('#toolbox').append(' Artifacts'); + +} + +window.artifact.requestData = function() { + if (isIdle()) { + artifact.idle = true; + } else { + window.postAjax('getArtifactInfo', {}, artifact.handleSuccess, artifact.handleError); + } +} + +window.artifact.idleResume = function() { + if (artifact.idle) { + artifact.idle = false; + artifact.requestData(); + } +} + +window.artifact.handleSuccess = function(data) { + artifact.processData (data); + + setTimeout (artifact.requestData, artifact.REFRESH_SUCCESS*1000); +} + +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.artifacts) { + console.warn('Failed to find artifacts in artifact response'); + return; + } + + artifact.clearData(); + + $.each (data.artifacts, function(i,artData) { + if (artData.artifactId != 'jarvis') { + // jarvis 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! + }); + + + // redraw the artifact layer + artifact.updateLayer(); + +} + + +window.artifact.clearData = function() { + + artifact.portalInfo = {}; + artifact.artifactTypes = {}; +} + +window.artifact.processFragmentInfos = function (id, fragments) { + $.each(fragments, function(i, fragment) { + if (!artifact.portalInfo[fragment.portalGuid]) { + artifact.portalInfo[fragment.portalGuid] = { _entityData: fragment.portalInfo }; + } + + if (!artifact.portalInfo[fragment.portalGuid][id]) artifact.portalInfo[fragment.portalGuid][id] = {}; + + if (!artifact.portalInfo[fragment.portalGuid][id].fragments) artifact.portalInfo[fragment.portalGuid][id].fragments = []; + + $.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 }; + } + + if (!artifact.portalInfo[target.portalGuid][id]) artifact.portalInfo[target.portalGuid][id] = {}; + + artifact.portalInfo[target.portalGuid][id].target = target.team === 'RESISTANCE' ? TEAM_RES : TEAM_ENL; + }); +} + +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() { + 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; +} + +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._entityData.locationE6.latE6/1E6, data._entityData.locationE6.lngE6/1E6]); + + // jarvis shard icon + var iconUrl = undefined; + var iconSize = 0; + + 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 + } + 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 + + var count = data.jarvis.fragments ? data.jarvis.fragments.length : 0; + + 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 + } + + 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}); + + artifact._layer.addLayer(marker); + } else { + console.warn('Oops! no URL for artifact portal icon?!'); + } + }); + +} + + +window.artifact.showArtifactList = function() { + + + var html = '
Artifact portals
'; + + var types = { 'jarvis': 'Jarvis Shards' }; + + $.each(types, function(type, name) { + + html += '
'+types[type]+'
'; + + html += ''; + + $.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._entityData.locationE6.latE6/1E6+','+data._entityData.locationE6.lngE6/1E6+'])'; + html += ''; + + html += ''; + + } + }); + + html += '
PortalDetails
'+escapeHtmlSpecialChars(data._entityData.portalV2.descriptiveText.TITLE)+''; + + if (data[type].target) { + html += ''+(data[type].target==TEAM_RES?'Resistance':'Enlightened')+' target '; + } + + if (data[type].fragments) { + html += 'Shard: #'+data[type].fragments.join(', #')+' '; + } + + html += '
'; + }); + + + dialog({ + title: 'Artifacts', + html: html, + width: 400, + position: {my: 'right center', at: 'center-60 center', of: window, collision: 'fit'} + }); + +} diff --git a/code/boot.js b/code/boot.js index 220e1531..7d39eceb 100644 --- a/code/boot.js +++ b/code/boot.js @@ -525,6 +525,7 @@ function boot() { window.setupLargeImagePreview(); window.setupSidebarToggle(); window.updateGameScore(); + window.artifact.setup(); window.setupPlayerStat(); window.setupTooltips(); window.chat.setup(); diff --git a/code/map_data_render.js b/code/map_data_render.js index 85b89b9c..fe9d685a 100644 --- a/code/map_data_render.js +++ b/code/map_data_render.js @@ -33,7 +33,8 @@ window.Render.prototype.clearPortalsBelowLevel = function(level) { var count = 0; for (var guid in window.portals) { var p = portals[guid]; - if (parseInt(p.options.level) < level && guid !== selectedPortal) { + // clear portals below specified level - unless it's the selected portal, or it's relevant to artifacts + if (parseInt(p.options.level) < level && guid !== selectedPortal && !artifact.isInterestingPortal(guid)) { this.deletePortalEntity(guid); count++; } @@ -46,7 +47,7 @@ window.Render.prototype.clearEntitiesOutsideBounds = function(bounds) { for (var guid in window.portals) { var p = portals[guid]; - if (!bounds.contains (p.getLatLng()) && guid !== selectedPortal) { + if (!bounds.contains (p.getLatLng()) && guid !== selectedPortal && !artifact.isInterestingPortal(guid)) { this.deletePortalEntity(guid); pcount++; } @@ -128,6 +129,7 @@ window.Render.prototype.endRenderPass = function() { // check to see if there's eny entities we haven't seen. if so, delete them for (var guid in window.portals) { + // special case for selected portal - it's kept even if not seen if (!(guid in this.seenPortalsGuid) && guid !== selectedPortal) { this.deletePortalEntity(guid); } @@ -153,25 +155,32 @@ window.Render.prototype.bringPortalsToFront = function() { for (var lvl in portalsFactionLayers) { // portals are stored in separate layers per faction // to avoid giving weight to one faction or another, we'll push portals to front based on GUID order - var portals = {}; + var lvlPortals = {}; for (var fac in portalsFactionLayers[lvl]) { var layer = portalsFactionLayers[lvl][fac]; if (layer._map) { layer.eachLayer (function(p) { - portals[p.options.guid] = p; + lvlPortals[p.options.guid] = p; }); } } - var guids = Object.keys(portals); + var guids = Object.keys(lvlPortals); guids.sort(); for (var j in guids) { var guid = guids[j]; + lvlPortals[guid].bringToFront(); + } + } + + // artifact portals are always brought to the front, above all others + $.each(artifact.getInterestingPortals(), function(i,guid) { + if (portals[guid] && portals[guid]._map) { portals[guid].bringToFront(); } + }); - } } @@ -544,7 +553,7 @@ window.Render.prototype.resetPortalClusters = function() { var guid = c[i]; var p = window.portals[guid]; var layerGroup = portalsFactionLayers[parseInt(p.options.level)][p.options.team]; - if (i= 0) { + console.log('IITC: found request munge set index '+i+' in stock intel site'); + window.activeRequestMungeSet = i; + } + } + } else { + console.error('IITC: failed to find the stock site function for detecting munge set'); + } + + if (window.activeRequestMungeSet===undefined) { + console.error('IITC: failed to find request munge set - IITC will likely fail'); + window.activeRequestMungeSet = 0; + } +} + + + +// niantic now add some munging to the request parameters. so far, only two sets of this munging have been seen +window.requestDataMunge = function(data) { + var activeMunge = window.requestParameterMunges[window.activeRequestMungeSet]; + + function munge(obj) { + if (Object.prototype.toString.call(obj) === '[object Array]') { + // an array - munge each element of it + var newobj = []; + for (var i in obj) { + newobj[i] = munge(obj[i]); + } + return newobj; + } else if (typeof obj === 'object') { + // an object: munge each property name, and pass the value through the munge process + var newobj = Object(); + for (var p in obj) { + var m = activeMunge[p]; + if (m === undefined) { + console.error('Error: failed to find munge for object property '+p); + newobj[p] = obj[p]; + } else { + // rename the property + newobj[m] = munge(obj[p]); + } + } + return newobj; + } else { + // neither an array or an object - so must be a simple value. return it unmodified + return obj; + } + }; + + var newdata = munge(data); + return newdata; +} diff --git a/code/portal_detail_display.js b/code/portal_detail_display.js index 47967e57..03820882 100644 --- a/code/portal_detail_display.js +++ b/code/portal_detail_display.js @@ -31,7 +31,8 @@ window.renderPortalDetails = function(guid) { var playerText = player ? ['owner', player] : null; var time = d.captured - ? '' + ? '' + unixTimeToString(d.captured.capturedTime) + '' : null; var sinceText = time ? ['since', time] : null; @@ -46,6 +47,26 @@ window.renderPortalDetails = function(guid) { linkedFields, getAttackApGainText(d), getHackDetailsText(d), getMitigationText(d) ]; + + // artifact details + + //niantic hard-code the fact it's just jarvis shards/targets - so until more examples exist, we'll do the same + //(at some future point we can iterate through all the artifact types and add rows as needed) + var jarvisArtifact = artifact.getPortalData (guid, 'jarvis'); + if (jarvisArtifact) { + // the genFourColumnTable function below doesn't handle cases where one column is null and the other isn't - so default to *someting* in both columns + var target = ['',''], shards = ['shards','(none)']; + if (jarvisArtifact.target) { + target = ['target', ''+(jarvisArtifact.target==TEAM_RES?'Resistance':'Enlightened')+'']; + } + if (jarvisArtifact.fragments) { + shards = [jarvisArtifact.fragments.length>1?'shards':'shard', '#'+jarvisArtifact.fragments.join(', #')]; + } + + randDetails.push (target, shards); + } + + randDetails = '' + genFourColumnTable(randDetails) + '
'; var resoDetails = '' + getResonatorDetails(d) + '
'; diff --git a/code/utils_misc.js b/code/utils_misc.js index 4e61790f..63c9fb4c 100644 --- a/code/utils_misc.js +++ b/code/utils_misc.js @@ -2,8 +2,8 @@ window.aboutIITC = function() { var v = (script_info.script && script_info.script.version || script_info.dateTimeVersion) + ' ['+script_info.buildName+']'; - if (typeof android !== 'undefined' && android && android.getVersionCode) { - v += '[IITC Mobile '+android.getVersionCode()+']'; + if (typeof android !== 'undefined' && android && android.getVersionName) { + v += '[IITC Mobile '+android.getVersionName()+']'; } var plugins = '