366 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			366 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // PORTAL DETAILS TOOLS //////////////////////////////////////////////
 | |
| // hand any of these functions the details-hash of a portal, and they
 | |
| // will return useful, but raw data.
 | |
| 
 | |
| // returns a float. Displayed portal level is always rounded down from
 | |
| // that value.
 | |
| window.getPortalLevel = function(d) {
 | |
|   var lvl = 0;
 | |
|   var hasReso = false;
 | |
|   $.each(d.resonators, function(ind, reso) {
 | |
|     if(!reso) return true;
 | |
|     lvl += parseInt(reso.level);
 | |
|     hasReso = true;
 | |
|   });
 | |
|   return hasReso ? Math.max(1, lvl/8) : 0;
 | |
| }
 | |
| 
 | |
| window.getTotalPortalEnergy = function(d) {
 | |
|   var nrg = 0;
 | |
|   $.each(d.resonators, function(ind, reso) {
 | |
|     if(!reso) return true;
 | |
|     var level = parseInt(reso.level);
 | |
|     var max = RESO_NRG[level];
 | |
|     nrg += max;
 | |
|   });
 | |
|   return nrg;
 | |
| }
 | |
| 
 | |
| // For backwards compatibility
 | |
| window.getPortalEnergy = window.getTotalPortalEnergy;
 | |
| 
 | |
| window.getCurrentPortalEnergy = function(d) {
 | |
|   var nrg = 0;
 | |
|   $.each(d.resonators, function(ind, reso) {
 | |
|     if(!reso) return true;
 | |
|     nrg += parseInt(reso.energy);
 | |
|   });
 | |
|   return nrg;
 | |
| }
 | |
| 
 | |
| window.getPortalRange = function(d) {
 | |
|   // formula by the great gals and guys at
 | |
|   // http://decodeingress.me/2012/11/18/ingress-portal-levels-and-link-range/
 | |
| 
 | |
|   var lvl = 0;
 | |
|   var resoMissing = false;
 | |
|   // currently we get a short resonator array when some are missing
 | |
|   if (d.resonators.length < 8) {
 | |
|     resoMissing = true;
 | |
|   }
 | |
|   // but in the past we used to always get an array of 8, but will 'null' objects for some entries. maybe that will return?
 | |
|   $.each(d.resonators, function(ind, reso) {
 | |
|     if(!reso) {
 | |
|       resoMissing = true;
 | |
|       return;
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   var range = {
 | |
|     base: 160*Math.pow(getPortalLevel(d), 4),
 | |
|     boost: getLinkAmpRangeBoost(d)
 | |
|   };
 | |
| 
 | |
|   range.range = range.boost * range.base;
 | |
|   range.isLinkable = !resoMissing;
 | |
| 
 | |
|   return range;
 | |
| }
 | |
| 
 | |
| window.getLinkAmpRangeBoost = function(d) {
 | |
|   // additional range boost calculation
 | |
| 
 | |
|   // link amps scale: first is full, second a quarter, the last two an eighth
 | |
|   var scale = [1.0, 0.25, 0.125, 0.125];
 | |
| 
 | |
|   var boost = 0.0;  // initial boost is 0.0 (i.e. no boost over standard range)
 | |
| 
 | |
|   var linkAmps = getPortalModsByType(d, 'LINK_AMPLIFIER');
 | |
| 
 | |
|   linkAmps.forEach(function(mod, i) {
 | |
|     // link amp stat LINK_RANGE_MULTIPLIER is 2000 for rare, and gives 2x boost to the range
 | |
|     // and very-rare is 7000 and gives 7x the range
 | |
|     var baseMultiplier = mod.stats.LINK_RANGE_MULTIPLIER/1000;
 | |
|     boost += baseMultiplier*scale[i];
 | |
|   });
 | |
| 
 | |
|   return (linkAmps.length > 0) ? boost : 1.0;
 | |
| }
 | |
| 
 | |
| 
 | |
| window.getAttackApGain = function(d,fieldCount,linkCount) {
 | |
|   if (!fieldCount) fieldCount = 0;
 | |
| 
 | |
|   var resoCount = 0;
 | |
|   var maxResonators = MAX_RESO_PER_PLAYER.slice(0);
 | |
|   var curResonators = [ 0, 0, 0, 0, 0, 0, 0, 0, 0];
 | |
| 
 | |
|   for(var n = PLAYER.level + 1; n < 9; n++) {
 | |
|     maxResonators[n] = 0;
 | |
|   }
 | |
|   $.each(d.resonators, function(ind, reso) {
 | |
|     if(!reso)
 | |
|       return true;
 | |
|     resoCount += 1;
 | |
|     var reslevel=parseInt(reso.level);
 | |
|     if(reso.owner === PLAYER.nickname) {
 | |
|       if(maxResonators[reslevel] > 0) {
 | |
|         maxResonators[reslevel] -= 1;
 | |
|       }
 | |
|     } else {
 | |
|       curResonators[reslevel] += 1;
 | |
|     }
 | |
|   });
 | |
| 
 | |
| 
 | |
|   var resoAp = resoCount * DESTROY_RESONATOR;
 | |
|   var linkAp = linkCount * DESTROY_LINK;
 | |
|   var fieldAp = fieldCount * DESTROY_FIELD;
 | |
|   var destroyAp = resoAp + linkAp + fieldAp;
 | |
|   var captureAp = CAPTURE_PORTAL + 8 * DEPLOY_RESONATOR + COMPLETION_BONUS;
 | |
|   var enemyAp = destroyAp + captureAp;
 | |
|   var deployCount = 8 - resoCount;
 | |
|   var completionAp = (deployCount > 0) ? COMPLETION_BONUS : 0;
 | |
|   var upgradeCount = 0;
 | |
|   var upgradeAvailable = maxResonators[8];
 | |
|   for(var n = 7; n >= 0; n--) {
 | |
|     upgradeCount += curResonators[n];
 | |
|     if(upgradeAvailable < upgradeCount) {
 | |
|         upgradeCount -= (upgradeCount - upgradeAvailable);
 | |
|     }
 | |
|     upgradeAvailable += maxResonators[n];
 | |
|   }
 | |
|   var friendlyAp = deployCount * DEPLOY_RESONATOR + upgradeCount * UPGRADE_ANOTHERS_RESONATOR + completionAp;
 | |
|   return {
 | |
|     friendlyAp: friendlyAp,
 | |
|     deployCount: deployCount,
 | |
|     upgradeCount: upgradeCount,
 | |
|     enemyAp: enemyAp,
 | |
|     destroyAp: destroyAp,
 | |
|     resoAp: resoAp,
 | |
|     captureAp: captureAp
 | |
|   };
 | |
| }
 | |
| 
 | |
| //This function will return the potential level a player can upgrade it to
 | |
| window.potentialPortalLevel = function(d) {
 | |
|   var current_level = getPortalLevel(d);
 | |
|   var potential_level = current_level;
 | |
|   
 | |
|   if(PLAYER.team === d.team) {
 | |
|     var resonators_on_portal = d.resonators;
 | |
|     var resonator_levels = new Array();
 | |
|     // figure out how many of each of these resonators can be placed by the player
 | |
|     var player_resontators = new Array();
 | |
|     for(var i=1;i<=MAX_PORTAL_LEVEL; i++) {
 | |
|       player_resontators[i] = i > PLAYER.level ? 0 : MAX_RESO_PER_PLAYER[i];
 | |
|     }
 | |
|     $.each(resonators_on_portal, function(ind, reso) {
 | |
|       if(reso !== null && reso.owner === window.PLAYER.nickname) {
 | |
|         player_resontators[reso.level]--;
 | |
|       }
 | |
|       resonator_levels.push(reso === null ? 0 : reso.level);  
 | |
|     });
 | |
|     
 | |
|     resonator_levels.sort(function(a, b) {
 | |
|       return(a - b);
 | |
|     });
 | |
|     
 | |
|     // Max out portal
 | |
|     var install_index = 0;
 | |
|     for(var i=MAX_PORTAL_LEVEL;i>=1; i--) {
 | |
|       for(var install = player_resontators[i]; install>0; install--) {
 | |
|         if(resonator_levels[install_index] < i) {
 | |
|           resonator_levels[install_index] = i;
 | |
|           install_index++;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     //console.log(resonator_levels);
 | |
|     potential_level = resonator_levels.reduce(function(a, b) {return a + b;}) / 8;
 | |
|   }
 | |
|   return(potential_level);
 | |
| }
 | |
| 
 | |
| 
 | |
| window.fixPortalImageUrl = function(url) {
 | |
|   if (url) {
 | |
|     if (window.location.protocol === 'https:') {
 | |
|       url = url.indexOf('www.panoramio.com') !== -1
 | |
|             ? url.replace(/^http:\/\/www/, 'https://ssl').replace('small', 'medium')
 | |
|             : url.replace(/^http:\/\//, '//');
 | |
|     }
 | |
|     return url;
 | |
|   } else {
 | |
|     return DEFAULT_PORTAL_IMG;
 | |
|   }
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| window.getPortalModsByType = function(d, type) {
 | |
|   var mods = [];
 | |
| 
 | |
|   var typeToStat = {
 | |
|     RES_SHIELD: 'MITIGATION',
 | |
|     FORCE_AMP: 'FORCE_AMPLIFIER',
 | |
|     TURRET: 'HIT_BONUS',  // and/or ATTACK_FREQUENCY??
 | |
|     HEATSINK: 'HACK_SPEED',
 | |
|     MULTIHACK: 'BURNOUT_INSULATION',
 | |
|     LINK_AMPLIFIER: 'LINK_RANGE_MULTIPLIER',
 | |
|     ULTRA_LINK_AMP: 'OUTGOING_LINKS_BONUS', // and/or LINK_DEFENSE_BOOST??
 | |
|   };
 | |
| 
 | |
|   var stat = typeToStat[type];
 | |
| 
 | |
|   $.each(d.mods || [], function(i,mod) {
 | |
|     if (mod && mod.stats.hasOwnProperty(stat)) mods.push(mod);
 | |
|   });
 | |
| 
 | |
| 
 | |
|   // sorting mods by the stat keeps code simpler, when calculating combined mod effects
 | |
|   mods.sort (function(a,b) {
 | |
|     return b.stats[stat] - a.stats[stat];
 | |
|   });
 | |
| 
 | |
|   return mods;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| window.getPortalShieldMitigation = function(d) {
 | |
|   var shields = getPortalModsByType(d, 'RES_SHIELD');
 | |
| 
 | |
|   var mitigation = 0;
 | |
|   $.each(shields, function(i,s) {
 | |
|     mitigation += parseInt(s.stats.MITIGATION);
 | |
|   });
 | |
| 
 | |
|   return mitigation;
 | |
| }
 | |
| 
 | |
| window.getPortalLinksMitigation = function(linkCount) {
 | |
|   var mitigation = Math.round(400/9*Math.atan(linkCount/Math.E));
 | |
|   return mitigation;
 | |
| }
 | |
| 
 | |
| window.getPortalMitigationDetails = function(d,linkCount) {
 | |
|   var mitigation = {
 | |
|     shields: getPortalShieldMitigation(d),
 | |
|     links: getPortalLinksMitigation(linkCount)
 | |
|   };
 | |
| 
 | |
|   // mitigation is limited to 95% (as confirmed by Brandon Badger on G+)
 | |
|   mitigation.total = Math.min(95, mitigation.shields+mitigation.links);
 | |
| 
 | |
|   mitigation.excess = (mitigation.shields+mitigation.links) - mitigation.total;
 | |
| 
 | |
|   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) {
 | |
| 
 | |
|   var heatsinks = getPortalModsByType(d, 'HEATSINK');
 | |
|   var multihacks = getPortalModsByType(d, 'MULTIHACK');
 | |
| 
 | |
|   // first mod of type is fully effective, the others are only 50% effective
 | |
|   var effectivenessReduction = [ 1, 0.5, 0.5, 0.5 ];
 | |
| 
 | |
|   var cooldownTime = 300; // 5 mins - 300 seconds 
 | |
| 
 | |
|   $.each(heatsinks, function(index,mod) {
 | |
|     var hackSpeed = parseInt(mod.stats.HACK_SPEED)/1000000;
 | |
|     cooldownTime = Math.round(cooldownTime * (1 - hackSpeed * effectivenessReduction[index]));
 | |
|   });
 | |
| 
 | |
|   var numHacks = 4; // default hacks
 | |
| 
 | |
|   $.each(multihacks, function(index,mod) {
 | |
|     var extraHacks = parseInt(mod.stats.BURNOUT_INSULATION);
 | |
|     numHacks = numHacks + (extraHacks * effectivenessReduction[index]);
 | |
|   });
 | |
| 
 | |
|   return {cooldown: cooldownTime, hacks: numHacks, burnout: cooldownTime*(numHacks-1)};
 | |
| }
 | |
| 
 | |
| // given a detailed portal structure, return summary portal data, as seen in the map tile data
 | |
| window.getPortalSummaryData = function(d) {
 | |
| 
 | |
|   // NOTE: the summary data reports unclaimed portals as level 1 - not zero as elsewhere in IITC
 | |
|   var level = parseInt(getPortalLevel(d));
 | |
|   if (level == 0) level = 1; //niantic returns neutral portals as level 1, not 0 as used throughout IITC elsewhere
 | |
| 
 | |
|   var resCount = 0;
 | |
|   if (d.resonators) {
 | |
|     for (var x in d.resonators) {
 | |
|       if (d.resonators[x]) resCount++;
 | |
|     }
 | |
|   }
 | |
|   var maxEnergy = getTotalPortalEnergy(d);
 | |
|   var curEnergy = getCurrentPortalEnergy(d);
 | |
|   var health = maxEnergy>0 ? parseInt(curEnergy/maxEnergy*100) : 0;
 | |
| 
 | |
|   return {
 | |
|     level: level,
 | |
|     title: d.title,
 | |
|     image: d.image,
 | |
|     resCount: resCount,
 | |
|     latE6: d.latE6,
 | |
|     health: health,
 | |
|     team: d.team,
 | |
|     lngE6: d.lngE6,
 | |
|     type: 'portal'
 | |
|   };
 | |
| }
 | |
| 
 | |
| window.getPortalAttackValues = function(d) {
 | |
|   var forceamps = getPortalModsByType(d, 'FORCE_AMP');
 | |
|   var turrets = getPortalModsByType(d, 'TURRET');
 | |
| 
 | |
|   // at the time of writing, only rare force amps and turrets have been seen in the wild, so there's a little guesswork
 | |
|   // at how the stats work and combine
 | |
|   // algorithm has been compied from getLinkAmpRangeBoost
 | |
|   // FIXME: only extract stats and put the calculation in a method to be used for link range, force amplifier and attack
 | |
|   // frequency
 | |
|   // note: scanner shows rounded values (adding a second FA shows: 2.5x+0.2x=2.8x, which should be 2.5x+0.25x=2.75x)
 | |
| 
 | |
|   // amplifier scale: first is full, second a quarter, the last two an eighth
 | |
|   var scale = [1.0, 0.25, 0.125, 0.125];
 | |
| 
 | |
|   var attackValues = {
 | |
|     hit_bonus: 0,
 | |
|     force_amplifier: 0,
 | |
|     attack_frequency: 0,
 | |
|   };
 | |
| 
 | |
|   forceamps.forEach(function(mod, i) {
 | |
|     // force amp stat FORCE_AMPLIFIER is 2000 for rare, and gives 2x boost to the range
 | |
|     var baseMultiplier = mod.stats.FORCE_AMPLIFIER / 1000;
 | |
|     attackValues.force_amplifier += baseMultiplier * scale[i];
 | |
|   });
 | |
| 
 | |
|   turrets.forEach(function(mod, i) {
 | |
|     // turret stat ATTACK_FREQUENCY is 2000 for rare, and gives 2x boost to the range
 | |
|     var baseMultiplier = mod.stats.ATTACK_FREQUENCY / 1000;
 | |
|     attackValues.attack_frequency += baseMultiplier * scale[i];
 | |
| 
 | |
|     attackValues.hit_bonus += mod.stats.HIT_BONUS / 10000;
 | |
|   });
 | |
| 
 | |
|   return attackValues;
 | |
| }
 | |
| 
 | |
| 
 |