diff --git a/plugins/ap-list.css b/plugins/ap-list.css
index 1ccb441c..c54365f6 100644
--- a/plugins/ap-list.css
+++ b/plugins/ap-list.css
@@ -46,11 +46,16 @@
}
.ap-list-td-link-eny {
- width: 80%;
+ width: 70%;
}
.ap-list-td-link-frd {
- width: 85%;
+ width: 75%;
+}
+
+.ap-list-td-ap {
+ width: 18%;
+ white-space:nowrap;
}
.ap-list-checkbox-outer {
diff --git a/plugins/ap-list.user.js b/plugins/ap-list.user.js
index 7de36dee..c6f91392 100644
--- a/plugins/ap-list.user.js
+++ b/plugins/ap-list.user.js
@@ -1,7 +1,7 @@
// ==UserScript==
// @id iitc-plugin-ap-list@xelio
// @name IITC plugin: AP List
-// @version 0.4.2.@@DATETIMEVERSION@@
+// @version 0.4.3.@@DATETIMEVERSION@@
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
// @updateURL @@UPDATEURL@@
// @downloadURL @@DOWNLOADURL@@
@@ -33,6 +33,7 @@ window.plugin.apList.playerApGainFunc = new Array(2);
window.plugin.apList.topMaxCount = 10;
window.plugin.apList.sideLabelClass = {};
+window.plugin.apList.tableColumns = new Array(2);
window.plugin.apList.useCachedPortals = false;
window.plugin.apList.cacheBounds;
@@ -52,29 +53,51 @@ window.plugin.apList.handleUpdate = function() {
// Generate html table from top portals
window.plugin.apList.updatePortalTable = function(side) {
- var displayEnemy = (plugin.apList.displaySide === window.plugin.apList.SIDE_ENEMY);
-
- var content = '
';
+ var table = ''
+ + ''
+ + plugin.apList.tableHeaderBuilder(side)
+ + '';
+
for(var i = 0; i < plugin.apList.topMaxCount; i++) {
var portal = plugin.apList.sortedPortals[side][i];
- content += '';
- // Only enemy portal list will display destroy checkbox
- if(displayEnemy) {
- content += ''
- + (portal ? plugin.apList.getPortalDestroyCheckbox(portal) : ' ')
- + ' | ';
- }
- content += ''
- + (portal ? plugin.apList.getPortalLink(portal) : ' ')
- + ' | '
- + ''
- + (portal ? plugin.apList.getPortalApText(portal) : ' ')
- + ' | '
- + '
';
+ table += ''
+ + plugin.apList.tableRowBuilder(side, portal)
+ + '';
}
- content += "
";
- $('div#ap-list-table').html(content);
+
+ table += "
";
+ $('div#ap-list-table').html(table);
+}
+
+window.plugin.apList.tableHeaderBuilder = function(side) {
+ var headerRow = '';
+
+ $.each(plugin.apList.tableColumns[side], function(ind, column) {
+ var cssClass = column.headerTooltip ? (column.cssClass + ' help') : column.cssClass;
+ var title = column.headerTooltip ? column.headerTooltip : '';
+ headerRow += ''
+ + column.header
+ + ' | ';
+ });
+
+ headerRow += '
';
+ return headerRow;
+}
+
+window.plugin.apList.tableRowBuilder = function(side,portal) {
+ var row = "";
+
+ $.each(plugin.apList.tableColumns[side], function(ind, column) {
+ var content = portal ? column.contentFunction(portal) : ' ';
+ row += ''
+ + content
+ + ' | ';
+ });
+
+ row += '
';
+ return row;
}
window.plugin.apList.getPortalDestroyCheckbox = function(portal) {
@@ -141,6 +164,19 @@ window.plugin.apList.getPortalApTitle = function(portal) {
return t;
}
+window.plugin.apList.getPortalEffectiveLvText = function(portal) {
+ var title = plugin.apList.getPortalEffectiveLvTitle(portal);
+ return '' + portal.effectiveLevel.effectiveLevel + '
';
+}
+
+window.plugin.apList.getPortalEffectiveLvTitle = function(portal) {
+ var t = 'Effective energy:\t' + portal.effectiveLevel.effectiveEnergy + '\n'
+ + 'Effect of Shields:\t' + portal.effectiveLevel.effectOfShields + '\n'
+ + 'Effect of resos dist:\t' + portal.effectiveLevel.effectOfResoDistance + '\n'
+ + 'Origin Level:\t' + portal.effectiveLevel.originLevel;
+ return t;
+}
+
// portal link - single click: select portal
// double click: zoom to and select portal
// hover: show address
@@ -195,6 +231,7 @@ window.plugin.apList.updateSortedPortals = function() {
var getApGainFunc = plugin.apList.playerApGainFunc[side];
// Assign playerApGain and guid to cachedPortal
cachedPortal.playerApGain = getApGainFunc(portal);
+ cachedPortal.effectiveLevel = plugin.apList.getEffectiveLevel(portal);
cachedPortal.guid = value.options.guid;
}
plugin.apList.cachedPortals[key] = cachedPortal;
@@ -408,6 +445,84 @@ window.plugin.apList.getAttackApGain = function(d) {
}
}
+window.plugin.apList.getEffectiveLevel = function(portal) {
+ var effectiveEnergy = 0;
+ var effectiveLevel = 0;
+
+ var resosStats = plugin.apList.getResonatorsStats(portal);
+ var shieldsMitigation = plugin.apList.getShieldsMitigation(portal);
+
+ // Calculate effective energy
+
+ // Portal damage = Damage output * (1 - shieldsMitigation / 100)
+ // Reverse it and we get
+ // Damage output = Portal damage * (100 / (100 - shieldsMitigation))
+ var effectOfShields = 100 / (100 - shieldsMitigation);
+ // If avgResoDistance is 0, 8 resonators in the same place and can be treated as 1 resonator.
+ // So the minimum effect of resonator distance is 1/8
+ var effectOfResoDistance = (1 + (resosStats.avgResoDistance / HACK_RANGE) * 7 ) / 8;
+
+ effectiveEnergy = resosStats.currentEnergy * effectOfShields * effectOfResoDistance;
+
+ // Calculate effective level
+ for(var i = MAX_PORTAL_LEVEL; i >= 0; i--) {
+ var baseLevel = i;
+ var baseLevelEnergy = RESO_NRG[baseLevel] * 8;
+ if(effectiveEnergy >= baseLevelEnergy) {
+ var energyToNextLevel = baseLevel === MAX_PORTAL_LEVEL
+ ? baseLevelEnergy - RESO_NRG[MAX_PORTAL_LEVEL - 1] * 8 // Extrapolate
+ : RESO_NRG[baseLevel + 1] * 8 - baseLevelEnergy; // Interpolate
+
+ var additionalLevel = (effectiveEnergy - baseLevelEnergy) / energyToNextLevel;
+ effectiveLevel = baseLevel + additionalLevel;
+ break;
+ }
+ }
+
+ // Account for damage do to player by portal
+ var portalLevel = parseInt(getPortalLevel(portal));
+ if(effectiveLevel < portalLevel) {
+ var energyPect = resosStats.currentEnergy / resosStats.totalEnergy;
+ effectiveLevel = effectiveLevel * (1-energyPect) + portalLevel * energyPect;
+ }
+
+ return {
+ effectiveLevel: effectiveLevel.toFixed(1),
+ effectiveEnergy: parseInt(effectiveEnergy),
+ effectOfShields: effectOfShields.toFixed(2),
+ effectOfResoDistance: effectOfResoDistance.toFixed(2),
+ originLevel: portalLevel
+ };
+}
+
+window.plugin.apList.getResonatorsStats = function(portal) {
+ var totalEnergy = 0;
+ var currentEnergy = 0;
+ var avgResoDistance = 0;
+
+ $.each(portal.resonatorArray.resonators, function(ind, reso) {
+ if (!reso)
+ return true;
+ totalEnergy += RESO_NRG[reso.level];
+ currentEnergy += reso.energyTotal;
+ avgResoDistance += (reso.distanceToPortal / 8);
+ });
+ return {
+ totalEnergy: totalEnergy,
+ currentEnergy: currentEnergy,
+ avgResoDistance: avgResoDistance};
+}
+
+window.plugin.apList.getShieldsMitigation = function(portal) {
+ var shieldsMitigation = 0;
+ $.each(portal.portalV2.linkedModArray, function(ind, mod) {
+ if(!mod)
+ return true;
+ shieldsMitigation += parseInt(mod.stats.MITIGATION);
+ });
+ return shieldsMitigation;
+}
+
window.plugin.apList.selectPortal = function(guid) {
renderPortalDetails(guid);
plugin.apList.setPortalLocationIndicator(guid);
@@ -504,6 +619,52 @@ window.plugin.apList.setupVar = function() {
= "#ap-list-eny";
}
+// Setup table columns for header builder and row builder
+window.plugin.apList.setupTableColumns = function() {
+ var enemyColumns = new Array();
+ var friendlyColumns = new Array();
+
+ // AP and Eff. LV columns are same in enemy and friendly table
+ var apColumn = {
+ header: 'AP',
+ cssClass: 'ap-list-td-ap',
+ contentFunction: plugin.apList.getPortalApText
+ };
+ var effectiveLevelColumn = {
+ header: 'EL',
+ headerTooltip: 'Effective Level',
+ cssClass: 'ap-list-td-eff-lv',
+ contentFunction: plugin.apList.getPortalEffectiveLvText
+ };
+
+ // Columns: Checkbox | Portal | AP | Eff. LV
+ enemyColumns.push({
+ header: '',
+ headerTooltip: 'Select checkbox to \nsimulating destruction',
+ cssClass: 'ap-list-td-checkbox',
+ contentFunction: plugin.apList.getPortalDestroyCheckbox
+ });
+ enemyColumns.push({
+ header: 'Portal',
+ cssClass: 'ap-list-td-link ap-list-td-link-eny',
+ contentFunction: plugin.apList.getPortalLink
+ });
+ enemyColumns.push(apColumn);
+ enemyColumns.push(effectiveLevelColumn);
+
+ // Columns: Portal | AP | Eff. LV
+ friendlyColumns.push({
+ header: 'Portal',
+ cssClass: 'ap-list-td-link ap-list-td-link-frd',
+ contentFunction: plugin.apList.getPortalLink
+ });
+ friendlyColumns.push(apColumn);
+ friendlyColumns.push(effectiveLevelColumn);
+
+ plugin.apList.tableColumns[plugin.apList.SIDE_ENEMY] = enemyColumns;
+ plugin.apList.tableColumns[plugin.apList.SIDE_FRIENDLY] = friendlyColumns;
+}
+
window.plugin.apList.setupCSS = function() {
$("