diff --git a/gui/builtinGraphs/base/getter.py b/gui/builtinGraphs/base/getter.py
index 153661044..0ddbfc169 100644
--- a/gui/builtinGraphs/base/getter.py
+++ b/gui/builtinGraphs/base/getter.py
@@ -32,7 +32,7 @@ class PointGetter(metaclass=ABCMeta):
raise NotImplementedError
@abstractmethod
- def getPoint(self, mainParamRange, miscParams, fit, tgt):
+ def getPoint(self, mainParam, miscParams, fit, tgt):
raise NotImplementedError
diff --git a/gui/builtinGraphs/fitDamageStats/cache/__init__.py b/gui/builtinGraphs/fitDamageStats/cache/__init__.py
new file mode 100644
index 000000000..35a28399a
--- /dev/null
+++ b/gui/builtinGraphs/fitDamageStats/cache/__init__.py
@@ -0,0 +1,22 @@
+# =============================================================================
+# 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 .projected import ProjectedDataCache
+from .time import TimeCache
diff --git a/gui/builtinGraphs/fitDamageStats/projectedCache.py b/gui/builtinGraphs/fitDamageStats/cache/projected.py
similarity index 100%
rename from gui/builtinGraphs/fitDamageStats/projectedCache.py
rename to gui/builtinGraphs/fitDamageStats/cache/projected.py
diff --git a/gui/builtinGraphs/fitDamageStats/timeCache.py b/gui/builtinGraphs/fitDamageStats/cache/time.py
similarity index 100%
rename from gui/builtinGraphs/fitDamageStats/timeCache.py
rename to gui/builtinGraphs/fitDamageStats/cache/time.py
diff --git a/gui/builtinGraphs/fitDamageStats/calc/__init__.py b/gui/builtinGraphs/fitDamageStats/calc/__init__.py
new file mode 100644
index 000000000..ea1a4f707
--- /dev/null
+++ b/gui/builtinGraphs/fitDamageStats/calc/__init__.py
@@ -0,0 +1,18 @@
+# =============================================================================
+# 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 .
+# =============================================================================
diff --git a/gui/builtinGraphs/fitDamageStats/calc.py b/gui/builtinGraphs/fitDamageStats/calc/application.py
similarity index 67%
rename from gui/builtinGraphs/fitDamageStats/calc.py
rename to gui/builtinGraphs/fitDamageStats/calc/application.py
index c10844626..227097d1c 100644
--- a/gui/builtinGraphs/fitDamageStats/calc.py
+++ b/gui/builtinGraphs/fitDamageStats/calc/application.py
@@ -21,12 +21,89 @@
import math
from functools import lru_cache
+from eos.const import FittingHardpoint
from eos.saveddata.fit import Fit
+from gui.builtinGraphs.fitDamageStats.helper import getTgtRadius
from service.const import GraphDpsDroneMode
from service.settings import GraphSettings
-from .helper import getTgtMaxVelocity, getTgtSigRadius, getTgtRadius
+def getApplicationPerKey(fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
+ applicationMap = {}
+ for mod in fit.modules:
+ if not mod.isDealingDamage():
+ continue
+ if mod.hardpoint == FittingHardpoint.TURRET:
+ applicationMap[mod] = getTurretMult(
+ mod=mod,
+ fit=fit,
+ tgt=tgt,
+ atkSpeed=atkSpeed,
+ atkAngle=atkAngle,
+ distance=distance,
+ tgtSpeed=tgtSpeed,
+ tgtAngle=tgtAngle,
+ tgtSigRadius=tgtSigRadius)
+ elif mod.hardpoint == FittingHardpoint.MISSILE:
+ applicationMap[mod] = getLauncherMult(
+ mod=mod,
+ fit=fit,
+ distance=distance,
+ tgtSpeed=tgtSpeed,
+ tgtSigRadius=tgtSigRadius)
+ elif mod.item.group.name in ('Smart Bomb', 'Structure Area Denial Module'):
+ applicationMap[mod] = getSmartbombMult(
+ mod=mod,
+ distance=distance)
+ elif mod.item.group.name == 'Missile Launcher Bomb':
+ applicationMap[mod] = getBombMult(
+ mod=mod,
+ fit=fit,
+ tgt=tgt,
+ distance=distance,
+ tgtSigRadius=tgtSigRadius)
+ elif mod.item.group.name == 'Structure Guided Bomb Launcher':
+ applicationMap[mod] = getGuidedBombMult(
+ mod=mod,
+ fit=fit,
+ distance=distance,
+ tgtSigRadius=tgtSigRadius)
+ elif mod.item.group.name in ('Super Weapon', 'Structure Doomsday Weapon'):
+ applicationMap[mod] = getDoomsdayMult(
+ mod=mod,
+ tgt=tgt,
+ distance=distance,
+ tgtSigRadius=tgtSigRadius)
+ for drone in fit.drones:
+ if not drone.isDealingDamage():
+ continue
+ applicationMap[drone] = getDroneMult(
+ drone=drone,
+ fit=fit,
+ tgt=tgt,
+ atkSpeed=atkSpeed,
+ atkAngle=atkAngle,
+ distance=distance,
+ tgtSpeed=tgtSpeed,
+ tgtAngle=tgtAngle,
+ tgtSigRadius=tgtSigRadius)
+ for fighter in fit.fighters:
+ if not fighter.isDealingDamage():
+ continue
+ for ability in fighter.abilities:
+ if not ability.dealsDamage or not ability.active:
+ continue
+ applicationMap[(fighter, ability.effectID)] = getFighterAbilityMult(
+ fighter=fighter,
+ ability=ability,
+ fit=fit,
+ distance=distance,
+ tgtSpeed=tgtSpeed,
+ tgtSigRadius=tgtSigRadius)
+ return applicationMap
+
+
+# Item application multiplier calculation
def getTurretMult(mod, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
cth = _calcTurretChanceToHit(
atkSpeed=atkSpeed,
@@ -197,115 +274,7 @@ def getFighterAbilityMult(fighter, ability, fit, distance, tgtSpeed, tgtSigRadiu
return mult
-def getWebbedSpeed(fit, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighters, distance):
- # Can slow down non-immune fits and target profiles
- if isinstance(tgt, Fit) and tgt.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
- return currentUnwebbedSpeed
- maxUnwebbedSpeed = getTgtMaxVelocity(tgt)
- try:
- speedRatio = currentUnwebbedSpeed / maxUnwebbedSpeed
- except ZeroDivisionError:
- currentWebbedSpeed = 0
- else:
- appliedMultipliers = {}
- # Modules first, they are applied always the same way
- for wData in webMods:
- appliedBoost = wData.boost * _calcRangeFactor(
- atkOptimalRange=wData.optimal,
- atkFalloffRange=wData.falloff,
- distance=distance)
- if appliedBoost:
- appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
- maxWebbedSpeed = getTgtMaxVelocity(tgt, extraMultipliers=appliedMultipliers)
- currentWebbedSpeed = maxWebbedSpeed * speedRatio
- # Drones and fighters
- mobileWebs = []
- mobileWebs.extend(webFighters)
- # Drones have range limit
- if distance is None or distance <= fit.extraAttributes['droneControlRange']:
- mobileWebs.extend(webDrones)
- atkRadius = fit.ship.getModifiedItemAttr('radius')
- # As mobile webs either follow the target or stick to the attacking ship,
- # if target is within mobile web optimal - it can be applied unconditionally
- longEnoughMws = [mw for mw in mobileWebs if distance is None or distance <= mw.optimal - atkRadius + mw.radius]
- if longEnoughMws:
- for mwData in longEnoughMws:
- appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + mwData.boost / 100, mwData.resAttrID))
- mobileWebs.remove(mwData)
- maxWebbedSpeed = getTgtMaxVelocity(tgt, extraMultipliers=appliedMultipliers)
- currentWebbedSpeed = maxWebbedSpeed * speedRatio
- # Apply remaining webs, from fastest to slowest
- droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
- while mobileWebs:
- # Process in batches unified by speed to save up resources
- fastestMwSpeed = max(mobileWebs, key=lambda mw: mw.speed).speed
- fastestMws = [mw for mw in mobileWebs if mw.speed == fastestMwSpeed]
- for mwData in fastestMws:
- # Faster than target or set to follow it - apply full slowdown
- if (droneOpt == GraphDpsDroneMode.auto and mwData.speed >= currentWebbedSpeed) or droneOpt == GraphDpsDroneMode.followTarget:
- appliedMwBoost = mwData.boost
- # Otherwise project from the center of the ship
- else:
- if distance is None:
- rangeFactorDistance = None
- else:
- rangeFactorDistance = distance + atkRadius - mwData.radius
- appliedMwBoost = mwData.boost * _calcRangeFactor(
- atkOptimalRange=mwData.optimal,
- atkFalloffRange=mwData.falloff,
- distance=rangeFactorDistance)
- appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + appliedMwBoost / 100, mwData.resAttrID))
- mobileWebs.remove(mwData)
- maxWebbedSpeed = getTgtMaxVelocity(tgt, extraMultipliers=appliedMultipliers)
- currentWebbedSpeed = maxWebbedSpeed * speedRatio
- return currentWebbedSpeed
-
-
-def getTpMult(fit, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance):
- # Can blow non-immune fits and target profiles
- if isinstance(tgt, Fit) and tgt.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
- return 1
- untpedSig = getTgtSigRadius(tgt)
- # Modules
- appliedMultipliers = {}
- for tpData in tpMods:
- appliedBoost = tpData.boost * _calcRangeFactor(
- atkOptimalRange=tpData.optimal,
- atkFalloffRange=tpData.falloff,
- distance=distance)
- if appliedBoost:
- appliedMultipliers.setdefault(tpData.stackingGroup, []).append((1 + appliedBoost / 100, tpData.resAttrID))
- # Drones and fighters
- mobileTps = []
- mobileTps.extend(tpFighters)
- # Drones have range limit
- if distance is None or distance <= fit.extraAttributes['droneControlRange']:
- mobileTps.extend(tpDrones)
- droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
- atkRadius = fit.ship.getModifiedItemAttr('radius')
- for mtpData in mobileTps:
- # Faster than target or set to follow it - apply full TP
- if (droneOpt == GraphDpsDroneMode.auto and mtpData.speed >= tgtSpeed) or droneOpt == GraphDpsDroneMode.followTarget:
- appliedMtpBoost = mtpData.boost
- # Otherwise project from the center of the ship
- else:
- if distance is None:
- rangeFactorDistance = None
- else:
- rangeFactorDistance = distance + atkRadius - mtpData.radius
- appliedMtpBoost = mtpData.boost * _calcRangeFactor(
- atkOptimalRange=mtpData.optimal,
- atkFalloffRange=mtpData.falloff,
- distance=rangeFactorDistance)
- appliedMultipliers.setdefault(mtpData.stackingGroup, []).append((1 + appliedMtpBoost / 100, mtpData.resAttrID))
- tpedSig = getTgtSigRadius(tgt, extraMultipliers=appliedMultipliers)
- if tpedSig == math.inf and untpedSig == math.inf:
- return 1
- mult = tpedSig / untpedSig
- return mult
-
-
-# Turret-specific
+# Turret-specific math
@lru_cache(maxsize=50)
def _calcTurretMult(chanceToHit):
"""Calculate damage multiplier for turret-based weapons."""
@@ -356,7 +325,7 @@ def _calcTrackingFactor(atkTracking, atkOptimalSigRadius, angularSpeed, tgtSigRa
return 0.5 ** (((angularSpeed * atkOptimalSigRadius) / (atkTracking * tgtSigRadius)) ** 2)
-# Missile-specific
+# Missile-specific math
@lru_cache(maxsize=200)
def _calcMissileFactor(atkEr, atkEv, atkDrf, tgtSpeed, tgtSigRadius):
"""Missile application."""
@@ -379,7 +348,7 @@ def _calcAggregatedDrf(reductionFactor, reductionSensitivity):
return math.log(reductionFactor) / math.log(reductionSensitivity)
-# Generic
+# Generic math
def _calcRangeFactor(atkOptimalRange, atkFalloffRange, distance):
"""Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
if distance is None:
diff --git a/gui/builtinGraphs/fitDamageStats/calc/projected.py b/gui/builtinGraphs/fitDamageStats/calc/projected.py
new file mode 100644
index 000000000..8ca99d7e3
--- /dev/null
+++ b/gui/builtinGraphs/fitDamageStats/calc/projected.py
@@ -0,0 +1,135 @@
+# =============================================================================
+# 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 eos.saveddata.fit import Fit
+from gui.builtinGraphs.fitDamageStats.helper import getTgtMaxVelocity, getTgtSigRadius
+from service.const import GraphDpsDroneMode
+from service.settings import GraphSettings
+from .application import _calcRangeFactor
+
+
+def getWebbedSpeed(fit, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighters, distance):
+ # Can slow down non-immune fits and target profiles
+ if isinstance(tgt, Fit) and tgt.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
+ return currentUnwebbedSpeed
+ maxUnwebbedSpeed = getTgtMaxVelocity(tgt)
+ try:
+ speedRatio = currentUnwebbedSpeed / maxUnwebbedSpeed
+ except ZeroDivisionError:
+ currentWebbedSpeed = 0
+ else:
+ appliedMultipliers = {}
+ # Modules first, they are applied always the same way
+ for wData in webMods:
+ appliedBoost = wData.boost * _calcRangeFactor(
+ atkOptimalRange=wData.optimal,
+ atkFalloffRange=wData.falloff,
+ distance=distance)
+ if appliedBoost:
+ appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
+ maxWebbedSpeed = getTgtMaxVelocity(tgt, extraMultipliers=appliedMultipliers)
+ currentWebbedSpeed = maxWebbedSpeed * speedRatio
+ # Drones and fighters
+ mobileWebs = []
+ mobileWebs.extend(webFighters)
+ # Drones have range limit
+ if distance is None or distance <= fit.extraAttributes['droneControlRange']:
+ mobileWebs.extend(webDrones)
+ atkRadius = fit.ship.getModifiedItemAttr('radius')
+ # As mobile webs either follow the target or stick to the attacking ship,
+ # if target is within mobile web optimal - it can be applied unconditionally
+ longEnoughMws = [mw for mw in mobileWebs if distance is None or distance <= mw.optimal - atkRadius + mw.radius]
+ if longEnoughMws:
+ for mwData in longEnoughMws:
+ appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + mwData.boost / 100, mwData.resAttrID))
+ mobileWebs.remove(mwData)
+ maxWebbedSpeed = getTgtMaxVelocity(tgt, extraMultipliers=appliedMultipliers)
+ currentWebbedSpeed = maxWebbedSpeed * speedRatio
+ # Apply remaining webs, from fastest to slowest
+ droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
+ while mobileWebs:
+ # Process in batches unified by speed to save up resources
+ fastestMwSpeed = max(mobileWebs, key=lambda mw: mw.speed).speed
+ fastestMws = [mw for mw in mobileWebs if mw.speed == fastestMwSpeed]
+ for mwData in fastestMws:
+ # Faster than target or set to follow it - apply full slowdown
+ if (droneOpt == GraphDpsDroneMode.auto and mwData.speed >= currentWebbedSpeed) or droneOpt == GraphDpsDroneMode.followTarget:
+ appliedMwBoost = mwData.boost
+ # Otherwise project from the center of the ship
+ else:
+ if distance is None:
+ rangeFactorDistance = None
+ else:
+ rangeFactorDistance = distance + atkRadius - mwData.radius
+ appliedMwBoost = mwData.boost * _calcRangeFactor(
+ atkOptimalRange=mwData.optimal,
+ atkFalloffRange=mwData.falloff,
+ distance=rangeFactorDistance)
+ appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + appliedMwBoost / 100, mwData.resAttrID))
+ mobileWebs.remove(mwData)
+ maxWebbedSpeed = getTgtMaxVelocity(tgt, extraMultipliers=appliedMultipliers)
+ currentWebbedSpeed = maxWebbedSpeed * speedRatio
+ return currentWebbedSpeed
+
+
+def getTpMult(fit, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance):
+ # Can blow non-immune fits and target profiles
+ if isinstance(tgt, Fit) and tgt.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
+ return 1
+ untpedSig = getTgtSigRadius(tgt)
+ # Modules
+ appliedMultipliers = {}
+ for tpData in tpMods:
+ appliedBoost = tpData.boost * _calcRangeFactor(
+ atkOptimalRange=tpData.optimal,
+ atkFalloffRange=tpData.falloff,
+ distance=distance)
+ if appliedBoost:
+ appliedMultipliers.setdefault(tpData.stackingGroup, []).append((1 + appliedBoost / 100, tpData.resAttrID))
+ # Drones and fighters
+ mobileTps = []
+ mobileTps.extend(tpFighters)
+ # Drones have range limit
+ if distance is None or distance <= fit.extraAttributes['droneControlRange']:
+ mobileTps.extend(tpDrones)
+ droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
+ atkRadius = fit.ship.getModifiedItemAttr('radius')
+ for mtpData in mobileTps:
+ # Faster than target or set to follow it - apply full TP
+ if (droneOpt == GraphDpsDroneMode.auto and mtpData.speed >= tgtSpeed) or droneOpt == GraphDpsDroneMode.followTarget:
+ appliedMtpBoost = mtpData.boost
+ # Otherwise project from the center of the ship
+ else:
+ if distance is None:
+ rangeFactorDistance = None
+ else:
+ rangeFactorDistance = distance + atkRadius - mtpData.radius
+ appliedMtpBoost = mtpData.boost * _calcRangeFactor(
+ atkOptimalRange=mtpData.optimal,
+ atkFalloffRange=mtpData.falloff,
+ distance=rangeFactorDistance)
+ appliedMultipliers.setdefault(mtpData.stackingGroup, []).append((1 + appliedMtpBoost / 100, mtpData.resAttrID))
+ tpedSig = getTgtSigRadius(tgt, extraMultipliers=appliedMultipliers)
+ if tpedSig == math.inf and untpedSig == math.inf:
+ return 1
+ mult = tpedSig / untpedSig
+ return mult
diff --git a/gui/builtinGraphs/fitDamageStats/getter.py b/gui/builtinGraphs/fitDamageStats/getter.py
new file mode 100644
index 000000000..2e4a3d3eb
--- /dev/null
+++ b/gui/builtinGraphs/fitDamageStats/getter.py
@@ -0,0 +1,407 @@
+# =============================================================================
+# 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 eos.config
+from eos.utils.spoolSupport import SpoolType, SpoolOptions
+from eos.utils.stats import DmgTypes
+from gui.builtinGraphs.base import PointGetter, SmoothPointGetter
+from service.settings import GraphSettings
+from .calc.application import getApplicationPerKey
+from .calc.projected import getWebbedSpeed, getTpMult
+from .helper import getTgtSigRadius
+
+
+def applyDamage(dmgMap, applicationMap):
+ total = DmgTypes(0, 0, 0, 0)
+ for key, dmg in dmgMap.items():
+ total += dmg * applicationMap.get(key, 0)
+ return total
+
+
+# Y mixins
+class YDpsMixin:
+
+ def _getDamagePerKey(self, fit, time):
+ # Use data from time cache if time was not specified
+ if time is not None:
+ return self.graph._timeCache.getDpsDataPoint(fit, time)
+ # Compose map ourselves using current fit settings if time is not specified
+ dpsMap = {}
+ defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
+ for mod in fit.modules:
+ if not mod.isDealingDamage():
+ continue
+ dpsMap[mod] = mod.getDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
+ for drone in fit.drones:
+ if not drone.isDealingDamage():
+ continue
+ dpsMap[drone] = drone.getDps()
+ for fighter in fit.fighters:
+ if not fighter.isDealingDamage():
+ continue
+ for effectID, effectDps in fighter.getDpsPerEffect().items():
+ dpsMap[(fighter, effectID)] = effectDps
+ return dpsMap
+
+ def _prepareTimeCache(self, fit, maxTime):
+ self.graph._timeCache.prepareDpsData(fit=fit, maxTime=maxTime)
+
+ def _getTimeCacheData(self, fit):
+ return self.graph._timeCache.getDpsData(fit)
+
+
+class YVolleyMixin:
+
+ def _getDamagePerKey(self, fit, time):
+ # Use data from time cache if time was not specified
+ if time is not None:
+ return self.graph._timeCache.getVolleyDataPoint(fit, time)
+ # Compose map ourselves using current fit settings if time is not specified
+ volleyMap = {}
+ defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
+ for mod in fit.modules:
+ if not mod.isDealingDamage():
+ continue
+ volleyMap[mod] = mod.getVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
+ for drone in fit.drones:
+ if not drone.isDealingDamage():
+ continue
+ volleyMap[drone] = drone.getVolley()
+ for fighter in fit.fighters:
+ if not fighter.isDealingDamage():
+ continue
+ for effectID, effectVolley in fighter.getVolleyPerEffect().items():
+ volleyMap[(fighter, effectID)] = effectVolley
+ return volleyMap
+
+ def _prepareTimeCache(self, fit, maxTime):
+ self.graph._timeCache.prepareVolleyData(fit=fit, maxTime=maxTime)
+
+ def _getTimeCacheData(self, fit):
+ return self.graph._timeCache.getVolleyData(fit)
+
+
+class YInflictedDamageMixin:
+
+ def _getDamagePerKey(self, fit, time):
+ # Damage inflicted makes no sense without time specified
+ if time is None:
+ raise ValueError
+ return self.graph._timeCache.getDmgDataPoint(fit, time)
+
+ def _prepareTimeCache(self, fit, maxTime):
+ self.graph._timeCache.prepareDmgData(fit=fit, maxTime=maxTime)
+
+ def _getTimeCacheData(self, fit):
+ return self.graph._timeCache.getDmgData(fit)
+
+
+# X mixins
+class XDistanceMixin(SmoothPointGetter):
+
+ def _getCommonData(self, miscParams, fit, tgt):
+ # Process params into more convenient form
+ miscParamMap = dict(miscParams)
+ # Prepare time cache here because we need to do it only once,
+ # and this function is called once per point info fetch
+ self._prepareTimeCache(fit=fit, maxTime=miscParamMap['time'])
+ return {
+ 'applyProjected': GraphSettings.getInstance().get('applyProjected'),
+ 'miscParamMap': miscParamMap,
+ 'dmgMap': self._getDamagePerKey(fit=fit, time=miscParamMap['time'])}
+
+ def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
+ miscParamMap = commonData['miscParamMap']
+ tgtSpeed = miscParamMap['tgtSpeed']
+ tgtSigRadius = getTgtSigRadius(tgt)
+ if commonData['applyProjected']:
+ webMods, tpMods = self.graph._projectedCache.getProjModData(fit)
+ webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(fit)
+ webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(fit)
+ tgtSpeed = getWebbedSpeed(
+ fit=fit,
+ tgt=tgt,
+ currentUnwebbedSpeed=tgtSpeed,
+ webMods=webMods,
+ webDrones=webDrones,
+ webFighters=webFighters,
+ distance=x)
+ tgtSigRadius = tgtSigRadius * getTpMult(
+ fit=fit,
+ tgt=tgt,
+ tgtSpeed=tgtSpeed,
+ tpMods=tpMods,
+ tpDrones=tpDrones,
+ tpFighters=tpFighters,
+ distance=x)
+ applicationMap = getApplicationPerKey(
+ fit=fit,
+ tgt=tgt,
+ atkSpeed=miscParamMap['atkSpeed'],
+ atkAngle=miscParamMap['atkAngle'],
+ distance=x,
+ tgtSpeed=tgtSpeed,
+ tgtAngle=miscParamMap['tgtAngle'],
+ tgtSigRadius=tgtSigRadius)
+ y = applyDamage(dmgMap=commonData['dmgMap'], applicationMap=applicationMap).total
+ return y
+
+
+class XTimeMixin(PointGetter):
+
+ def getRange(self, mainParamRange, miscParams, fit, tgt):
+ xs = []
+ ys = []
+ minTime, maxTime = mainParamRange[1]
+ # Process params into more convenient form
+ miscParamMap = dict(miscParams)
+ tgtSpeed = miscParamMap['tgtSpeed']
+ tgtSigRadius = getTgtSigRadius(tgt)
+ if GraphSettings.getInstance().get('applyProjected'):
+ webMods, tpMods = self.graph._projectedCache.getProjModData(fit)
+ webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(fit)
+ webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(fit)
+ tgtSpeed = getWebbedSpeed(
+ fit=fit,
+ tgt=tgt,
+ currentUnwebbedSpeed=tgtSpeed,
+ webMods=webMods,
+ webDrones=webDrones,
+ webFighters=webFighters,
+ distance=miscParamMap['distance'])
+ tgtSigRadius = tgtSigRadius * getTpMult(
+ fit=fit,
+ tgt=tgt,
+ tgtSpeed=tgtSpeed,
+ tpMods=tpMods,
+ tpDrones=tpDrones,
+ tpFighters=tpFighters,
+ distance=miscParamMap['distance'])
+ # Get all data we need for all times into maps/caches
+ applicationMap = getApplicationPerKey(
+ fit=fit,
+ tgt=tgt,
+ atkSpeed=miscParamMap['atkSpeed'],
+ atkAngle=miscParamMap['atkAngle'],
+ distance=miscParamMap['distance'],
+ tgtSpeed=tgtSpeed,
+ tgtAngle=miscParamMap['tgtAngle'],
+ tgtSigRadius=tgtSigRadius)
+ self._prepareTimeCache(fit=fit, maxTime=maxTime)
+ timeCache = self._getTimeCacheData(fit)
+ # Custom iteration for time graph to show all data points
+ currentDmg = None
+ currentTime = None
+ for currentTime in sorted(timeCache):
+ prevDmg = currentDmg
+ currentDmgData = timeCache[currentTime]
+ currentDmg = applyDamage(dmgMap=currentDmgData, applicationMap=applicationMap).total
+ if currentTime < minTime:
+ continue
+ # First set of data points
+ if not xs:
+ # Start at exactly requested time, at last known value
+ initialDmg = prevDmg or 0
+ xs.append(minTime)
+ ys.append(initialDmg)
+ # If current time is bigger then starting, extend plot to that time with old value
+ if currentTime > minTime:
+ xs.append(currentTime)
+ ys.append(initialDmg)
+ # If new value is different, extend it with new point to the new value
+ if currentDmg != prevDmg:
+ xs.append(currentTime)
+ ys.append(currentDmg)
+ continue
+ # Last data point
+ if currentTime >= maxTime:
+ xs.append(maxTime)
+ ys.append(prevDmg)
+ break
+ # Anything in-between
+ if currentDmg != prevDmg:
+ if prevDmg is not None:
+ xs.append(currentTime)
+ ys.append(prevDmg)
+ xs.append(currentTime)
+ ys.append(currentDmg)
+ # Special case - there are no damage dealers
+ if currentDmg is None and currentTime is None:
+ xs.append(minTime)
+ ys.append(0)
+ # Make sure that last data point is always at max time
+ if maxTime > (currentTime or 0):
+ xs.append(maxTime)
+ ys.append(currentDmg or 0)
+ return xs, ys
+
+ def getPoint(self, mainParam, miscParams, fit, tgt):
+ pass
+
+
+class XTgtSpeedMixin(SmoothPointGetter):
+
+ def _getCommonData(self, miscParams, fit, tgt):
+ # Process params into more convenient form
+ miscParamMap = dict(miscParams)
+ # Prepare time cache here because we need to do it only once,
+ # and this function is called once per point info fetch
+ self._prepareTimeCache(fit=fit, maxTime=miscParamMap['time'])
+ return {
+ 'applyProjected': GraphSettings.getInstance().get('applyProjected'),
+ 'miscParamMap': miscParamMap,
+ 'dmgMap': self._getDamagePerKey(fit=fit, time=miscParamMap['time'])}
+
+ def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
+ miscParamMap = commonData['miscParamMap']
+ tgtSpeed = x
+ tgtSigRadius = getTgtSigRadius(tgt)
+ if commonData['applyProjected']:
+ webMods, tpMods = self.graph._projectedCache.getProjModData(fit)
+ webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(fit)
+ webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(fit)
+ tgtSpeed = getWebbedSpeed(
+ fit=fit,
+ tgt=tgt,
+ currentUnwebbedSpeed=tgtSpeed,
+ webMods=webMods,
+ webDrones=webDrones,
+ webFighters=webFighters,
+ distance=miscParamMap['distance'])
+ tgtSigRadius = tgtSigRadius * getTpMult(
+ fit=fit,
+ tgt=tgt,
+ tgtSpeed=tgtSpeed,
+ tpMods=tpMods,
+ tpDrones=tpDrones,
+ tpFighters=tpFighters,
+ distance=miscParamMap['distance'])
+ applicationMap = getApplicationPerKey(
+ fit=fit,
+ tgt=tgt,
+ atkSpeed=miscParamMap['atkSpeed'],
+ atkAngle=miscParamMap['atkAngle'],
+ distance=miscParamMap['distance'],
+ tgtSpeed=tgtSpeed,
+ tgtAngle=miscParamMap['tgtAngle'],
+ tgtSigRadius=tgtSigRadius)
+ y = applyDamage(dmgMap=commonData['dmgMap'], applicationMap=applicationMap).total
+ return y
+
+
+class XTgtSigRadiusMixin(SmoothPointGetter):
+
+ def _getCommonData(self, miscParams, fit, tgt):
+ # Process params into more convenient form
+ miscParamMap = dict(miscParams)
+ tgtSpeed = miscParamMap['tgtSpeed']
+ tgtSigMult = 1
+ if GraphSettings.getInstance().get('applyProjected'):
+ webMods, tpMods = self.graph._projectedCache.getProjModData(fit)
+ webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(fit)
+ webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(fit)
+ tgtSpeed = getWebbedSpeed(
+ fit=fit,
+ tgt=tgt,
+ currentUnwebbedSpeed=tgtSpeed,
+ webMods=webMods,
+ webDrones=webDrones,
+ webFighters=webFighters,
+ distance=miscParamMap['distance'])
+ tgtSigMult = getTpMult(
+ fit=fit,
+ tgt=tgt,
+ tgtSpeed=tgtSpeed,
+ tpMods=tpMods,
+ tpDrones=tpDrones,
+ tpFighters=tpFighters,
+ distance=miscParamMap['distance'])
+ # Prepare time cache here because we need to do it only once,
+ # and this function is called once per point info fetch
+ self._prepareTimeCache(fit=fit, maxTime=miscParamMap['time'])
+ return {
+ 'miscParamMap': miscParamMap,
+ 'tgtSpeed': tgtSpeed,
+ 'tgtSigMult': tgtSigMult,
+ 'dmgMap': self._getDamagePerKey(fit=fit, time=miscParamMap['time'])}
+
+ def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
+ miscParamMap = commonData['miscParamMap']
+ tgtSigRadius = x * commonData['tgtSigMult']
+ applicationMap = getApplicationPerKey(
+ fit=fit,
+ tgt=tgt,
+ atkSpeed=miscParamMap['atkSpeed'],
+ atkAngle=miscParamMap['atkAngle'],
+ distance=miscParamMap['distance'],
+ tgtSpeed=commonData['tgtSpeed'],
+ tgtAngle=miscParamMap['tgtAngle'],
+ tgtSigRadius=tgtSigRadius)
+ y = applyDamage(dmgMap=commonData['dmgMap'], applicationMap=applicationMap).total
+ return y
+
+
+# Final getters
+class Distance2DpsGetter(XDistanceMixin, YDpsMixin):
+ pass
+
+
+class Distance2VolleyGetter(XDistanceMixin, YVolleyMixin):
+ pass
+
+
+class Distance2InflictedDamageGetter(XDistanceMixin, YInflictedDamageMixin):
+ pass
+
+
+class Time2DpsGetter(XTimeMixin, YDpsMixin):
+ pass
+
+
+class Time2VolleyGetter(XTimeMixin, YVolleyMixin):
+ pass
+
+
+class Time2InflictedDamageGetter(XTimeMixin, YInflictedDamageMixin):
+ pass
+
+
+class TgtSpeed2DpsGetter(XTgtSpeedMixin, YDpsMixin):
+ pass
+
+
+class TgtSpeed2VolleyGetter(XTgtSpeedMixin, YVolleyMixin):
+ pass
+
+
+class TgtSpeed2InflictedDamageGetter(XTgtSpeedMixin, YInflictedDamageMixin):
+ pass
+
+
+class TgtSigRadius2DpsGetter(XTgtSigRadiusMixin, YDpsMixin):
+ pass
+
+
+class TgtSigRadius2VolleyGetter(XTgtSigRadiusMixin, YVolleyMixin):
+ pass
+
+
+class TgtSigRadius2InflictedDamageGetter(XTgtSigRadiusMixin, YInflictedDamageMixin):
+ pass
diff --git a/gui/builtinGraphs/fitDamageStats/graph.py b/gui/builtinGraphs/fitDamageStats/graph.py
index 61dfe8ce3..f7c06db5a 100644
--- a/gui/builtinGraphs/fitDamageStats/graph.py
+++ b/gui/builtinGraphs/fitDamageStats/graph.py
@@ -18,20 +18,15 @@
# =============================================================================
-import eos.config
-from eos.const import FittingHardpoint
-from eos.utils.spoolSupport import SpoolType, SpoolOptions
-from eos.utils.stats import DmgTypes
from gui.builtinGraphs.base import FitGraph, XDef, YDef, Input, VectorDef
from service.const import GraphCacheCleanupReason
-from service.settings import GraphSettings
-from .calc import (
- getTurretMult, getLauncherMult, getDroneMult, getFighterAbilityMult,
- getSmartbombMult, getDoomsdayMult, getBombMult, getGuidedBombMult,
- getWebbedSpeed, getTpMult)
+from .getter import (
+ Distance2DpsGetter, Distance2VolleyGetter, Distance2InflictedDamageGetter,
+ Time2DpsGetter, Time2VolleyGetter, Time2InflictedDamageGetter,
+ TgtSpeed2DpsGetter, TgtSpeed2VolleyGetter, TgtSpeed2InflictedDamageGetter,
+ TgtSigRadius2DpsGetter, TgtSigRadius2VolleyGetter, TgtSigRadius2InflictedDamageGetter)
from .helper import getTgtMaxVelocity, getTgtSigRadius
-from .projectedCache import ProjectedDataCache
-from .timeCache import TimeCache
+from .cache import ProjectedDataCache, TimeCache
class FitDamageStatsGraph(FitGraph):
@@ -91,445 +86,19 @@ class FitDamageStatsGraph(FitGraph):
('distance', 'km'): lambda v, fit, tgt: None if v is None else v / 1000,
('tgtSpeed', '%'): lambda v, fit, tgt: v * 100 / getTgtMaxVelocity(tgt),
('tgtSigRad', '%'): lambda v, fit, tgt: v * 100 / getTgtSigRadius(tgt)}
-
- def _distance2dpsFull(self, mainParam, miscParams, fit, tgt):
- return self._xDistanceGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- dmgFunc=self._getDpsPerKey, timeCachePrepFunc=self._timeCache.prepareDpsData)
-
- def _distance2volleyFull(self, mainParam, miscParams, fit, tgt):
- return self._xDistanceGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- dmgFunc=self._getVolleyPerKey, timeCachePrepFunc=self._timeCache.prepareVolleyData)
-
- def _distance2damageFull(self, mainParam, miscParams, fit, tgt):
- return self._xDistanceGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- dmgFunc=self._getDmgPerKey, timeCachePrepFunc=self._timeCache.prepareDmgData)
-
- def _time2dpsFull(self, mainParam, miscParams, fit, tgt):
- return self._xTimeGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- timeCachePrepFunc=self._timeCache.prepareDpsData, timeCacheGetFunc=self._timeCache.getDpsData)
-
- def _time2volleyFull(self, mainParam, miscParams, fit, tgt):
- return self._xTimeGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- timeCachePrepFunc=self._timeCache.prepareVolleyData, timeCacheGetFunc=self._timeCache.getVolleyData)
-
- def _time2damageFull(self, mainParam, miscParams, fit, tgt):
- return self._xTimeGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- timeCachePrepFunc=self._timeCache.prepareDmgData, timeCacheGetFunc=self._timeCache.getDmgData)
-
- def _tgtSpeed2dpsFull(self, mainParam, miscParams, fit, tgt):
- return self._xTgtSpeedGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- dmgFunc=self._getDpsPerKey, timeCachePrepFunc=self._timeCache.prepareDpsData)
-
- def _tgtSpeed2volleyFull(self, mainParam, miscParams, fit, tgt):
- return self._xTgtSpeedGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- dmgFunc=self._getVolleyPerKey, timeCachePrepFunc=self._timeCache.prepareVolleyData)
-
- def _tgtSpeed2damageFull(self, mainParam, miscParams, fit, tgt):
- return self._xTgtSpeedGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- dmgFunc=self._getDmgPerKey, timeCachePrepFunc=self._timeCache.prepareDmgData)
-
- def _tgtSigRad2dpsFull(self, mainParam, miscParams, fit, tgt):
- return self._xTgtSigRadiusGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- dmgFunc=self._getDpsPerKey, timeCachePrepFunc=self._timeCache.prepareDpsData)
-
- def _tgtSigRad2volleyFull(self, mainParam, miscParams, fit, tgt):
- return self._xTgtSigRadiusGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- dmgFunc=self._getVolleyPerKey, timeCachePrepFunc=self._timeCache.prepareVolleyData)
-
- def _tgtSigRad2damageFull(self, mainParam, miscParams, fit, tgt):
- return self._xTgtSigRadiusGetter(
- mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt,
- dmgFunc=self._getDmgPerKey, timeCachePrepFunc=self._timeCache.prepareDmgData)
-
_getters = {
- ('distance', 'dps'): _distance2dpsFull,
- ('distance', 'volley'): _distance2volleyFull,
- ('distance', 'damage'): _distance2damageFull,
- ('time', 'dps'): _time2dpsFull,
- ('time', 'volley'): _time2volleyFull,
- ('time', 'damage'): _time2damageFull,
- ('tgtSpeed', 'dps'): _tgtSpeed2dpsFull,
- ('tgtSpeed', 'volley'): _tgtSpeed2volleyFull,
- ('tgtSpeed', 'damage'): _tgtSpeed2damageFull,
- ('tgtSigRad', 'dps'): _tgtSigRad2dpsFull,
- ('tgtSigRad', 'volley'): _tgtSigRad2volleyFull,
- ('tgtSigRad', 'damage'): _tgtSigRad2damageFull}
-
- # Point getter helpers
- def _xDistanceGetter(self, mainParam, miscParams, fit, tgt, dmgFunc, timeCachePrepFunc):
- xs = []
- ys = []
- applyProjected = GraphSettings.getInstance().get('applyProjected')
- # Process params into more convenient form
- miscParamMap = dict(miscParams)
- # Get all data we need for all distances into maps/caches
- timeCachePrepFunc(fit, miscParamMap['time'])
- dmgMap = dmgFunc(fit=fit, time=miscParamMap['time'])
- # Go through distances and calculate distance-dependent data
- for distance in self._iterLinear(mainParam[1]):
- tgtSpeed = miscParamMap['tgtSpeed']
- tgtSigRadius = getTgtSigRadius(tgt)
- if applyProjected:
- webMods, tpMods = self._projectedCache.getProjModData(fit)
- webDrones, tpDrones = self._projectedCache.getProjDroneData(fit)
- webFighters, tpFighters = self._projectedCache.getProjFighterData(fit)
- tgtSpeed = getWebbedSpeed(
- fit=fit,
- tgt=tgt,
- currentUnwebbedSpeed=tgtSpeed,
- webMods=webMods,
- webDrones=webDrones,
- webFighters=webFighters,
- distance=distance)
- tgtSigRadius = tgtSigRadius * getTpMult(
- fit=fit,
- tgt=tgt,
- tgtSpeed=tgtSpeed,
- tpMods=tpMods,
- tpDrones=tpDrones,
- tpFighters=tpFighters,
- distance=distance)
- applicationMap = self._getApplicationPerKey(
- fit=fit,
- tgt=tgt,
- atkSpeed=miscParamMap['atkSpeed'],
- atkAngle=miscParamMap['atkAngle'],
- distance=distance,
- tgtSpeed=tgtSpeed,
- tgtAngle=miscParamMap['tgtAngle'],
- tgtSigRadius=tgtSigRadius)
- dmg = self._aggregate(dmgMap=dmgMap, applicationMap=applicationMap).total
- xs.append(distance)
- ys.append(dmg)
- return xs, ys
-
- def _xTimeGetter(self, mainParam, miscParams, fit, tgt, timeCachePrepFunc, timeCacheGetFunc):
- xs = []
- ys = []
- minTime, maxTime = mainParam[1]
- # Process params into more convenient form
- miscParamMap = dict(miscParams)
- tgtSpeed = miscParamMap['tgtSpeed']
- tgtSigRadius = getTgtSigRadius(tgt)
- if GraphSettings.getInstance().get('applyProjected'):
- webMods, tpMods = self._projectedCache.getProjModData(fit)
- webDrones, tpDrones = self._projectedCache.getProjDroneData(fit)
- webFighters, tpFighters = self._projectedCache.getProjFighterData(fit)
- tgtSpeed = getWebbedSpeed(
- fit=fit,
- tgt=tgt,
- currentUnwebbedSpeed=tgtSpeed,
- webMods=webMods,
- webDrones=webDrones,
- webFighters=webFighters,
- distance=miscParamMap['distance'])
- tgtSigRadius = tgtSigRadius * getTpMult(
- fit=fit,
- tgt=tgt,
- tgtSpeed=tgtSpeed,
- tpMods=tpMods,
- tpDrones=tpDrones,
- tpFighters=tpFighters,
- distance=miscParamMap['distance'])
- # Get all data we need for all times into maps/caches
- applicationMap = self._getApplicationPerKey(
- fit=fit,
- tgt=tgt,
- atkSpeed=miscParamMap['atkSpeed'],
- atkAngle=miscParamMap['atkAngle'],
- distance=miscParamMap['distance'],
- tgtSpeed=tgtSpeed,
- tgtAngle=miscParamMap['tgtAngle'],
- tgtSigRadius=tgtSigRadius)
- timeCachePrepFunc(fit, maxTime)
- timeCache = timeCacheGetFunc(fit)
- # Custom iteration for time graph to show all data points
- currentDmg = None
- currentTime = None
- for currentTime in sorted(timeCache):
- prevDmg = currentDmg
- currentDmgData = timeCache[currentTime]
- currentDmg = self._aggregate(dmgMap=currentDmgData, applicationMap=applicationMap).total
- if currentTime < minTime:
- continue
- # First set of data points
- if not xs:
- # Start at exactly requested time, at last known value
- initialDmg = prevDmg or 0
- xs.append(minTime)
- ys.append(initialDmg)
- # If current time is bigger then starting, extend plot to that time with old value
- if currentTime > minTime:
- xs.append(currentTime)
- ys.append(initialDmg)
- # If new value is different, extend it with new point to the new value
- if currentDmg != prevDmg:
- xs.append(currentTime)
- ys.append(currentDmg)
- continue
- # Last data point
- if currentTime >= maxTime:
- xs.append(maxTime)
- ys.append(prevDmg)
- break
- # Anything in-between
- if currentDmg != prevDmg:
- if prevDmg is not None:
- xs.append(currentTime)
- ys.append(prevDmg)
- xs.append(currentTime)
- ys.append(currentDmg)
- # Special case - there are no damage dealers
- if currentDmg is None and currentTime is None:
- xs.append(minTime)
- ys.append(0)
- # Make sure that last data point is always at max time
- if maxTime > (currentTime or 0):
- xs.append(maxTime)
- ys.append(currentDmg or 0)
- return xs, ys
-
- def _xTgtSpeedGetter(self, mainParam, miscParams, fit, tgt, dmgFunc, timeCachePrepFunc):
- xs = []
- ys = []
- applyProjected = GraphSettings.getInstance().get('applyProjected')
- # Process params into more convenient form
- miscParamMap = dict(miscParams)
- # Get all data we need for all target speeds into maps/caches
- timeCachePrepFunc(fit, miscParamMap['time'])
- dmgMap = dmgFunc(fit=fit, time=miscParamMap['time'])
- # Go through target speeds and calculate distance-dependent data
- for tgtSpeed in self._iterLinear(mainParam[1]):
- # Get separate internal speed to calculate proper application, for graph
- # itself we still want to show pre-modification speed on X axis
- tgtSpeedInternal = tgtSpeed
- tgtSigRadius = getTgtSigRadius(tgt)
- if applyProjected:
- webMods, tpMods = self._projectedCache.getProjModData(fit)
- webDrones, tpDrones = self._projectedCache.getProjDroneData(fit)
- webFighters, tpFighters = self._projectedCache.getProjFighterData(fit)
- tgtSpeedInternal = getWebbedSpeed(
- fit=fit,
- tgt=tgt,
- currentUnwebbedSpeed=tgtSpeedInternal,
- webMods=webMods,
- webDrones=webDrones,
- webFighters=webFighters,
- distance=miscParamMap['distance'])
- tgtSigRadius = tgtSigRadius * getTpMult(
- fit=fit,
- tgt=tgt,
- tgtSpeed=tgtSpeedInternal,
- tpMods=tpMods,
- tpDrones=tpDrones,
- tpFighters=tpFighters,
- distance=miscParamMap['distance'])
- applicationMap = self._getApplicationPerKey(
- fit=fit,
- tgt=tgt,
- atkSpeed=miscParamMap['atkSpeed'],
- atkAngle=miscParamMap['atkAngle'],
- distance=miscParamMap['distance'],
- tgtSpeed=tgtSpeedInternal,
- tgtAngle=miscParamMap['tgtAngle'],
- tgtSigRadius=tgtSigRadius)
- dmg = self._aggregate(dmgMap=dmgMap, applicationMap=applicationMap).total
- xs.append(tgtSpeed)
- ys.append(dmg)
- return xs, ys
-
- def _xTgtSigRadiusGetter(self, mainParam, miscParams, fit, tgt, dmgFunc, timeCachePrepFunc):
- xs = []
- ys = []
- # Process params into more convenient form
- miscParamMap = dict(miscParams)
- tgtSpeed = miscParamMap['tgtSpeed']
- tgtSigMult = 1
- if GraphSettings.getInstance().get('applyProjected'):
- webMods, tpMods = self._projectedCache.getProjModData(fit)
- webDrones, tpDrones = self._projectedCache.getProjDroneData(fit)
- webFighters, tpFighters = self._projectedCache.getProjFighterData(fit)
- tgtSpeed = getWebbedSpeed(
- fit=fit,
- tgt=tgt,
- currentUnwebbedSpeed=tgtSpeed,
- webMods=webMods,
- webDrones=webDrones,
- webFighters=webFighters,
- distance=miscParamMap['distance'])
- tgtSigMult = getTpMult(
- fit=fit,
- tgt=tgt,
- tgtSpeed=tgtSpeed,
- tpMods=tpMods,
- tpDrones=tpDrones,
- tpFighters=tpFighters,
- distance=miscParamMap['distance'])
- # Get all data we need for all target speeds into maps/caches
- timeCachePrepFunc(fit, miscParamMap['time'])
- dmgMap = dmgFunc(fit=fit, time=miscParamMap['time'])
- # Go through target speeds and calculate distance-dependent data
- for tgtSigRadius in self._iterLinear(mainParam[1]):
- # Separate variable to show base signature on X axis and use modified
- # signature in calculations
- tgtSigRadiusInternal = tgtSigRadius * tgtSigMult
- applicationMap = self._getApplicationPerKey(
- fit=fit,
- tgt=tgt,
- atkSpeed=miscParamMap['atkSpeed'],
- atkAngle=miscParamMap['atkAngle'],
- distance=miscParamMap['distance'],
- tgtSpeed=tgtSpeed,
- tgtAngle=miscParamMap['tgtAngle'],
- tgtSigRadius=tgtSigRadiusInternal)
- dmg = self._aggregate(dmgMap=dmgMap, applicationMap=applicationMap).total
- xs.append(tgtSigRadius)
- ys.append(dmg)
- return xs, ys
-
- # Damage data per key getters
- def _getDpsPerKey(self, fit, time):
- # Use data from time cache if time was not specified
- if time is not None:
- return self._timeCache.getDpsDataPoint(fit, time)
- # Compose map ourselves using current fit settings if time is not specified
- dpsMap = {}
- defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
- for mod in fit.modules:
- if not mod.isDealingDamage():
- continue
- dpsMap[mod] = mod.getDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
- for drone in fit.drones:
- if not drone.isDealingDamage():
- continue
- dpsMap[drone] = drone.getDps()
- for fighter in fit.fighters:
- if not fighter.isDealingDamage():
- continue
- for effectID, effectDps in fighter.getDpsPerEffect().items():
- dpsMap[(fighter, effectID)] = effectDps
- return dpsMap
-
- def _getVolleyPerKey(self, fit, time):
- # Use data from time cache if time was not specified
- if time is not None:
- return self._timeCache.getVolleyDataPoint(fit, time)
- # Compose map ourselves using current fit settings if time is not specified
- volleyMap = {}
- defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
- for mod in fit.modules:
- if not mod.isDealingDamage():
- continue
- volleyMap[mod] = mod.getVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
- for drone in fit.drones:
- if not drone.isDealingDamage():
- continue
- volleyMap[drone] = drone.getVolley()
- for fighter in fit.fighters:
- if not fighter.isDealingDamage():
- continue
- for effectID, effectVolley in fighter.getVolleyPerEffect().items():
- volleyMap[(fighter, effectID)] = effectVolley
- return volleyMap
-
- def _getDmgPerKey(self, fit, time):
- # Damage inflicted makes no sense without time specified
- if time is None:
- raise ValueError
- return self._timeCache.getDmgDataPoint(fit, time)
-
- # Application getter
- def _getApplicationPerKey(self, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
- applicationMap = {}
- for mod in fit.modules:
- if not mod.isDealingDamage():
- continue
- if mod.hardpoint == FittingHardpoint.TURRET:
- applicationMap[mod] = getTurretMult(
- mod=mod,
- fit=fit,
- tgt=tgt,
- atkSpeed=atkSpeed,
- atkAngle=atkAngle,
- distance=distance,
- tgtSpeed=tgtSpeed,
- tgtAngle=tgtAngle,
- tgtSigRadius=tgtSigRadius)
- elif mod.hardpoint == FittingHardpoint.MISSILE:
- applicationMap[mod] = getLauncherMult(
- mod=mod,
- fit=fit,
- distance=distance,
- tgtSpeed=tgtSpeed,
- tgtSigRadius=tgtSigRadius)
- elif mod.item.group.name in ('Smart Bomb', 'Structure Area Denial Module'):
- applicationMap[mod] = getSmartbombMult(
- mod=mod,
- distance=distance)
- elif mod.item.group.name == 'Missile Launcher Bomb':
- applicationMap[mod] = getBombMult(
- mod=mod,
- fit=fit,
- tgt=tgt,
- distance=distance,
- tgtSigRadius=tgtSigRadius)
- elif mod.item.group.name == 'Structure Guided Bomb Launcher':
- applicationMap[mod] = getGuidedBombMult(
- mod=mod,
- fit=fit,
- distance=distance,
- tgtSigRadius=tgtSigRadius)
- elif mod.item.group.name in ('Super Weapon', 'Structure Doomsday Weapon'):
- applicationMap[mod] = getDoomsdayMult(
- mod=mod,
- tgt=tgt,
- distance=distance,
- tgtSigRadius=tgtSigRadius)
- for drone in fit.drones:
- if not drone.isDealingDamage():
- continue
- applicationMap[drone] = getDroneMult(
- drone=drone,
- fit=fit,
- tgt=tgt,
- atkSpeed=atkSpeed,
- atkAngle=atkAngle,
- distance=distance,
- tgtSpeed=tgtSpeed,
- tgtAngle=tgtAngle,
- tgtSigRadius=tgtSigRadius)
- for fighter in fit.fighters:
- if not fighter.isDealingDamage():
- continue
- for ability in fighter.abilities:
- if not ability.dealsDamage or not ability.active:
- continue
- applicationMap[(fighter, ability.effectID)] = getFighterAbilityMult(
- fighter=fighter,
- ability=ability,
- fit=fit,
- distance=distance,
- tgtSpeed=tgtSpeed,
- tgtSigRadius=tgtSigRadius)
- return applicationMap
-
- # Calculate damage from maps
- def _aggregate(self, dmgMap, applicationMap):
- total = DmgTypes(0, 0, 0, 0)
- for key, dmg in dmgMap.items():
- total += dmg * applicationMap.get(key, 0)
- return total
+ ('distance', 'dps'): Distance2DpsGetter,
+ ('distance', 'volley'): Distance2VolleyGetter,
+ ('distance', 'damage'): Distance2InflictedDamageGetter,
+ ('time', 'dps'): Time2DpsGetter,
+ ('time', 'volley'): Time2VolleyGetter,
+ ('time', 'damage'): Time2InflictedDamageGetter,
+ ('tgtSpeed', 'dps'): TgtSpeed2DpsGetter,
+ ('tgtSpeed', 'volley'): TgtSpeed2VolleyGetter,
+ ('tgtSpeed', 'damage'): TgtSpeed2InflictedDamageGetter,
+ ('tgtSigRad', 'dps'): TgtSigRadius2DpsGetter,
+ ('tgtSigRad', 'volley'): TgtSigRadius2VolleyGetter,
+ ('tgtSigRad', 'damage'): TgtSigRadius2InflictedDamageGetter}
FitDamageStatsGraph.register()
diff --git a/gui/builtinGraphs/fitDamageStats/helper.py b/gui/builtinGraphs/fitDamageStats/helper.py
index ca265719e..d2fba6ce8 100644
--- a/gui/builtinGraphs/fitDamageStats/helper.py
+++ b/gui/builtinGraphs/fitDamageStats/helper.py
@@ -64,7 +64,7 @@ def getTgtRadius(tgt):
return radius
-# Just copypaste penalization chain calculation code (with some modifications,
+# 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):
diff --git a/gui/builtinGraphs/fitWarpTime/subwarpCache.py b/gui/builtinGraphs/fitWarpTime/cache.py
similarity index 100%
rename from gui/builtinGraphs/fitWarpTime/subwarpCache.py
rename to gui/builtinGraphs/fitWarpTime/cache.py
diff --git a/gui/builtinGraphs/fitWarpTime/graph.py b/gui/builtinGraphs/fitWarpTime/graph.py
index a3068509e..9b3ad40eb 100644
--- a/gui/builtinGraphs/fitWarpTime/graph.py
+++ b/gui/builtinGraphs/fitWarpTime/graph.py
@@ -21,7 +21,7 @@
from service.const import GraphCacheCleanupReason
from gui.builtinGraphs.base import FitGraph, XDef, YDef, Input
from .getter import Distance2TimeGetter, AU_METERS
-from .subwarpCache import SubwarpSpeedCache
+from .cache import SubwarpSpeedCache
class FitWarpTimeGraph(FitGraph):