diff --git a/gui/builtinGraphs/base.py b/gui/builtinGraphs/base.py
index 1c651dc35..cada75c4e 100644
--- a/gui/builtinGraphs/base.py
+++ b/gui/builtinGraphs/base.py
@@ -40,8 +40,6 @@ class FitGraph(metaclass=ABCMeta):
def __init__(self):
# Format: {(fit ID, target type, target ID): data}
self._plotCache = {}
- # Format: {fit ID: data}
- self._calcCache = {}
@property
@abstractmethod
@@ -91,7 +89,6 @@ class FitGraph(metaclass=ABCMeta):
# Clear everything
if fitID is None:
self._plotCache.clear()
- self._calcCache.clear()
return
# Clear plot cache
plotKeysToClear = set()
@@ -103,9 +100,10 @@ class FitGraph(metaclass=ABCMeta):
plotKeysToClear.add(cacheKey)
for cacheKey in plotKeysToClear:
del self._plotCache[cacheKey]
- # Clear calc cache
- if fitID in self._calcCache:
- del self._calcCache[fitID]
+ self._clearInternalCache(fitID=fitID)
+
+ def _clearInternalCache(self, fitID):
+ return
# Calculation stuff
def _calcPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt):
diff --git a/gui/builtinGraphs/fitDamageStats/cacheTime.py b/gui/builtinGraphs/fitDamageStats/cacheTime.py
deleted file mode 100644
index b711ecadb..000000000
--- a/gui/builtinGraphs/fitDamageStats/cacheTime.py
+++ /dev/null
@@ -1,24 +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 .
-# =============================================================================
-
-
-class TimeCache:
-
- def __init__(self):
- self.data = {}
diff --git a/gui/builtinGraphs/fitDamageStats/graph.py b/gui/builtinGraphs/fitDamageStats/graph.py
index 1ef75fd1c..3172a4701 100644
--- a/gui/builtinGraphs/fitDamageStats/graph.py
+++ b/gui/builtinGraphs/fitDamageStats/graph.py
@@ -18,20 +18,24 @@
# =============================================================================
-from copy import copy
-from itertools import chain
-
import eos.config
from eos.const import FittingHardpoint, FittingModuleState
from eos.utils.float import floatUnerr
from eos.utils.spoolSupport import SpoolType, SpoolOptions
-from eos.utils.stats import DmgTypes
from gui.builtinGraphs.base import FitGraph, XDef, YDef, Input, VectorDef
from .calc import getTurretMult, getLauncherMult, getDroneMult, getFighterAbilityMult
+from .timeCache import TimeCache
class FitDamageStatsGraph(FitGraph):
+ def __init__(self):
+ super().__init__()
+ self._timeCache = TimeCache()
+
+ def _clearInternalCache(self, fitID):
+ self._timeCache.clear(fitID)
+
# UI stuff
name = 'Damage Stats'
xDefs = [
@@ -112,19 +116,19 @@ class FitDamageStatsGraph(FitGraph):
def _time2dps(self, mainInput, miscInputs, fit, tgt):
def calcDpsTmp(timeDmg):
return floatUnerr(sum(dts[0].total for dts in timeDmg.values()))
- self._generateTimeCacheDpsVolley(fit, mainInput[1][1])
+ self._timeCache.generateFinalFormDpsVolley(fit, mainInput[1][1])
return self._composeTimeGraph(mainInput, fit, 'finalDpsVolley', calcDpsTmp)
def _time2volley(self, mainInput, miscInputs, fit, tgt):
def calcVolleyTmp(timeDmg):
return floatUnerr(sum(dts[1].total for dts in timeDmg.values()))
- self._generateTimeCacheDpsVolley(fit, mainInput[1][1])
+ self._timeCache.generateFinalFormDpsVolley(fit, mainInput[1][1])
return self._composeTimeGraph(mainInput, fit, 'finalDpsVolley', calcVolleyTmp)
def _time2damage(self, mainInput, miscInputs, fit, tgt):
def calcDamageTmp(timeDmg):
return floatUnerr(sum(dt.total for dt in timeDmg.values()))
- self._generateTimeCacheDmg(fit, mainInput[1][1])
+ self._timeCache.generateFinalFormDmg(fit, mainInput[1][1])
return self._composeTimeGraph(mainInput, fit, 'finalDmg', calcDamageTmp)
def _tgtSpeed2dps(self, mainInput, miscInputs, fit, tgt):
@@ -159,266 +163,12 @@ class FitDamageStatsGraph(FitGraph):
('tgtSigRad', 'volley'): _tgtSigRad2volley,
('tgtSigRad', 'damage'): _tgtSigRad2damage}
- # Cache generation
- def _generateTimeCacheDpsVolley(self, fit, 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._generateTimeCacheIntermediate(fit, maxTime)
- timeCache = self._calcCache[fit.ID]['timeCache']
- # Final cache has been generated already, don't do anything
- if 'finalDpsVolley' in timeCache:
- return
- # Convert cache from segments with assigned values into points
- # which are located at times when dps/volley values change
- pointCache = {}
- for key, dmgList in timeCache['intermediateDpsVolley'].items():
- pointData = pointCache[key] = {}
- prevDps = None
- prevVolley = None
- prevTimeEnd = None
- for timeStart, timeEnd, dps, volley in dmgList:
- # First item
- if not pointData:
- pointData[timeStart] = (dps, volley)
- # Gap between items
- elif floatUnerr(prevTimeEnd) < floatUnerr(timeStart):
- pointData[prevTimeEnd] = (DmgTypes(0, 0, 0, 0), DmgTypes(0, 0, 0, 0))
- pointData[timeStart] = (dps, volley)
- # Changed value
- elif dps != prevDps or volley != prevVolley:
- pointData[timeStart] = (dps, volley)
- prevDps = dps
- prevVolley = volley
- prevTimeEnd = timeEnd
- # We have another intermediate form, do not need old one any longer
- del timeCache['intermediateDpsVolley']
- changesByTime = {}
- for key, dmgMap in pointCache.items():
- for time in dmgMap:
- changesByTime.setdefault(time, []).append(key)
- # Here we convert cache to following format:
- # {time: {key: (dps, volley}}
- finalCache = timeCache['finalDpsVolley'] = {}
- timeDmgData = {}
- for time in sorted(changesByTime):
- timeDmgData = copy(timeDmgData)
- for key in changesByTime[time]:
- timeDmgData[key] = pointCache[key][time]
- finalCache[time] = timeDmgData
-
- def _generateTimeCacheDmg(self, fit, 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._generateTimeCacheIntermediate(fit, maxTime)
- timeCache = self._calcCache[fit.ID]['timeCache']
- # Final cache has been generated already, don't do anything
- if 'finalDmg' in timeCache:
- return
- intCache = timeCache['intermediateDmg']
- changesByTime = {}
- for key, dmgMap in intCache.items():
- for time in dmgMap:
- changesByTime.setdefault(time, []).append(key)
- # Here we convert cache to following format:
- # {time: {key: damage done by key at this time}}
- finalCache = timeCache['finalDmg'] = {}
- timeDmgData = {}
- for time in sorted(changesByTime):
- timeDmgData = copy(timeDmgData)
- for key in changesByTime[time]:
- keyDmg = intCache[key][time]
- if key in timeDmgData:
- timeDmgData[key] = timeDmgData[key] + keyDmg
- else:
- timeDmgData[key] = keyDmg
- finalCache[time] = timeDmgData
- # We do not need intermediate cache once we have final
- del timeCache['intermediateDmg']
-
- def _generateTimeCacheIntermediate(self, fit, maxTime):
- if self._isTimeCacheValid(fit, maxTime):
- return
- timeCache = self._calcCache.setdefault(fit.ID, {})['timeCache'] = {'maxTime': maxTime}
- intCacheDpsVolley = timeCache['intermediateDpsVolley'] = {}
- intCacheDmg = timeCache['intermediateDmg'] = {}
-
- def addDpsVolley(ddKey, addedTimeStart, addedTimeFinish, addedVolleys):
- if not addedVolleys:
- return
- volleySum = sum(addedVolleys, DmgTypes(0, 0, 0, 0))
- if volleySum.total > 0:
- addedDps = volleySum / (addedTimeFinish - addedTimeStart)
- # We can take "just best" volley, no matter target resistances, because all
- # known items have the same damage type ratio throughout their cycle - and
- # applying resistances doesn't change final outcome
- bestVolley = max(addedVolleys, key=lambda v: v.total)
- ddCacheDps = intCacheDpsVolley.setdefault(ddKey, [])
- ddCacheDps.append((addedTimeStart, addedTimeFinish, addedDps, bestVolley))
-
- def addDmg(ddKey, addedTime, addedDmg):
- if addedDmg.total == 0:
- return
- intCacheDmg.setdefault(ddKey, {})[addedTime] = addedDmg
-
- # Modules
- for mod in fit.modules:
- if not mod.isDealingDamage():
- continue
- cycleParams = mod.getCycleParameters(reloadOverride=True)
- if cycleParams is None:
- continue
- currentTime = 0
- nonstopCycles = 0
- for cycleTimeMs, inactiveTimeMs in cycleParams.iterCycles():
- cycleVolleys = []
- volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
- for volleyTimeMs, volley in volleyParams.items():
- cycleVolleys.append(volley)
- addDmg(mod, currentTime + volleyTimeMs / 1000, volley)
- addDpsVolley(mod, currentTime, currentTime + cycleTimeMs / 1000, cycleVolleys)
- if inactiveTimeMs > 0:
- nonstopCycles = 0
- else:
- nonstopCycles += 1
- if currentTime > maxTime:
- break
- currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
- # Drones
- for drone in fit.drones:
- if not drone.isDealingDamage():
- continue
- cycleParams = drone.getCycleParameters(reloadOverride=True)
- if cycleParams is None:
- continue
- currentTime = 0
- volleyParams = drone.getVolleyParameters()
- for cycleTimeMs, inactiveTimeMs in cycleParams.iterCycles():
- cycleVolleys = []
- for volleyTimeMs, volley in volleyParams.items():
- cycleVolleys.append(volley)
- addDmg(drone, currentTime + volleyTimeMs / 1000, volley)
- addDpsVolley(drone, currentTime, currentTime + cycleTimeMs / 1000, cycleVolleys)
- if currentTime > maxTime:
- break
- currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
- # Fighters
- for fighter in fit.fighters:
- if not fighter.isDealingDamage():
- continue
- cycleParams = fighter.getCycleParametersPerEffectOptimizedDps(reloadOverride=True)
- if cycleParams is None:
- continue
- volleyParams = fighter.getVolleyParametersPerEffect()
- for effectID, abilityCycleParams in cycleParams.items():
- if effectID not in volleyParams:
- continue
- currentTime = 0
- abilityVolleyParams = volleyParams[effectID]
- for cycleTimeMs, inactiveTimeMs in abilityCycleParams.iterCycles():
- cycleVolleys = []
- for volleyTimeMs, volley in abilityVolleyParams.items():
- cycleVolleys.append(volley)
- addDmg((fighter, effectID), currentTime + volleyTimeMs / 1000, volley)
- addDpsVolley((fighter, effectID), currentTime, currentTime + cycleTimeMs / 1000, cycleVolleys)
- if currentTime > maxTime:
- break
- currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
-
- def _isTimeCacheValid(self, fit, maxTime):
- try:
- cacheMaxTime = self._calcCache[fit.ID]['timeCache']['maxTime']
- except KeyError:
- return False
- return maxTime <= cacheMaxTime
-
- def _generateTimeCacheDps(self, fit, maxTime):
- if fit.ID in self._calcCache and 'timeDps' in self._calcCache[fit.ID]:
- return
- intermediateCache = []
-
- def addDmg(addedTimeStart, addedTimeFinish, addedDmg):
- if addedDmg == 0:
- return
- addedDps = addedDmg / (addedTimeFinish - addedTimeStart)
- intermediateCache.append((addedTimeStart, addedTimeFinish, addedDps))
-
- for mod in fit.modules:
- if not mod.isDealingDamage():
- continue
- cycleParams = mod.getCycleParameters(reloadOverride=True)
- if cycleParams is None:
- continue
- currentTime = 0
- nonstopCycles = 0
- for cycleTimeMs, inactiveTimeMs in cycleParams.iterCycles():
- cycleDamage = 0
- volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
- for volleyTimeMs, volley in volleyParams.items():
- cycleDamage += volley.total
- addDmg(currentTime, currentTime + cycleTimeMs / 1000, cycleDamage)
- currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
- if inactiveTimeMs > 0:
- nonstopCycles = 0
- else:
- nonstopCycles += 1
- if currentTime > maxTime:
- break
- for drone in fit.drones:
- if not drone.isDealingDamage():
- continue
- cycleParams = drone.getCycleParameters(reloadOverride=True)
- if cycleParams is None:
- continue
- currentTime = 0
- for cycleTimeMs, inactiveTimeMs in cycleParams.iterCycles():
- cycleDamage = 0
- volleyParams = drone.getVolleyParameters()
- for volleyTimeMs, volley in volleyParams.items():
- cycleDamage += volley.total
- addDmg(currentTime, currentTime + cycleTimeMs / 1000, cycleDamage)
- currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
- if currentTime > maxTime:
- break
- for fighter in fit.fighters:
- if not fighter.isDealingDamage():
- continue
- cycleParams = fighter.getCycleParametersPerEffectOptimizedDps(reloadOverride=True)
- if cycleParams is None:
- continue
- volleyParams = fighter.getVolleyParametersPerEffect()
- for effectID, abilityCycleParams in cycleParams.items():
- if effectID not in volleyParams:
- continue
- abilityVolleyParams = volleyParams[effectID]
- currentTime = 0
- for cycleTimeMs, inactiveTimeMs in abilityCycleParams.iterCycles():
- cycleDamage = 0
- for volleyTimeMs, volley in abilityVolleyParams.items():
- cycleDamage += volley.total
- addDmg(currentTime, currentTime + cycleTimeMs / 1000, cycleDamage)
- currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
- if currentTime > maxTime:
- break
-
- # Post-process cache
- finalCache = {}
- for time in sorted(set(chain((i[0] for i in intermediateCache), (i[1] for i in intermediateCache)))):
- entries = (e for e in intermediateCache if e[0] <= time < e[1])
- dps = sum(e[2] for e in entries)
- finalCache[time] = dps
- fitCache = self._calcCache.setdefault(fit.ID, {})
- fitCache['timeDps'] = finalCache
-
def _composeTimeGraph(self, mainInput, fit, cacheName, calcFunc):
xs = []
ys = []
minTime, maxTime = mainInput[1]
- cache = self._calcCache[fit.ID]['timeCache'][cacheName]
+ cache = self._timeCache.getData(fit.ID, cacheName)
currentDps = None
currentTime = None
for currentTime in sorted(cache):
diff --git a/gui/builtinGraphs/fitDamageStats/timeCache.py b/gui/builtinGraphs/fitDamageStats/timeCache.py
new file mode 100644
index 000000000..8a8a905ab
--- /dev/null
+++ b/gui/builtinGraphs/fitDamageStats/timeCache.py
@@ -0,0 +1,215 @@
+# =============================================================================
+# Copyright (C) 2010 Diego Duclos
+#
+# This file is part of pyfa.
+#
+# pyfa is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# pyfa is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pyfa. If not, see .
+# =============================================================================
+
+
+from copy import copy
+
+from eos.utils.float import floatUnerr
+from eos.utils.spoolSupport import SpoolType, SpoolOptions
+from eos.utils.stats import DmgTypes
+
+
+class TimeCache:
+
+ def __init__(self):
+ self._data = {}
+
+ def clear(self, fitID):
+ if fitID is None:
+ self._data.clear()
+ elif fitID in self._data:
+ del self._data[fitID]
+
+ def getData(self, fitID, cacheType):
+ return self._data[fitID][cacheType]
+
+ def generateFinalFormDpsVolley(self, fit, 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]
+ # Final cache has been generated already, don't do anything
+ if 'finalDpsVolley' in fitCache:
+ return
+ # Convert cache from segments with assigned values into points
+ # which are located at times when dps/volley values change
+ pointCache = {}
+ for key, dmgList in fitCache['internalDpsVolley'].items():
+ pointData = pointCache[key] = {}
+ prevDps = None
+ prevVolley = None
+ prevTimeEnd = None
+ for timeStart, timeEnd, dps, volley in dmgList:
+ # First item
+ if not pointData:
+ pointData[timeStart] = (dps, volley)
+ # Gap between items
+ elif floatUnerr(prevTimeEnd) < floatUnerr(timeStart):
+ pointData[prevTimeEnd] = (DmgTypes(0, 0, 0, 0), DmgTypes(0, 0, 0, 0))
+ pointData[timeStart] = (dps, volley)
+ # Changed value
+ elif dps != prevDps or volley != prevVolley:
+ pointData[timeStart] = (dps, volley)
+ prevDps = dps
+ prevVolley = volley
+ prevTimeEnd = timeEnd
+ # We have data in another form, do not need old one any longer
+ del fitCache['internalDpsVolley']
+ changesByTime = {}
+ for key, dmgMap in pointCache.items():
+ for time in dmgMap:
+ changesByTime.setdefault(time, []).append(key)
+ # Here we convert cache to following format:
+ # {time: {key: (dps, volley}}
+ finalCache = fitCache['finalDpsVolley'] = {}
+ timeDmgData = {}
+ for time in sorted(changesByTime):
+ timeDmgData = copy(timeDmgData)
+ for key in changesByTime[time]:
+ timeDmgData[key] = pointCache[key][time]
+ finalCache[time] = timeDmgData
+
+ def generateFinalFormDmg(self, fit, 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]
+ # Final cache has been generated already, don't do anything
+ if 'finalDmg' in fitCache:
+ return
+ intCache = fitCache['internalDmg']
+ changesByTime = {}
+ for key, dmgMap in intCache.items():
+ for time in dmgMap:
+ changesByTime.setdefault(time, []).append(key)
+ # Here we convert cache to following format:
+ # {time: {key: damage done by key at this time}}
+ finalCache = fitCache['finalDmg'] = {}
+ timeDmgData = {}
+ for time in sorted(changesByTime):
+ timeDmgData = copy(timeDmgData)
+ for key in changesByTime[time]:
+ keyDmg = intCache[key][time]
+ if key in timeDmgData:
+ timeDmgData[key] = timeDmgData[key] + keyDmg
+ else:
+ timeDmgData[key] = keyDmg
+ finalCache[time] = timeDmgData
+ # We do not need internal cache once we have final
+ del fitCache['internalDmg']
+
+ def _generateInternalForm(self, fit, maxTime):
+ if self._isTimeCacheValid(fit, maxTime):
+ return
+ fitCache = self._data[fit.ID] = {'maxTime': maxTime}
+ intCacheDpsVolley = fitCache['internalDpsVolley'] = {}
+ intCacheDmg = fitCache['internalDmg'] = {}
+
+ def addDpsVolley(ddKey, addedTimeStart, addedTimeFinish, addedVolleys):
+ if not addedVolleys:
+ return
+ volleySum = sum(addedVolleys, DmgTypes(0, 0, 0, 0))
+ if volleySum.total > 0:
+ addedDps = volleySum / (addedTimeFinish - addedTimeStart)
+ # We can take "just best" volley, no matter target resistances, because all
+ # known items have the same damage type ratio throughout their cycle - and
+ # applying resistances doesn't change final outcome
+ bestVolley = max(addedVolleys, key=lambda v: v.total)
+ ddCacheDps = intCacheDpsVolley.setdefault(ddKey, [])
+ ddCacheDps.append((addedTimeStart, addedTimeFinish, addedDps, bestVolley))
+
+ def addDmg(ddKey, addedTime, addedDmg):
+ if addedDmg.total == 0:
+ return
+ intCacheDmg.setdefault(ddKey, {})[addedTime] = addedDmg
+
+ # Modules
+ for mod in fit.modules:
+ if not mod.isDealingDamage():
+ continue
+ cycleParams = mod.getCycleParameters(reloadOverride=True)
+ if cycleParams is None:
+ continue
+ currentTime = 0
+ nonstopCycles = 0
+ for cycleTimeMs, inactiveTimeMs in cycleParams.iterCycles():
+ cycleVolleys = []
+ volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
+ for volleyTimeMs, volley in volleyParams.items():
+ cycleVolleys.append(volley)
+ addDmg(mod, currentTime + volleyTimeMs / 1000, volley)
+ addDpsVolley(mod, currentTime, currentTime + cycleTimeMs / 1000, cycleVolleys)
+ if inactiveTimeMs > 0:
+ nonstopCycles = 0
+ else:
+ nonstopCycles += 1
+ if currentTime > maxTime:
+ break
+ currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
+ # Drones
+ for drone in fit.drones:
+ if not drone.isDealingDamage():
+ continue
+ cycleParams = drone.getCycleParameters(reloadOverride=True)
+ if cycleParams is None:
+ continue
+ currentTime = 0
+ volleyParams = drone.getVolleyParameters()
+ for cycleTimeMs, inactiveTimeMs in cycleParams.iterCycles():
+ cycleVolleys = []
+ for volleyTimeMs, volley in volleyParams.items():
+ cycleVolleys.append(volley)
+ addDmg(drone, currentTime + volleyTimeMs / 1000, volley)
+ addDpsVolley(drone, currentTime, currentTime + cycleTimeMs / 1000, cycleVolleys)
+ if currentTime > maxTime:
+ break
+ currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
+ # Fighters
+ for fighter in fit.fighters:
+ if not fighter.isDealingDamage():
+ continue
+ cycleParams = fighter.getCycleParametersPerEffectOptimizedDps(reloadOverride=True)
+ if cycleParams is None:
+ continue
+ volleyParams = fighter.getVolleyParametersPerEffect()
+ for effectID, abilityCycleParams in cycleParams.items():
+ if effectID not in volleyParams:
+ continue
+ currentTime = 0
+ abilityVolleyParams = volleyParams[effectID]
+ for cycleTimeMs, inactiveTimeMs in abilityCycleParams.iterCycles():
+ cycleVolleys = []
+ for volleyTimeMs, volley in abilityVolleyParams.items():
+ cycleVolleys.append(volley)
+ addDmg((fighter, effectID), currentTime + volleyTimeMs / 1000, volley)
+ addDpsVolley((fighter, effectID), currentTime, currentTime + cycleTimeMs / 1000, cycleVolleys)
+ if currentTime > maxTime:
+ break
+ currentTime += cycleTimeMs / 1000 + inactiveTimeMs / 1000
+
+ def _isTimeCacheValid(self, fit, maxTime):
+ try:
+ cacheMaxTime = self._data[fit.ID]['maxTime']
+ except KeyError:
+ return False
+ return maxTime <= cacheMaxTime