diff --git a/config.py b/config.py
index dddd59edb..b515ae909 100644
--- a/config.py
+++ b/config.py
@@ -39,6 +39,7 @@ loggingLevel = None
logging_setup = None
cipher = None
clientHash = None
+experimentalFeatures = None
ESI_CACHE = 'esi_cache'
@@ -103,6 +104,7 @@ def defPaths(customSavePath=None):
global cipher
global clientHash
global version
+ global experimentalFeatures
pyfalog.debug("Configuring Pyfa")
@@ -168,6 +170,10 @@ def defPaths(customSavePath=None):
logPath = os.path.join(savePath, logFile)
+ experimentalFeatures = getattr(configforced, "experimentalFeatures", experimentalFeatures)
+ if experimentalFeatures is None:
+ experimentalFeatures = False
+
# DON'T MODIFY ANYTHING BELOW
import eos.config
diff --git a/eos/calc.py b/eos/calc.py
index 7896f65d4..02cbf5d18 100644
--- a/eos/calc.py
+++ b/eos/calc.py
@@ -18,6 +18,38 @@
# =============================================================================
+import math
+
+
+# Just copy-paste penalization chain calculation code (with some modifications,
+# as multipliers arrive in different form) in here to not make actual attribute
+# calculations slower than they already are due to extra function calls
+def calculateMultiplier(multipliers):
+ """
+ multipliers: dictionary in format:
+ {stacking group name: [(mult, resist attr ID), (mult, resist attr ID)]}
+ """
+ val = 1
+ for penalizedMultipliers in multipliers.values():
+ # A quick explanation of how this works:
+ # 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
+ l1 = [v[0] for v in penalizedMultipliers if v[0] > 1]
+ l2 = [v[0] for v in penalizedMultipliers if v[0] < 1]
+ # 2: The most significant bonuses take the smallest penalty,
+ # This means we'll have to sort
+ abssort = lambda _val: -abs(_val - 1)
+ l1.sort(key=abssort)
+ l2.sort(key=abssort)
+ # 3: The first module doesn't get penalized at all
+ # Any module after the first takes penalties according to:
+ # 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
+ for l in (l1, l2):
+ for i in range(len(l)):
+ bonus = l[i]
+ val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
+ return val
+
+
def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedRange=True):
"""Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
if distance is None:
@@ -31,3 +63,9 @@ def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedR
return 1
else:
return 0
+
+
+def calculateLockTime(srcScanRes, tgtSigRadius):
+ if not srcScanRes or not tgtSigRadius:
+ return None
+ return min(40000 / srcScanRes / math.asinh(tgtSigRadius) ** 2, 30 * 60)
diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py
index 4ca3e8294..66b43541f 100644
--- a/eos/saveddata/fit.py
+++ b/eos/saveddata/fit.py
@@ -21,13 +21,14 @@ import datetime
import time
from copy import deepcopy
from itertools import chain
-from math import asinh, log, sqrt
+from math import log, sqrt
from logbook import Logger
from sqlalchemy.orm import reconstructor, validates
import eos.db
from eos import capSim
+from eos.calc import calculateMultiplier, calculateLockTime
from eos.const import CalcType, FitSystemSecurity, FittingHardpoint, FittingModuleState, FittingSlot, ImplantLocation
from eos.effectHandlerHelpers import (
HandledBoosterList, HandledDroneCargoList, HandledImplantList,
@@ -1529,9 +1530,7 @@ class Fit:
def calculateLockTime(self, radius):
scanRes = self.ship.getModifiedItemAttr("scanResolution")
if scanRes is not None and scanRes > 0:
- # Yes, this function returns time in seconds, not miliseconds.
- # 40,000 is indeed the correct constant here.
- return min(40000 / scanRes / asinh(radius) ** 2, 30 * 60)
+ return calculateLockTime(srcScanRes=scanRes, tgtSigRadius=radius)
else:
return self.ship.getModifiedItemAttr("scanSpeed") / 1000.0
@@ -1626,6 +1625,22 @@ class Fit:
if ability.active:
yield fighter, ability
+ def getDampMultScanRes(self):
+ damps = []
+ for mod in self.activeModulesIter():
+ for effectName in ('remoteSensorDampFalloff', 'structureModuleEffectRemoteSensorDampener'):
+ if effectName in mod.item.effects:
+ damps.append((mod.getModifiedItemAttr('scanResolutionBonus'), 'default'))
+ if 'doomsdayAOEDamp' in mod.item.effects:
+ damps.append((mod.getModifiedItemAttr('scanResolutionBonus'), 'default'))
+ for drone in self.activeDronesIter():
+ if 'remoteSensorDampEntity' in drone.item.effects:
+ damps.extend(drone.amountActive * ((drone.getModifiedItemAttr('scanResolutionBonus'), 'default'),))
+ mults = {}
+ for strength, stackingGroup in damps:
+ mults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
+ return calculateMultiplier(mults)
+
def __deepcopy__(self, memo=None):
fitCopy = Fit()
# Character and owner are not copied
diff --git a/graphs/calc.py b/graphs/calc.py
index 27f40ce1b..9cb868eba 100644
--- a/graphs/calc.py
+++ b/graphs/calc.py
@@ -18,40 +18,9 @@
# =============================================================================
-import math
-
from service.settings import GraphSettings
-# Just copy-paste penalization chain calculation code (with some modifications,
-# as multipliers arrive in different form) in here to not make actual attribute
-# calculations slower than they already are due to extra function calls
-def calculateMultiplier(multipliers):
- """
- multipliers: dictionary in format:
- {stacking group name: [(mult, resist attr ID), (mult, resist attr ID)]}
- """
- val = 1
- for penalizedMultipliers in multipliers.values():
- # A quick explanation of how this works:
- # 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
- l1 = [v[0] for v in penalizedMultipliers if v[0] > 1]
- l2 = [v[0] for v in penalizedMultipliers if v[0] < 1]
- # 2: The most significant bonuses take the smallest penalty,
- # This means we'll have to sort
- abssort = lambda _val: -abs(_val - 1)
- l1.sort(key=abssort)
- l2.sort(key=abssort)
- # 3: The first module doesn't get penalized at all
- # Any module after the first takes penalties according to:
- # 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
- for l in (l1, l2):
- for i in range(len(l)):
- bonus = l[i]
- val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
- return val
-
-
def checkLockRange(src, distance):
if distance is None:
return True
diff --git a/graphs/data/__init__.py b/graphs/data/__init__.py
index e4207f1cf..abd740ea0 100644
--- a/graphs/data/__init__.py
+++ b/graphs/data/__init__.py
@@ -18,6 +18,9 @@
# =============================================================================
+import config as _config
+
+
from . import fitDamageStats
from . import fitEwarStats
from . import fitRemoteReps
@@ -25,4 +28,7 @@ from . import fitShieldRegen
from . import fitCapacitor
from . import fitMobility
from . import fitWarpTime
-from . import fitLockTime
+from . import fitLockTimeOutgoing
+
+if _config.experimentalFeatures:
+ from . import fitLockTimeIncoming
diff --git a/graphs/data/fitEwarStats/getter.py b/graphs/data/fitEwarStats/getter.py
index c1121d656..2bad90f3f 100644
--- a/graphs/data/fitEwarStats/getter.py
+++ b/graphs/data/fitEwarStats/getter.py
@@ -20,8 +20,8 @@
import math
-from eos.calc import calculateRangeFactor
-from graphs.calc import calculateMultiplier, checkLockRange, checkDroneControlRange
+from eos.calc import calculateMultiplier, calculateRangeFactor
+from graphs.calc import checkLockRange, checkDroneControlRange
from graphs.data.base import SmoothPointGetter
diff --git a/graphs/data/fitLockTime/__init__.py b/graphs/data/fitLockTimeIncoming/__init__.py
similarity index 91%
rename from graphs/data/fitLockTime/__init__.py
rename to graphs/data/fitLockTimeIncoming/__init__.py
index 13f22a3ae..4d33df5ad 100644
--- a/graphs/data/fitLockTime/__init__.py
+++ b/graphs/data/fitLockTimeIncoming/__init__.py
@@ -18,7 +18,7 @@
# =============================================================================
-from .graph import FitLockTimeGraph
+from .graph import FitLockTimeIncomingGraph
-FitLockTimeGraph.register()
+FitLockTimeIncomingGraph.register()
diff --git a/graphs/data/fitLockTimeIncoming/getter.py b/graphs/data/fitLockTimeIncoming/getter.py
new file mode 100644
index 000000000..b0decbb39
--- /dev/null
+++ b/graphs/data/fitLockTimeIncoming/getter.py
@@ -0,0 +1,39 @@
+# =============================================================================
+# Copyright (C) 2010 Diego Duclos
+#
+# This file is part of pyfa.
+#
+# pyfa is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pyfa is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyfa. If not, see .
+# =============================================================================
+
+
+from eos.calc import calculateLockTime
+from graphs.data.base import SmoothPointGetter
+
+
+class ScanRes2LockTimeGetter(SmoothPointGetter):
+
+ def _getCommonData(self, miscParams, src, tgt):
+ if miscParams['applyDamps']:
+ scanResMult = src.item.getDampMultScanRes()
+ else:
+ scanResMult = 1
+ return {'scanResMult': scanResMult}
+
+ def _calculatePoint(self, x, miscParams, src, tgt, commonData):
+ scanRes = x
+ time = calculateLockTime(
+ srcScanRes=scanRes * commonData['scanResMult'],
+ tgtSigRadius=src.item.ship.getModifiedItemAttr('signatureRadius'))
+ return time
diff --git a/graphs/data/fitLockTimeIncoming/graph.py b/graphs/data/fitLockTimeIncoming/graph.py
new file mode 100644
index 000000000..f177e8bf5
--- /dev/null
+++ b/graphs/data/fitLockTimeIncoming/graph.py
@@ -0,0 +1,40 @@
+# =============================================================================
+# Copyright (C) 2010 Diego Duclos
+#
+# This file is part of pyfa.
+#
+# pyfa is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pyfa is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyfa. If not, see .
+# =============================================================================
+
+
+import math
+
+from graphs.data.base import FitGraph, XDef, YDef, Input, InputCheckbox
+from .getter import ScanRes2LockTimeGetter
+
+
+class FitLockTimeIncomingGraph(FitGraph):
+
+ # UI stuff
+ internalName = 'lockTimeIncomingGraph'
+ name = 'Lock Time (Incoming)'
+ xDefs = [XDef(handle='scanRes', unit='mm', label='Scan Resolution', mainInput=('scanRes', 'mm'))]
+ yDefs = [YDef(handle='time', unit='s', label='Lock time')]
+ inputs = [Input(handle='scanRes', unit='mm', label='Scan Resolution', iconID=74, defaultValue=None, defaultRange=(100, 1000))]
+ checkboxes = [InputCheckbox(handle='applyDamps', label='Apply sensor dampeners', defaultValue=True)]
+ srcExtraCols = ('SigRadius', 'Damp ScanRes')
+
+ # Calculation stuff
+ _limiters = {'scanRes': lambda src, tgt: (1, math.inf)}
+ _getters = {('scanRes', 'time'): ScanRes2LockTimeGetter}
diff --git a/graphs/data/fitLockTimeOutgoing/__init__.py b/graphs/data/fitLockTimeOutgoing/__init__.py
new file mode 100644
index 000000000..8713af700
--- /dev/null
+++ b/graphs/data/fitLockTimeOutgoing/__init__.py
@@ -0,0 +1,24 @@
+# =============================================================================
+# Copyright (C) 2010 Diego Duclos
+#
+# This file is part of pyfa.
+#
+# pyfa is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pyfa is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyfa. If not, see .
+# =============================================================================
+
+
+from .graph import FitLockTimeOutgoingGraph
+
+
+FitLockTimeOutgoingGraph.register()
diff --git a/graphs/data/fitLockTime/getter.py b/graphs/data/fitLockTimeOutgoing/getter.py
similarity index 100%
rename from graphs/data/fitLockTime/getter.py
rename to graphs/data/fitLockTimeOutgoing/getter.py
diff --git a/graphs/data/fitLockTime/graph.py b/graphs/data/fitLockTimeOutgoing/graph.py
similarity index 91%
rename from graphs/data/fitLockTime/graph.py
rename to graphs/data/fitLockTimeOutgoing/graph.py
index 66199fc89..aa5732a98 100644
--- a/graphs/data/fitLockTime/graph.py
+++ b/graphs/data/fitLockTimeOutgoing/graph.py
@@ -20,14 +20,14 @@
import math
-from graphs.data.base import FitGraph, Input, XDef, YDef
+from graphs.data.base import FitGraph, XDef, YDef, Input
from .getter import TgtSigRadius2LockTimeGetter
-class FitLockTimeGraph(FitGraph):
+class FitLockTimeOutgoingGraph(FitGraph):
# UI stuff
- internalName = 'lockTimeGraph'
+ internalName = 'lockTimeOutgoingGraph'
name = 'Lock Time'
xDefs = [XDef(handle='tgtSigRad', unit='m', label='Target signature radius', mainInput=('tgtSigRad', 'm'))]
yDefs = [YDef(handle='time', unit='s', label='Lock time')]
diff --git a/graphs/wrapper.py b/graphs/wrapper.py
index 129522792..a24998369 100644
--- a/graphs/wrapper.py
+++ b/graphs/wrapper.py
@@ -18,11 +18,11 @@
# =============================================================================
+from eos.calc import calculateMultiplier
from eos.saveddata.damagePattern import DamagePattern
from eos.saveddata.fit import Fit
from eos.saveddata.targetProfile import TargetProfile
from service.const import TargetResistMode
-from .calc import calculateMultiplier
class BaseWrapper:
diff --git a/gui/builtinViewColumns/dampScanRes.py b/gui/builtinViewColumns/dampScanRes.py
new file mode 100644
index 000000000..0d6280d57
--- /dev/null
+++ b/gui/builtinViewColumns/dampScanRes.py
@@ -0,0 +1,60 @@
+# =============================================================================
+# Copyright (C) 2010 Diego Duclos
+#
+# This file is part of pyfa.
+#
+# pyfa is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pyfa is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyfa. If not, see .
+# =============================================================================
+
+# noinspection PyPackageRequirements
+import wx
+
+from eos.saveddata.fit import Fit
+from graphs.wrapper import BaseWrapper
+from gui.bitmap_loader import BitmapLoader
+from eos.utils.float import floatUnerr
+from gui.utils.numberFormatter import formatAmount
+from gui.viewColumn import ViewColumn
+
+
+class DampScanResColumn(ViewColumn):
+
+ name = 'Damp ScanRes'
+
+ def __init__(self, fittingView, params):
+ ViewColumn.__init__(self, fittingView)
+ self.imageId = fittingView.imageList.GetImageIndex(74, 'icons')
+ self.bitmap = BitmapLoader.getBitmap(74, 'icons')
+ self.mask = wx.LIST_MASK_IMAGE
+
+ def getText(self, stuff):
+ if isinstance(stuff, BaseWrapper):
+ stuff = stuff.item
+ mult = 1
+ if isinstance(stuff, Fit):
+ mult = floatUnerr(stuff.getDampMultScanRes())
+ if mult == 1:
+ text = ''
+ else:
+ text = '{}%'.format(formatAmount((mult - 1) * 100, 3, 0, 0, forceSign=True))
+ return text
+
+ def getImageId(self, stuff):
+ return -1
+
+ def getToolTip(self, stuff):
+ return 'Scan resolution dampening'
+
+
+DampScanResColumn.register()
diff --git a/gui/viewColumn.py b/gui/viewColumn.py
index 28c39bb3d..43bed258c 100644
--- a/gui/viewColumn.py
+++ b/gui/viewColumn.py
@@ -77,6 +77,7 @@ from gui.builtinViewColumns import ( # noqa: E402, F401
baseIcon,
baseName,
capacitorUse,
+ dampScanRes,
graphColor,
graphLightness,
graphLineStyle,