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