Restore DPS vs range graph

This commit is contained in:
DarkPhoenix
2019-05-19 14:02:24 +03:00
parent 16fdd5a5e6
commit 0c31f756a8
5 changed files with 122 additions and 183 deletions

View File

@@ -22,75 +22,56 @@ from math import exp, log, radians, sin, inf
from logbook import Logger
from eos.const import FittingHardpoint, FittingModuleState
from eos.graph import Graph
from eos.graph import SmoothGraph
pyfalog = Logger(__name__)
class FitDpsRangeGraph(Graph):
class FitDpsVsRangeGraph(SmoothGraph):
defaults = {
"angle" : 0,
"distance" : 0,
"signatureRadius": None,
"velocity" : 0
}
def __init__(self, fit, data=None):
Graph.__init__(self, fit, self.calcDps, data if data is not None else self.defaults)
self.fit = fit
def calcDps(self, data):
ew = {'signatureRadius': [], 'velocity': []}
fit = self.fit
def getYForX(self, fit, extraData, distance):
tgtSpeed = extraData['speed']
tgtSigRad = extraData['signatureRadius'] if extraData['signatureRadius'] is not None else inf
angle = extraData['angle']
tgtSigRadMods = []
tgtSpeedMods = []
total = 0
distance = data["distance"] * 1000
abssort = lambda _val: -abs(_val - 1)
distance = distance * 1000
for mod in fit.modules:
if not mod.isEmpty and mod.state >= FittingModuleState.ACTIVE:
if "remoteTargetPaintFalloff" in mod.item.effects or "structureModuleEffectTargetPainter" in mod.item.effects:
ew['signatureRadius'].append(
1 + (mod.getModifiedItemAttr("signatureRadiusBonus") / 100) * self.calculateModuleMultiplier(
mod, data))
tgtSigRadMods.append(
1 + (mod.getModifiedItemAttr("signatureRadiusBonus") / 100)
* self.calculateModuleMultiplier(mod, distance))
if "remoteWebifierFalloff" in mod.item.effects or "structureModuleEffectStasisWebifier" in mod.item.effects:
if distance <= mod.getModifiedItemAttr("maxRange"):
ew['velocity'].append(1 + (mod.getModifiedItemAttr("speedFactor") / 100))
tgtSpeedMods.append(1 + (mod.getModifiedItemAttr("speedFactor") / 100))
elif mod.getModifiedItemAttr("falloffEffectiveness") > 0:
# I am affected by falloff
ew['velocity'].append(
1 + (mod.getModifiedItemAttr("speedFactor") / 100) * self.calculateModuleMultiplier(mod,
data))
tgtSpeedMods.append(
1 + (mod.getModifiedItemAttr("speedFactor") / 100) *
self.calculateModuleMultiplier(mod, distance))
ew['signatureRadius'].sort(key=abssort)
ew['velocity'].sort(key=abssort)
for attr, values in ew.items():
val = data[attr]
try:
for i in range(len(values)):
bonus = values[i]
val *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289)
data[attr] = val
except Exception as e:
pyfalog.critical("Caught exception in calcDPS.")
pyfalog.critical(e)
tgtSpeed = self.penalizeModChain(tgtSpeed, tgtSpeedMods)
tgtSigRad = self.penalizeModChain(tgtSigRad, tgtSigRadMods)
attRad = fit.ship.getModifiedItemAttr('radius', 0)
for mod in fit.modules:
dps = mod.getDps(targetResists=fit.targetResists).total
if mod.hardpoint == FittingHardpoint.TURRET:
if mod.state >= FittingModuleState.ACTIVE:
total += dps * self.calculateTurretMultiplier(mod, data)
total += dps * self.calculateTurretMultiplier(fit, mod, distance, angle, tgtSpeed, tgtSigRad)
elif mod.hardpoint == FittingHardpoint.MISSILE:
if mod.state >= FittingModuleState.ACTIVE and mod.maxRange is not None and mod.maxRange >= distance:
total += dps * self.calculateMissileMultiplier(mod, data)
if mod.state >= FittingModuleState.ACTIVE and mod.maxRange is not None and (mod.maxRange - attRad) >= distance:
total += dps * self.calculateMissileMultiplier(mod, tgtSpeed, tgtSigRad)
if distance <= fit.extraAttributes["droneControlRange"]:
if distance <= fit.extraAttributes['droneControlRange']:
for drone in fit.drones:
multiplier = 1 if drone.getModifiedItemAttr("maxVelocity") > 1 else self.calculateTurretMultiplier(
drone, data)
multiplier = 1 if drone.getModifiedItemAttr('maxVelocity') > 1 else self.calculateTurretMultiplier(
fit, drone, distance, angle, tgtSpeed, tgtSigRad)
dps = drone.getDps(targetResists=fit.targetResists).total
total += dps * multiplier
@@ -103,88 +84,79 @@ class FitDpsRangeGraph(Graph):
if ability.dealsDamage and ability.active:
if ability.effectID not in fighterDpsMap:
continue
multiplier = self.calculateFighterMissileMultiplier(ability, data)
multiplier = self.calculateFighterMissileMultiplier(tgtSpeed, tgtSigRad, ability)
dps = fighterDpsMap[ability.effectID].total
total += dps * multiplier
return total
@staticmethod
def calculateMissileMultiplier(mod, data):
targetSigRad = data["signatureRadius"]
targetVelocity = data["velocity"]
explosionRadius = mod.getModifiedChargeAttr("aoeCloudSize")
targetSigRad = explosionRadius if targetSigRad is None else targetSigRad
explosionVelocity = mod.getModifiedChargeAttr("aoeVelocity")
damageReductionFactor = mod.getModifiedChargeAttr("aoeDamageReductionFactor")
def calculateMissileMultiplier(mod, tgtSpeed, tgtSigRad):
explosionRadius = mod.getModifiedChargeAttr('aoeCloudSize')
explosionVelocity = mod.getModifiedChargeAttr('aoeVelocity')
damageReductionFactor = mod.getModifiedChargeAttr('aoeDamageReductionFactor')
sigRadiusFactor = targetSigRad / explosionRadius
if targetVelocity:
velocityFactor = (explosionVelocity / explosionRadius * targetSigRad / targetVelocity) ** damageReductionFactor
sigRadiusFactor = tgtSigRad / explosionRadius
if tgtSpeed:
velocityFactor = (explosionVelocity / explosionRadius * tgtSigRad / tgtSpeed) ** damageReductionFactor
else:
velocityFactor = 1
return min(sigRadiusFactor, velocityFactor, 1)
def calculateTurretMultiplier(self, mod, data):
@classmethod
def calculateTurretMultiplier(cls, fit, mod, distance, angle, tgtSpeed, tgtSigRad):
# Source for most of turret calculation info: http://wiki.eveonline.com/en/wiki/Falloff
chanceToHit = self.calculateTurretChanceToHit(mod, data)
chanceToHit = cls.calculateTurretChanceToHit(fit, mod, distance, angle, tgtSpeed, tgtSigRad)
if chanceToHit > 0.01:
# AvgDPS = Base Damage * [ ( ChanceToHit^2 + ChanceToHit + 0.0499 ) / 2 ]
multiplier = (chanceToHit ** 2 + chanceToHit + 0.0499) / 2
else:
# All hits are wreckings
multiplier = chanceToHit * 3
dmgScaling = mod.getModifiedItemAttr("turretDamageScalingRadius")
dmgScaling = mod.getModifiedItemAttr('turretDamageScalingRadius')
if dmgScaling:
targetSigRad = data["signatureRadius"]
multiplier = min(1, (float(targetSigRad) / dmgScaling) ** 2)
multiplier = min(1, (float(tgtSigRad) / dmgScaling) ** 2)
return multiplier
@staticmethod
def calculateFighterMissileMultiplier(ability, data):
def calculateFighterMissileMultiplier(tgtSpeed, tgtSigRad, ability):
prefix = ability.attrPrefix
targetSigRad = data["signatureRadius"]
targetVelocity = data["velocity"]
explosionRadius = ability.fighter.getModifiedItemAttr("{}ExplosionRadius".format(prefix))
explosionVelocity = ability.fighter.getModifiedItemAttr("{}ExplosionVelocity".format(prefix))
damageReductionFactor = ability.fighter.getModifiedItemAttr("{}ReductionFactor".format(prefix), None)
explosionRadius = ability.fighter.getModifiedItemAttr('{}ExplosionRadius'.format(prefix))
explosionVelocity = ability.fighter.getModifiedItemAttr('{}ExplosionVelocity'.format(prefix))
damageReductionFactor = ability.fighter.getModifiedItemAttr('{}ReductionFactor'.format(prefix), None)
# the following conditionals are because CCP can't keep a decent naming convention, as if fighter implementation
# wasn't already fucked.
if damageReductionFactor is None:
damageReductionFactor = ability.fighter.getModifiedItemAttr("{}DamageReductionFactor".format(prefix))
damageReductionFactor = ability.fighter.getModifiedItemAttr('{}DamageReductionFactor'.format(prefix))
damageReductionSensitivity = ability.fighter.getModifiedItemAttr("{}ReductionSensitivity".format(prefix), None)
damageReductionSensitivity = ability.fighter.getModifiedItemAttr('{}ReductionSensitivity'.format(prefix), None)
if damageReductionSensitivity is None:
damageReductionSensitivity = ability.fighter.getModifiedItemAttr(
"{}DamageReductionSensitivity".format(prefix))
damageReductionSensitivity = ability.fighter.getModifiedItemAttr('{}DamageReductionSensitivity'.format(prefix))
targetSigRad = explosionRadius if targetSigRad is None else targetSigRad
sigRadiusFactor = targetSigRad / explosionRadius
sigRadiusFactor = tgtSigRad / explosionRadius
if targetVelocity:
velocityFactor = (explosionVelocity / explosionRadius * targetSigRad / targetVelocity) ** (
if tgtSpeed:
velocityFactor = (explosionVelocity / explosionRadius * tgtSigRad / tgtSpeed) ** (
log(damageReductionFactor) / log(damageReductionSensitivity))
else:
velocityFactor = 1
return min(sigRadiusFactor, velocityFactor, 1)
def calculateTurretChanceToHit(self, mod, data):
distance = data["distance"] * 1000
tracking = mod.getModifiedItemAttr("trackingSpeed")
@staticmethod
def calculateTurretChanceToHit(fit, mod, distance, angle, tgtSpeed, tgtSigRad):
tracking = mod.getModifiedItemAttr('trackingSpeed')
turretOptimal = mod.maxRange
turretFalloff = mod.falloff
turretSigRes = mod.getModifiedItemAttr("optimalSigRadius")
targetSigRad = data["signatureRadius"]
targetSigRad = turretSigRes if targetSigRad is None else targetSigRad
transversal = sin(radians(data["angle"])) * data["velocity"]
turretSigRes = mod.getModifiedItemAttr('optimalSigRadius')
transversal = sin(radians(angle)) * tgtSpeed
# Angular velocity is calculated using range from ship center to target center.
# We do not know target radius but we know attacker radius
angDistance = distance + self.fit.ship.getModifiedItemAttr('radius', 0)
angDistance = distance + fit.ship.getModifiedItemAttr('radius', 0)
if angDistance == 0 and transversal == 0:
angularVelocity = 0
elif angDistance == 0 and transversal != 0:
@@ -192,18 +164,30 @@ class FitDpsRangeGraph(Graph):
else:
angularVelocity = transversal / angDistance
trackingEq = (((angularVelocity / tracking) *
(turretSigRes / targetSigRad)) ** 2)
(turretSigRes / tgtSigRad)) ** 2)
rangeEq = ((max(0, distance - turretOptimal)) / turretFalloff) ** 2
return 0.5 ** (trackingEq + rangeEq)
@staticmethod
def calculateModuleMultiplier(mod, data):
def calculateModuleMultiplier(mod, distance):
# Simplified formula, we make some assumptions about the module
# This is basically the calculateTurretChanceToHit without tracking values
distance = data["distance"] * 1000
turretOptimal = mod.maxRange
turretFalloff = mod.falloff
rangeEq = ((max(0, distance - turretOptimal)) / turretFalloff) ** 2
return 0.5 ** rangeEq
@staticmethod
def penalizeModChain(value, mods):
mods.sort(key=lambda v: -abs(v - 1))
try:
for i in range(len(mods)):
bonus = mods[i]
value *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289)
return value
except Exception as e:
pyfalog.critical('Caught exception when penalizing modifier chain.')
pyfalog.critical(e)
return value