Rework damage over time graph to show actual damage dealt per volley

This commit is contained in:
DarkPhoenix
2019-05-12 04:44:27 +03:00
parent e26bcb2e5e
commit 54eea7d702
7 changed files with 120 additions and 25 deletions

View File

@@ -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("-")

View File

@@ -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

View File

@@ -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 {}

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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)