Rework DPS graph to use new getters as well

This commit is contained in:
DarkPhoenix
2019-08-02 14:45:11 +03:00
parent 8ebec1f957
commit a63b543e0c
12 changed files with 684 additions and 564 deletions

View File

@@ -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

View 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

View 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/>.
# =============================================================================

View File

@@ -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:

View 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

View 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

View File

@@ -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()

View File

@@ -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):

View File

@@ -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):