Store wrappers in graph lists

This commit is contained in:
DarkPhoenix
2019-08-03 23:56:44 +03:00
parent 1b2bff8a77
commit e821b2d09c
26 changed files with 604 additions and 567 deletions

View File

@@ -28,11 +28,11 @@ class PointGetter(metaclass=ABCMeta):
self.graph = graph
@abstractmethod
def getRange(self, xRange, miscParams, fit, tgt):
def getRange(self, xRange, miscParams, src, tgt):
raise NotImplementedError
@abstractmethod
def getPoint(self, x, miscParams, fit, tgt):
def getPoint(self, x, miscParams, src, tgt):
raise NotImplementedError
@@ -41,16 +41,16 @@ class SmoothPointGetter(PointGetter, metaclass=ABCMeta):
_baseResolution = 200
_extraDepth = 0
def getRange(self, xRange, miscParams, fit, tgt):
def getRange(self, xRange, miscParams, src, tgt):
xs = []
ys = []
commonData = self._getCommonData(miscParams=miscParams, fit=fit, tgt=tgt)
commonData = self._getCommonData(miscParams=miscParams, src=src, tgt=tgt)
def addExtraPoints(x1, y1, x2, y2, depth):
if depth <= 0 or y1 == y2:
return
newX = (x1 + x2) / 2
newY = self._calculatePoint(x=newX, miscParams=miscParams, fit=fit, tgt=tgt, commonData=commonData)
newY = self._calculatePoint(x=newX, miscParams=miscParams, src=src, tgt=tgt, commonData=commonData)
addExtraPoints(x1=prevX, y1=prevY, x2=newX, y2=newY, depth=depth - 1)
xs.append(newX)
ys.append(newY)
@@ -60,7 +60,7 @@ class SmoothPointGetter(PointGetter, metaclass=ABCMeta):
prevY = None
# Go through X points defined by our resolution setting
for x in self._xIterLinear(xRange):
y = self._calculatePoint(x=x, miscParams=miscParams, fit=fit, tgt=tgt, commonData=commonData)
y = self._calculatePoint(x=x, miscParams=miscParams, src=src, tgt=tgt, commonData=commonData)
if prevX is not None and prevY is not None:
# And if Y values of adjacent data points are not equal, add extra points
# depending on extra depth setting
@@ -71,9 +71,9 @@ class SmoothPointGetter(PointGetter, metaclass=ABCMeta):
ys.append(y)
return xs, ys
def getPoint(self, x, miscParams, fit, tgt):
commonData = self._getCommonData(miscParams=miscParams, fit=fit, tgt=tgt)
return self._calculatePoint(x=x, miscParams=miscParams, fit=fit, tgt=tgt, commonData=commonData)
def getPoint(self, x, miscParams, src, tgt):
commonData = self._getCommonData(miscParams=miscParams, src=src, tgt=tgt)
return self._calculatePoint(x=x, miscParams=miscParams, src=src, tgt=tgt, commonData=commonData)
def _xIterLinear(self, xRange):
xLow = min(xRange)
@@ -87,9 +87,9 @@ class SmoothPointGetter(PointGetter, metaclass=ABCMeta):
for i in range(self._baseResolution + 1):
yield xLow + step * i
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, tgt):
return {}
@abstractmethod
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
raise NotImplementedError

View File

@@ -22,8 +22,6 @@ import math
from abc import ABCMeta, abstractmethod
from collections import OrderedDict
from eos.saveddata.fit import Fit
from eos.saveddata.targetProfile import TargetProfile
from eos.utils.float import floatUnerr
from service.const import GraphCacheCleanupReason
@@ -91,18 +89,20 @@ class FitGraph(metaclass=ABCMeta):
tgtVectorDef = None
hasTargets = False
def getPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt=None):
if isinstance(tgt, Fit):
def getPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, src, tgt=None):
if tgt is not None and tgt.isFit:
tgtType = 'fit'
elif isinstance(tgt, TargetProfile):
elif tgt is not None and tgt.isProfile:
tgtType = 'profile'
else:
tgtType = None
cacheKey = (fit.ID, tgtType, getattr(tgt, 'ID', None))
cacheKey = (src.itemID, tgtType, getattr(tgt, 'itemID', None))
try:
plotData = self._plotCache[cacheKey][(ySpec, xSpec)]
except KeyError:
plotData = self._calcPlotPoints(mainInput, miscInputs, xSpec, ySpec, fit, tgt)
plotData = self._calcPlotPoints(
mainInput=mainInput, miscInputs=miscInputs,
xSpec=xSpec, ySpec=ySpec, src=src, tgt=tgt)
self._plotCache.setdefault(cacheKey, {})[(ySpec, xSpec)] = plotData
return plotData
@@ -136,17 +136,23 @@ class FitGraph(metaclass=ABCMeta):
return
# Calculation stuff
def _calcPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt):
mainParamRange, miscParams = self._normalizeInputs(mainInput=mainInput, miscInputs=miscInputs, fit=fit, tgt=tgt)
mainParamRange, miscParams = self._limitParams(mainParamRange=mainParamRange, miscParams=miscParams, fit=fit, tgt=tgt)
xs, ys = self._getPoints(xRange=mainParamRange[1], miscParams=miscParams, xSpec=xSpec, ySpec=ySpec, fit=fit, tgt=tgt)
ys = self._denormalizeValues(ys, ySpec, fit, tgt)
def _calcPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, src, tgt):
mainParamRange, miscParams = self._normalizeInputs(
mainInput=mainInput, miscInputs=miscInputs,
src=src, tgt=tgt)
mainParamRange, miscParams = self._limitParams(
mainParamRange=mainParamRange, miscParams=miscParams,
src=src, tgt=tgt)
xs, ys = self._getPoints(
xRange=mainParamRange[1], miscParams=miscParams,
xSpec=xSpec, ySpec=ySpec, src=src, tgt=tgt)
ys = self._denormalizeValues(values=ys, axisSpec=ySpec, src=src, tgt=tgt)
# Sometimes x denormalizer may fail (e.g. during conversion of 0 ship speed to %).
# If both inputs and outputs are in %, do some extra processing to at least have
# proper graph which shows that fit has the same value over whole specified
# relative parameter range
# proper graph which shows the same value over whole specified relative parameter
# range
try:
xs = self._denormalizeValues(xs, xSpec, fit, tgt)
xs = self._denormalizeValues(values=xs, axisSpec=xSpec, src=src, tgt=tgt)
except ZeroDivisionError:
if mainInput.unit == xSpec.unit == '%' and len(set(floatUnerr(y) for y in ys)) == 1:
xs = [min(mainInput.value), max(mainInput.value)]
@@ -163,11 +169,11 @@ class FitGraph(metaclass=ABCMeta):
_normalizers = {}
def _normalizeInputs(self, mainInput, miscInputs, fit, tgt):
def _normalizeInputs(self, mainInput, miscInputs, src, tgt):
key = (mainInput.handle, mainInput.unit)
if key in self._normalizers:
normalizer = self._normalizers[key]
mainParamRange = (mainInput.handle, tuple(normalizer(v, fit, tgt) for v in mainInput.value))
mainParamRange = (mainInput.handle, tuple(normalizer(v, src, tgt) for v in mainInput.value))
else:
mainParamRange = (mainInput.handle, mainInput.value)
miscParams = []
@@ -175,7 +181,7 @@ class FitGraph(metaclass=ABCMeta):
key = (miscInput.handle, miscInput.unit)
if key in self._normalizers:
normalizer = self._normalizers[key]
miscParam = (miscInput.handle, normalizer(miscInput.value, fit, tgt))
miscParam = (miscInput.handle, normalizer(miscInput.value, src, tgt))
else:
miscParam = (miscInput.handle, miscInput.value)
miscParams.append(miscParam)
@@ -183,7 +189,7 @@ class FitGraph(metaclass=ABCMeta):
_limiters = {}
def _limitParams(self, mainParamRange, miscParams, fit, tgt):
def _limitParams(self, mainParamRange, miscParams, src, tgt):
def limitToRange(val, limitRange):
if val is None:
@@ -195,7 +201,7 @@ class FitGraph(metaclass=ABCMeta):
mainHandle, mainValue = mainParamRange
if mainHandle in self._limiters:
limiter = self._limiters[mainHandle]
newMainParamRange = (mainHandle, tuple(limitToRange(v, limiter(fit, tgt)) for v in mainValue))
newMainParamRange = (mainHandle, tuple(limitToRange(v, limiter(src, tgt)) for v in mainValue))
else:
newMainParamRange = mainParamRange
newMiscParams = []
@@ -203,7 +209,7 @@ class FitGraph(metaclass=ABCMeta):
miscHandle, miscValue = miscParam
if miscHandle in self._limiters:
limiter = self._limiters[miscHandle]
newMiscParam = (miscHandle, limitToRange(miscValue, limiter(fit, tgt)))
newMiscParam = (miscHandle, limitToRange(miscValue, limiter(src, tgt)))
newMiscParams.append(newMiscParam)
else:
newMiscParams.append(miscParam)
@@ -211,20 +217,20 @@ class FitGraph(metaclass=ABCMeta):
_getters = {}
def _getPoints(self, xRange, miscParams, xSpec, ySpec, fit, tgt):
def _getPoints(self, xRange, miscParams, xSpec, ySpec, src, tgt):
try:
getterClass = self._getters[(xSpec.handle, ySpec.handle)]
except KeyError:
return [], []
else:
getter = getterClass(graph=self)
return getter.getRange(xRange=xRange, miscParams=miscParams, fit=fit, tgt=tgt)
return getter.getRange(xRange=xRange, miscParams=miscParams, src=src, tgt=tgt)
_denormalizers = {}
def _denormalizeValues(self, values, axisSpec, fit, tgt):
def _denormalizeValues(self, values, axisSpec, src, tgt):
key = (axisSpec.handle, axisSpec.unit)
if key in self._denormalizers:
denormalizer = self._denormalizers[key]
values = [denormalizer(v, fit, tgt) for v in values]
values = [denormalizer(v, src, tgt) for v in values]
return values

View File

@@ -25,12 +25,12 @@ from graphs.data.base import SmoothPointGetter
class Time2CapAmountGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, tgt):
return {
'maxCapAmount': fit.ship.getModifiedItemAttr('capacitorCapacity'),
'capRegenTime': fit.ship.getModifiedItemAttr('rechargeRate') / 1000}
'maxCapAmount': src.item.ship.getModifiedItemAttr('capacitorCapacity'),
'capRegenTime': src.item.ship.getModifiedItemAttr('rechargeRate') / 1000}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
time = x
capAmount = calculateCapAmount(
maxCapAmount=commonData['maxCapAmount'],
@@ -41,12 +41,12 @@ class Time2CapAmountGetter(SmoothPointGetter):
class Time2CapRegenGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, tgt):
return {
'maxCapAmount': fit.ship.getModifiedItemAttr('capacitorCapacity'),
'capRegenTime': fit.ship.getModifiedItemAttr('rechargeRate') / 1000}
'maxCapAmount': src.item.ship.getModifiedItemAttr('capacitorCapacity'),
'capRegenTime': src.item.ship.getModifiedItemAttr('rechargeRate') / 1000}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
time = x
capAmount = calculateCapAmount(
maxCapAmount=commonData['maxCapAmount'],
@@ -62,19 +62,19 @@ class Time2CapRegenGetter(SmoothPointGetter):
# Useless, but valid combination of x and y
class CapAmount2CapAmountGetter(SmoothPointGetter):
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
capAmount = x
return capAmount
class CapAmount2CapRegenGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, tgt):
return {
'maxCapAmount': fit.ship.getModifiedItemAttr('capacitorCapacity'),
'capRegenTime': fit.ship.getModifiedItemAttr('rechargeRate') / 1000}
'maxCapAmount': src.item.ship.getModifiedItemAttr('capacitorCapacity'),
'capRegenTime': src.item.ship.getModifiedItemAttr('rechargeRate') / 1000}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
capAmount = x
capRegen = calculateCapRegen(
maxCapAmount=commonData['maxCapAmount'],

View File

@@ -41,13 +41,13 @@ class FitCapRegenGraph(FitGraph):
# Calculation stuff
_normalizers = {
('capAmount', '%'): lambda v, fit, tgt: v / 100 * fit.ship.getModifiedItemAttr('capacitorCapacity')}
('capAmount', '%'): lambda v, src, tgt: v / 100 * src.item.ship.getModifiedItemAttr('capacitorCapacity')}
_limiters = {
'capAmount': lambda fit, tgt: (0, fit.ship.getModifiedItemAttr('capacitorCapacity'))}
'capAmount': lambda src, tgt: (0, src.item.ship.getModifiedItemAttr('capacitorCapacity'))}
_getters = {
('time', 'capAmount'): Time2CapAmountGetter,
('time', 'capRegen'): Time2CapRegenGetter,
('capAmount', 'capAmount'): CapAmount2CapAmountGetter,
('capAmount', 'capRegen'): CapAmount2CapRegenGetter}
_denormalizers = {
('capAmount', '%'): lambda v, fit, tgt: v * 100 / fit.ship.getModifiedItemAttr('capacitorCapacity')}
('capAmount', '%'): lambda v, src, tgt: v * 100 / src.item.ship.getModifiedItemAttr('capacitorCapacity')}

View File

@@ -31,15 +31,15 @@ MobileProjData = namedtuple('MobileProjData', ('boost', 'optimal', 'falloff', 's
class ProjectedDataCache(FitDataCache):
def getProjModData(self, fit):
def getProjModData(self, src):
try:
projectedData = self._data[fit.ID]['modules']
projectedData = self._data[src.item.ID]['modules']
except KeyError:
# Format of items for both: (boost strength, optimal, falloff, stacking group, resistance attr ID)
webMods = []
tpMods = []
projectedData = self._data.setdefault(fit.ID, {})['modules'] = (webMods, tpMods)
for mod in fit.modules:
projectedData = self._data.setdefault(src.item.ID, {})['modules'] = (webMods, tpMods)
for mod in src.item.modules:
if mod.state <= FittingModuleState.ONLINE:
continue
for webEffectName in ('remoteWebifierFalloff', 'structureModuleEffectStasisWebifier'):
@@ -53,7 +53,7 @@ class ProjectedDataCache(FitDataCache):
if 'doomsdayAOEWeb' in mod.item.effects:
webMods.append(ModProjData(
mod.getModifiedItemAttr('speedFactor'),
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - fit.ship.getModifiedItemAttr('radius')),
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
mod.falloff or 0,
'default',
getResistanceAttrID(modifyingItem=mod, effect=mod.item.effects['doomsdayAOEWeb'])))
@@ -68,21 +68,21 @@ class ProjectedDataCache(FitDataCache):
if 'doomsdayAOEPaint' in mod.item.effects:
tpMods.append(ModProjData(
mod.getModifiedItemAttr('signatureRadiusBonus'),
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - fit.ship.getModifiedItemAttr('radius')),
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange') - src.getRadius()),
mod.falloff or 0,
'default',
getResistanceAttrID(modifyingItem=mod, effect=mod.item.effects['doomsdayAOEPaint'])))
return projectedData
def getProjDroneData(self, fit):
def getProjDroneData(self, src):
try:
projectedData = self._data[fit.ID]['drones']
projectedData = self._data[src.item.ID]['drones']
except KeyError:
# Format of items for both: (boost strength, optimal, falloff, stacking group, resistance attr ID, drone speed, drone radius)
webDrones = []
tpDrones = []
projectedData = self._data.setdefault(fit.ID, {})['drones'] = (webDrones, tpDrones)
for drone in fit.drones:
projectedData = self._data.setdefault(src.item.ID, {})['drones'] = (webDrones, tpDrones)
for drone in src.item.drones:
if drone.amountActive <= 0:
continue
if 'remoteWebifierEntity' in drone.item.effects:
@@ -105,15 +105,15 @@ class ProjectedDataCache(FitDataCache):
drone.getModifiedItemAttr('radius')),))
return projectedData
def getProjFighterData(self, fit):
def getProjFighterData(self, src):
try:
projectedData = self._data[fit.ID]['fighters']
projectedData = self._data[src.item.ID]['fighters']
except KeyError:
# Format of items for both: (boost strength, optimal, falloff, stacking group, resistance attr ID, fighter speed, fighter radius)
webFighters = []
tpFighters = []
projectedData = self._data.setdefault(fit.ID, {})['fighters'] = (webFighters, tpFighters)
for fighter in fit.fighters:
projectedData = self._data.setdefault(src.item.ID, {})['fighters'] = (webFighters, tpFighters)
for fighter in src.item.fighters:
if not fighter.active:
continue
for ability in fighter.abilities:

View File

@@ -29,45 +29,45 @@ from graphs.data.base import FitDataCache
class TimeCache(FitDataCache):
# Whole data getters
def getDpsData(self, fit):
def getDpsData(self, src):
"""Return DPS data in {time: {key: dps}} format."""
return self._data[fit.ID]['finalDps']
return self._data[src.item.ID]['finalDps']
def getVolleyData(self, fit):
def getVolleyData(self, src):
"""Return volley data in {time: {key: volley}} format."""
return self._data[fit.ID]['finalVolley']
return self._data[src.item.ID]['finalVolley']
def getDmgData(self, fit):
def getDmgData(self, src):
"""Return inflicted damage data in {time: {key: damage}} format."""
return self._data[fit.ID]['finalDmg']
return self._data[src.item.ID]['finalDmg']
# Specific data point getters
def getDpsDataPoint(self, fit, time):
def getDpsDataPoint(self, src, time):
"""Get DPS data by specified time in {key: dps} format."""
return self._getDataPoint(fit, time, self.getDpsData)
return self._getDataPoint(src=src, time=time, dataFunc=self.getDpsData)
def getVolleyDataPoint(self, fit, time):
def getVolleyDataPoint(self, src, time):
"""Get volley data by specified time in {key: volley} format."""
return self._getDataPoint(fit, time, self.getVolleyData)
return self._getDataPoint(src=src, time=time, dataFunc=self.getVolleyData)
def getDmgDataPoint(self, fit, time):
def getDmgDataPoint(self, src, time):
"""Get inflicted damage data by specified time in {key: dmg} format."""
return self._getDataPoint(fit, time, self.getDmgData)
return self._getDataPoint(src=src, time=time, dataFunc=self.getDmgData)
# Preparation functions
def prepareDpsData(self, fit, maxTime):
self._prepareDpsVolleyData(fit, maxTime)
def prepareDpsData(self, src, maxTime):
self._prepareDpsVolleyData(src=src, maxTime=maxTime)
def prepareVolleyData(self, fit, maxTime):
self._prepareDpsVolleyData(fit, maxTime)
def prepareVolleyData(self, src, maxTime):
self._prepareDpsVolleyData(src=src, maxTime=maxTime)
def prepareDmgData(self, fit, maxTime):
def prepareDmgData(self, src, maxTime):
# Time is none means that time parameter has to be ignored,
# we do not need cache for that
if maxTime is None:
return
self._generateInternalForm(fit, maxTime)
fitCache = self._data[fit.ID]
self._generateInternalForm(src=src, maxTime=maxTime)
fitCache = self._data[src.item.ID]
# Final cache has been generated already, don't do anything
if 'finalDmg' in fitCache:
return
@@ -93,13 +93,13 @@ class TimeCache(FitDataCache):
del fitCache['internalDmg']
# Private stuff
def _prepareDpsVolleyData(self, fit, maxTime):
def _prepareDpsVolleyData(self, src, maxTime):
# Time is none means that time parameter has to be ignored,
# we do not need cache for that
if maxTime is None:
return True
self._generateInternalForm(fit, maxTime)
fitCache = self._data[fit.ID]
self._generateInternalForm(src=src, maxTime=maxTime)
fitCache = self._data[src.item.ID]
# Final cache has been generated already, don't do anything
if 'finalDps' in fitCache and 'finalVolley' in fitCache:
return
@@ -147,10 +147,10 @@ class TimeCache(FitDataCache):
finalDpsCache[time] = timeDpsData
finalVolleyCache[time] = timeVolleyData
def _generateInternalForm(self, fit, maxTime):
if self._isTimeCacheValid(fit, maxTime):
def _generateInternalForm(self, src, maxTime):
if self._isTimeCacheValid(src=src, maxTime=maxTime):
return
fitCache = self._data[fit.ID] = {'maxTime': maxTime}
fitCache = self._data[src.item.ID] = {'maxTime': maxTime}
intCacheDpsVolley = fitCache['internalDpsVolley'] = {}
intCacheDmg = fitCache['internalDmg'] = {}
@@ -173,7 +173,7 @@ class TimeCache(FitDataCache):
intCacheDmg.setdefault(ddKey, {})[addedTime] = addedDmg
# Modules
for mod in fit.modules:
for mod in src.item.modules:
if not mod.isDealingDamage():
continue
cycleParams = mod.getCycleParameters(reloadOverride=True)
@@ -196,7 +196,7 @@ class TimeCache(FitDataCache):
break
currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
# Drones
for drone in fit.drones:
for drone in src.item.drones:
if not drone.isDealingDamage():
continue
cycleParams = drone.getCycleParameters(reloadOverride=True)
@@ -214,7 +214,7 @@ class TimeCache(FitDataCache):
break
currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
# Fighters
for fighter in fit.fighters:
for fighter in src.item.fighters:
if not fighter.isDealingDamage():
continue
cycleParams = fighter.getCycleParametersPerEffectOptimizedDps(reloadOverride=True)
@@ -236,15 +236,15 @@ class TimeCache(FitDataCache):
break
currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
def _isTimeCacheValid(self, fit, maxTime):
def _isTimeCacheValid(self, src, maxTime):
try:
cacheMaxTime = self._data[fit.ID]['maxTime']
cacheMaxTime = self._data[src.item.ID]['maxTime']
except KeyError:
return False
return maxTime <= cacheMaxTime
def _getDataPoint(self, fit, time, dataFunc):
data = dataFunc(fit)
def _getDataPoint(self, src, time, dataFunc):
data = dataFunc(src)
timesBefore = [t for t in data if floatUnerr(t) <= floatUnerr(time)]
try:
time = max(timesBefore)

View File

@@ -22,22 +22,20 @@ import math
from functools import lru_cache
from eos.const import FittingHardpoint
from eos.saveddata.fit import Fit
from eos.utils.float import floatUnerr
from graphs.data.fitDamageStats.helper import getTgtRadius
from service.const import GraphDpsDroneMode
from service.settings import GraphSettings
def getApplicationPerKey(fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
applicationMap = {}
for mod in fit.modules:
for mod in src.item.modules:
if not mod.isDealingDamage():
continue
if mod.hardpoint == FittingHardpoint.TURRET:
applicationMap[mod] = getTurretMult(
mod=mod,
fit=fit,
src=src,
tgt=tgt,
atkSpeed=atkSpeed,
atkAngle=atkAngle,
@@ -48,7 +46,7 @@ def getApplicationPerKey(fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
elif mod.hardpoint == FittingHardpoint.MISSILE:
applicationMap[mod] = getLauncherMult(
mod=mod,
fit=fit,
src=src,
distance=distance,
tgtSpeed=tgtSpeed,
tgtSigRadius=tgtSigRadius)
@@ -59,14 +57,14 @@ def getApplicationPerKey(fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
elif mod.item.group.name == 'Missile Launcher Bomb':
applicationMap[mod] = getBombMult(
mod=mod,
fit=fit,
src=src,
tgt=tgt,
distance=distance,
tgtSigRadius=tgtSigRadius)
elif mod.item.group.name == 'Structure Guided Bomb Launcher':
applicationMap[mod] = getGuidedBombMult(
mod=mod,
fit=fit,
src=src,
distance=distance,
tgtSigRadius=tgtSigRadius)
elif mod.item.group.name in ('Super Weapon', 'Structure Doomsday Weapon'):
@@ -75,12 +73,12 @@ def getApplicationPerKey(fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
tgt=tgt,
distance=distance,
tgtSigRadius=tgtSigRadius)
for drone in fit.drones:
for drone in src.item.drones:
if not drone.isDealingDamage():
continue
applicationMap[drone] = getDroneMult(
drone=drone,
fit=fit,
src=src,
tgt=tgt,
atkSpeed=atkSpeed,
atkAngle=atkAngle,
@@ -88,7 +86,7 @@ def getApplicationPerKey(fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
tgtSpeed=tgtSpeed,
tgtAngle=tgtAngle,
tgtSigRadius=tgtSigRadius)
for fighter in fit.fighters:
for fighter in src.item.fighters:
if not fighter.isDealingDamage():
continue
for ability in fighter.abilities:
@@ -97,7 +95,7 @@ def getApplicationPerKey(fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
applicationMap[(fighter, ability.effectID)] = getFighterAbilityMult(
fighter=fighter,
ability=ability,
fit=fit,
src=src,
distance=distance,
tgtSpeed=tgtSpeed,
tgtSigRadius=tgtSigRadius)
@@ -108,11 +106,11 @@ def getApplicationPerKey(fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn
# Item application multiplier calculation
def getTurretMult(mod, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
def getTurretMult(mod, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
cth = _calcTurretChanceToHit(
atkSpeed=atkSpeed,
atkAngle=atkAngle,
atkRadius=fit.ship.getModifiedItemAttr('radius'),
atkRadius=src.getRadius(),
atkOptimalRange=mod.maxRange,
atkFalloffRange=mod.falloff,
atkTracking=mod.getModifiedItemAttr('trackingSpeed'),
@@ -120,17 +118,17 @@ def getTurretMult(mod, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngl
distance=distance,
tgtSpeed=tgtSpeed,
tgtAngle=tgtAngle,
tgtRadius=getTgtRadius(tgt),
tgtRadius=tgt.getRadius(),
tgtSigRadius=tgtSigRadius)
mult = _calcTurretMult(cth)
return mult
def getLauncherMult(mod, fit, distance, tgtSpeed, tgtSigRadius):
def getLauncherMult(mod, src, distance, tgtSpeed, tgtSigRadius):
modRange = mod.maxRange
if modRange is None:
return 0
if distance is not None and distance + fit.ship.getModifiedItemAttr('radius') > modRange:
if distance is not None and distance + src.getRadius() > modRange:
return 0
mult = _calcMissileFactor(
atkEr=mod.getModifiedChargeAttr('aoeCloudSize'),
@@ -157,8 +155,8 @@ def getDoomsdayMult(mod, tgt, distance, tgtSigRadius):
return 0
# Single-target titan DDs are vs capitals only
if {'superWeaponAmarr', 'superWeaponCaldari', 'superWeaponGallente', 'superWeaponMinmatar'}.intersection(mod.item.effects):
# Disallow only against subcap fits, allow against cap fits and tgt profiles
if isinstance(tgt, Fit) and not tgt.ship.item.requiresSkill('Capital Ships'):
# Disallow only against subcaps, allow against caps and tgt profiles
if tgt.isFit and not tgt.item.ship.item.requiresSkill('Capital Ships'):
return 0
damageSig = mod.getModifiedItemAttr('doomsdayDamageRadius') or mod.getModifiedItemAttr('signatureRadius')
if not damageSig:
@@ -166,13 +164,13 @@ def getDoomsdayMult(mod, tgt, distance, tgtSigRadius):
return min(1, tgtSigRadius / damageSig)
def getBombMult(mod, fit, tgt, distance, tgtSigRadius):
def getBombMult(mod, src, tgt, distance, tgtSigRadius):
modRange = mod.maxRange
if modRange is None:
return 0
blastRadius = mod.getModifiedChargeAttr('explosionRange')
atkRadius = fit.ship.getModifiedItemAttr('radius')
tgtRadius = getTgtRadius(tgt)
atkRadius = src.getRadius()
tgtRadius = tgt.getRadius()
# Bomb starts in the center of the ship
# Also here we assume that it affects target as long as blast
# touches its surface, not center - I did not check this
@@ -185,11 +183,11 @@ def getBombMult(mod, fit, tgt, distance, tgtSigRadius):
tgtSigRadius=tgtSigRadius)
def getGuidedBombMult(mod, fit, distance, tgtSigRadius):
def getGuidedBombMult(mod, src, distance, tgtSigRadius):
modRange = mod.maxRange
if modRange is None:
return 0
if distance is not None and distance > modRange - fit.ship.getModifiedItemAttr('radius'):
if distance is not None and distance > modRange - src.getRadius():
return 0
eR = mod.getModifiedChargeAttr('aoeCloudSize')
if eR == 0:
@@ -198,8 +196,8 @@ def getGuidedBombMult(mod, fit, distance, tgtSigRadius):
return min(1, tgtSigRadius / eR)
def getDroneMult(drone, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
if distance is not None and distance > fit.extraAttributes['droneControlRange']:
def getDroneMult(drone, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
if distance is not None and distance > src.item.extraAttributes['droneControlRange']:
return 0
droneSpeed = drone.getModifiedItemAttr('maxVelocity')
# Hard to simulate drone behavior, so assume chance to hit is 1 for mobile drones
@@ -219,8 +217,8 @@ def getDroneMult(drone, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAng
cthDistance = None
else:
# As distance is ship surface to ship surface, we adjust it according
# to attacker fit's radiuses to have drone surface to ship surface distance
cthDistance = distance + fit.ship.getModifiedItemAttr('radius') - droneRadius
# to attacker ship's radiuses to have drone surface to ship surface distance
cthDistance = distance + src.getRadius() - droneRadius
cth = _calcTurretChanceToHit(
atkSpeed=min(atkSpeed, droneSpeed),
atkAngle=atkAngle,
@@ -232,13 +230,13 @@ def getDroneMult(drone, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAng
distance=cthDistance,
tgtSpeed=tgtSpeed,
tgtAngle=tgtAngle,
tgtRadius=getTgtRadius(tgt),
tgtRadius=tgt.getRadius(),
tgtSigRadius=tgtSigRadius)
mult = _calcTurretMult(cth)
return mult
def getFighterAbilityMult(fighter, ability, fit, distance, tgtSpeed, tgtSigRadius):
def getFighterAbilityMult(fighter, ability, src, distance, tgtSpeed, tgtSigRadius):
fighterSpeed = fighter.getModifiedItemAttr('maxVelocity')
attrPrefix = ability.attrPrefix
# It's bomb attack
@@ -257,7 +255,7 @@ def getFighterAbilityMult(fighter, ability, fit, distance, tgtSpeed, tgtSigRadiu
if distance is None:
rangeFactorDistance = None
else:
rangeFactorDistance = distance + fit.ship.getModifiedItemAttr('radius') - fighter.getModifiedItemAttr('radius')
rangeFactorDistance = distance + src.getRadius() - fighter.getModifiedItemAttr('radius')
rangeFactor = _calcRangeFactor(
atkOptimalRange=fighter.getModifiedItemAttr('{}RangeOptimal'.format(attrPrefix)) or fighter.getModifiedItemAttr('{}Range'.format(attrPrefix)),
atkFalloffRange=fighter.getModifiedItemAttr('{}RangeFalloff'.format(attrPrefix)),

View File

@@ -20,19 +20,17 @@
import math
from eos.saveddata.fit import Fit
from eos.utils.float import floatUnerr
from graphs.data.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'):
def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighters, distance):
# Can slow down non-immune ships and target profiles
if tgt.isFit and tgt.item.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
return currentUnwebbedSpeed
maxUnwebbedSpeed = getTgtMaxVelocity(tgt)
maxUnwebbedSpeed = tgt.getMaxVelocity()
try:
speedRatio = currentUnwebbedSpeed / maxUnwebbedSpeed
except ZeroDivisionError:
@@ -47,15 +45,15 @@ def getWebbedSpeed(fit, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
distance=distance)
if appliedBoost:
appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID))
maxWebbedSpeed = getTgtMaxVelocity(tgt, extraMultipliers=appliedMultipliers)
maxWebbedSpeed = tgt.getMaxVelocity(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']:
if distance is None or distance <= src.extraAttributes['droneControlRange']:
mobileWebs.extend(webDrones)
atkRadius = fit.ship.getModifiedItemAttr('radius')
atkRadius = src.getRadius()
# 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]
@@ -63,7 +61,7 @@ def getWebbedSpeed(fit, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
for mwData in longEnoughMws:
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + mwData.boost / 100, mwData.resAttrID))
mobileWebs.remove(mwData)
maxWebbedSpeed = getTgtMaxVelocity(tgt, extraMultipliers=appliedMultipliers)
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
currentWebbedSpeed = maxWebbedSpeed * speedRatio
# Apply remaining webs, from fastest to slowest
droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
@@ -87,17 +85,17 @@ def getWebbedSpeed(fit, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighte
distance=rangeFactorDistance)
appliedMultipliers.setdefault(mwData.stackingGroup, []).append((1 + appliedMwBoost / 100, mwData.resAttrID))
mobileWebs.remove(mwData)
maxWebbedSpeed = getTgtMaxVelocity(tgt, extraMultipliers=appliedMultipliers)
maxWebbedSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers)
currentWebbedSpeed = maxWebbedSpeed * speedRatio
# Ensure consistent results - round off a little to avoid float errors
return floatUnerr(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'):
def getTpMult(src, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance):
# Can blow non-immune ships and target profiles
if tgt.isFit and tgt.item.ship.getModifiedItemAttr('disallowOffensiveModifiers'):
return 1
untpedSig = getTgtSigRadius(tgt)
untpedSig = tgt.getSigRadius()
# Modules
appliedMultipliers = {}
for tpData in tpMods:
@@ -111,10 +109,10 @@ def getTpMult(fit, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance):
mobileTps = []
mobileTps.extend(tpFighters)
# Drones have range limit
if distance is None or distance <= fit.extraAttributes['droneControlRange']:
if distance is None or distance <= src.item.extraAttributes['droneControlRange']:
mobileTps.extend(tpDrones)
droneOpt = GraphSettings.getInstance().get('mobileDroneMode')
atkRadius = fit.ship.getModifiedItemAttr('radius')
atkRadius = src.getRadius()
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:
@@ -130,7 +128,7 @@ def getTpMult(fit, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance):
atkFalloffRange=mtpData.falloff,
distance=rangeFactorDistance)
appliedMultipliers.setdefault(mtpData.stackingGroup, []).append((1 + appliedMtpBoost / 100, mtpData.resAttrID))
tpedSig = getTgtSigRadius(tgt, extraMultipliers=appliedMultipliers)
tpedSig = tgt.getSigRadius(extraMultipliers=appliedMultipliers)
if tpedSig == math.inf and untpedSig == math.inf:
return 1
mult = tpedSig / untpedSig

View File

@@ -25,7 +25,6 @@ from graphs.data.base import PointGetter, SmoothPointGetter
from service.settings import GraphSettings
from .calc.application import getApplicationPerKey
from .calc.projected import getTpMult, getWebbedSpeed
from .helper import getTgtSigRadius
def applyDamage(dmgMap, applicationMap):
@@ -38,88 +37,88 @@ def applyDamage(dmgMap, applicationMap):
# Y mixins
class YDpsMixin:
def _getDamagePerKey(self, fit, time):
def _getDamagePerKey(self, src, time):
# Use data from time cache if time was not specified
if time is not None:
return self._getTimeCacheDataPoint(fit=fit, time=time)
return self._getTimeCacheDataPoint(src=src, time=time)
# Compose map ourselves using current fit settings if time is not specified
dpsMap = {}
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
for mod in fit.modules:
for mod in src.item.modules:
if not mod.isDealingDamage():
continue
dpsMap[mod] = mod.getDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
for drone in fit.drones:
for drone in src.item.drones:
if not drone.isDealingDamage():
continue
dpsMap[drone] = drone.getDps()
for fighter in fit.fighters:
for fighter in src.item.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 _prepareTimeCache(self, src, maxTime):
self.graph._timeCache.prepareDpsData(src=src, maxTime=maxTime)
def _getTimeCacheData(self, fit):
return self.graph._timeCache.getDpsData(fit=fit)
def _getTimeCacheData(self, src):
return self.graph._timeCache.getDpsData(src=src)
def _getTimeCacheDataPoint(self, fit, time):
return self.graph._timeCache.getDpsDataPoint(fit=fit, time=time)
def _getTimeCacheDataPoint(self, src, time):
return self.graph._timeCache.getDpsDataPoint(src=src, time=time)
class YVolleyMixin:
def _getDamagePerKey(self, fit, time):
def _getDamagePerKey(self, src, time):
# Use data from time cache if time was not specified
if time is not None:
return self._getTimeCacheDataPoint(fit=fit, time=time)
return self._getTimeCacheDataPoint(src=src, time=time)
# Compose map ourselves using current fit settings if time is not specified
volleyMap = {}
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
for mod in fit.modules:
for mod in src.item.modules:
if not mod.isDealingDamage():
continue
volleyMap[mod] = mod.getVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
for drone in fit.drones:
for drone in src.item.drones:
if not drone.isDealingDamage():
continue
volleyMap[drone] = drone.getVolley()
for fighter in fit.fighters:
for fighter in src.item.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 _prepareTimeCache(self, src, maxTime):
self.graph._timeCache.prepareVolleyData(src=src, maxTime=maxTime)
def _getTimeCacheData(self, fit):
return self.graph._timeCache.getVolleyData(fit=fit)
def _getTimeCacheData(self, src):
return self.graph._timeCache.getVolleyData(src=src)
def _getTimeCacheDataPoint(self, fit, time):
return self.graph._timeCache.getVolleyDataPoint(fit=fit, time=time)
def _getTimeCacheDataPoint(self, src, time):
return self.graph._timeCache.getVolleyDataPoint(src=src, time=time)
class YInflictedDamageMixin:
def _getDamagePerKey(self, fit, time):
def _getDamagePerKey(self, src, time):
# Damage inflicted makes no sense without time specified
if time is None:
raise ValueError
return self._getTimeCacheDataPoint(fit=fit, time=time)
return self._getTimeCacheDataPoint(src=src, time=time)
def _prepareTimeCache(self, fit, maxTime):
self.graph._timeCache.prepareDmgData(fit=fit, maxTime=maxTime)
def _prepareTimeCache(self, src, maxTime):
self.graph._timeCache.prepareDmgData(src=src, maxTime=maxTime)
def _getTimeCacheData(self, fit):
return self.graph._timeCache.getDmgData(fit=fit)
def _getTimeCacheData(self, src):
return self.graph._timeCache.getDmgData(src=src)
def _getTimeCacheDataPoint(self, fit, time):
return self.graph._timeCache.getDmgDataPoint(fit=fit, time=time)
def _getTimeCacheDataPoint(self, src, time):
return self.graph._timeCache.getDmgDataPoint(src=src, time=time)
# X mixins
@@ -128,28 +127,28 @@ class XDistanceMixin(SmoothPointGetter):
_baseResolution = 50
_extraDepth = 2
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, 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'])
self._prepareTimeCache(src=src, maxTime=miscParamMap['time'])
return {
'applyProjected': GraphSettings.getInstance().get('applyProjected'),
'miscParamMap': miscParamMap,
'dmgMap': self._getDamagePerKey(fit=fit, time=miscParamMap['time'])}
'dmgMap': self._getDamagePerKey(src=src, time=miscParamMap['time'])}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
distance = x
miscParamMap = commonData['miscParamMap']
tgtSpeed = miscParamMap['tgtSpeed']
tgtSigRadius = getTgtSigRadius(tgt)
tgtSigRadius = tgt.getSigRadius()
if commonData['applyProjected']:
webMods, tpMods = self.graph._projectedCache.getProjModData(fit)
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(fit)
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(fit)
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
tgtSpeed = getWebbedSpeed(
fit=fit,
src=src,
tgt=tgt,
currentUnwebbedSpeed=tgtSpeed,
webMods=webMods,
@@ -157,7 +156,7 @@ class XDistanceMixin(SmoothPointGetter):
webFighters=webFighters,
distance=distance)
tgtSigRadius = tgtSigRadius * getTpMult(
fit=fit,
src=src,
tgt=tgt,
tgtSpeed=tgtSpeed,
tpMods=tpMods,
@@ -165,7 +164,7 @@ class XDistanceMixin(SmoothPointGetter):
tpFighters=tpFighters,
distance=distance)
applicationMap = getApplicationPerKey(
fit=fit,
src=src,
tgt=tgt,
atkSpeed=miscParamMap['atkSpeed'],
atkAngle=miscParamMap['atkAngle'],
@@ -179,17 +178,17 @@ class XDistanceMixin(SmoothPointGetter):
class XTimeMixin(PointGetter):
def _prepareApplicationMap(self, miscParams, fit, tgt):
def _prepareApplicationMap(self, miscParams, src, tgt):
# Process params into more convenient form
miscParamMap = dict(miscParams)
tgtSpeed = miscParamMap['tgtSpeed']
tgtSigRadius = getTgtSigRadius(tgt)
tgtSigRadius = tgt.getSigRadius()
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)
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
tgtSpeed = getWebbedSpeed(
fit=fit,
src=src,
tgt=tgt,
currentUnwebbedSpeed=tgtSpeed,
webMods=webMods,
@@ -197,7 +196,7 @@ class XTimeMixin(PointGetter):
webFighters=webFighters,
distance=miscParamMap['distance'])
tgtSigRadius = tgtSigRadius * getTpMult(
fit=fit,
src=src,
tgt=tgt,
tgtSpeed=tgtSpeed,
tpMods=tpMods,
@@ -206,7 +205,7 @@ class XTimeMixin(PointGetter):
distance=miscParamMap['distance'])
# Get all data we need for all times into maps/caches
applicationMap = getApplicationPerKey(
fit=fit,
src=src,
tgt=tgt,
atkSpeed=miscParamMap['atkSpeed'],
atkAngle=miscParamMap['atkAngle'],
@@ -216,14 +215,14 @@ class XTimeMixin(PointGetter):
tgtSigRadius=tgtSigRadius)
return applicationMap
def getRange(self, xRange, miscParams, fit, tgt):
def getRange(self, xRange, miscParams, src, tgt):
xs = []
ys = []
minTime, maxTime = xRange
# Prepare time cache and various shared data
self._prepareTimeCache(fit=fit, maxTime=maxTime)
timeCache = self._getTimeCacheData(fit=fit)
applicationMap = self._prepareApplicationMap(miscParams=miscParams, fit=fit, tgt=tgt)
self._prepareTimeCache(src=src, maxTime=maxTime)
timeCache = self._getTimeCacheData(src=src)
applicationMap = self._prepareApplicationMap(miscParams=miscParams, src=src, tgt=tgt)
# Custom iteration for time graph to show all data points
currentDmg = None
currentTime = None
@@ -270,12 +269,12 @@ class XTimeMixin(PointGetter):
ys.append(currentDmg or 0)
return xs, ys
def getPoint(self, x, miscParams, fit, tgt):
def getPoint(self, x, miscParams, src, tgt):
time = x
# Prepare time cache and various data
self._prepareTimeCache(fit=fit, maxTime=time)
dmgData = self._getTimeCacheDataPoint(fit=fit, time=time)
applicationMap = self._prepareApplicationMap(miscParams=miscParams, fit=fit, tgt=tgt)
self._prepareTimeCache(src=src, maxTime=time)
dmgData = self._getTimeCacheDataPoint(src=src, time=time)
applicationMap = self._prepareApplicationMap(miscParams=miscParams, src=src, tgt=tgt)
y = applyDamage(dmgMap=dmgData, applicationMap=applicationMap).total
return y
@@ -285,27 +284,27 @@ class XTgtSpeedMixin(SmoothPointGetter):
_baseResolution = 50
_extraDepth = 2
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, 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'])
self._prepareTimeCache(src=src, maxTime=miscParamMap['time'])
return {
'applyProjected': GraphSettings.getInstance().get('applyProjected'),
'miscParamMap': miscParamMap,
'dmgMap': self._getDamagePerKey(fit=fit, time=miscParamMap['time'])}
'dmgMap': self._getDamagePerKey(src=src, time=miscParamMap['time'])}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
tgtSpeed = x
miscParamMap = commonData['miscParamMap']
tgtSigRadius = getTgtSigRadius(tgt)
tgtSigRadius = tgt.getSigRadius()
if commonData['applyProjected']:
webMods, tpMods = self.graph._projectedCache.getProjModData(fit)
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(fit)
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(fit)
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
tgtSpeed = getWebbedSpeed(
fit=fit,
src=src,
tgt=tgt,
currentUnwebbedSpeed=tgtSpeed,
webMods=webMods,
@@ -313,7 +312,7 @@ class XTgtSpeedMixin(SmoothPointGetter):
webFighters=webFighters,
distance=miscParamMap['distance'])
tgtSigRadius = tgtSigRadius * getTpMult(
fit=fit,
src=src,
tgt=tgt,
tgtSpeed=tgtSpeed,
tpMods=tpMods,
@@ -321,7 +320,7 @@ class XTgtSpeedMixin(SmoothPointGetter):
tpFighters=tpFighters,
distance=miscParamMap['distance'])
applicationMap = getApplicationPerKey(
fit=fit,
src=src,
tgt=tgt,
atkSpeed=miscParamMap['atkSpeed'],
atkAngle=miscParamMap['atkAngle'],
@@ -338,17 +337,17 @@ class XTgtSigRadiusMixin(SmoothPointGetter):
_baseResolution = 50
_extraDepth = 2
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, 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)
webMods, tpMods = self.graph._projectedCache.getProjModData(src)
webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src)
webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src)
tgtSpeed = getWebbedSpeed(
fit=fit,
src=src,
tgt=tgt,
currentUnwebbedSpeed=tgtSpeed,
webMods=webMods,
@@ -356,7 +355,7 @@ class XTgtSigRadiusMixin(SmoothPointGetter):
webFighters=webFighters,
distance=miscParamMap['distance'])
tgtSigMult = getTpMult(
fit=fit,
src=src,
tgt=tgt,
tgtSpeed=tgtSpeed,
tpMods=tpMods,
@@ -365,18 +364,18 @@ class XTgtSigRadiusMixin(SmoothPointGetter):
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'])
self._prepareTimeCache(src=src, maxTime=miscParamMap['time'])
return {
'miscParamMap': miscParamMap,
'tgtSpeed': tgtSpeed,
'tgtSigMult': tgtSigMult,
'dmgMap': self._getDamagePerKey(fit=fit, time=miscParamMap['time'])}
'dmgMap': self._getDamagePerKey(src=src, time=miscParamMap['time'])}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
tgtSigRadius = x
miscParamMap = commonData['miscParamMap']
applicationMap = getApplicationPerKey(
fit=fit,
src=src,
tgt=tgt,
atkSpeed=miscParamMap['atkSpeed'],
atkAngle=miscParamMap['atkAngle'],

View File

@@ -26,7 +26,6 @@ from .getter import (
Time2DpsGetter, Time2VolleyGetter, Time2InflictedDamageGetter,
TgtSpeed2DpsGetter, TgtSpeed2VolleyGetter, TgtSpeed2InflictedDamageGetter,
TgtSigRadius2DpsGetter, TgtSigRadius2VolleyGetter, TgtSigRadius2InflictedDamageGetter)
from .helper import getTgtMaxVelocity, getTgtSigRadius
class FitDamageStatsGraph(FitGraph):
@@ -76,12 +75,12 @@ class FitDamageStatsGraph(FitGraph):
# Calculation stuff
_normalizers = {
('distance', 'km'): lambda v, fit, tgt: None if v is None else v * 1000,
('atkSpeed', '%'): lambda v, fit, tgt: v / 100 * fit.ship.getModifiedItemAttr('maxVelocity'),
('tgtSpeed', '%'): lambda v, fit, tgt: v / 100 * getTgtMaxVelocity(tgt),
('tgtSigRad', '%'): lambda v, fit, tgt: v / 100 * getTgtSigRadius(tgt)}
('distance', 'km'): lambda v, src, tgt: None if v is None else v * 1000,
('atkSpeed', '%'): lambda v, src, tgt: v / 100 * src.getMaxVelocity(),
('tgtSpeed', '%'): lambda v, src, tgt: v / 100 * tgt.getMaxVelocity(),
('tgtSigRad', '%'): lambda v, src, tgt: v / 100 * tgt.getSigRadius()}
_limiters = {
'time': lambda fit, tgt: (0, 2500)}
'time': lambda src, tgt: (0, 2500)}
_getters = {
('distance', 'dps'): Distance2DpsGetter,
('distance', 'volley'): Distance2VolleyGetter,
@@ -96,6 +95,6 @@ class FitDamageStatsGraph(FitGraph):
('tgtSigRad', 'volley'): TgtSigRadius2VolleyGetter,
('tgtSigRad', 'damage'): TgtSigRadius2InflictedDamageGetter}
_denormalizers = {
('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)}
('distance', 'km'): lambda v, src, tgt: None if v is None else v / 1000,
('tgtSpeed', '%'): lambda v, src, tgt: v * 100 / tgt.getMaxVelocity(),
('tgtSigRad', '%'): lambda v, src, tgt: v * 100 / tgt.getSigRadius()}

View File

@@ -1,89 +0,0 @@
# =============================================================================
# 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 eos.saveddata.targetProfile import TargetProfile
def getTgtMaxVelocity(tgt, extraMultipliers=None):
if isinstance(tgt, Fit):
if extraMultipliers:
maxVelocity = tgt.ship.getModifiedItemAttrWithExtraMods('maxVelocity', extraMultipliers=extraMultipliers)
else:
maxVelocity = tgt.ship.getModifiedItemAttr('maxVelocity')
elif isinstance(tgt, TargetProfile):
maxVelocity = tgt.maxVelocity
if extraMultipliers:
maxVelocity *= _calculateMultiplier(extraMultipliers)
else:
maxVelocity = None
return maxVelocity
def getTgtSigRadius(tgt, extraMultipliers=None):
if isinstance(tgt, Fit):
if extraMultipliers:
sigRadius = tgt.ship.getModifiedItemAttrWithExtraMods('signatureRadius', extraMultipliers=extraMultipliers)
else:
sigRadius = tgt.ship.getModifiedItemAttr('signatureRadius')
elif isinstance(tgt, TargetProfile):
sigRadius = tgt.signatureRadius
if extraMultipliers:
sigRadius *= _calculateMultiplier(extraMultipliers)
else:
sigRadius = None
return sigRadius
def getTgtRadius(tgt):
if isinstance(tgt, Fit):
radius = tgt.ship.getModifiedItemAttr('radius')
elif isinstance(tgt, TargetProfile):
radius = tgt.radius
else:
radius = None
return radius
# 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):
val = 1
for penalizedMultipliers in multipliers.values():
# A quick explanation of how this works:
# 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
l1 = [v[0] for v in penalizedMultipliers if v[0] > 1]
l2 = [v[0] for v in penalizedMultipliers if v[0] < 1]
# 2: The most significant bonuses take the smallest penalty,
# This means we'll have to sort
abssort = lambda _val: -abs(_val - 1)
l1.sort(key=abssort)
l2.sort(key=abssort)
# 3: The first module doesn't get penalized at all
# Any module after the first takes penalties according to:
# 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
for l in (l1, l2):
for i in range(len(l)):
bonus = l[i]
val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289)
return val

View File

@@ -25,13 +25,13 @@ from graphs.data.base import SmoothPointGetter
class Time2SpeedGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, tgt):
return {
'maxSpeed': fit.ship.getModifiedItemAttr('maxVelocity'),
'mass': fit.ship.getModifiedItemAttr('mass'),
'agility': fit.ship.getModifiedItemAttr('agility')}
'maxSpeed': src.getMaxVelocity(),
'mass': src.item.ship.getModifiedItemAttr('mass'),
'agility': src.item.ship.getModifiedItemAttr('agility')}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
time = x
maxSpeed = commonData['maxSpeed']
mass = commonData['mass']
@@ -43,13 +43,13 @@ class Time2SpeedGetter(SmoothPointGetter):
class Time2DistanceGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, tgt):
return {
'maxSpeed': fit.ship.getModifiedItemAttr('maxVelocity'),
'mass': fit.ship.getModifiedItemAttr('mass'),
'agility': fit.ship.getModifiedItemAttr('agility')}
'maxSpeed': src.getMaxVelocity(),
'mass': src.item.ship.getModifiedItemAttr('mass'),
'agility': src.item.ship.getModifiedItemAttr('agility')}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
time = x
maxSpeed = commonData['maxSpeed']
mass = commonData['mass']

View File

@@ -41,4 +41,4 @@ class FitMobilityVsTimeGraph(FitGraph):
('time', 'speed'): Time2SpeedGetter,
('time', 'distance'): Time2DistanceGetter}
_denormalizers = {
('distance', 'km'): lambda v, fit, tgt: v / 1000}
('distance', 'km'): lambda v, src, tgt: v / 1000}

View File

@@ -25,12 +25,12 @@ from graphs.data.base import SmoothPointGetter
class Time2ShieldAmountGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, tgt):
return {
'maxShieldAmount': fit.ship.getModifiedItemAttr('shieldCapacity'),
'shieldRegenTime': fit.ship.getModifiedItemAttr('shieldRechargeRate') / 1000}
'maxShieldAmount': src.item.ship.getModifiedItemAttr('shieldCapacity'),
'shieldRegenTime': src.item.ship.getModifiedItemAttr('shieldRechargeRate') / 1000}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
time = x
shieldAmount = calculateShieldAmount(
maxShieldAmount=commonData['maxShieldAmount'],
@@ -41,12 +41,12 @@ class Time2ShieldAmountGetter(SmoothPointGetter):
class Time2ShieldRegenGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, tgt):
return {
'maxShieldAmount': fit.ship.getModifiedItemAttr('shieldCapacity'),
'shieldRegenTime': fit.ship.getModifiedItemAttr('shieldRechargeRate') / 1000}
'maxShieldAmount': src.item.ship.getModifiedItemAttr('shieldCapacity'),
'shieldRegenTime': src.item.ship.getModifiedItemAttr('shieldRechargeRate') / 1000}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
time = x
shieldAmount = calculateShieldAmount(
maxShieldAmount=commonData['maxShieldAmount'],
@@ -62,19 +62,19 @@ class Time2ShieldRegenGetter(SmoothPointGetter):
# Useless, but valid combination of x and y
class ShieldAmount2ShieldAmountGetter(SmoothPointGetter):
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
shieldAmount = x
return shieldAmount
class ShieldAmount2ShieldRegenGetter(SmoothPointGetter):
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, tgt):
return {
'maxShieldAmount': fit.ship.getModifiedItemAttr('shieldCapacity'),
'shieldRegenTime': fit.ship.getModifiedItemAttr('shieldRechargeRate') / 1000}
'maxShieldAmount': src.item.ship.getModifiedItemAttr('shieldCapacity'),
'shieldRegenTime': src.item.ship.getModifiedItemAttr('shieldRechargeRate') / 1000}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
shieldAmount = x
shieldRegen = calculateShieldRegen(
maxShieldAmount=commonData['maxShieldAmount'],

View File

@@ -46,15 +46,15 @@ class FitShieldRegenGraph(FitGraph):
# Calculation stuff
_normalizers = {
('shieldAmount', '%'): lambda v, fit, tgt: v / 100 * fit.ship.getModifiedItemAttr('shieldCapacity')}
('shieldAmount', '%'): lambda v, src, tgt: v / 100 * src.item.ship.getModifiedItemAttr('shieldCapacity')}
_limiters = {
'shieldAmount': lambda fit, tgt: (0, fit.ship.getModifiedItemAttr('shieldCapacity'))}
'shieldAmount': lambda src, tgt: (0, src.item.ship.getModifiedItemAttr('shieldCapacity'))}
_getters = {
('time', 'shieldAmount'): Time2ShieldAmountGetter,
('time', 'shieldRegen'): Time2ShieldRegenGetter,
('shieldAmount', 'shieldAmount'): ShieldAmount2ShieldAmountGetter,
('shieldAmount', 'shieldRegen'): ShieldAmount2ShieldRegenGetter}
_denormalizers = {
('shieldAmount', '%'): lambda v, fit, tgt: v * 100 / fit.ship.getModifiedItemAttr('shieldCapacity'),
('shieldAmount', 'EHP'): lambda v, fit, tgt: fit.damagePattern.effectivify(fit, v, 'shield'),
('shieldRegen', 'EHP/s'): lambda v, fit, tgt: fit.damagePattern.effectivify(fit, v, 'shield')}
('shieldAmount', '%'): lambda v, src, tgt: v * 100 / src.item.ship.getModifiedItemAttr('shieldCapacity'),
('shieldAmount', 'EHP'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield'),
('shieldRegen', 'EHP/s'): lambda v, src, tgt: src.item.damagePattern.effectivify(src.item, v, 'shield')}

View File

@@ -24,9 +24,9 @@ from graphs.data.base import FitDataCache
class SubwarpSpeedCache(FitDataCache):
def getSubwarpSpeed(self, fit):
def getSubwarpSpeed(self, src):
try:
subwarpSpeed = self._data[fit.ID]
subwarpSpeed = self._data[src.item.ID]
except KeyError:
modStates = {}
disallowedGroups = (
@@ -40,34 +40,34 @@ class SubwarpSpeedCache(FitDataCache):
'Cynosural Field Generator',
'Clone Vat Bay',
'Jump Portal Generator')
for mod in fit.modules:
for mod in src.item.modules:
if mod.item is not None and mod.item.group.name in disallowedGroups and mod.state >= FittingModuleState.ACTIVE:
modStates[mod] = mod.state
mod.state = FittingModuleState.ONLINE
projFitStates = {}
for projFit in fit.projectedFits:
projectionInfo = projFit.getProjectionInfo(fit.ID)
for projFit in src.item.projectedFits:
projectionInfo = projFit.getProjectionInfo(src.item.ID)
if projectionInfo is not None and projectionInfo.active:
projFitStates[projectionInfo] = projectionInfo.active
projectionInfo.active = False
projModStates = {}
for mod in fit.projectedModules:
for mod in src.item.projectedModules:
if not mod.isExclusiveSystemEffect and mod.state >= FittingModuleState.ACTIVE:
projModStates[mod] = mod.state
mod.state = FittingModuleState.ONLINE
projDroneStates = {}
for drone in fit.projectedDrones:
for drone in src.item.projectedDrones:
if drone.amountActive > 0:
projDroneStates[drone] = drone.amountActive
drone.amountActive = 0
projFighterStates = {}
for fighter in fit.projectedFighters:
for fighter in src.item.projectedFighters:
if fighter.active:
projFighterStates[fighter] = fighter.active
fighter.active = False
fit.calculateModifiedAttributes()
subwarpSpeed = fit.ship.getModifiedItemAttr('maxVelocity')
self._data[fit.ID] = subwarpSpeed
src.item.calculateModifiedAttributes()
subwarpSpeed = src.getMaxVelocity()
self._data[src.item.ID] = subwarpSpeed
for projInfo, state in projFitStates.items():
projInfo.active = state
for mod, state in modStates.items():
@@ -78,5 +78,5 @@ class SubwarpSpeedCache(FitDataCache):
drone.amountActive = amountActive
for fighter, state in projFighterStates.items():
fighter.active = state
fit.calculateModifiedAttributes()
src.item.calculateModifiedAttributes()
return subwarpSpeed

View File

@@ -30,12 +30,12 @@ class Distance2TimeGetter(SmoothPointGetter):
_baseResolution = 500
def _getCommonData(self, miscParams, fit, tgt):
def _getCommonData(self, miscParams, src, tgt):
return {
'subwarpSpeed': self.graph._subspeedCache.getSubwarpSpeed(fit),
'warpSpeed': fit.warpSpeed}
'subwarpSpeed': self.graph._subspeedCache.getSubwarpSpeed(src),
'warpSpeed': src.item.warpSpeed}
def _calculatePoint(self, x, miscParams, fit, tgt, commonData):
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
distance = x
time = calculate_time_in_warp(
max_subwarp_speed=commonData['subwarpSpeed'],

View File

@@ -51,12 +51,12 @@ class FitWarpTimeGraph(FitGraph):
# Calculation stuff
_normalizers = {
('distance', 'AU'): lambda v, fit, tgt: v * AU_METERS,
('distance', 'km'): lambda v, fit, tgt: v * 1000}
('distance', 'AU'): lambda v, src, tgt: v * AU_METERS,
('distance', 'km'): lambda v, src, tgt: v * 1000}
_limiters = {
'distance': lambda fit, tgt: (0, fit.maxWarpDistance * AU_METERS)}
'distance': lambda src, tgt: (0, src.item.maxWarpDistance * AU_METERS)}
_getters = {
('distance', 'time'): Distance2TimeGetter}
_denormalizers = {
('distance', 'AU'): lambda v, fit, tgt: v / AU_METERS,
('distance', 'km'): lambda v, fit, tgt: v / 1000}
('distance', 'AU'): lambda v, src, tgt: v / AU_METERS,
('distance', 'km'): lambda v, src, tgt: v / 1000}