Rework DPS graph to use new getters as well
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
22
gui/builtinGraphs/fitDamageStats/cache/__init__.py
vendored
Normal file
22
gui/builtinGraphs/fitDamageStats/cache/__init__.py
vendored
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
from .projected import ProjectedDataCache
|
||||
from .time import TimeCache
|
||||
18
gui/builtinGraphs/fitDamageStats/calc/__init__.py
Normal file
18
gui/builtinGraphs/fitDamageStats/calc/__init__.py
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
@@ -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:
|
||||
135
gui/builtinGraphs/fitDamageStats/calc/projected.py
Normal file
135
gui/builtinGraphs/fitDamageStats/calc/projected.py
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
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
|
||||
407
gui/builtinGraphs/fitDamageStats/getter.py
Normal file
407
gui/builtinGraphs/fitDamageStats/getter.py
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
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
|
||||
@@ -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()
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user