/**
* @file This file contains functions that handle the extraction and computation of raw data
* from portal details for various purposes.
* @module portal_info
*/
/**
* Calculates the displayed level of a portal, which is always rounded down from the actual float value.
*
* @function getPortalLevel
* @param {Object} d - The portal detail object containing resonator information.
* @returns {number} The calculated portal level.
*/
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;
};
/**
* Calculates the total energy capacity of a portal based on its resonators.
*
* @function getTotalPortalEnergy
* @param {Object} d - The portal detail object containing resonator information.
* @returns {number} The total energy capacity of the portal.
*/
window.getTotalPortalEnergy = function (d) {
var nrg = 0;
$.each(d.resonators, function (ind, reso) {
if (!reso) return true;
var level = parseInt(reso.level);
var max = window.RESO_NRG[level];
nrg += max;
});
return nrg;
};
// For backwards compatibility
window.getPortalEnergy = window.getTotalPortalEnergy;
/**
* Calculates the current energy of a portal based on its resonators.
*
* @function getCurrentPortalEnergy
* @param {Object} d - The portal detail object containing resonator information.
* @returns {number} The current energy of the portal.
*/
window.getCurrentPortalEnergy = function (d) {
var nrg = 0;
$.each(d.resonators, function (ind, reso) {
if (!reso) return true;
nrg += parseInt(reso.energy);
});
return nrg;
};
/**
* Calculates the range of a portal for creating links. The range depends on portal level and any installed Link Amps.
*
* @function getPortalRange
* @param {Object} d - The portal detail object containing details about the team and resonators.
* @returns {Object} An object containing the base range (`base`), boost multiplier (`boost`),
* total range after applying the boost (`range`),
* and a boolean indicating if the portal is linkable (`isLinkable`).
*/
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 range = {
base: window.teamStringToId(d.team) === window.TEAM_MAC ? window.LINK_RANGE_MAC[d.level + 1] : 160 * Math.pow(window.getPortalLevel(d), 4),
boost: window.getLinkAmpRangeBoost(d),
};
range.range = range.boost * range.base;
range.isLinkable = d.resCount === 8;
return range;
};
/**
* Calculates the boost in link range provided by installed Link Amps.
*
* @function getLinkAmpRangeBoost
* @param {Object} d - The portal detail object containing mod information.
* @returns {number} The total boost factor for the link range.
*/
window.getLinkAmpRangeBoost = function (d) {
if (window.teamStringToId(d.team) === window.TEAM_MAC) {
return 1.0;
}
// 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 = window.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;
};
/**
* Calculates the potential AP gain from attacking a portal.
*
* @function getAttackApGain
* @param {Object} d - The portal detail object containing resonator and ownership information.
* @param {number} fieldCount - The number of fields attached to the portal.
* @param {number} linkCount - The number of links attached to the portal.
* @returns {Object} An object detailing various components of AP gain, including totals for friendly and enemy factions.
*/
window.getAttackApGain = function (d, fieldCount, linkCount) {
if (!fieldCount) fieldCount = 0;
var resoCount = 0;
var maxResonators = window.MAX_RESO_PER_PLAYER.slice(0);
var curResonators = [0, 0, 0, 0, 0, 0, 0, 0, 0];
for (let n = window.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 === window.PLAYER.nickname) {
if (maxResonators[reslevel] > 0) {
maxResonators[reslevel] -= 1;
}
} else {
curResonators[reslevel] += 1;
}
});
var resoAp = resoCount * window.DESTROY_RESONATOR;
var linkAp = linkCount * window.DESTROY_LINK;
var fieldAp = fieldCount * window.DESTROY_FIELD;
var destroyAp = resoAp + linkAp + fieldAp;
var captureAp = window.CAPTURE_PORTAL + 8 * window.DEPLOY_RESONATOR + window.COMPLETION_BONUS;
var enemyAp = destroyAp + captureAp;
var deployCount = 8 - resoCount;
var completionAp = deployCount > 0 ? window.COMPLETION_BONUS : 0;
var upgradeCount = 0;
var upgradeAvailable = maxResonators[8];
for (let n = 7; n >= 0; n--) {
upgradeCount += curResonators[n];
if (upgradeAvailable < upgradeCount) {
upgradeCount -= upgradeCount - upgradeAvailable;
}
upgradeAvailable += maxResonators[n];
}
var friendlyAp = deployCount * window.DEPLOY_RESONATOR + upgradeCount * window.UPGRADE_ANOTHERS_RESONATOR + completionAp;
return {
friendlyAp: friendlyAp,
deployCount: deployCount,
upgradeCount: upgradeCount,
enemyAp: enemyAp,
destroyAp: destroyAp,
resoAp: resoAp,
captureAp: captureAp,
};
};
/**
* Corrects the portal image URL to match the current protocol (http/https).
*
* @function fixPortalImageUrl
* @param {string} url - The original image URL.
* @returns {string} The corrected image URL.
*/
window.fixPortalImageUrl = function (url) {
if (url) {
if (window.location.protocol === 'https:') {
url = url.replace(/^http:\/\//, '//');
}
return url;
} else {
return window.DEFAULT_PORTAL_IMG;
}
};
/**
* Returns a list of portal mods filtered by a specific type.
*
* @function getPortalModsByType
* @param {Object} d - The portal detail object containing mod information.
* @param {string} type - The type of mods to filter (e.g., 'RES_SHIELD', 'LINK_AMPLIFIER').
* @returns {Array} An array of mods matching the specified type.
*/
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 && Object.hasOwn(mod.stats, 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;
};
/**
* Calculates the total mitigation provided by shields installed on a portal.
*
* @function getPortalShieldMitigation
* @param {Object} d - The portal detail object containing mod information.
* @returns {number} The total mitigation value from all shields installed on the portal.
*/
window.getPortalShieldMitigation = function (d) {
var shields = window.getPortalModsByType(d, 'RES_SHIELD');
var mitigation = 0;
$.each(shields, function (i, s) {
mitigation += parseInt(s.stats.MITIGATION);
});
return mitigation;
};
/**
* Calculates the link defense boost provided by installed Ultra Link Amps.
*
* @function getPortalLinkDefenseBoost
* @param {Object} d - The portal detail object containing mod information.
* @returns {number} The total link defense boost factor.
*/
window.getPortalLinkDefenseBoost = function (d) {
var ultraLinkAmps = window.getPortalModsByType(d, 'ULTRA_LINK_AMP');
var linkDefenseBoost = 1;
$.each(ultraLinkAmps, function (index, ultraLinkAmp) {
linkDefenseBoost *= parseInt(ultraLinkAmp.stats.LINK_DEFENSE_BOOST) / 1000;
});
return Math.round(10 * linkDefenseBoost) / 10;
};
/**
* Calculates the additional mitigation provided by links attached to a portal.
*
* @function getPortalLinksMitigation
* @param {number} linkCount - The number of links attached to the portal.
* @returns {number} The additional mitigation value provided by the links.
*/
window.getPortalLinksMitigation = function (linkCount) {
var mitigation = Math.round((400 / 9) * Math.atan(linkCount / Math.E));
return mitigation;
};
/**
* Calculates detailed mitigation information for a portal, including contributions from shields and links.
*
* @function getPortalMitigationDetails
* @param {Object} d - The portal detail object containing mod and resonator information.
* @param {number} linkCount - The number of links attached to the portal.
* @returns {Object} An object detailing various components of mitigation.
*/
window.getPortalMitigationDetails = function (d, linkCount) {
var linkDefenseBoost = window.getPortalLinkDefenseBoost(d);
var mitigation = {
shields: window.getPortalShieldMitigation(d),
links: window.getPortalLinksMitigation(linkCount) * linkDefenseBoost,
linkDefenseBoost: linkDefenseBoost,
};
// mitigation is limited to 95% (as confirmed by Brandon Badger on G+)
mitigation.total = Math.min(95, mitigation.shields + mitigation.links);
var excess = mitigation.shields + mitigation.links - mitigation.total;
mitigation.excess = Math.round(10 * excess) / 10;
return mitigation;
};
/**
* Calculates the maximum number of outgoing links that can be created from a portal.
*
* @function getMaxOutgoingLinks
* @param {Object} d - The portal detail object containing mod information.
* @returns {number} The maximum number of outgoing links.
*/
window.getMaxOutgoingLinks = function (d) {
var linkAmps = window.getPortalModsByType(d, 'ULTRA_LINK_AMP');
var links = 8;
linkAmps.forEach(function (mod) {
links += parseInt(mod.stats.OUTGOING_LINKS_BONUS);
});
return links;
};
/**
* Calculates hack-related details of a portal, such as hack cooldown and burnout time.
*
* @function getPortalHackDetails
* @param {Object} d - The portal detail object containing mod information.
* @returns {Object} An object containing hack-related details like cooldown time, hack count, and burnout time.
*/
window.getPortalHackDetails = function (d) {
var heatsinks = window.getPortalModsByType(d, 'HEATSINK');
var multihacks = window.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 = window.BASE_HACK_COOLDOWN;
$.each(heatsinks, function (index, mod) {
var hackSpeed = parseInt(mod.stats.HACK_SPEED) / 1000000;
cooldownTime = Math.round(cooldownTime * (1 - hackSpeed * effectivenessReduction[index]));
});
var hackCount = window.BASE_HACK_COUNT; // default hacks
$.each(multihacks, function (index, mod) {
var extraHacks = parseInt(mod.stats.BURNOUT_INSULATION);
hackCount = hackCount + extraHacks * effectivenessReduction[index];
});
return { cooldown: cooldownTime, hacks: hackCount, burnout: cooldownTime * (hackCount - 1) };
};
/**
* Converts detailed portal information into a summary format similar to that seen in the map tile data.
*
* @function getPortalSummaryData
* @param {Object} d - The detailed portal data.
* @returns {Object} A summary of the portal data, including level, title, image, resonator count, health, and team.
*/
window.getPortalSummaryData = function (d) {
// NOTE: the summary data reports unclaimed portals as level 1 - not zero as elsewhere in IITC
var level = Math.floor(window.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 = window.getTotalPortalEnergy(d);
var curEnergy = window.getCurrentPortalEnergy(d);
var health = maxEnergy > 0 ? Math.floor((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',
};
};
/**
* Calculates various attack values of a portal, including hit bonus, force amplifier, and attack frequency.
*
* @function getPortalAttackValues
* @param {Object} d - The portal detail object containing mod information.
* @returns {Object} An object containing attack values such as hit bonus, force amplifier, and attack frequency.
*/
window.getPortalAttackValues = function (d) {
var forceamps = window.getPortalModsByType(d, 'FORCE_AMP');
var turrets = window.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;
};