Rework damage over time graph to show actual damage dealt per volley
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
import itertools
|
||||
|
||||
|
||||
class Graph(object):
|
||||
class Graph:
|
||||
|
||||
def __init__(self, fit, function, data=None):
|
||||
self.fit = fit
|
||||
@@ -54,7 +54,7 @@ class Graph(object):
|
||||
yield point, self.function(point)
|
||||
|
||||
|
||||
class Data(object):
|
||||
class Data:
|
||||
def __init__(self, name, dataString, step=None):
|
||||
self.name = name
|
||||
self.step = step
|
||||
@@ -83,7 +83,7 @@ class Data(object):
|
||||
return len(self.data) == 1 and self.data[0].isConstant()
|
||||
|
||||
|
||||
class Constant(object):
|
||||
class Constant:
|
||||
def __init__(self, const):
|
||||
if isinstance(const, str):
|
||||
self.value = None if const == "" else float(const)
|
||||
@@ -98,7 +98,7 @@ class Constant(object):
|
||||
return True
|
||||
|
||||
|
||||
class Range(object):
|
||||
class Range:
|
||||
|
||||
def __init__(self, string, step):
|
||||
start, end = string.split("-")
|
||||
|
||||
@@ -31,13 +31,63 @@ class FitDmgTimeGraph(Graph):
|
||||
defaults = {"time": 0}
|
||||
|
||||
def __init__(self, fit, data=None):
|
||||
Graph.__init__(self, fit, self.calcDps, data if data is not None else self.defaults)
|
||||
Graph.__init__(self, fit, self.calcDmg, data if data is not None else self.defaults)
|
||||
self.fit = fit
|
||||
|
||||
def calcDps(self, data):
|
||||
def calcDmg(self, data):
|
||||
fit = self.fit
|
||||
time = data["time"]
|
||||
dmg = 0
|
||||
for i in range(round(time) + 1):
|
||||
dmg += fit.getTotalDps(spoolOptions=SpoolOptions(SpoolType.TIME, i, True)).total
|
||||
return dmg
|
||||
# We'll handle calculations in milliseconds
|
||||
maxTime = data["time"] * 1000
|
||||
currentDmg = 0
|
||||
for mod in fit.modules:
|
||||
cycleParams = mod.getCycleParameters(reloadOverride=True)
|
||||
if cycleParams is None:
|
||||
continue
|
||||
currentTime = 0
|
||||
nonstopCycles = 0
|
||||
for cycleTime, inactiveTime in cycleParams.iterCycles():
|
||||
volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
|
||||
for volleyTime, volley in volleyParams.items():
|
||||
if currentTime + volleyTime <= maxTime:
|
||||
currentDmg += volley.total
|
||||
currentTime += cycleTime
|
||||
currentTime += inactiveTime
|
||||
if inactiveTime == 0:
|
||||
nonstopCycles += 1
|
||||
else:
|
||||
nonstopCycles = 0
|
||||
if currentTime > maxTime:
|
||||
break
|
||||
for drone in fit.drones:
|
||||
cycleParams = drone.getCycleParameters(reloadOverride=True)
|
||||
if cycleParams is None:
|
||||
continue
|
||||
currentTime = 0
|
||||
volleyParams = drone.getVolleyParameters()
|
||||
for cycleTime, inactiveTime in cycleParams.iterCycles():
|
||||
for volleyTime, volley in volleyParams.items():
|
||||
if currentTime + volleyTime <= maxTime:
|
||||
currentDmg += volley.total
|
||||
currentTime += cycleTime
|
||||
currentTime += inactiveTime
|
||||
if currentTime > maxTime:
|
||||
break
|
||||
for fighter in fit.fighters:
|
||||
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 cycleTime, inactiveTime in abilityCycleParams.iterCycles():
|
||||
for volleyTime, volley in abilityVolleyParams.items():
|
||||
if currentTime + volleyTime <= maxTime:
|
||||
currentDmg += volley.total
|
||||
currentTime += cycleTime
|
||||
currentTime += inactiveTime
|
||||
if currentTime > maxTime:
|
||||
break
|
||||
return currentDmg
|
||||
|
||||
@@ -222,8 +222,17 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def getDpsPerEffect(self, targetResists=None):
|
||||
if not self.active or self.amountActive <= 0:
|
||||
return {}
|
||||
cycleParams = self.getCycleParametersPerEffectOptimizedDps(targetResists=targetResists)
|
||||
dpsMap = {}
|
||||
for ability in self.abilities:
|
||||
if ability.effectID in cycleParams:
|
||||
cycleTime = cycleParams[ability.effectID].averageTime
|
||||
dpsMap[ability.effectID] = ability.getDps(targetResists=targetResists, cycleTimeOverride=cycleTime)
|
||||
return dpsMap
|
||||
|
||||
def getCycleParametersPerEffectOptimizedDps(self, targetResists=None, reloadOverride=None):
|
||||
cycleParamsInfinite = self.getCycleParametersPerEffectInfinite()
|
||||
cycleParamsReload = self.getCycleParametersPerEffect()
|
||||
cycleParamsReload = self.getCycleParametersPerEffect(reloadOverride=reloadOverride)
|
||||
dpsMapOnlyInfinite = {}
|
||||
dpsMapAllWithReloads = {}
|
||||
# Decide if it's better to keep steady dps up and never reload or reload from time to time
|
||||
@@ -236,19 +245,19 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
dpsMapAllWithReloads[ability.effectID] = ability.getDps(targetResists=targetResists, cycleTimeOverride=cycleTime)
|
||||
totalOnlyInfinite = sum(i.total for i in dpsMapOnlyInfinite.values())
|
||||
totalAllWithReloads = sum(i.total for i in dpsMapAllWithReloads.values())
|
||||
return dpsMapOnlyInfinite if totalOnlyInfinite >= totalAllWithReloads else dpsMapAllWithReloads
|
||||
return cycleParamsInfinite if totalOnlyInfinite >= totalAllWithReloads else cycleParamsReload
|
||||
|
||||
def getCycleParametersPerEffectInfinite(self):
|
||||
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities if a.numShots == 0}
|
||||
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities if a.numShots == 0 and a.cycleTime > 0}
|
||||
|
||||
def getCycleParametersPerEffect(self, reloadOverride=None):
|
||||
factorReload = reloadOverride if reloadOverride is not None else self.owner.factorReload
|
||||
# Assume it can cycle infinitely
|
||||
if not factorReload:
|
||||
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities}
|
||||
limitedAbilities = [a for a in self.abilities if a.numShots > 0]
|
||||
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities if a.cycleTime > 0}
|
||||
limitedAbilities = [a for a in self.abilities if a.numShots > 0 and a.cycleTime > 0]
|
||||
if len(limitedAbilities) == 0:
|
||||
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities}
|
||||
return {a.effectID: CycleInfo(a.cycleTime, 0, math.inf) for a in self.abilities if a.cycleTime > 0}
|
||||
validAbilities = [a for a in self.abilities if a.cycleTime > 0]
|
||||
if len(validAbilities) == 0:
|
||||
return {}
|
||||
|
||||
@@ -401,8 +401,12 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
volley = self.getModifiedItemAttr("specialtyMiningAmount") or self.getModifiedItemAttr(
|
||||
"miningAmount") or 0
|
||||
if volley:
|
||||
cycleTime = self.getCycleParameters().averageTime
|
||||
self.__miningyield = volley / (cycleTime / 1000.0)
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
self.__miningyield = 0
|
||||
else:
|
||||
cycleTime = cycleParams.averageTime
|
||||
self.__miningyield = volley / (cycleTime / 1000.0)
|
||||
else:
|
||||
self.__miningyield = 0
|
||||
else:
|
||||
@@ -452,9 +456,12 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return volleyParams[min(volleyParams)]
|
||||
|
||||
def getDps(self, spoolOptions=None, targetResists=None, ignoreState=False):
|
||||
volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
|
||||
avgCycleTime = self.getCycleParameters().averageTime
|
||||
dmgDuringCycle = DmgTypes(0, 0, 0, 0)
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
return dmgDuringCycle
|
||||
volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
|
||||
avgCycleTime = cycleParams.averageTime
|
||||
if len(volleyParams) == 0 or avgCycleTime == 0:
|
||||
return dmgDuringCycle
|
||||
for volleyValue in volleyParams.values():
|
||||
@@ -494,7 +501,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
else:
|
||||
return None, 0
|
||||
if rrAmount:
|
||||
rrAmount *= 1 / (self.getCycleParameters().averageTime / 1000)
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
return None, 0
|
||||
rrAmount *= 1 / (cycleParams.averageTime / 1000)
|
||||
if module.item.group.name == "Ancillary Remote Armor Repairer" and module.charge:
|
||||
rrAmount *= module.getModifiedItemAttr("chargedArmorDamageMultiplier", 1)
|
||||
|
||||
@@ -852,6 +862,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
cycles_until_reload = math.inf
|
||||
|
||||
active_time = self.rawCycleTime
|
||||
if active_time == 0:
|
||||
return None
|
||||
forced_inactive_time = self.reactivationDelay
|
||||
reload_time = self.reloadTime
|
||||
# Effects which cannot be reloaded have the same processing whether
|
||||
@@ -917,7 +929,10 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def capUse(self):
|
||||
capNeed = self.getModifiedItemAttr("capacitorNeed")
|
||||
if capNeed and self.state >= FittingModuleState.ACTIVE:
|
||||
cycleTime = self.getCycleParameters().averageTime
|
||||
cycleParams = self.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
return 0
|
||||
cycleTime = cycleParams.averageTime
|
||||
if cycleTime > 0:
|
||||
capUsed = capNeed / (cycleTime / 1000.0)
|
||||
return capUsed
|
||||
|
||||
@@ -15,6 +15,12 @@ class CycleInfo:
|
||||
def averageTime(self):
|
||||
return self.activeTime + self.inactiveTime
|
||||
|
||||
def iterCycles(self):
|
||||
i = 0
|
||||
while i < self.quantity:
|
||||
yield self.activeTime, self.inactiveTime
|
||||
i += 1
|
||||
|
||||
def _getCycleQuantity(self):
|
||||
return self.quantity
|
||||
|
||||
@@ -37,6 +43,14 @@ class CycleSequence:
|
||||
"""Get average time between cycles."""
|
||||
return self._getTime() / self._getCycleQuantity()
|
||||
|
||||
def iterCycles(self):
|
||||
i = 0
|
||||
while i < self.quantity:
|
||||
for cycleInfo in self.sequence:
|
||||
for cycleTime, inactiveTime in cycleInfo.iterCycles():
|
||||
yield cycleTime, inactiveTime
|
||||
i += 1
|
||||
|
||||
def _getCycleQuantity(self):
|
||||
quantity = 0
|
||||
for item in self.sequence:
|
||||
|
||||
@@ -140,7 +140,10 @@ class Miscellanea(ViewColumn):
|
||||
return "+ " + ", ".join(info), "Slot Modifiers"
|
||||
elif itemGroup == "Energy Neutralizer":
|
||||
neutAmount = stuff.getModifiedItemAttr("energyNeutralizerAmount")
|
||||
cycleTime = stuff.getCycleParameters().averageTime
|
||||
cycleParams = stuff.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
return "", None
|
||||
cycleTime = cycleParams.averageTime
|
||||
if not neutAmount or not cycleTime:
|
||||
return "", None
|
||||
capPerSec = float(-neutAmount) * 1000 / cycleTime
|
||||
@@ -149,7 +152,10 @@ class Miscellanea(ViewColumn):
|
||||
return text, tooltip
|
||||
elif itemGroup == "Energy Nosferatu":
|
||||
neutAmount = stuff.getModifiedItemAttr("powerTransferAmount")
|
||||
cycleTime = stuff.getCycleParameters().averageTime
|
||||
cycleParams = stuff.getCycleParameters()
|
||||
if cycleParams is None:
|
||||
return "", None
|
||||
cycleTime = cycleParams.averageTime
|
||||
if not neutAmount or not cycleTime:
|
||||
return "", None
|
||||
capPerSec = float(-neutAmount) * 1000 / cycleTime
|
||||
|
||||
@@ -275,6 +275,7 @@ class GraphFrame(wx.Frame):
|
||||
x, y = success, status
|
||||
|
||||
self.subplot.plot(x, y)
|
||||
self.subplot.set_ylim(bottom=0)
|
||||
legend.append(fit.name)
|
||||
except Exception as ex:
|
||||
pyfalog.warning("Invalid values in '{0}'", fit.name)
|
||||
|
||||
Reference in New Issue
Block a user