Rework internal interfaces to be able to specify spoolup in more flexible manner and carry output dps types
This commit is contained in:
@@ -75,7 +75,7 @@ class FitDpsGraph(Graph):
|
||||
pyfalog.critical(e)
|
||||
|
||||
for mod in fit.modules:
|
||||
dps = mod.damageStats(fit.targetResists)[1]
|
||||
dps = mod.getDps(targetResists=fit.targetResists).total
|
||||
if mod.hardpoint == Hardpoint.TURRET:
|
||||
if mod.state >= State.ACTIVE:
|
||||
total += dps * self.calculateTurretMultiplier(mod, data)
|
||||
@@ -88,7 +88,7 @@ class FitDpsGraph(Graph):
|
||||
for drone in fit.drones:
|
||||
multiplier = 1 if drone.getModifiedItemAttr("maxVelocity") > 1 else self.calculateTurretMultiplier(
|
||||
drone, data)
|
||||
dps = drone.damageStats(fit.targetResists)[0]
|
||||
dps = drone.getDps(targetResists=fit.targetResists).total
|
||||
total += dps * multiplier
|
||||
|
||||
# this is janky as fuck
|
||||
@@ -98,7 +98,7 @@ class FitDpsGraph(Graph):
|
||||
for ability in fighter.abilities:
|
||||
if ability.dealsDamage and ability.active:
|
||||
multiplier = self.calculateFighterMissileMultiplier(ability, data)
|
||||
dps = ability.damageStats(fit.targetResists)[0]
|
||||
dps = ability.getDps(targetResists=fit.targetResists).total
|
||||
total += dps * multiplier
|
||||
|
||||
return total
|
||||
|
||||
@@ -24,12 +24,13 @@ from sqlalchemy.orm import validates, reconstructor
|
||||
import eos.db
|
||||
from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
|
||||
def __init__(self, item):
|
||||
@@ -65,8 +66,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def build(self):
|
||||
""" Build object. Assumes proper and valid item already set """
|
||||
self.__charge = None
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__miningyield = None
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__itemModifiedAttributes.original = self.__item.attributes
|
||||
@@ -120,39 +120,42 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def hasAmmo(self):
|
||||
return self.charge is not None
|
||||
|
||||
def getDps(self):
|
||||
return self.damageStats()[0]
|
||||
def getVolley(self, targetResists=None):
|
||||
if not self.dealsDamage or self.amountActive <= 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
if self.__baseVolley is None:
|
||||
dmgGetter = self.getModifiedChargeAttr if self.hasAmmo else self.getModifiedItemAttr
|
||||
dmgMult = self.amountActive * (self.getModifiedItemAttr("damageMultiplier") or 1)
|
||||
self.__baseVolley = DmgTypes(
|
||||
em=(dmgGetter("emDamage") or 0) * dmgMult,
|
||||
thermal=(dmgGetter("thermalDamage") or 0) * dmgMult,
|
||||
kinetic=(dmgGetter("kineticDamage") or 0) * dmgMult,
|
||||
explosive=(dmgGetter("explosiveDamage") or 0) * dmgMult)
|
||||
volley = DmgTypes(
|
||||
em=self.__baseVolley.em * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
|
||||
def getVolley(self):
|
||||
return self.damageStats()[1]
|
||||
def getDps(self, targetResists=None):
|
||||
volley = self.getVolley(targetResists=targetResists)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
cycleAttr = "missileLaunchDuration" if self.hasAmmo else "speed"
|
||||
cycleTime = self.getModifiedItemAttr(cycleAttr)
|
||||
dpsFactor = 1 / (cycleTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
kinetic=volley.kinetic * dpsFactor,
|
||||
explosive=volley.explosive * dpsFactor)
|
||||
return dps
|
||||
|
||||
def changeType(self, typeID):
|
||||
self.itemID = typeID
|
||||
self.init()
|
||||
|
||||
def damageStats(self, targetResists=None):
|
||||
if self.__dps is None:
|
||||
self.__volley = 0
|
||||
self.__dps = 0
|
||||
if self.dealsDamage is True and self.amountActive > 0:
|
||||
if self.hasAmmo:
|
||||
attr = "missileLaunchDuration"
|
||||
getter = self.getModifiedChargeAttr
|
||||
else:
|
||||
attr = "speed"
|
||||
getter = self.getModifiedItemAttr
|
||||
|
||||
cycleTime = self.getModifiedItemAttr(attr)
|
||||
|
||||
volley = sum(
|
||||
[(getter("%sDamage" % d) or 0) * (1 - getattr(targetResists, "%sAmount" % d, 0)) for d in self.DAMAGE_TYPES])
|
||||
volley *= self.amountActive
|
||||
volley *= self.getModifiedItemAttr("damageMultiplier") or 1
|
||||
self.__volley = volley
|
||||
self.__dps = volley / (cycleTime / 1000.0)
|
||||
|
||||
return self.__dps, self.__volley
|
||||
|
||||
@property
|
||||
def miningStats(self):
|
||||
if self.__miningyield is None:
|
||||
@@ -210,8 +213,7 @@ class Drone(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return val
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__miningyield = None
|
||||
self.itemModifiedAttributes.clear()
|
||||
self.chargeModifiedAttributes.clear()
|
||||
|
||||
@@ -26,6 +26,7 @@ from eos.effectHandlerHelpers import HandledItem, HandledCharge
|
||||
from eos.modifiedAttributeDict import ModifiedAttributeDict, ItemAttrShortcut, ChargeAttrShortcut
|
||||
from eos.saveddata.fighterAbility import FighterAbility
|
||||
from eos.saveddata.module import Slot
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -87,8 +88,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def build(self):
|
||||
""" Build object. Assumes proper and valid item already set """
|
||||
self.__charge = None
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__miningyield = None
|
||||
self.__itemModifiedAttributes = ModifiedAttributeDict()
|
||||
self.__chargeModifiedAttributes = ModifiedAttributeDict()
|
||||
@@ -172,45 +172,88 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
def hasAmmo(self):
|
||||
return self.charge is not None
|
||||
|
||||
def getDps(self):
|
||||
return self.damageStats()[0]
|
||||
def getVolley(self, targetResists=None):
|
||||
if not self.active or self.amountActive <= 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
if self.__baseVolley is None:
|
||||
em = 0
|
||||
therm = 0
|
||||
kin = 0
|
||||
exp = 0
|
||||
for ability in self.abilities:
|
||||
# Not passing resists here as we want to calculate and store base volley
|
||||
abilityVolley = ability.getVolley()
|
||||
em += abilityVolley.em
|
||||
therm += abilityVolley.thermal
|
||||
kin += abilityVolley.kinetic
|
||||
exp += abilityVolley.explosive
|
||||
self.__baseVolley = DmgTypes(em, therm, kin, exp)
|
||||
volley = DmgTypes(
|
||||
em=self.__baseVolley.em * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
|
||||
def getVolley(self):
|
||||
return self.damageStats()[1]
|
||||
|
||||
def damageStats(self, targetResists=None):
|
||||
if self.__dps is None:
|
||||
self.__volley = 0
|
||||
self.__dps = 0
|
||||
if self.active and self.amountActive > 0:
|
||||
for ability in self.abilities:
|
||||
dps, volley = ability.damageStats(targetResists)
|
||||
self.__dps += dps
|
||||
self.__volley += volley
|
||||
|
||||
# For forward compatability this assumes a fighter
|
||||
# can have more than 2 damaging abilities and/or
|
||||
# multiple that use charges.
|
||||
if self.owner.factorReload:
|
||||
activeTimes = []
|
||||
reloadTimes = []
|
||||
constantDps = 0
|
||||
for ability in self.abilities:
|
||||
if not ability.active:
|
||||
continue
|
||||
if ability.numShots == 0:
|
||||
dps, volley = ability.damageStats(targetResists)
|
||||
constantDps += dps
|
||||
continue
|
||||
activeTimes.append(ability.numShots * ability.cycleTime)
|
||||
reloadTimes.append(ability.reloadTime)
|
||||
|
||||
if len(activeTimes) > 0:
|
||||
shortestActive = sorted(activeTimes)[0]
|
||||
longestReload = sorted(reloadTimes, reverse=True)[0]
|
||||
self.__dps = max(constantDps, self.__dps * shortestActive / (shortestActive + longestReload))
|
||||
|
||||
return self.__dps, self.__volley
|
||||
def getDps(self, targetResists=None):
|
||||
if not self.active or self.amountActive <= 0:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
# Analyze cooldowns when reload is factored in
|
||||
if self.owner.factorReload:
|
||||
activeTimes = []
|
||||
reloadTimes = []
|
||||
peakEm = 0
|
||||
peakTherm = 0
|
||||
peakKin = 0
|
||||
peakExp = 0
|
||||
steadyEm = 0
|
||||
steadyTherm = 0
|
||||
steadyKin = 0
|
||||
steadyExp = 0
|
||||
for ability in self.abilities:
|
||||
abilityDps = ability.getDps(targetResists=targetResists)
|
||||
# Peak dps
|
||||
peakEm += abilityDps.em
|
||||
peakTherm += abilityDps.thermal
|
||||
peakKin += abilityDps.kinetic
|
||||
peakExp += abilityDps.explosive
|
||||
# Infinite use - add to steady dps
|
||||
if ability.numShots == 0:
|
||||
steadyEm += abilityDps.em
|
||||
steadyTherm += abilityDps.thermal
|
||||
steadyKin += abilityDps.kinetic
|
||||
steadyExp += abilityDps.explosive
|
||||
else:
|
||||
activeTimes.append(ability.numShots * ability.cycleTime)
|
||||
reloadTimes.append(ability.reloadTime)
|
||||
steadyDps = DmgTypes(steadyEm, steadyTherm, steadyKin, steadyExp)
|
||||
if len(activeTimes) > 0:
|
||||
shortestActive = sorted(activeTimes)[0]
|
||||
longestReload = sorted(reloadTimes, reverse=True)[0]
|
||||
peakDps = DmgTypes(peakEm, peakTherm, peakKin, peakExp)
|
||||
peakAdjustFactor = shortestActive / (shortestActive + longestReload)
|
||||
peakDpsAdjusted = DmgTypes(
|
||||
em=peakDps.em * peakAdjustFactor,
|
||||
thermal=peakDps.thermal * peakAdjustFactor,
|
||||
kinetic=peakDps.kinetic * peakAdjustFactor,
|
||||
explosive=peakDps.explosive * peakAdjustFactor)
|
||||
dps = max(steadyDps, peakDpsAdjusted, key=lambda d: d.total)
|
||||
return dps
|
||||
else:
|
||||
return steadyDps
|
||||
# Just sum all abilities when not taking reload into consideration
|
||||
else:
|
||||
em = 0
|
||||
therm = 0
|
||||
kin = 0
|
||||
exp = 0
|
||||
for ability in self.abilities:
|
||||
abilityDps = ability.getDps(targetResists=targetResists)
|
||||
em += abilityDps.em
|
||||
therm += abilityDps.thermal
|
||||
kin += abilityDps.kinetic
|
||||
exp += abilityDps.explosive
|
||||
return DmgTypes(em, therm, kin, exp)
|
||||
|
||||
@property
|
||||
def maxRange(self):
|
||||
@@ -253,8 +296,7 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return val
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
self.__volley = None
|
||||
self.__baseVolley = None
|
||||
self.__miningyield = None
|
||||
self.itemModifiedAttributes.clear()
|
||||
self.chargeModifiedAttributes.clear()
|
||||
|
||||
@@ -21,12 +21,12 @@ from logbook import Logger
|
||||
|
||||
from sqlalchemy.orm import reconstructor
|
||||
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
|
||||
class FighterAbility(object):
|
||||
DAMAGE_TYPES = ("em", "kinetic", "explosive", "thermal")
|
||||
DAMAGE_TYPES2 = ("EM", "Kin", "Exp", "Therm")
|
||||
|
||||
# We aren't able to get data on the charges that can be stored with fighters. So we hardcode that data here, keyed
|
||||
# with the fighter squadron role
|
||||
@@ -118,30 +118,38 @@ class FighterAbility(object):
|
||||
|
||||
return speed
|
||||
|
||||
def damageStats(self, targetResists=None):
|
||||
if self.__dps is None:
|
||||
self.__volley = 0
|
||||
self.__dps = 0
|
||||
if self.dealsDamage and self.active:
|
||||
cycleTime = self.cycleTime
|
||||
def getVolley(self, targetResists=None):
|
||||
if not self.dealsDamage or not self.active:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
if self.attrPrefix == "fighterAbilityLaunchBomb":
|
||||
em = self.fighter.getModifiedChargeAttr("emDamage") or 0
|
||||
therm = self.fighter.getModifiedChargeAttr("thermalDamage") or 0
|
||||
kin = self.fighter.getModifiedChargeAttr("kineticDamage") or 0
|
||||
exp = self.fighter.getModifiedChargeAttr("explosiveDamage") or 0
|
||||
else:
|
||||
em = self.fighter.getModifiedItemAttr("{}DamageEM".format(self.attrPrefix)) or 0
|
||||
therm = self.fighter.getModifiedItemAttr("{}DamageTherm".format(self.attrPrefix)) or 0
|
||||
kin = self.fighter.getModifiedItemAttr("{}DamageKin".format(self.attrPrefix)) or 0
|
||||
exp = self.fighter.getModifiedItemAttr("{}DamageExp".format(self.attrPrefix)) or 0
|
||||
dmgMult = self.fighter.amountActive * (self.fighter.getModifiedItemAttr("{}DamageMultiplier".format(self.attrPrefix)) or 1)
|
||||
volley = DmgTypes(
|
||||
em=em * dmgMult * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=therm * dmgMult * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=kin * dmgMult * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=exp * dmgMult * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
|
||||
if self.attrPrefix == "fighterAbilityLaunchBomb":
|
||||
# bomb calcs
|
||||
volley = sum([(self.fighter.getModifiedChargeAttr("%sDamage" % attr) or 0) * (
|
||||
1 - getattr(targetResists, "%sAmount" % attr, 0)) for attr in self.DAMAGE_TYPES])
|
||||
else:
|
||||
volley = sum(map(lambda d2, d:
|
||||
(self.fighter.getModifiedItemAttr(
|
||||
"{}Damage{}".format(self.attrPrefix, d2)) or 0) *
|
||||
(1 - getattr(targetResists, "{}Amount".format(d), 0)),
|
||||
self.DAMAGE_TYPES2, self.DAMAGE_TYPES))
|
||||
|
||||
volley *= self.fighter.amountActive
|
||||
volley *= self.fighter.getModifiedItemAttr("{}DamageMultiplier".format(self.attrPrefix)) or 1
|
||||
self.__volley += volley
|
||||
self.__dps += volley / (cycleTime / 1000.0)
|
||||
|
||||
return self.__dps, self.__volley
|
||||
def getDps(self, targetResists=None):
|
||||
volley = self.getVolley(targetResists=targetResists)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
dpsFactor = 1 / (self.cycleTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
kinetic=volley.kinetic * dpsFactor,
|
||||
explosive=volley.explosive * dpsFactor)
|
||||
return dps
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
|
||||
@@ -155,9 +155,7 @@ class Fit(object):
|
||||
def targetResists(self, targetResists):
|
||||
self.__targetResists = targetResists
|
||||
self.__weaponDps = None
|
||||
self.__weaponDpsSpool = None
|
||||
self.__weaponVolley = None
|
||||
self.__weaponVolleySpool = None
|
||||
self.__droneDps = None
|
||||
self.__droneVolley = None
|
||||
|
||||
@@ -279,25 +277,15 @@ class Fit(object):
|
||||
def projectedFighters(self):
|
||||
return self.__projectedFighters
|
||||
|
||||
def getWeaponDps(self, spool=False):
|
||||
if spool:
|
||||
if self.__weaponDpsSpool is None:
|
||||
self.calculateWeaponStats()
|
||||
return self.__weaponDpsSpool
|
||||
else:
|
||||
if self.__weaponDps is None:
|
||||
self.calculateWeaponStats()
|
||||
return self.__weaponDps
|
||||
def getWeaponDps(self):
|
||||
if self.__weaponDps is None:
|
||||
self.calculateWeaponStats()
|
||||
return self.__weaponDps
|
||||
|
||||
def getWeaponVolley(self, spool=False):
|
||||
if spool:
|
||||
if self.__weaponVolleySpool is None:
|
||||
self.calculateWeaponStats()
|
||||
return self.__weaponVolleySpool
|
||||
else:
|
||||
if self.__weaponVolley is None:
|
||||
self.calculateWeaponStats()
|
||||
return self.__weaponVolley
|
||||
def getWeaponVolley(self):
|
||||
if self.__weaponVolley is None:
|
||||
self.calculateWeaponStats()
|
||||
return self.__weaponVolley
|
||||
|
||||
def getDroneDps(self):
|
||||
if self.__droneDps is None:
|
||||
@@ -309,11 +297,11 @@ class Fit(object):
|
||||
self.calculateWeaponStats()
|
||||
return self.__droneVolley
|
||||
|
||||
def getTotalDps(self, spool=False):
|
||||
return self.getDroneDps() + self.getWeaponDps(spool=spool)
|
||||
def getTotalDps(self):
|
||||
return self.getDroneDps() + self.getWeaponDps()
|
||||
|
||||
def getTotalVolley(self, spool=False):
|
||||
return self.getDroneVolley() + self.getWeaponVolley(spool=spool)
|
||||
def getTotalVolley(self):
|
||||
return self.getDroneVolley() + self.getWeaponVolley()
|
||||
|
||||
@property
|
||||
def minerYield(self):
|
||||
@@ -412,9 +400,7 @@ class Fit(object):
|
||||
def clear(self, projected=False, command=False):
|
||||
self.__effectiveTank = None
|
||||
self.__weaponDps = None
|
||||
self.__weaponDpsSpool = None
|
||||
self.__weaponVolley = None
|
||||
self.__weaponVolleySpool = None
|
||||
self.__minerYield = None
|
||||
self.__effectiveSustainableTank = None
|
||||
self.__sustainableTank = None
|
||||
@@ -1543,34 +1529,25 @@ class Fit(object):
|
||||
self.__droneYield = droneYield
|
||||
|
||||
def calculateWeaponStats(self):
|
||||
weaponDps = 0
|
||||
weaponDpsSpool = 0
|
||||
droneDps = 0
|
||||
weaponVolley = 0
|
||||
weaponVolleySpool = 0
|
||||
droneVolley = 0
|
||||
weaponDps = 0
|
||||
droneDps = 0
|
||||
|
||||
for mod in self.modules:
|
||||
dps, dpsSpool, volley, volleySpool = mod.damageStats(self.targetResists)
|
||||
weaponDps += dps
|
||||
weaponDpsSpool += dpsSpool
|
||||
weaponVolley += volley
|
||||
weaponVolleySpool += volleySpool
|
||||
weaponVolley += mod.getVolley(targetResists=self.targetResists).total
|
||||
weaponDps += mod.getDps(targetResists=self.targetResists).total
|
||||
|
||||
for drone in self.drones:
|
||||
dps, volley = drone.damageStats(self.targetResists)
|
||||
droneDps += dps
|
||||
droneVolley += volley
|
||||
droneVolley += drone.getVolley(targetResists=self.targetResists).total
|
||||
droneDps += drone.getDps(targetResists=self.targetResists).total
|
||||
|
||||
for fighter in self.fighters:
|
||||
dps, volley = fighter.damageStats(self.targetResists)
|
||||
droneDps += dps
|
||||
droneVolley += volley
|
||||
droneVolley += fighter.getVolley(targetResists=self.targetResists).total
|
||||
droneDps += fighter.getDps(targetResists=self.targetResists).total
|
||||
|
||||
self.__weaponDps = weaponDps
|
||||
self.__weaponDpsSpool = weaponDpsSpool
|
||||
self.__weaponVolley = weaponVolley
|
||||
self.__weaponVolleySpool = weaponVolleySpool
|
||||
self.__droneDps = droneDps
|
||||
self.__droneVolley = droneVolley
|
||||
|
||||
|
||||
@@ -28,8 +28,9 @@ from eos.enum import Enum
|
||||
from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict
|
||||
from eos.saveddata.citadel import Citadel
|
||||
from eos.saveddata.mutator import Mutator
|
||||
from eos.utils.spoolSupport import SpoolType, calculateSpoolup
|
||||
from eos.utils.float import floatUnerr
|
||||
from eos.utils.spoolSupport import calculateSpoolup
|
||||
from eos.utils.stats import DmgTypes
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
@@ -97,7 +98,6 @@ class Hardpoint(Enum):
|
||||
|
||||
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
"""An instance of this class represents a module together with its charge and modified attributes"""
|
||||
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
|
||||
MINING_ATTRIBUTES = ("miningAmount",)
|
||||
SYSTEM_GROUPS = ("Effect Beacon", "MassiveEnvironments", "Abyssal Hazards", "Non-Interactable Object")
|
||||
|
||||
@@ -170,12 +170,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
if self.__charge and self.__charge.category.name != "Charge":
|
||||
self.__charge = None
|
||||
|
||||
self.__dps = None
|
||||
self.__dpsSpoolZero = None
|
||||
self.__dpsSpoolFull = None
|
||||
self.__volley = None
|
||||
self.__volleySpoolZero = None
|
||||
self.__volleySpoolFull = None
|
||||
self.__baseVolley = None
|
||||
self.__miningyield = None
|
||||
self.__reloadTime = None
|
||||
self.__reloadForce = None
|
||||
@@ -417,48 +412,6 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
self.__itemModifiedAttributes.clear()
|
||||
|
||||
def damageStats(self, targetResists):
|
||||
if self.__dps is None:
|
||||
self.__dps = 0
|
||||
self.__dpsSpoolZero = 0
|
||||
self.__dpsSpoolFull = 0
|
||||
self.__volley = 0
|
||||
self.__volleySpoolZero = 0
|
||||
self.__volleySpoolFull = 0
|
||||
|
||||
if not self.isEmpty and self.state >= State.ACTIVE:
|
||||
if self.charge:
|
||||
func = self.getModifiedChargeAttr
|
||||
else:
|
||||
func = self.getModifiedItemAttr
|
||||
|
||||
cycleTime = self.cycleTime
|
||||
spoolMultMax = self.getModifiedItemAttr("damageMultiplierBonusMax") or 0
|
||||
spoolMultPerCycle = self.getModifiedItemAttr("damageMultiplierBonusPerCycle") or 0
|
||||
|
||||
# Base volley
|
||||
volleyBase = sum([(func("%sDamage" % attr) or 0) * (1 - getattr(targetResists, "%sAmount" % attr, 0)) for attr in self.DAMAGE_TYPES])
|
||||
volleyBase *= self.getModifiedItemAttr("damageMultiplier") or 1
|
||||
volleySpoolDefault = volleyBase * (1 + calculateSpoolup(spoolMultMax, spoolMultPerCycle, cycleTime / 1000, self.spoolType, self.spoolAmount))
|
||||
volleySpoolFull = volleyBase * (1 + calculateSpoolup(spoolMultMax, spoolMultPerCycle, cycleTime / 1000, SpoolType.SCALE, 1))
|
||||
if volleyBase:
|
||||
# Some weapons repeat multiple times in one cycle (think doomsdays)
|
||||
# Get the number of times it fires off
|
||||
weaponDoT = max(
|
||||
self.getModifiedItemAttr("doomsdayDamageDuration", 1) / self.getModifiedItemAttr("doomsdayDamageCycleTime", 1),
|
||||
1
|
||||
)
|
||||
|
||||
self.__volley = volleySpoolDefault
|
||||
self.__volleySpoolZero = volleyBase
|
||||
self.__volleySpoolFull = volleySpoolFull
|
||||
dpsFactor = weaponDoT / (cycleTime / 1000.0)
|
||||
self.__dps = volleySpoolDefault * dpsFactor
|
||||
self.__dpsSpoolZero = volleyBase * dpsFactor
|
||||
self.__dpsSpoolFull = volleySpoolFull * dpsFactor
|
||||
|
||||
return self.__dps, self.__dpsSpoolFull, self.__volley, self.__volleySpoolFull
|
||||
|
||||
@property
|
||||
def miningStats(self):
|
||||
if self.__miningyield is None:
|
||||
@@ -478,17 +431,46 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
|
||||
return self.__miningyield
|
||||
|
||||
def getDps(self, spool=False):
|
||||
if spool:
|
||||
return self.damageStats(None)[1]
|
||||
else:
|
||||
return self.damageStats(None)[0]
|
||||
def getVolley(self, spoolType=None, spoolAmount=None, targetResists=None):
|
||||
if self.isEmpty or self.state < State.ACTIVE:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
if self.__baseVolley is None:
|
||||
dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr
|
||||
dmgMult = self.getModifiedItemAttr("damageMultiplier") or 1
|
||||
self.__baseVolley = DmgTypes(
|
||||
em=(dmgGetter("emDamage") or 0) * dmgMult,
|
||||
thermal=(dmgGetter("thermalDamage") or 0) * dmgMult,
|
||||
kinetic=(dmgGetter("kineticDamage") or 0) * dmgMult,
|
||||
explosive=(dmgGetter("explosiveDamage") or 0) * dmgMult)
|
||||
spoolMultiplier = 1 + calculateSpoolup(
|
||||
self.getModifiedItemAttr("damageMultiplierBonusMax") or 0,
|
||||
self.getModifiedItemAttr("damageMultiplierBonusPerCycle") or 0,
|
||||
self.cycleTime / 1000,
|
||||
spoolType if spoolType is not None else self.spoolType,
|
||||
# Using spool type as condition as it should define if we're using
|
||||
# passed spoolup parameters or not
|
||||
spoolAmount if spoolType is not None else self.spoolAmount)
|
||||
volley = DmgTypes(
|
||||
em=self.__baseVolley.em * spoolMultiplier * (1 - getattr(targetResists, "emAmount", 0)),
|
||||
thermal=self.__baseVolley.thermal * spoolMultiplier * (1 - getattr(targetResists, "thermalAmount", 0)),
|
||||
kinetic=self.__baseVolley.kinetic * spoolMultiplier * (1 - getattr(targetResists, "kineticAmount", 0)),
|
||||
explosive=self.__baseVolley.explosive * spoolMultiplier * (1 - getattr(targetResists, "explosiveAmount", 0)))
|
||||
return volley
|
||||
|
||||
def getVolley(self, spool=False):
|
||||
if spool:
|
||||
return self.damageStats(None)[3]
|
||||
else:
|
||||
return self.damageStats(None)[2]
|
||||
def getDps(self, spoolType=None, spoolAmount=None, targetResists=None):
|
||||
volley = self.getVolley(spoolType=spoolType, spoolAmount=spoolAmount, targetResists=targetResists)
|
||||
if not volley:
|
||||
return DmgTypes(0, 0, 0, 0)
|
||||
# Some weapons repeat multiple times in one cycle (bosonic doomsdays)
|
||||
# Get the number of times it fires off
|
||||
volleysPerCycle = max(self.getModifiedItemAttr("doomsdayDamageDuration", 1) / self.getModifiedItemAttr("doomsdayDamageCycleTime", 1), 1)
|
||||
dpsFactor = volleysPerCycle / (self.cycleTime / 1000)
|
||||
dps = DmgTypes(
|
||||
em=volley.em * dpsFactor,
|
||||
thermal=volley.thermal * dpsFactor,
|
||||
kinetic=volley.kinetic * dpsFactor,
|
||||
explosive=volley.explosive * dpsFactor)
|
||||
return dps
|
||||
|
||||
@property
|
||||
def reloadTime(self):
|
||||
@@ -741,12 +723,7 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
|
||||
return val
|
||||
|
||||
def clear(self):
|
||||
self.__dps = None
|
||||
self.__dpsSpoolZero = None
|
||||
self.__dpsSpoolFull = None
|
||||
self.__volley = None
|
||||
self.__volleySpoolZero = None
|
||||
self.__volleySpoolFull = None
|
||||
self.__baseVolley = None
|
||||
self.__miningyield = None
|
||||
self.__reloadTime = None
|
||||
self.__reloadForce = None
|
||||
|
||||
61
eos/utils/stats.py
Normal file
61
eos/utils/stats.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# ===============================================================================
|
||||
# Copyright (C) 2010 Diego Duclos
|
||||
#
|
||||
# This file is part of eos.
|
||||
#
|
||||
# eos is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# eos 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 Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with eos. If not, see <http://www.gnu.org/licenses/>.
|
||||
# ===============================================================================
|
||||
|
||||
|
||||
class DmgTypes:
|
||||
"""Container for damage data stats."""
|
||||
|
||||
def __init__(self, em, thermal, kinetic, explosive):
|
||||
self.em = em
|
||||
self.thermal = thermal
|
||||
self.kinetic = kinetic
|
||||
self.explosive = explosive
|
||||
self.total = em + thermal + kinetic + explosive
|
||||
|
||||
# Iterator is needed to support tuple-style unpacking
|
||||
def __iter__(self):
|
||||
yield self.em
|
||||
yield self.thermal
|
||||
yield self.kinetic
|
||||
yield self.explosive
|
||||
yield self.total
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, DmgTypes):
|
||||
return NotImplemented
|
||||
return all((
|
||||
self.em == other.em,
|
||||
self.thermal == other.thermal,
|
||||
self.kinetic == other.kinetic,
|
||||
self.explosive == other.explosive,
|
||||
self.total == other.total))
|
||||
|
||||
def __bool__(self):
|
||||
return any((
|
||||
self.em, self.thermal, self.kinetic,
|
||||
self.explosive, self.total))
|
||||
|
||||
def __hash__(self):
|
||||
return hash((
|
||||
DmgTypes.__qualname__,
|
||||
self.em,
|
||||
self.thermal,
|
||||
self.kinetic,
|
||||
self.explosive,
|
||||
self.total))
|
||||
@@ -154,10 +154,10 @@ class FirepowerViewFull(StatsView):
|
||||
else:
|
||||
return "{}-{}".format(formatAmount(preSpool, *fmt_options), formatAmount(postSpool, *fmt_options))
|
||||
|
||||
stats = (("labelFullDpsWeapon", lambda: fit.getWeaponDps(spool=False), lambda: fit.getWeaponDps(spool=True), 3, 0, 0, "%s DPS", None),
|
||||
stats = (("labelFullDpsWeapon", lambda: fit.getWeaponDps(), lambda: fit.getWeaponDps(), 3, 0, 0, "%s DPS", None),
|
||||
("labelFullDpsDrone", lambda: fit.getDroneDps(), lambda: fit.getDroneDps(), 3, 0, 0, "%s DPS", None),
|
||||
("labelFullVolleyTotal", lambda: fit.getTotalVolley(spool=False), lambda: fit.getTotalVolley(spool=True), 3, 0, 0, "%s", "Volley: %.1f"),
|
||||
("labelFullDpsTotal", lambda: fit.getTotalDps(spool=False), lambda: fit.getTotalDps(spool=True), 3, 0, 0, "%s", "DPS: %s"))
|
||||
("labelFullVolleyTotal", lambda: fit.getTotalVolley(), lambda: fit.getTotalVolley(), 3, 0, 0, "%s", "Volley: %.1f"),
|
||||
("labelFullDpsTotal", lambda: fit.getTotalDps(), lambda: fit.getTotalDps(), 3, 0, 0, "%s", "DPS: %s"))
|
||||
# See GH issue #
|
||||
# if fit is not None and fit.totalYield > 0:
|
||||
# self.miningyield.Show()
|
||||
|
||||
@@ -303,7 +303,7 @@ class EfsPort():
|
||||
weaponSystems = []
|
||||
groups = {}
|
||||
for mod in fit.modules:
|
||||
if mod.getDps() > 0:
|
||||
if mod.getDps().total > 0:
|
||||
# Group weapon + ammo combinations that occur more than once
|
||||
keystr = str(mod.itemID) + "-" + str(mod.chargeID)
|
||||
if keystr in groups:
|
||||
@@ -346,10 +346,10 @@ class EfsPort():
|
||||
else:
|
||||
maxRange = stats.maxRange
|
||||
statDict = {
|
||||
"dps": stats.getDps(spool=True) * n, "capUse": stats.capUse * n, "falloff": stats.falloff,
|
||||
"dps": stats.getDps().total * n, "capUse": stats.capUse * n, "falloff": stats.falloff,
|
||||
"type": typeing, "name": name, "optimal": maxRange,
|
||||
"numCharges": stats.numCharges, "numShots": stats.numShots, "reloadTime": stats.reloadTime,
|
||||
"cycleTime": stats.cycleTime, "volley": stats.getVolley(spool=True) * n, "tracking": tracking,
|
||||
"cycleTime": stats.cycleTime, "volley": stats.getVolley().total * n, "tracking": tracking,
|
||||
"maxVelocity": maxVelocity, "explosionDelay": explosionDelay, "damageReductionFactor": damageReductionFactor,
|
||||
"explosionRadius": explosionRadius, "explosionVelocity": explosionVelocity, "aoeFieldRange": aoeFieldRange,
|
||||
"damageMultiplierBonusMax": stats.getModifiedItemAttr("damageMultiplierBonusMax"),
|
||||
@@ -357,19 +357,19 @@ class EfsPort():
|
||||
}
|
||||
weaponSystems.append(statDict)
|
||||
for drone in fit.drones:
|
||||
if drone.getDps() > 0 and drone.amountActive > 0:
|
||||
if drone.getDps().total > 0 and drone.amountActive > 0:
|
||||
droneAttr = drone.getModifiedItemAttr
|
||||
# Drones are using the old tracking formula for trackingSpeed. This updates it to match turrets.
|
||||
newTracking = droneAttr("trackingSpeed") / (droneAttr("optimalSigRadius") / 40000)
|
||||
statDict = {
|
||||
"dps": drone.getDps(), "cycleTime": drone.cycleTime, "type": "Drone",
|
||||
"dps": drone.getDps().total, "cycleTime": drone.cycleTime, "type": "Drone",
|
||||
"optimal": drone.maxRange, "name": drone.item.name, "falloff": drone.falloff,
|
||||
"maxSpeed": droneAttr("maxVelocity"), "tracking": newTracking,
|
||||
"volley": drone.getVolley()
|
||||
"volley": drone.getVolley().total
|
||||
}
|
||||
weaponSystems.append(statDict)
|
||||
for fighter in fit.fighters:
|
||||
if fighter.getDps() > 0 and fighter.amountActive > 0:
|
||||
if fighter.getDps().total > 0 and fighter.amountActive > 0:
|
||||
fighterAttr = fighter.getModifiedItemAttr
|
||||
abilities = []
|
||||
if "fighterAbilityAttackMissileDamageEM" in fighter.item.attributes.keys():
|
||||
@@ -381,10 +381,10 @@ class EfsPort():
|
||||
ability = EfsPort.getFighterAbilityData(fighterAttr, fighter, baseRef)
|
||||
abilities.append(ability)
|
||||
statDict = {
|
||||
"dps": fighter.getDps(), "type": "Fighter", "name": fighter.item.name,
|
||||
"dps": fighter.getDps().total, "type": "Fighter", "name": fighter.item.name,
|
||||
"maxSpeed": fighterAttr("maxVelocity"), "abilities": abilities,
|
||||
"ehp": fighterAttr("shieldCapacity") / 0.8875 * fighter.amountActive,
|
||||
"volley": fighter.getVolley(), "signatureRadius": fighterAttr("signatureRadius")
|
||||
"volley": fighter.getVolley().total, "signatureRadius": fighterAttr("signatureRadius")
|
||||
}
|
||||
weaponSystems.append(statDict)
|
||||
return weaponSystems
|
||||
@@ -625,9 +625,9 @@ class EfsPort():
|
||||
dataDict = {
|
||||
"name": fitName, "ehp": fit.ehp, "droneDPS": fit.getDroneDps(),
|
||||
"droneVolley": fit.getDroneVolley(), "hp": fit.hp, "maxTargets": fit.maxTargets,
|
||||
"maxSpeed": fit.maxSpeed, "weaponVolley": fit.getWeaponVolley(spool=True), "totalVolley": fit.getTotalVolley(spool=True),
|
||||
"maxSpeed": fit.maxSpeed, "weaponVolley": fit.getWeaponVolley(), "totalVolley": fit.getTotalVolley(),
|
||||
"maxTargetRange": fit.maxTargetRange, "scanStrength": fit.scanStrength,
|
||||
"weaponDPS": fit.getWeaponDps(spool=True), "alignTime": fit.alignTime, "signatureRadius": fitModAttr("signatureRadius"),
|
||||
"weaponDPS": fit.getWeaponDps(), "alignTime": fit.alignTime, "signatureRadius": fitModAttr("signatureRadius"),
|
||||
"weapons": weaponSystems, "scanRes": fitModAttr("scanResolution"),
|
||||
"capUsed": fit.capUsed, "capRecharge": fit.capRecharge,
|
||||
"rigSlots": fitModAttr("rigSlots"), "lowSlots": fitModAttr("lowSlots"),
|
||||
|
||||
Reference in New Issue
Block a user