Store wrappers in graph lists
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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')}
|
||||
|
||||
28
graphs/data/fitDamageStats/cache/projected.py
vendored
28
graphs/data/fitDamageStats/cache/projected.py
vendored
@@ -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:
|
||||
|
||||
64
graphs/data/fitDamageStats/cache/time.py
vendored
64
graphs/data/fitDamageStats/cache/time.py
vendored
@@ -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)
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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
|
||||
@@ -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']
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -247,15 +247,20 @@ class GraphFrame(wx.Frame):
|
||||
|
||||
mainInput, miscInputs = self.ctrlPanel.getValues()
|
||||
view = self.getView()
|
||||
fits = self.ctrlPanel.fits
|
||||
sources = self.ctrlPanel.sources
|
||||
if view.hasTargets:
|
||||
targets = self.ctrlPanel.targets
|
||||
iterList = tuple(itertools.product(fits, targets))
|
||||
iterList = tuple(itertools.product(sources, self.ctrlPanel.targets))
|
||||
else:
|
||||
iterList = tuple((f, None) for f in fits)
|
||||
for fit, target in iterList:
|
||||
iterList = tuple((f, None) for f in sources)
|
||||
for source, target in iterList:
|
||||
try:
|
||||
xs, ys = view.getPlotPoints(mainInput, miscInputs, chosenX, chosenY, fit, target)
|
||||
xs, ys = view.getPlotPoints(
|
||||
mainInput=mainInput,
|
||||
miscInputs=miscInputs,
|
||||
xSpec=chosenX,
|
||||
ySpec=chosenY,
|
||||
src=source,
|
||||
tgt=target)
|
||||
|
||||
# Figure out min and max Y
|
||||
min_y_this = min(ys, default=None)
|
||||
@@ -275,11 +280,11 @@ class GraphFrame(wx.Frame):
|
||||
self.subplot.plot(xs, ys)
|
||||
|
||||
if target is None:
|
||||
legend.append(self.getObjName(fit))
|
||||
legend.append(source.shortName)
|
||||
else:
|
||||
legend.append('{} vs {}'.format(self.getObjName(fit), self.getObjName(target)))
|
||||
legend.append('{} vs {}'.format(source.shortName, target.shortName))
|
||||
except Exception as ex:
|
||||
pyfalog.warning('Invalid values in "{0}"', fit.name)
|
||||
pyfalog.warning('Invalid values in "{0}"', source.name)
|
||||
self.canvas.draw()
|
||||
self.Refresh()
|
||||
return
|
||||
@@ -330,12 +335,3 @@ class GraphFrame(wx.Frame):
|
||||
|
||||
self.canvas.draw()
|
||||
self.Refresh()
|
||||
|
||||
@staticmethod
|
||||
def getObjName(thing):
|
||||
if isinstance(thing, Fit):
|
||||
return '{} ({})'.format(thing.name, thing.ship.item.getShortName())
|
||||
elif isinstance(thing, TargetProfile):
|
||||
return thing.name
|
||||
return ''
|
||||
|
||||
|
||||
@@ -23,12 +23,13 @@ import wx
|
||||
|
||||
import gui.display
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from graphs.wrapper import SourceWrapper, TargetWrapper
|
||||
from gui.contextMenu import ContextMenu
|
||||
from service.const import GraphCacheCleanupReason
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
class BaseList(gui.display.Display):
|
||||
class BaseWrapperList(gui.display.Display):
|
||||
|
||||
DEFAULT_COLS = (
|
||||
'Base Icon',
|
||||
@@ -37,7 +38,7 @@ class BaseList(gui.display.Display):
|
||||
def __init__(self, graphFrame, parent):
|
||||
super().__init__(parent)
|
||||
self.graphFrame = graphFrame
|
||||
self.fits = []
|
||||
self._wrappers = []
|
||||
|
||||
self.hoveredRow = None
|
||||
self.hoveredColumn = None
|
||||
@@ -47,6 +48,15 @@ class BaseList(gui.display.Display):
|
||||
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
|
||||
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
|
||||
|
||||
@property
|
||||
def wrappers(self):
|
||||
return sorted(self._wrappers, key=lambda w: w.isFit)
|
||||
|
||||
# UI-related stuff
|
||||
@property
|
||||
def defaultTTText(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def refreshExtraColumns(self, extraColSpecs):
|
||||
baseColNames = set()
|
||||
for baseColName in self.DEFAULT_COLS:
|
||||
@@ -63,31 +73,13 @@ class BaseList(gui.display.Display):
|
||||
self.appendColumnBySpec(colSpec)
|
||||
self.refreshView()
|
||||
|
||||
def handleDrag(self, type, fitID):
|
||||
if type == 'fit':
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit not in self.fits:
|
||||
self.fits.append(fit)
|
||||
self.updateView()
|
||||
self.graphFrame.draw()
|
||||
def refreshView(self):
|
||||
self.refresh(self.wrappers)
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
self.removeListItems(self.getSelectedListItems())
|
||||
event.Skip()
|
||||
|
||||
def OnLeftDClick(self, event):
|
||||
row, _ = self.HitTest(event.Position)
|
||||
item = self.getListItem(row)
|
||||
if item is None:
|
||||
return
|
||||
self.removeListItems([item])
|
||||
def updateView(self):
|
||||
self.update(self.wrappers)
|
||||
|
||||
# UI event handling
|
||||
def OnMouseMove(self, event):
|
||||
row, _, col = self.HitTestSubItem(event.Position)
|
||||
if row != self.hoveredRow or col != self.hoveredColumn:
|
||||
@@ -97,7 +89,7 @@ class BaseList(gui.display.Display):
|
||||
self.hoveredRow = row
|
||||
self.hoveredColumn = col
|
||||
if row != -1 and col != -1 and col < self.ColumnCount:
|
||||
item = self.getListItem(row)
|
||||
item = self.getWrapper(row)
|
||||
if item is None:
|
||||
return
|
||||
tooltip = self.activeColumns[col].getToolTip(item)
|
||||
@@ -115,70 +107,138 @@ class BaseList(gui.display.Display):
|
||||
self.hoveredColumn = None
|
||||
event.Skip()
|
||||
|
||||
# Fit events
|
||||
def handleDrag(self, type, fitID):
|
||||
if type == 'fit' and not self.containsFitID(fitID):
|
||||
sFit = Fit.getInstance()
|
||||
fit = sFit.getFit(fitID)
|
||||
self.appendItem(fit)
|
||||
self.updateView()
|
||||
self.graphFrame.draw()
|
||||
|
||||
def OnLeftDClick(self, event):
|
||||
row, _ = self.HitTest(event.Position)
|
||||
wrapper = self.getWrapper(row)
|
||||
if wrapper is None:
|
||||
return
|
||||
self.removeWrappers([wrapper])
|
||||
|
||||
def kbEvent(self, event):
|
||||
keycode = event.GetKeyCode()
|
||||
mstate = wx.GetMouseState()
|
||||
if keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
|
||||
self.selectAll()
|
||||
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
|
||||
self.removeWrappers(self.getSelectedWrappers())
|
||||
event.Skip()
|
||||
|
||||
# Wrapper-related methods
|
||||
@property
|
||||
def wrapperClass(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def getWrapper(self, row):
|
||||
if row == -1:
|
||||
return None
|
||||
try:
|
||||
return self._wrappers[row]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def removeWrappers(self, wrappers):
|
||||
wrappers = set(wrappers).union(self._wrappers)
|
||||
if not wrappers:
|
||||
return
|
||||
for wrapper in wrappers:
|
||||
self._wrappers.remove(wrapper)
|
||||
self.updateView()
|
||||
for wrapper in wrappers:
|
||||
if wrapper.isFit:
|
||||
self.graphFrame.clearCache(reason=GraphCacheCleanupReason.fitRemoved, extraData=wrapper.fitID)
|
||||
elif wrapper.isProfile:
|
||||
self.graphFrame.clearCache(reason=GraphCacheCleanupReason.profileRemoved, extraData=wrapper.profileID)
|
||||
self.graphFrame.draw()
|
||||
|
||||
def getSelectedWrappers(self):
|
||||
wrappers = []
|
||||
for row in self.getSelectedRows():
|
||||
wrapper = self.getWrapper(row)
|
||||
if wrapper is None:
|
||||
continue
|
||||
wrappers.append(wrapper)
|
||||
return wrappers
|
||||
|
||||
def appendItem(self, item):
|
||||
self._wrappers.append(self.wrapperClass(item))
|
||||
|
||||
def containsFitID(self, fitID):
|
||||
for wrapper in self._wrappers:
|
||||
if wrapper.isFit and wrapper.itemID == fitID:
|
||||
return True
|
||||
return False
|
||||
|
||||
def containsProfileID(self, profileID):
|
||||
for wrapper in self._wrappers:
|
||||
if wrapper.isProfile and wrapper.itemID == profileID:
|
||||
return True
|
||||
return False
|
||||
|
||||
# Wrapper-related events
|
||||
def OnFitRenamed(self, event):
|
||||
if event.fitID in [f.ID for f in self.fits]:
|
||||
if self.containsFitID(event.fitID):
|
||||
self.updateView()
|
||||
|
||||
def OnFitChanged(self, event):
|
||||
if set(event.fitIDs).union(f.ID for f in self.fits):
|
||||
if set(event.fitIDs).union(w.itemID for w in self._wrappers if w.isFit):
|
||||
self.updateView()
|
||||
|
||||
def OnFitRemoved(self, event):
|
||||
fit = next((f for f in self.fits if f.ID == event.fitID), None)
|
||||
if fit is not None:
|
||||
self.fits.remove(fit)
|
||||
wrapper = next((w for w in self._wrappers if w.isFit and w.itemID == event.fitID), None)
|
||||
if wrapper is not None:
|
||||
self._wrappers.remove(wrapper)
|
||||
self.updateView()
|
||||
|
||||
@property
|
||||
def defaultTTText(self):
|
||||
raise NotImplementedError
|
||||
def OnProfileRenamed(self, event):
|
||||
if self.containsProfileID(event.profileID):
|
||||
self.updateView()
|
||||
|
||||
def refreshView(self):
|
||||
raise NotImplementedError
|
||||
def OnProfileChanged(self, event):
|
||||
if self.containsProfileID(event.profileID):
|
||||
self.updateView()
|
||||
|
||||
def updateView(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def getListItem(self, row):
|
||||
raise NotImplementedError
|
||||
|
||||
def removeListItems(self, items):
|
||||
raise NotImplementedError
|
||||
|
||||
def getSelectedListItems(self):
|
||||
items = []
|
||||
for row in self.getSelectedRows():
|
||||
item = self.getListItem(row)
|
||||
if item is None:
|
||||
continue
|
||||
items.append(item)
|
||||
return items
|
||||
def OnProfileRemoved(self, event):
|
||||
wrapper = next((w for w in self._wrappers if w.isProfile and w.itemID == event.profileID), None)
|
||||
if wrapper is not None:
|
||||
self._wrappers.remove(wrapper)
|
||||
self.updateView()
|
||||
|
||||
# Context menu handlers
|
||||
def addFit(self, fit):
|
||||
if fit is None:
|
||||
return
|
||||
if fit in self.fits:
|
||||
if self.containsFitID(fit.ID):
|
||||
return
|
||||
self.fits.append(fit)
|
||||
self.appendItem(fit)
|
||||
self.updateView()
|
||||
self.graphFrame.draw()
|
||||
|
||||
def getExistingFitIDs(self):
|
||||
return [f.ID for f in self.fits]
|
||||
return [w.itemID for w in self._wrappers if w.isFit]
|
||||
|
||||
def addFitsByIDs(self, fitIDs):
|
||||
sFit = Fit.getInstance()
|
||||
for fitID in fitIDs:
|
||||
if self.containsFitID(fitID):
|
||||
continue
|
||||
fit = sFit.getFit(fitID)
|
||||
if fit is not None:
|
||||
self.fits.append(fit)
|
||||
self.appendItem(fit)
|
||||
self.updateView()
|
||||
self.graphFrame.draw()
|
||||
|
||||
|
||||
class FitList(BaseList):
|
||||
class SourceWrapperList(BaseWrapperList):
|
||||
|
||||
wrapperClass = SourceWrapper
|
||||
|
||||
def __init__(self, graphFrame, parent):
|
||||
super().__init__(graphFrame, parent)
|
||||
@@ -187,19 +247,13 @@ class FitList(BaseList):
|
||||
|
||||
fit = Fit.getInstance().getFit(self.graphFrame.mainFrame.getActiveFit())
|
||||
if fit is not None:
|
||||
self.fits.append(fit)
|
||||
self.appendItem(fit)
|
||||
self.updateView()
|
||||
|
||||
def refreshView(self):
|
||||
self.refresh(self.fits)
|
||||
|
||||
def updateView(self):
|
||||
self.update(self.fits)
|
||||
|
||||
def spawnMenu(self, event):
|
||||
selection = self.getSelectedListItems()
|
||||
selection = self.getSelectedWrappers()
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
mainItem = self.getListItem(clickedPos)
|
||||
mainItem = self.getWrapper(clickedPos)
|
||||
|
||||
sourceContext = 'graphFitList'
|
||||
itemContext = None if mainItem is None else 'Fit'
|
||||
@@ -207,51 +261,27 @@ class FitList(BaseList):
|
||||
if menu:
|
||||
self.PopupMenu(menu)
|
||||
|
||||
def getListItem(self, row):
|
||||
if row == -1:
|
||||
return None
|
||||
try:
|
||||
return self.fits[row]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def removeListItems(self, items):
|
||||
toRemove = [i for i in items if i in self.fits]
|
||||
if not toRemove:
|
||||
return
|
||||
for fit in toRemove:
|
||||
self.fits.remove(fit)
|
||||
self.updateView()
|
||||
for fit in toRemove:
|
||||
self.graphFrame.clearCache(reason=GraphCacheCleanupReason.fitRemoved, extraData=fit.ID)
|
||||
self.graphFrame.draw()
|
||||
|
||||
@property
|
||||
def defaultTTText(self):
|
||||
return 'Drag a fit into this list to graph it'
|
||||
|
||||
|
||||
class TargetList(BaseList):
|
||||
class TargetWrapperList(BaseWrapperList):
|
||||
|
||||
wrapperClass = TargetWrapper
|
||||
|
||||
def __init__(self, graphFrame, parent):
|
||||
super().__init__(graphFrame, parent)
|
||||
|
||||
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
|
||||
|
||||
self.profiles = []
|
||||
self.profiles.append(TargetProfile.getIdeal())
|
||||
self.appendItem(TargetProfile.getIdeal())
|
||||
self.updateView()
|
||||
|
||||
def refreshView(self):
|
||||
self.refresh(self.targets)
|
||||
|
||||
def updateView(self):
|
||||
self.update(self.targets)
|
||||
|
||||
def spawnMenu(self, event):
|
||||
selection = self.getSelectedListItems()
|
||||
selection = self.getSelectedWrappers()
|
||||
clickedPos = self.getRowByAbs(event.Position)
|
||||
mainItem = self.getListItem(clickedPos)
|
||||
mainItem = self.getWrapper(clickedPos)
|
||||
|
||||
sourceContext = 'graphTgtList'
|
||||
itemContext = None if mainItem is None else 'Target'
|
||||
@@ -259,56 +289,6 @@ class TargetList(BaseList):
|
||||
if menu:
|
||||
self.PopupMenu(menu)
|
||||
|
||||
def getListItem(self, row):
|
||||
if row == -1:
|
||||
return None
|
||||
|
||||
numFits = len(self.fits)
|
||||
numProfiles = len(self.profiles)
|
||||
|
||||
if (numFits + numProfiles) == 0:
|
||||
return None
|
||||
|
||||
if row < numFits:
|
||||
return self.fits[row]
|
||||
else:
|
||||
return self.profiles[row - numFits]
|
||||
|
||||
def removeListItems(self, items):
|
||||
fitsToRemove = [i for i in items if i in self.fits]
|
||||
profilesToRemove = [i for i in items if i in self.profiles]
|
||||
if not fitsToRemove and not profilesToRemove:
|
||||
return
|
||||
for fit in fitsToRemove:
|
||||
self.fits.remove(fit)
|
||||
for profile in profilesToRemove:
|
||||
self.profiles.remove(profile)
|
||||
self.updateView()
|
||||
for fit in fitsToRemove:
|
||||
self.graphFrame.clearCache(reason=GraphCacheCleanupReason.fitRemoved, extraData=fit.ID)
|
||||
for profile in profilesToRemove:
|
||||
self.graphFrame.clearCache(reason=GraphCacheCleanupReason.profileRemoved, extraData=profile.ID)
|
||||
self.graphFrame.draw()
|
||||
|
||||
# Target profile events
|
||||
def OnProfileRenamed(self, event):
|
||||
if event.profileID in [tp.ID for tp in self.profiles]:
|
||||
self.updateView()
|
||||
|
||||
def OnProfileChanged(self, event):
|
||||
if event.profileID in [tp.ID for tp in self.profiles]:
|
||||
self.updateView()
|
||||
|
||||
def OnProfileRemoved(self, event):
|
||||
profile = next((tp for tp in self.profiles if tp.ID == event.profileID), None)
|
||||
if profile is not None:
|
||||
self.profiles.remove(profile)
|
||||
self.updateView()
|
||||
|
||||
@property
|
||||
def targets(self):
|
||||
return self.fits + self.profiles
|
||||
|
||||
@property
|
||||
def defaultTTText(self):
|
||||
return 'Drag a fit into this list to have your fits graphed against it'
|
||||
@@ -317,8 +297,8 @@ class TargetList(BaseList):
|
||||
def addProfile(self, profile):
|
||||
if profile is None:
|
||||
return
|
||||
if profile in self.profiles:
|
||||
if self.containsProfileID(profile.ID):
|
||||
return
|
||||
self.profiles.append(profile)
|
||||
self.appendItem(profile)
|
||||
self.updateView()
|
||||
self.graphFrame.draw()
|
||||
|
||||
@@ -28,7 +28,7 @@ from gui.contextMenu import ContextMenu
|
||||
from gui.utils.inputs import FloatBox, FloatRangeBox
|
||||
from service.const import GraphCacheCleanupReason
|
||||
from service.fit import Fit
|
||||
from .lists import FitList, TargetList
|
||||
from .lists import SourceWrapperList, TargetWrapperList
|
||||
from .vector import VectorPicker
|
||||
|
||||
|
||||
@@ -114,10 +114,10 @@ class GraphControlPanel(wx.Panel):
|
||||
mainSizer.Add(optsSizer, 0, wx.EXPAND | wx.ALL, 10)
|
||||
|
||||
srcTgtSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.fitList = FitList(graphFrame, self)
|
||||
self.fitList.SetMinSize((270, -1))
|
||||
srcTgtSizer.Add(self.fitList, 1, wx.EXPAND | wx.ALL, 0)
|
||||
self.targetList = TargetList(graphFrame, self)
|
||||
self.sourceList = SourceWrapperList(graphFrame, self)
|
||||
self.sourceList.SetMinSize((270, -1))
|
||||
srcTgtSizer.Add(self.sourceList, 1, wx.EXPAND | wx.ALL, 0)
|
||||
self.targetList = TargetWrapperList(graphFrame, self)
|
||||
self.targetList.SetMinSize((270, -1))
|
||||
srcTgtSizer.Add(self.targetList, 1, wx.EXPAND | wx.LEFT, 10)
|
||||
mainSizer.Add(srcTgtSizer, 1, wx.EXPAND | wx.LEFT | wx.BOTTOM | wx.RIGHT, 10)
|
||||
@@ -162,7 +162,7 @@ class GraphControlPanel(wx.Panel):
|
||||
self.tgtVectorLabel.Show(False)
|
||||
|
||||
# Source and target list
|
||||
self.fitList.refreshExtraColumns(view.srcExtraCols)
|
||||
self.sourceList.refreshExtraColumns(view.srcExtraCols)
|
||||
self.targetList.refreshExtraColumns(view.tgtExtraCols)
|
||||
self.targetList.Show(view.hasTargets)
|
||||
|
||||
@@ -333,34 +333,37 @@ class GraphControlPanel(wx.Panel):
|
||||
return self.xSubSelection.GetClientData(self.xSubSelection.GetSelection())
|
||||
|
||||
@property
|
||||
def fits(self):
|
||||
return self.fitList.fits
|
||||
def sources(self):
|
||||
return self.sourceList.wrappers
|
||||
|
||||
@property
|
||||
def targets(self):
|
||||
return self.targetList.targets
|
||||
return self.targetList.wrappers
|
||||
|
||||
# Fit events
|
||||
def OnFitRenamed(self, event):
|
||||
self.fitList.OnFitRenamed(event)
|
||||
self.sourceList.OnFitRenamed(event)
|
||||
self.targetList.OnFitRenamed(event)
|
||||
|
||||
def OnFitChanged(self, event):
|
||||
self.fitList.OnFitChanged(event)
|
||||
self.sourceList.OnFitChanged(event)
|
||||
self.targetList.OnFitChanged(event)
|
||||
|
||||
def OnFitRemoved(self, event):
|
||||
self.fitList.OnFitRemoved(event)
|
||||
self.sourceList.OnFitRemoved(event)
|
||||
self.targetList.OnFitRemoved(event)
|
||||
|
||||
# Target profile events
|
||||
def OnProfileRenamed(self, event):
|
||||
self.sourceList.OnProfileRenamed(event)
|
||||
self.targetList.OnProfileRenamed(event)
|
||||
|
||||
def OnProfileChanged(self, event):
|
||||
self.sourceList.OnProfileChanged(event)
|
||||
self.targetList.OnProfileChanged(event)
|
||||
|
||||
def OnProfileRemoved(self, event):
|
||||
self.sourceList.OnProfileRemoved(event)
|
||||
self.targetList.OnProfileRemoved(event)
|
||||
|
||||
def formatLabel(self, axisDef):
|
||||
|
||||
131
graphs/wrapper.py
Normal file
131
graphs/wrapper.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# =============================================================================
|
||||
# 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
|
||||
|
||||
|
||||
class BaseWrapper:
|
||||
|
||||
def __init__(self, item):
|
||||
self.item = item
|
||||
|
||||
@property
|
||||
def isFit(self):
|
||||
return isinstance(self.item, Fit)
|
||||
|
||||
@property
|
||||
def isProfile(self):
|
||||
return isinstance(self.item, TargetProfile)
|
||||
|
||||
@property
|
||||
def itemID(self):
|
||||
return self.item.ID
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self.isFit:
|
||||
return '{} ({})'.format(self.item.name, self.item.ship.item.name)
|
||||
elif self.isProfile:
|
||||
return self.item.name
|
||||
return ''
|
||||
|
||||
@property
|
||||
def shortName(self):
|
||||
if self.isFit:
|
||||
return '{} ({})'.format(self.item.name, self.item.ship.item.getShortName())
|
||||
elif self.isProfile:
|
||||
return self.item.name
|
||||
return ''
|
||||
|
||||
def getMaxVelocity(self, extraMultipliers=None):
|
||||
if self.isFit:
|
||||
if extraMultipliers:
|
||||
maxVelocity = self.item.ship.getModifiedItemAttrWithExtraMods('maxVelocity', extraMultipliers=extraMultipliers)
|
||||
else:
|
||||
maxVelocity = self.item.ship.getModifiedItemAttr('maxVelocity')
|
||||
elif self.isProfile:
|
||||
maxVelocity = self.item.maxVelocity
|
||||
if extraMultipliers:
|
||||
maxVelocity *= _calculateMultiplier(extraMultipliers)
|
||||
else:
|
||||
maxVelocity = None
|
||||
return maxVelocity
|
||||
|
||||
def getSigRadius(self, extraMultipliers=None):
|
||||
if self.isFit:
|
||||
if extraMultipliers:
|
||||
sigRadius = self.item.ship.getModifiedItemAttrWithExtraMods('signatureRadius', extraMultipliers=extraMultipliers)
|
||||
else:
|
||||
sigRadius = self.item.ship.getModifiedItemAttr('signatureRadius')
|
||||
elif self.isProfile:
|
||||
sigRadius = self.item.signatureRadius
|
||||
if extraMultipliers:
|
||||
sigRadius *= _calculateMultiplier(extraMultipliers)
|
||||
else:
|
||||
sigRadius = None
|
||||
return sigRadius
|
||||
|
||||
def getRadius(self):
|
||||
if self.isFit:
|
||||
radius = self.item.ship.getModifiedItemAttr('radius')
|
||||
elif self.isProfile:
|
||||
radius = self.item.radius
|
||||
else:
|
||||
radius = None
|
||||
return radius
|
||||
|
||||
|
||||
class SourceWrapper(BaseWrapper):
|
||||
pass
|
||||
|
||||
|
||||
class TargetWrapper(BaseWrapper):
|
||||
|
||||
def __init__(self, item):
|
||||
super().__init__(item=item)
|
||||
self.resistMode = None
|
||||
|
||||
|
||||
# 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
|
||||
@@ -161,7 +161,7 @@ class RemoveItem(ContextMenuCombined):
|
||||
fitID=fitID, commandFitIDs=commandFitIDs))
|
||||
|
||||
def __handleGraphItem(self, callingWindow, mainItem, selection):
|
||||
callingWindow.removeListItems(selection)
|
||||
callingWindow.removeWrappers(selection)
|
||||
|
||||
|
||||
RemoveItem.register()
|
||||
|
||||
@@ -27,9 +27,10 @@ import gui.mainFrame
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from eos.utils.spoolSupport import SpoolOptions, SpoolType
|
||||
from graphs.wrapper import BaseWrapper
|
||||
from gui.bitmap_loader import BitmapLoader
|
||||
from gui.viewColumn import ViewColumn
|
||||
from gui.utils.numberFormatter import formatAmount
|
||||
from gui.viewColumn import ViewColumn
|
||||
|
||||
|
||||
class GraphColumn(ViewColumn, metaclass=ABCMeta):
|
||||
@@ -47,6 +48,8 @@ class GraphColumn(ViewColumn, metaclass=ABCMeta):
|
||||
raise NotImplementedError
|
||||
|
||||
def getText(self, stuff):
|
||||
if isinstance(stuff, BaseWrapper):
|
||||
stuff = stuff.item
|
||||
if isinstance(stuff, (Fit, TargetProfile)):
|
||||
val, unit = self._getValue(stuff)
|
||||
if val is None:
|
||||
@@ -59,6 +62,8 @@ class GraphColumn(ViewColumn, metaclass=ABCMeta):
|
||||
raise NotImplementedError
|
||||
|
||||
def getToolTip(self, stuff):
|
||||
if isinstance(stuff, BaseWrapper):
|
||||
stuff = stuff.item
|
||||
if isinstance(stuff, (Fit, TargetProfile)):
|
||||
return self._getFitTooltip()
|
||||
return ''
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from eos.saveddata.implant import Implant
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.module import Module, Rack
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
|
||||
from eos.const import FittingSlot
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.fit import Fit
|
||||
from eos.saveddata.implant import Implant
|
||||
from eos.saveddata.module import Module, Rack
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from graphs.wrapper import BaseWrapper
|
||||
from gui.viewColumn import ViewColumn
|
||||
|
||||
|
||||
@@ -21,6 +23,9 @@ class BaseIcon(ViewColumn):
|
||||
self.shipImage = fittingView.imageList.GetImageIndex("ship_small", "gui")
|
||||
|
||||
def getImageId(self, stuff):
|
||||
if isinstance(stuff, BaseWrapper):
|
||||
stuff = stuff.item
|
||||
|
||||
if isinstance(stuff, Drone):
|
||||
return -1
|
||||
elif isinstance(stuff, Fit):
|
||||
|
||||
@@ -21,19 +21,22 @@
|
||||
# noinspection PyPackageRequirements
|
||||
import wx
|
||||
from logbook import Logger
|
||||
|
||||
import gui.mainFrame
|
||||
from eos.const import FittingSlot
|
||||
from eos.saveddata.cargo import Cargo
|
||||
from eos.saveddata.implant import Implant
|
||||
from eos.saveddata.drone import Drone
|
||||
from eos.saveddata.fighter import Fighter
|
||||
from eos.saveddata.module import Module, Rack
|
||||
from eos.saveddata.fit import Fit, FitLite
|
||||
from eos.saveddata.implant import Implant
|
||||
from eos.saveddata.module import Module, Rack
|
||||
from eos.saveddata.targetProfile import TargetProfile
|
||||
from eos.const import FittingSlot
|
||||
from graphs.wrapper import BaseWrapper
|
||||
from gui.builtinContextMenus.envEffectAdd import AddEnvironmentEffect
|
||||
from gui.viewColumn import ViewColumn
|
||||
from service.fit import Fit as FitSvc
|
||||
from service.market import Market
|
||||
from gui.viewColumn import ViewColumn
|
||||
from gui.builtinContextMenus.envEffectAdd import AddEnvironmentEffect
|
||||
import gui.mainFrame
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -50,6 +53,9 @@ class BaseName(ViewColumn):
|
||||
self.projectedView = isinstance(fittingView, gui.builtinAdditionPanes.projectedView.ProjectedView)
|
||||
|
||||
def getText(self, stuff):
|
||||
if isinstance(stuff, BaseWrapper):
|
||||
stuff = stuff.item
|
||||
|
||||
if isinstance(stuff, Drone):
|
||||
return "%dx %s" % (stuff.amount, stuff.item.name)
|
||||
elif isinstance(stuff, Fighter):
|
||||
|
||||
Reference in New Issue
Block a user