diff --git a/gui/builtinGraphs/fitDamageStats/__init__.py b/gui/builtinGraphs/fitDamageStats/__init__.py
new file mode 100644
index 000000000..b01f971ca
--- /dev/null
+++ b/gui/builtinGraphs/fitDamageStats/__init__.py
@@ -0,0 +1 @@
+import gui.builtinGraphs.fitDamageStats.graph # noqa: E402,F401
diff --git a/gui/builtinGraphs/fitDamageStats/cacheTime.py b/gui/builtinGraphs/fitDamageStats/cacheTime.py
new file mode 100644
index 000000000..b711ecadb
--- /dev/null
+++ b/gui/builtinGraphs/fitDamageStats/cacheTime.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 .
+# =============================================================================
+
+
+class TimeCache:
+
+ def __init__(self):
+ self.data = {}
diff --git a/gui/builtinGraphs/fitDamageStats/calc.py b/gui/builtinGraphs/fitDamageStats/calc.py
new file mode 100644
index 000000000..fe7dc1124
--- /dev/null
+++ b/gui/builtinGraphs/fitDamageStats/calc.py
@@ -0,0 +1,207 @@
+# =============================================================================
+# 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
+
+
+def getTurretMult(mod, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
+ cth = _calcTurretChanceToHit(
+ atkSpeed=atkSpeed,
+ atkAngle=atkAngle,
+ atkRadius=fit.ship.getModifiedItemAttr('radius'),
+ atkOptimalRange=mod.maxRange,
+ atkFalloffRange=mod.falloff,
+ atkTracking=mod.getModifiedItemAttr('trackingSpeed'),
+ atkOptimalSigRadius=mod.getModifiedItemAttr('optimalSigRadius'),
+ distance=distance,
+ tgtSpeed=tgtSpeed,
+ tgtAngle=tgtAngle,
+ tgtRadius=tgt.ship.getModifiedItemAttr('radius'),
+ tgtSigRadius=tgtSigRadius)
+ mult = _calcTurretMult(cth)
+ return mult
+
+
+def getLauncherMult(mod, fit, distance, tgtSpeed, tgtSigRadius):
+ modRange = mod.maxRange
+ if modRange is None:
+ return 0
+ mult = _calcMissileMult(
+ atkRadius=fit.ship.getModifiedItemAttr('radius'),
+ atkRange=modRange,
+ atkEr=mod.getModifiedChargeAttr('aoeCloudSize'),
+ atkEv=mod.getModifiedChargeAttr('aoeVelocity'),
+ atkDrf=mod.getModifiedChargeAttr('aoeDamageReductionFactor'),
+ distance=distance,
+ tgtSpeed=tgtSpeed,
+ tgtSigRadius=tgtSigRadius)
+ return mult
+
+
+def getDroneMult(drone, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
+ if distance > fit.extraAttributes['droneControlRange']:
+ return 0
+ droneSpeed = drone.getModifiedItemAttr('maxVelocity')
+ # Hard to simulate drone behavior, so assume chance to hit is 1
+ # when drone is not sentry and is faster than its target
+ if droneSpeed > 1 and droneSpeed >= tgtSpeed:
+ cth = 1
+ # Otherwise put the drone into center of the ship, move it at its max speed or ship's speed
+ # (whichever is lower) towards direction of attacking ship and see how well it projects
+ else:
+ droneRadius = drone.getModifiedItemAttr('radius')
+ cth = _calcTurretChanceToHit(
+ atkSpeed=min(atkSpeed, droneSpeed),
+ atkAngle=atkAngle,
+ atkRadius=droneRadius,
+ atkOptimalRange=drone.maxRange,
+ atkFalloffRange=drone.falloff,
+ atkTracking=drone.getModifiedItemAttr('trackingSpeed'),
+ atkOptimalSigRadius=drone.getModifiedItemAttr('optimalSigRadius'),
+ # As distance is ship surface to ship surface, we adjust it according
+ # to attacker fit's radiuses to have drone surface to ship surface distance
+ distance=distance + fit.ship.getModifiedItemAttr('radius') - droneRadius,
+ tgtSpeed=tgtSpeed,
+ tgtAngle=tgtAngle,
+ tgtRadius=tgt.ship.getModifiedItemAttr('radius'),
+ tgtSigRadius=tgtSigRadius)
+ mult = _calcTurretMult(cth)
+ return mult
+
+
+def getFighterAbilityMult(fighter, ability, fit, distance, tgtSpeed, tgtSigRadius):
+ fighterSpeed = fighter.getModifiedItemAttr('maxVelocity')
+ attrPrefix = ability.attrPrefix
+ if fighterSpeed >= tgtSpeed:
+ rangeFactor = 1
+ # Same as with drones, if fighters are slower - put them to center of
+ # the ship and see how they apply
+ else:
+ rangeFactor = _calcRangeFactor(
+ atkOptimalRange=fighter.getModifiedItemAttr('{}RangeOptimal'.format(attrPrefix)),
+ atkFalloffRange=fighter.getModifiedItemAttr('{}RangeFalloff'.format(attrPrefix)),
+ distance=distance + fit.ship.getModifiedItemAttr('radius') - fighter.getModifiedItemAttr('radius'))
+ drf = fighter.getModifiedItemAttr('{}ReductionFactor'.format(attrPrefix), None)
+ if drf is None:
+ drf = fighter.getModifiedItemAttr('{}DamageReductionFactor'.format(attrPrefix))
+ drs = fighter.getModifiedItemAttr('{}ReductionSensitivity'.format(attrPrefix), None)
+ if drs is None:
+ drs = fighter.getModifiedItemAttr('{}DamageReductionSensitivity'.format(attrPrefix))
+ missileFactor = _calcMissileFactor(
+ atkEr=fighter.getModifiedItemAttr('{}ExplosionRadius'.format(attrPrefix)),
+ atkEv=fighter.getModifiedItemAttr('{}ExplosionVelocity'.format(attrPrefix)),
+ atkDrf=_calcAggregatedDrf(reductionFactor=drf, reductionSensitivity=drs),
+ tgtSpeed=tgtSpeed,
+ tgtSigRadius=tgtSigRadius)
+ mult = rangeFactor * missileFactor
+ return mult
+
+
+# Turret-specific
+def _calcTurretMult(chanceToHit):
+ """Calculate damage multiplier for turret-based weapons."""
+ # https://wiki.eveuniversity.org/Turret_mechanics#Damage
+ wreckingChance = min(chanceToHit, 0.01)
+ wreckingPart = wreckingChance * 3
+ normalChance = chanceToHit - wreckingChance
+ if normalChance > 0:
+ avgDamageMult = (0.01 + chanceToHit) / 2 + 0.49
+ normalPart = normalChance * avgDamageMult
+ else:
+ normalPart = 0
+ totalMult = normalPart + wreckingPart
+ return totalMult
+
+
+def _calcTurretChanceToHit(
+ atkSpeed, atkAngle, atkRadius, atkOptimalRange, atkFalloffRange, atkTracking, atkOptimalSigRadius,
+ distance, tgtSpeed, tgtAngle, tgtRadius, tgtSigRadius
+):
+ """Calculate chance to hit for turret-based weapons."""
+ # https://wiki.eveuniversity.org/Turret_mechanics#Hit_Math
+ angularSpeed = _calcAngularSpeed(atkSpeed, atkAngle, atkRadius, distance, tgtSpeed, tgtAngle, tgtRadius)
+ rangeFactor = _calcRangeFactor(atkOptimalRange, atkFalloffRange, distance)
+ trackingFactor = _calcTrackingFactor(atkTracking, atkOptimalSigRadius, angularSpeed, tgtSigRadius)
+ cth = rangeFactor * trackingFactor
+ return cth
+
+
+def _calcAngularSpeed(atkSpeed, atkAngle, atkRadius, distance, tgtSpeed, tgtAngle, tgtRadius):
+ """Calculate angular speed based on mobility parameters of two ships."""
+ atkAngle = atkAngle * math.pi / 180
+ tgtAngle = tgtAngle * math.pi / 180
+ ctcDistance = atkRadius + distance + tgtRadius
+ # Target is to the right of the attacker, so transversal is projection onto Y axis
+ transSpeed = abs(atkSpeed * math.sin(atkAngle) - tgtSpeed * math.sin(tgtAngle))
+ if ctcDistance == 0:
+ angularSpeed = 0 if transSpeed == 0 else math.inf
+ else:
+ angularSpeed = transSpeed / ctcDistance
+ return angularSpeed
+
+
+def _calcTrackingFactor(atkTracking, atkOptimalSigRadius, angularSpeed, tgtSigRadius):
+ """Calculate tracking chance to hit component."""
+ return 0.5 ** (((angularSpeed * atkOptimalSigRadius) / (atkTracking * tgtSigRadius)) ** 2)
+
+
+# Missile-specific
+def _calcMissileMult(atkRadius, atkRange, atkEr, atkEv, atkDrf, distance, tgtSpeed, tgtSigRadius):
+ """Calculate damage multiplier for missile launcher."""
+ # Missiles spawn in the center of the attacking ship
+ if distance + atkRadius > atkRange:
+ mult = 0
+ else:
+ mult = _calcMissileFactor(atkEr, atkEv, atkDrf, tgtSpeed, tgtSigRadius)
+ return mult
+
+
+def _calcFighterMult(atkOptimalRange, atkFalloffRange, atkEr, atkEv, atkDrf, distance, tgtSpeed, tgtSigRadius):
+ """Calculate damage multiplier for separate fighter ability,"""
+ rangeFactor = _calcRangeFactor(atkOptimalRange, atkFalloffRange, distance)
+ missileFactor = _calcMissileFactor(atkEr, atkEv, atkDrf, tgtSpeed, tgtSigRadius)
+ mult = rangeFactor * missileFactor
+ return mult
+
+
+def _calcMissileFactor(atkEr, atkEv, atkDrf, tgtSpeed, tgtSigRadius):
+ """Missile application."""
+ factors = [1]
+ # "Slow" part
+ factors.append(tgtSigRadius / atkEr)
+ # "Fast" part
+ if tgtSpeed > 0:
+ factors.append(((atkEv * tgtSigRadius) / (atkEr * tgtSpeed)) ** atkDrf)
+ totalMult = min(factors)
+ return totalMult
+
+
+def _calcAggregatedDrf(reductionFactor, reductionSensitivity):
+ """
+ Sometimes DRF is specified as 2 separate numbers,
+ here we combine them into generic form.
+ """
+ return math.log(reductionFactor) / math.log(reductionSensitivity)
+
+
+# Generic
+def _calcRangeFactor(atkOptimalRange, atkFalloffRange, distance):
+ """Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
+ return 0.5 ** ((max(0, distance - atkOptimalRange) / atkFalloffRange) ** 2)
diff --git a/gui/builtinGraphs/fitDamageStats.py b/gui/builtinGraphs/fitDamageStats/graph.py
similarity index 72%
rename from gui/builtinGraphs/fitDamageStats.py
rename to gui/builtinGraphs/fitDamageStats/graph.py
index 470d0496b..1ef75fd1c 100644
--- a/gui/builtinGraphs/fitDamageStats.py
+++ b/gui/builtinGraphs/fitDamageStats/graph.py
@@ -18,7 +18,6 @@
# =============================================================================
-import math
from copy import copy
from itertools import chain
@@ -27,7 +26,8 @@ from eos.const import FittingHardpoint, FittingModuleState
from eos.utils.float import floatUnerr
from eos.utils.spoolSupport import SpoolType, SpoolOptions
from eos.utils.stats import DmgTypes
-from .base import FitGraph, XDef, YDef, Input, VectorDef
+from gui.builtinGraphs.base import FitGraph, XDef, YDef, Input, VectorDef
+from .calc import getTurretMult, getLauncherMult, getDroneMult, getFighterAbilityMult
class FitDamageStatsGraph(FitGraph):
@@ -96,7 +96,7 @@ class FitDamageStatsGraph(FitGraph):
totalDps += modDps * getLauncherMult(
mod=mod,
fit=fit,
- distance=miscInputMap['distance'],
+ distance=distance,
tgtSpeed=miscInputMap['tgtSpeed'],
tgtSigRadius=tgtSigRad)
xs.append(distance)
@@ -459,186 +459,4 @@ class FitDamageStatsGraph(FitGraph):
return xs, ys
-def getTurretMult(mod, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
- cth = _calcTurretChanceToHit(
- atkSpeed=atkSpeed,
- atkAngle=atkAngle,
- atkRadius=fit.ship.getModifiedItemAttr('radius'),
- atkOptimalRange=mod.maxRange,
- atkFalloffRange=mod.falloff,
- atkTracking=mod.getModifiedItemAttr('trackingSpeed'),
- atkOptimalSigRadius=mod.getModifiedItemAttr('optimalSigRadius'),
- distance=distance,
- tgtSpeed=tgtSpeed,
- tgtAngle=tgtAngle,
- tgtRadius=tgt.ship.getModifiedItemAttr('radius'),
- tgtSigRadius=tgtSigRadius)
- mult = _calcTurretMult(cth)
- return mult
-
-
-def getLauncherMult(mod, fit, distance, tgtSpeed, tgtSigRadius):
- modRange = mod.maxRange
- if modRange is None:
- return 0
- mult = _calcMissileMult(
- atkRadius=fit.ship.getModifiedItemAttr('radius'),
- atkRange=modRange,
- atkEr=mod.getModifiedChargeAttr('aoeCloudSize'),
- atkEv=mod.getModifiedChargeAttr('aoeVelocity'),
- atkDrf=mod.getModifiedChargeAttr('aoeDamageReductionFactor'),
- distance=distance,
- tgtSpeed=tgtSpeed,
- tgtSigRadius=tgtSigRadius)
- return mult
-
-
-def getDroneMult(drone, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
- if distance > fit.extraAttributes['droneControlRange']:
- return 0
- droneSpeed = drone.getModifiedItemAttr('maxVelocity')
- # Hard to simulate drone behavior, so assume chance to hit is 1
- # when drone is not sentry and is faster than its target
- if droneSpeed > 1 and droneSpeed >= tgtSpeed:
- cth = 1
- # Otherwise put the drone into center of the ship, move it at its max speed or ship's speed
- # (whichever is lower) towards direction of attacking ship and see how well it projects
- else:
- droneRadius = drone.getModifiedItemAttr('radius')
- cth = _calcTurretChanceToHit(
- atkSpeed=min(atkSpeed, droneSpeed),
- atkAngle=atkAngle,
- atkRadius=droneRadius,
- atkOptimalRange=drone.maxRange,
- atkFalloffRange=drone.falloff,
- atkTracking=drone.getModifiedItemAttr('trackingSpeed'),
- atkOptimalSigRadius=drone.getModifiedItemAttr('optimalSigRadius'),
- # As distance is ship surface to ship surface, we adjust it according
- # to attacker fit's radiuses to have drone surface to ship surface distance
- distance=distance + fit.ship.getModifiedItemAttr('radius') - droneRadius,
- tgtSpeed=tgtSpeed,
- tgtAngle=tgtAngle,
- tgtRadius=tgt.ship.getModifiedItemAttr('radius'),
- tgtSigRadius=tgtSigRadius)
- mult = _calcTurretMult(cth)
- return mult
-
-
-def getFighterAbilityMult(fighter, ability, fit, distance, tgtSpeed, tgtSigRadius):
- fighterSpeed = fighter.getModifiedItemAttr('maxVelocity')
- attrPrefix = ability.attrPrefix
- if fighterSpeed >= tgtSpeed:
- rangeFactor = 1
- # Same as with drones, if fighters are slower - put them to center of
- # the ship and see how they apply
- else:
- rangeFactor = _calcRangeFactor(
- atkOptimalRange=fighter.getModifiedItemAttr('{}RangeOptimal'.format(attrPrefix)),
- atkFalloffRange=fighter.getModifiedItemAttr('{}RangeFalloff'.format(attrPrefix)),
- distance=distance + fit.ship.getModifiedItemAttr('radius') - fighter.getModifiedItemAttr('radius'))
- drf = fighter.getModifiedItemAttr('{}ReductionFactor'.format(attrPrefix), None)
- if drf is None:
- drf = fighter.getModifiedItemAttr('{}DamageReductionFactor'.format(attrPrefix))
- drs = fighter.getModifiedItemAttr('{}ReductionSensitivity'.format(attrPrefix), None)
- if drs is None:
- drs = fighter.getModifiedItemAttr('{}DamageReductionSensitivity'.format(attrPrefix))
- missileFactor = _calcMissileFactor(
- atkEr=fighter.getModifiedItemAttr('{}ExplosionRadius'.format(attrPrefix)),
- atkEv=fighter.getModifiedItemAttr('{}ExplosionVelocity'.format(attrPrefix)),
- atkDrf=_calcAggregatedDrf(reductionFactor=drf, reductionSensitivity=drs),
- tgtSpeed=tgtSpeed,
- tgtSigRadius=tgtSigRadius)
- mult = rangeFactor * missileFactor
- return mult
-
-
-# Turret-specific
-def _calcTurretMult(chanceToHit):
- """Calculate damage multiplier for turret-based weapons."""
- # https://wiki.eveuniversity.org/Turret_mechanics#Damage
- wreckingChance = min(chanceToHit, 0.01)
- wreckingPart = wreckingChance * 3
- normalChance = chanceToHit - wreckingChance
- if normalChance > 0:
- avgDamageMult = (0.01 + chanceToHit) / 2 + 0.49
- normalPart = normalChance * avgDamageMult
- else:
- normalPart = 0
- totalMult = normalPart + wreckingPart
- return totalMult
-
-
-def _calcTurretChanceToHit(
- atkSpeed, atkAngle, atkRadius, atkOptimalRange, atkFalloffRange, atkTracking, atkOptimalSigRadius,
- distance, tgtSpeed, tgtAngle, tgtRadius, tgtSigRadius
-):
- """Calculate chance to hit for turret-based weapons."""
- # https://wiki.eveuniversity.org/Turret_mechanics#Hit_Math
- angularSpeed = _calcAngularSpeed(atkSpeed, atkAngle, atkRadius, distance, tgtSpeed, tgtAngle, tgtRadius)
- rangeFactor = _calcRangeFactor(atkOptimalRange, atkFalloffRange, distance)
- trackingFactor = _calcTrackingFactor(atkTracking, atkOptimalSigRadius, angularSpeed, tgtSigRadius)
- cth = rangeFactor * trackingFactor
- return cth
-
-
-def _calcAngularSpeed(atkSpeed, atkAngle, atkRadius, distance, tgtSpeed, tgtAngle, tgtRadius):
- """Calculate angular speed based on mobility parameters of two ships."""
- atkAngle = atkAngle * math.pi / 180
- tgtAngle = tgtAngle * math.pi / 180
- ctcDistance = atkRadius + distance + tgtRadius
- # Target is to the right of the attacker, so transversal is projection onto Y axis
- transSpeed = abs(atkSpeed * math.sin(atkAngle) - tgtSpeed * math.sin(tgtAngle))
- if ctcDistance == 0:
- angularSpeed = 0 if transSpeed == 0 else math.inf
- else:
- angularSpeed = transSpeed / ctcDistance
- return angularSpeed
-
-
-def _calcTrackingFactor(atkTracking, atkOptimalSigRadius, angularSpeed, tgtSigRadius):
- """Calculate tracking chance to hit component."""
- return 0.5 ** (((angularSpeed * atkOptimalSigRadius) / (atkTracking * tgtSigRadius)) ** 2)
-
-
-# Missile-specific
-def _calcMissileMult(atkRadius, atkRange, atkEr, atkEv, atkDrf, distance, tgtSpeed, tgtSigRadius):
- """Calculate damage multiplier for missile launcher."""
- # Missiles spawn in the center of the attacking ship
- if distance + atkRadius > atkRange:
- mult = 0
- else:
- mult = _calcMissileFactor(atkEr, atkEv, atkDrf, tgtSpeed, tgtSigRadius)
- return mult
-
-
-def _calcFighterMult(atkOptimalRange, atkFalloffRange, atkEr, atkEv, atkDrf, distance, tgtSpeed, tgtSigRadius):
- """Calculate damage multiplier for separate fighter ability,"""
- rangeFactor = _calcRangeFactor(atkOptimalRange, atkFalloffRange, distance)
- missileFactor = _calcMissileFactor(atkEr, atkEv, atkDrf, tgtSpeed, tgtSigRadius)
- mult = rangeFactor * missileFactor
- return mult
-
-
-def _calcMissileFactor(atkEr, atkEv, atkDrf, tgtSpeed, tgtSigRadius):
- """Missile application."""
- slowPart = tgtSigRadius / atkEr
- fastPart = ((atkEv * tgtSigRadius) / (atkEr * tgtSpeed)) ** atkDrf
- totalMult = min(1, slowPart, fastPart)
- return totalMult
-
-
-def _calcAggregatedDrf(reductionFactor, reductionSensitivity):
- """
- Sometimes DRF is specified as 2 separate numbers,
- here we combine them into generic form.
- """
- return math.log(reductionFactor) / math.log(reductionSensitivity)
-
-
-# Generic
-def _calcRangeFactor(atkOptimalRange, atkFalloffRange, distance):
- """Range strength/chance factor, applicable to guns, ewar, RRs, etc."""
- return 0.5 ** ((max(0, distance - atkOptimalRange) / atkFalloffRange) ** 2)
-
-
FitDamageStatsGraph.register()