Merge branch 'dps_sim_graph' of github.com:pyfa-org/Pyfa into dps_sim_graph

This commit is contained in:
DarkPhoenix
2019-07-05 00:40:30 +03:00
41 changed files with 2725 additions and 1831 deletions

View File

@@ -1,76 +0,0 @@
# ===============================================================================
# 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/>.
# ===============================================================================
import math
from abc import ABCMeta, abstractmethod
class Graph(metaclass=ABCMeta):
def __init__(self):
self._cache = {}
@abstractmethod
def getPlotPoints(self, fit, extraData, xRange, xAmount):
raise NotImplementedError
def getYForX(self, fit, extraData, x):
raise NotImplementedError
def _xIter(self, fit, extraData, xRange, xAmount):
rangeLow, rangeHigh = self._limitXRange(xRange, fit, extraData)
# Amount is amount of ranges between points here, not amount of points
step = (rangeHigh - rangeLow) / xAmount
if step == 0:
yield xRange[0]
else:
current = rangeLow
# Take extra half step to make sure end of range is always included
# despite any possible float errors
while current <= (rangeHigh + step / 2):
yield current
current += step
def _limitXRange(self, xRange, fit, extraData):
rangeLow, rangeHigh = sorted(xRange)
limitLow, limitHigh = self._getXLimits(fit, extraData)
rangeLow = max(limitLow, rangeLow)
rangeHigh = min(limitHigh, rangeHigh)
return rangeLow, rangeHigh
def _getXLimits(self, fit, extraData):
return -math.inf, math.inf
def clearCache(self, key=None):
if key is None:
self._cache.clear()
elif key in self._cache:
del self._cache[key]
class SmoothGraph(Graph, metaclass=ABCMeta):
def getPlotPoints(self, fit, extraData, xRange, xAmount):
xs = []
ys = []
for x in self._xIter(fit, extraData, xRange, xAmount):
xs.append(x)
ys.append(self.getYForX(fit, extraData, x))
return xs, ys

View File

@@ -1,15 +0,0 @@
import math
from eos.graph import SmoothGraph
class FitCapAmountVsTimeGraph(SmoothGraph):
def getYForX(self, fit, extraData, time):
if time < 0:
return 0
maxCap = fit.ship.getModifiedItemAttr('capacitorCapacity')
regenTime = fit.ship.getModifiedItemAttr('rechargeRate') / 1000
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate
cap = maxCap * (1 + math.exp(5 * -time / regenTime) * -1) ** 2
return cap

View File

@@ -1,14 +0,0 @@
import math
from eos.graph import SmoothGraph
class FitCapRegenVsCapPercGraph(SmoothGraph):
def getYForX(self, fit, extraData, perc):
maxCap = fit.ship.getModifiedItemAttr('capacitorCapacity')
regenTime = fit.ship.getModifiedItemAttr('rechargeRate') / 1000
currentCap = maxCap * perc / 100
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate
regen = 10 * maxCap / regenTime * (math.sqrt(currentCap / maxCap) - currentCap / maxCap)
return regen

View File

@@ -1,17 +0,0 @@
import math
from eos.graph import SmoothGraph
class FitDistanceVsTimeGraph(SmoothGraph):
def getYForX(self, fit, extraData, time):
maxSpeed = fit.ship.getModifiedItemAttr('maxVelocity')
mass = fit.ship.getModifiedItemAttr('mass')
agility = fit.ship.getModifiedItemAttr('agility')
# Definite integral of:
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
distance_t = maxSpeed * time + (maxSpeed * agility * mass * math.exp((-time * 1000000) / (agility * mass)) / 1000000)
distance_0 = maxSpeed * 0 + (maxSpeed * agility * mass * math.exp((-0 * 1000000) / (agility * mass)) / 1000000)
distance = distance_t - distance_0
return distance

View File

@@ -1,151 +0,0 @@
# ===============================================================================
# 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/>.
# ===============================================================================
from eos.graph import Graph
from eos.utils.spoolSupport import SpoolType, SpoolOptions
from gui.utils.numberFormatter import roundToPrec
class FitDmgVsTimeGraph(Graph):
def getPlotPoints(self, fit, extraData, xRange, xAmount):
# We deliberately ignore xAmount here to build graph which will reflect
# all steps of building up the damage
minX, maxX = self._limitXRange(xRange, fit, extraData)
if fit.ID not in self._cache:
self.__generateCache(fit, maxX)
currentY = None
xs = []
ys = []
cache = self._cache[fit.ID]
for time in sorted(cache):
prevY = currentY
currentX = time / 1000
currentY = roundToPrec(cache[time], 6)
if currentX < minX:
continue
# First set of data points
if not xs:
# Start at exactly requested time, at last known value
initialY = prevY or 0
xs.append(minX)
ys.append(initialY)
# If current time is bigger then starting, extend plot to that time with old value
if currentX > minX:
xs.append(currentX)
ys.append(initialY)
# If new value is different, extend it with new point to the new value
if currentY != prevY:
xs.append(currentX)
ys.append(currentY)
continue
# Last data point
if currentX >= maxX:
xs.append(maxX)
ys.append(prevY)
break
# Anything in-between
if currentY != prevY:
if prevY is not None:
xs.append(currentX)
ys.append(prevY)
xs.append(currentX)
ys.append(currentY)
return xs, ys
def getYForX(self, fit, extraData, x):
time = x * 1000
cache = self._cache[fit.ID]
closestTime = max((t for t in cache if t <= time), default=None)
if closestTime is None:
return 0
return roundToPrec(cache[closestTime], 6)
def _getXLimits(self, fit, extraData):
return 0, 2500
def __generateCache(self, fit, maxTime):
cache = self._cache[fit.ID] = {}
def addDmg(addedTime, addedDmg):
if addedDmg == 0:
return
if addedTime not in cache:
prevTime = max((t for t in cache if t < addedTime), default=None)
if prevTime is None:
cache[addedTime] = 0
else:
cache[addedTime] = cache[prevTime]
for time in (t for t in cache if t >= addedTime):
cache[time] += addedDmg
# We'll handle calculations in milliseconds
maxTime = maxTime * 1000
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 cycleTime, inactiveTime in cycleParams.iterCycles():
volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
for volleyTime, volley in volleyParams.items():
addDmg(currentTime + volleyTime, volley.total)
if inactiveTime == 0:
nonstopCycles += 1
else:
nonstopCycles = 0
if currentTime > maxTime:
break
currentTime += cycleTime + inactiveTime
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 cycleTime, inactiveTime in cycleParams.iterCycles():
for volleyTime, volley in volleyParams.items():
addDmg(currentTime + volleyTime, volley.total)
if currentTime > maxTime:
break
currentTime += cycleTime + inactiveTime
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 cycleTime, inactiveTime in abilityCycleParams.iterCycles():
for volleyTime, volley in abilityVolleyParams.items():
addDmg(currentTime + volleyTime, volley.total)
if currentTime > maxTime:
break
currentTime += cycleTime + inactiveTime

View File

@@ -1,197 +0,0 @@
# ===============================================================================
# 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/>.
# ===============================================================================
from math import exp, log, radians, sin, inf
from logbook import Logger
import eos.config
from eos.const import FittingHardpoint, FittingModuleState
from eos.graph import SmoothGraph
from eos.utils.spoolSupport import SpoolType, SpoolOptions
pyfalog = Logger(__name__)
class FitDpsVsRangeGraph(SmoothGraph):
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 = 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:
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"):
tgtSpeedMods.append(1 + (mod.getModifiedItemAttr("speedFactor") / 100))
elif mod.getModifiedItemAttr("falloffEffectiveness") > 0:
# I am affected by falloff
tgtSpeedMods.append(
1 + (mod.getModifiedItemAttr("speedFactor") / 100) *
self.calculateModuleMultiplier(mod, distance))
tgtSpeed = self.penalizeModChain(tgtSpeed, tgtSpeedMods)
tgtSigRad = self.penalizeModChain(tgtSigRad, tgtSigRadMods)
attRad = fit.ship.getModifiedItemAttr('radius', 0)
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
for mod in fit.modules:
dps = mod.getDps(targetResists=fit.targetResists, spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total
if mod.hardpoint == FittingHardpoint.TURRET:
if mod.state >= FittingModuleState.ACTIVE:
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 - attRad) >= distance:
total += dps * self.calculateMissileMultiplier(mod, tgtSpeed, tgtSigRad)
if distance <= fit.extraAttributes['droneControlRange']:
for drone in fit.drones:
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
# this is janky as fuck
for fighter in fit.fighters:
if not fighter.active:
continue
fighterDpsMap = fighter.getDpsPerEffect(targetResists=fit.targetResists)
for ability in fighter.abilities:
if ability.dealsDamage and ability.active:
if ability.effectID not in fighterDpsMap:
continue
multiplier = self.calculateFighterMissileMultiplier(tgtSpeed, tgtSigRad, ability)
dps = fighterDpsMap[ability.effectID].total
total += dps * multiplier
return total
@staticmethod
def calculateMissileMultiplier(mod, tgtSpeed, tgtSigRad):
explosionRadius = mod.getModifiedChargeAttr('aoeCloudSize')
explosionVelocity = mod.getModifiedChargeAttr('aoeVelocity')
damageReductionFactor = mod.getModifiedChargeAttr('aoeDamageReductionFactor')
sigRadiusFactor = tgtSigRad / explosionRadius
if tgtSpeed:
velocityFactor = (explosionVelocity / explosionRadius * tgtSigRad / tgtSpeed) ** damageReductionFactor
else:
velocityFactor = 1
return min(sigRadiusFactor, velocityFactor, 1)
@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 = 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')
if dmgScaling:
multiplier = min(1, (float(tgtSigRad) / dmgScaling) ** 2)
return multiplier
@staticmethod
def calculateFighterMissileMultiplier(tgtSpeed, tgtSigRad, ability):
prefix = ability.attrPrefix
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))
damageReductionSensitivity = ability.fighter.getModifiedItemAttr('{}ReductionSensitivity'.format(prefix), None)
if damageReductionSensitivity is None:
damageReductionSensitivity = ability.fighter.getModifiedItemAttr('{}DamageReductionSensitivity'.format(prefix))
sigRadiusFactor = tgtSigRad / explosionRadius
if tgtSpeed:
velocityFactor = (explosionVelocity / explosionRadius * tgtSigRad / tgtSpeed) ** (
log(damageReductionFactor) / log(damageReductionSensitivity))
else:
velocityFactor = 1
return min(sigRadiusFactor, velocityFactor, 1)
@staticmethod
def calculateTurretChanceToHit(fit, mod, distance, angle, tgtSpeed, tgtSigRad):
tracking = mod.getModifiedItemAttr('trackingSpeed')
turretOptimal = mod.maxRange
turretFalloff = mod.falloff
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 + fit.ship.getModifiedItemAttr('radius', 0)
if angDistance == 0 and transversal == 0:
angularVelocity = 0
elif angDistance == 0 and transversal != 0:
angularVelocity = inf
else:
angularVelocity = transversal / angDistance
trackingEq = (((angularVelocity / tracking) *
(turretSigRes / tgtSigRad)) ** 2)
rangeEq = ((max(0, distance - turretOptimal)) / turretFalloff) ** 2
return 0.5 ** (trackingEq + rangeEq)
@staticmethod
def calculateModuleMultiplier(mod, distance):
# Simplified formula, we make some assumptions about the module
# This is basically the calculateTurretChanceToHit without tracking values
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

View File

@@ -1,165 +0,0 @@
# ===============================================================================
# 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/>.
# ===============================================================================
from itertools import chain
from eos.graph import Graph
from eos.utils.spoolSupport import SpoolType, SpoolOptions
from gui.utils.numberFormatter import roundToPrec
class FitDpsVsTimeGraph(Graph):
def getPlotPoints(self, fit, extraData, xRange, xAmount):
# We deliberately ignore xAmount here to build graph which will reflect
# all steps of building up the damage
minX, maxX = self._limitXRange(xRange, fit, extraData)
if fit.ID not in self._cache:
self.__generateCache(fit, maxX)
currentY = None
prevY = None
xs = []
ys = []
cache = self._cache[fit.ID]
for time in sorted(cache):
prevY = currentY
currentX = time / 1000
currentY = roundToPrec(cache[time], 6)
if currentX < minX:
continue
# First set of data points
if not xs:
# Start at exactly requested time, at last known value
initialY = prevY or 0
xs.append(minX)
ys.append(initialY)
# If current time is bigger then starting, extend plot to that time with old value
if currentX > minX:
xs.append(currentX)
ys.append(initialY)
# If new value is different, extend it with new point to the new value
if currentY != prevY:
xs.append(currentX)
ys.append(currentY)
continue
# Last data point
if currentX >= maxX:
xs.append(maxX)
ys.append(prevY)
break
# Anything in-between
if currentY != prevY:
if prevY is not None:
xs.append(currentX)
ys.append(prevY)
xs.append(currentX)
ys.append(currentY)
if max(xs) < maxX:
xs.append(maxX)
ys.append(currentY or 0)
return xs, ys
def getYForX(self, fit, extraData, x):
time = x * 1000
cache = self._cache[fit.ID]
closestTime = max((t for t in cache if t <= time), default=None)
if closestTime is None:
return 0
return roundToPrec(cache[closestTime], 6)
def _getXLimits(self, fit, extraData):
return 0, 2500
def __generateCache(self, fit, maxTime):
cache = []
def addDmg(addedTimeStart, addedTimeFinish, addedDmg):
if addedDmg == 0:
return
addedDps = 1000 * addedDmg / (addedTimeFinish - addedTimeStart)
cache.append((addedTimeStart, addedTimeFinish, addedDps))
# We'll handle calculations in milliseconds
maxTime = maxTime * 1000
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 cycleTime, inactiveTime in cycleParams.iterCycles():
cycleDamage = 0
volleyParams = mod.getVolleyParameters(spoolOptions=SpoolOptions(SpoolType.CYCLES, nonstopCycles, True))
for volleyTime, volley in volleyParams.items():
cycleDamage += volley.total
addDmg(currentTime, currentTime + cycleTime, cycleDamage)
currentTime += cycleTime + inactiveTime
if inactiveTime > 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 cycleTime, inactiveTime in cycleParams.iterCycles():
cycleDamage = 0
volleyParams = drone.getVolleyParameters()
for volleyTime, volley in volleyParams.items():
cycleDamage += volley.total
addDmg(currentTime, currentTime + cycleTime, cycleDamage)
currentTime += cycleTime + inactiveTime
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 cycleTime, inactiveTime in abilityCycleParams.iterCycles():
cycleDamage = 0
for volleyTime, volley in abilityVolleyParams.items():
cycleDamage += volley.total
addDmg(currentTime, currentTime + cycleTime, cycleDamage)
currentTime += cycleTime + inactiveTime
if currentTime > maxTime:
break
# Post-process cache
finalCache = {}
for time in sorted(set(chain((i[0] for i in cache), (i[1] for i in cache)))):
entries = (e for e in cache if e[0] <= time < e[1])
dps = sum(e[2] for e in entries)
finalCache[time] = dps
self._cache[fit.ID] = finalCache

View File

@@ -1,27 +0,0 @@
import math
from logbook import Logger
from eos.graph import SmoothGraph
pyfalog = Logger(__name__)
class FitShieldAmountVsTimeGraph(SmoothGraph):
def __init__(self):
super().__init__()
import gui.mainFrame
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getYForX(self, fit, extraData, time):
if time < 0:
return 0
maxShield = fit.ship.getModifiedItemAttr('shieldCapacity')
regenTime = fit.ship.getModifiedItemAttr('shieldRechargeRate') / 1000
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate (shield is similar to cap)
shield = maxShield * (1 + math.exp(5 * -time / regenTime) * -1) ** 2
useEhp = self.mainFrame.statsPane.nameViewMap["resistancesViewFull"].showEffective
if fit.damagePattern is not None and useEhp:
shield = fit.damagePattern.effectivify(fit, shield, 'shield')
return shield

View File

@@ -1,22 +0,0 @@
import math
from eos.graph import SmoothGraph
class FitShieldRegenVsShieldPercGraph(SmoothGraph):
def __init__(self):
super().__init__()
import gui.mainFrame
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def getYForX(self, fit, extraData, perc):
maxShield = fit.ship.getModifiedItemAttr('shieldCapacity')
regenTime = fit.ship.getModifiedItemAttr('shieldRechargeRate') / 1000
currentShield = maxShield * perc / 100
# https://wiki.eveuniversity.org/Capacitor#Capacitor_recharge_rate (shield is similar to cap)
regen = 10 * maxShield / regenTime * (math.sqrt(currentShield / maxShield) - currentShield / maxShield)
useEhp = self.mainFrame.statsPane.nameViewMap["resistancesViewFull"].showEffective
if fit.damagePattern is not None and useEhp:
regen = fit.damagePattern.effectivify(fit, regen, 'shield')
return regen

View File

@@ -1,14 +0,0 @@
import math
from eos.graph import SmoothGraph
class FitSpeedVsTimeGraph(SmoothGraph):
def getYForX(self, fit, extraData, time):
maxSpeed = fit.ship.getModifiedItemAttr('maxVelocity')
mass = fit.ship.getModifiedItemAttr('mass')
agility = fit.ship.getModifiedItemAttr('agility')
# https://wiki.eveuniversity.org/Acceleration#Mathematics_and_formulae
speed = maxSpeed * (1 - math.exp((-time * 1000000) / (agility * mass)))
return speed

View File

@@ -1,98 +0,0 @@
import math
from eos.const import FittingModuleState
from eos.graph import SmoothGraph
AU_METERS = 149597870700
class FitWarpTimeVsDistanceGraph(SmoothGraph):
def __init__(self):
super().__init__()
self.subwarpSpeed = None
def getYForX(self, fit, extraData, distance):
if distance == 0:
return 0
if fit.ID not in self._cache:
self.__generateCache(fit)
maxWarpSpeed = fit.warpSpeed
subwarpSpeed = self._cache[fit.ID]['cleanSubwarpSpeed']
time = calculate_time_in_warp(maxWarpSpeed, subwarpSpeed, distance * AU_METERS)
return time
def _getXLimits(self, fit, extraData):
return 0, fit.maxWarpDistance
def __generateCache(self, fit):
modStates = {}
for mod in fit.modules:
if mod.item is not None and mod.item.group.name in ('Propulsion Module', 'Mass Entanglers', 'Cloaking Device') and mod.state >= FittingModuleState.ACTIVE:
modStates[mod] = mod.state
mod.state = FittingModuleState.ONLINE
projFitStates = {}
for projFit in fit.projectedFits:
projectionInfo = projFit.getProjectionInfo(fit.ID)
if projectionInfo is not None and projectionInfo.active:
projFitStates[projectionInfo] = projectionInfo.active
projectionInfo.active = False
projModStates = {}
for mod in fit.projectedModules:
if not mod.isExclusiveSystemEffect and mod.state >= FittingModuleState.ACTIVE:
projModStates[mod] = mod.state
mod.state = FittingModuleState.ONLINE
projDroneStates = {}
for drone in fit.projectedDrones:
if drone.amountActive > 0:
projDroneStates[drone] = drone.amountActive
drone.amountActive = 0
projFighterStates = {}
for fighter in fit.projectedFighters:
if fighter.active:
projFighterStates[fighter] = fighter.active
fighter.active = False
fit.calculateModifiedAttributes()
self._cache[fit.ID] = {'cleanSubwarpSpeed': fit.ship.getModifiedItemAttr('maxVelocity')}
for projInfo, state in projFitStates.items():
projInfo.active = state
for mod, state in modStates.items():
mod.state = state
for mod, state in projModStates.items():
mod.state = state
for drone, amountActive in projDroneStates.items():
drone.amountActive = amountActive
for fighter, state in projFighterStates.items():
fighter.active = state
fit.calculateModifiedAttributes()
# Taken from https://wiki.eveuniversity.org/Warp_time_calculation#Implementation
# with minor modifications
# Warp speed in AU/s, subwarp speed in m/s, distance in m
def calculate_time_in_warp(max_warp_speed, max_subwarp_speed, warp_dist):
k_accel = max_warp_speed
k_decel = min(max_warp_speed / 3, 2)
warp_dropout_speed = max_subwarp_speed / 2
max_ms_warp_speed = max_warp_speed * AU_METERS
accel_dist = AU_METERS
decel_dist = max_ms_warp_speed / k_decel
minimum_dist = accel_dist + decel_dist
cruise_time = 0
if minimum_dist > warp_dist:
max_ms_warp_speed = warp_dist * k_accel * k_decel / (k_accel + k_decel)
else:
cruise_time = (warp_dist - minimum_dist) / max_ms_warp_speed
accel_time = math.log(max_ms_warp_speed / k_accel) / k_accel
decel_time = math.log(max_ms_warp_speed / warp_dropout_speed) / k_decel
total_time = cruise_time + accel_time + decel_time
return total_time

View File

@@ -202,6 +202,13 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
explosive=volleyValue.explosive * (1 - getattr(targetResists, "explosiveAmount", 0)))
return adjustedVolley
def getVolleyPerEffect(self, targetResists=None):
volleyParams = self.getVolleyParametersPerEffect(targetResists=targetResists)
volleyMap = {}
for effectID, volleyData in volleyParams.items():
volleyMap[effectID] = volleyData[0]
return volleyMap
def getVolley(self, targetResists=None):
volleyParams = self.getVolleyParametersPerEffect(targetResists=targetResists)
em = 0

View File

@@ -18,6 +18,9 @@
# ===============================================================================
from utils.repr import makeReprStr
class DmgTypes:
"""Container for damage data stats."""
@@ -68,3 +71,41 @@ class DmgTypes:
self.explosive += other.explosive
self._calcTotal()
return self
def __mul__(self, mul):
return type(self)(
em=self.em * mul,
thermal=self.thermal * mul,
kinetic=self.kinetic * mul,
explosive=self.explosive * mul)
def __imul__(self, mul):
if mul == 1:
return
self.em *= mul
self.thermal *= mul
self.kinetic *= mul
self.explosive *= mul
self._calcTotal()
return self
def __truediv__(self, div):
return type(self)(
em=self.em / div,
thermal=self.thermal / div,
kinetic=self.kinetic / div,
explosive=self.explosive / div)
def __itruediv__(self, div):
if div == 1:
return
self.em /= div
self.thermal /= div
self.kinetic /= div
self.explosive /= div
self._calcTotal()
return self
def __repr__(self):
spec = ['em', 'thermal', 'kinetic', 'explosive', 'total']
return makeReprStr(self, spec)