Plug in all calculations besides where X is time

This commit is contained in:
DarkPhoenix
2019-07-04 17:22:23 +03:00
parent c4f225003a
commit 63c45c5060
5 changed files with 271 additions and 99 deletions

View File

@@ -151,10 +151,6 @@ class Fighter(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
def abilities(self):
return self.__abilities or []
@property
def abilityMap(self):
return {a.effectID: a for a in self.abilities}
@property
def charge(self):
return self.__charge
@@ -206,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

@@ -72,6 +72,23 @@ class DmgTypes:
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,
@@ -80,6 +97,8 @@ class DmgTypes:
explosive=self.explosive / div)
def __itruediv__(self, div):
if div == 1:
return
self.em /= div
self.thermal /= div
self.kinetic /= div

View File

@@ -44,13 +44,12 @@ def getLauncherMult(mod, fit, distance, tgtSpeed, tgtSigRadius):
modRange = mod.maxRange
if modRange is None:
return 0
mult = _calcMissileMult(
atkRadius=fit.ship.getModifiedItemAttr('radius'),
atkRange=modRange,
if distance + fit.ship.getModifiedItemAttr('radius') > modRange:
return 0
mult = _calcMissileFactor(
atkEr=mod.getModifiedChargeAttr('aoeCloudSize'),
atkEv=mod.getModifiedChargeAttr('aoeVelocity'),
atkDrf=mod.getModifiedChargeAttr('aoeDamageReductionFactor'),
distance=distance,
tgtSpeed=tgtSpeed,
tgtSigRadius=tgtSigRadius)
return mult
@@ -167,25 +166,6 @@ def _calcTrackingFactor(atkTracking, atkOptimalSigRadius, angularSpeed, tgtSigRa
# Missile-specific
@lru_cache(maxsize=200)
def _calcMissileMult(atkRadius, atkRange, atkEr, atkEv, atkDrf, distance, tgtSpeed, tgtSigRadius):
"""Calculate damage multiplier for missile launcher."""
# Missiles spawn in the center of the attacking ship
if distance + atkRadius > atkRange:
mult = 0
else:
mult = _calcMissileFactor(atkEr, atkEv, atkDrf, tgtSpeed, tgtSigRadius)
return mult
@lru_cache(maxsize=200)
def _calcFighterMult(atkOptimalRange, atkFalloffRange, atkEr, atkEv, atkDrf, distance, tgtSpeed, tgtSigRadius):
"""Calculate damage multiplier for separate fighter ability,"""
rangeFactor = _calcRangeFactor(atkOptimalRange, atkFalloffRange, distance)
missileFactor = _calcMissileFactor(atkEr, atkEv, atkDrf, tgtSpeed, tgtSigRadius)
mult = rangeFactor * missileFactor
return mult
def _calcMissileFactor(atkEr, atkEv, atkDrf, tgtSpeed, tgtSigRadius):
"""Missile application."""
factors = [1]

View File

@@ -19,9 +19,10 @@
import eos.config
from eos.const import FittingHardpoint, FittingModuleState
from eos.const import FittingHardpoint
from eos.utils.float import floatUnerr
from eos.utils.spoolSupport import SpoolType, SpoolOptions
from eos.utils.stats import DmgTypes
from gui.builtinGraphs.base import FitGraph, XDef, YDef, Input, VectorDef
from .calc import getTurretMult, getLauncherMult, getDroneMult, getFighterAbilityMult
from .timeCache import TimeCache
@@ -63,82 +64,28 @@ class FitDamageStatsGraph(FitGraph):
('distance', 'km'): lambda v, fit, tgt: v * 1000,
('atkSpeed', '%'): lambda v, fit, tgt: v / 100 * fit.ship.getModifiedItemAttr('maxVelocity'),
('tgtSpeed', '%'): lambda v, fit, tgt: v / 100 * tgt.ship.getModifiedItemAttr('maxVelocity'),
('tgtSigRad', '%'): lambda v, fit, tgt: v / 100 * fit.ship.getModifiedItemAttr('signatureRadius')}
('tgtSigRad', '%'): lambda v, fit, tgt: v / 100 * tgt.ship.getModifiedItemAttr('signatureRadius')}
_limiters = {
'time': lambda fit, tgt: (0, 2500)}
_denormalizers = {
('distance', 'km'): lambda v, fit, tgt: v / 1000,
('tgtSpeed', '%'): lambda v, fit, tgt: v * 100 / tgt.ship.getModifiedItemAttr('maxVelocity'),
('tgtSigRad', '%'): lambda v, fit, tgt: v * 100 / fit.ship.getModifiedItemAttr('signatureRadius')}
('tgtSigRad', '%'): lambda v, fit, tgt: v * 100 / tgt.ship.getModifiedItemAttr('signatureRadius')}
def _distance2dps(self, mainInput, miscInputs, fit, tgt):
xs = []
ys = []
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
miscInputMap = dict(miscInputs)
tgtSigRad = miscInputMap.get('tgtSigRad', tgt.ship.getModifiedItemAttr('signatureRadius'))
for distance in self._iterLinear(mainInput[1]):
totalDps = 0
for mod in fit.modules:
if not mod.isDealingDamage():
continue
modDps = mod.getDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total
if mod.hardpoint == FittingHardpoint.TURRET:
if mod.state >= FittingModuleState.ACTIVE:
totalDps += modDps * getTurretMult(
mod=mod,
fit=fit,
tgt=tgt,
atkSpeed=miscInputMap['atkSpeed'],
atkAngle=miscInputMap['atkAngle'],
distance=distance,
tgtSpeed=miscInputMap['tgtSpeed'],
tgtAngle=miscInputMap['tgtAngle'],
tgtSigRadius=tgtSigRad)
elif mod.hardpoint == FittingHardpoint.MISSILE:
if mod.state >= FittingModuleState.ACTIVE:
totalDps += modDps * getLauncherMult(
mod=mod,
fit=fit,
distance=distance,
tgtSpeed=miscInputMap['tgtSpeed'],
tgtSigRadius=tgtSigRad)
for drone in fit.drones:
if not drone.isDealingDamage():
continue
droneDps = drone.getDps().total
totalDps += droneDps * getDroneMult(
drone=drone,
fit=fit,
tgt=tgt,
atkSpeed=miscInputMap['atkSpeed'],
atkAngle=miscInputMap['atkAngle'],
distance=distance,
tgtSpeed=miscInputMap['tgtSpeed'],
tgtAngle=miscInputMap['tgtAngle'],
tgtSigRadius=tgtSigRad)
for fighter in fit.fighters:
if not fighter.isDealingDamage():
continue
abilityMap = fighter.abilityMap
for effectID, abilityDps in fighter.getDpsPerEffect().items():
ability = abilityMap[effectID]
totalDps += abilityDps.total * getFighterAbilityMult(
fighter=fighter,
ability=ability,
fit=fit,
distance=distance,
tgtSpeed=miscInputMap['tgtSpeed'],
tgtSigRadius=tgtSigRad)
xs.append(distance)
ys.append(totalDps)
return xs, ys
return self._xDistanceGetter(
mainInput=mainInput, miscInputs=miscInputs, fit=fit, tgt=tgt,
dmgFunc=self._getDpsPerKey, timeCacheFunc=self._timeCache.prepareDpsData)
def _distance2volley(self, mainInput, miscInputs, fit, tgt):
return [], []
return self._xDistanceGetter(
mainInput=mainInput, miscInputs=miscInputs, fit=fit, tgt=tgt,
dmgFunc=self._getVolleyPerKey, timeCacheFunc=self._timeCache.prepareVolleyData)
def _distance2damage(self, mainInput, miscInputs, fit, tgt):
return [], []
return self._xDistanceGetter(
mainInput=mainInput, miscInputs=miscInputs, fit=fit, tgt=tgt,
dmgFunc=self._getDmgPerKey, timeCacheFunc=self._timeCache.prepareDmgData)
def _time2dps(self, mainInput, miscInputs, fit, tgt):
def calcDpsTmp(timeDmg):
@@ -159,22 +106,34 @@ class FitDamageStatsGraph(FitGraph):
return self._composeTimeGraph(mainInput, fit, self._timeCache.getDmgData, calcDamageTmp)
def _tgtSpeed2dps(self, mainInput, miscInputs, fit, tgt):
return [], []
return self._xTgtSpeedGetter(
mainInput=mainInput, miscInputs=miscInputs, fit=fit, tgt=tgt,
dmgFunc=self._getDpsPerKey, timeCacheFunc=self._timeCache.prepareDpsData)
def _tgtSpeed2volley(self, mainInput, miscInputs, fit, tgt):
return [], []
return self._xTgtSpeedGetter(
mainInput=mainInput, miscInputs=miscInputs, fit=fit, tgt=tgt,
dmgFunc=self._getVolleyPerKey, timeCacheFunc=self._timeCache.prepareVolleyData)
def _tgtSpeed2damage(self, mainInput, miscInputs, fit, tgt):
return [], []
return self._xTgtSpeedGetter(
mainInput=mainInput, miscInputs=miscInputs, fit=fit, tgt=tgt,
dmgFunc=self._getDmgPerKey, timeCacheFunc=self._timeCache.prepareDmgData)
def _tgtSigRad2dps(self, mainInput, miscInputs, fit, tgt):
return [], []
return self._xTgtSigRadiusGetter(
mainInput=mainInput, miscInputs=miscInputs, fit=fit, tgt=tgt,
dmgFunc=self._getDpsPerKey, timeCacheFunc=self._timeCache.prepareDpsData)
def _tgtSigRad2volley(self, mainInput, miscInputs, fit, tgt):
return [], []
return self._xTgtSigRadiusGetter(
mainInput=mainInput, miscInputs=miscInputs, fit=fit, tgt=tgt,
dmgFunc=self._getVolleyPerKey, timeCacheFunc=self._timeCache.prepareVolleyData)
def _tgtSigRad2damage(self, mainInput, miscInputs, fit, tgt):
return [], []
return self._xTgtSigRadiusGetter(
mainInput=mainInput, miscInputs=miscInputs, fit=fit, tgt=tgt,
dmgFunc=self._getDmgPerKey, timeCacheFunc=self._timeCache.prepareDmgData)
_getters = {
('distance', 'dps'): _distance2dps,
@@ -190,6 +149,188 @@ class FitDamageStatsGraph(FitGraph):
('tgtSigRad', 'volley'): _tgtSigRad2volley,
('tgtSigRad', 'damage'): _tgtSigRad2damage}
# Point getter helpers
def _xDistanceGetter(self, mainInput, miscInputs, fit, tgt, dmgFunc, timeCacheFunc):
xs = []
ys = []
tgtSigRadius = tgt.ship.getModifiedItemAttr('signatureRadius')
# Process inputs into more convenient form
miscInputMap = dict(miscInputs)
# Get all data we need for all distances into maps/caches
timeCacheFunc(fit, miscInputMap['time'])
dmgMap = dmgFunc(fit=fit, time=miscInputMap['time'])
# Go through distances and calculate distance-dependent data
for distance in self._iterLinear(mainInput[1]):
applicationMap = self._getApplicationPerKey(
fit=fit,
tgt=tgt,
atkSpeed=miscInputMap['atkSpeed'],
atkAngle=miscInputMap['atkAngle'],
distance=distance,
tgtSpeed=miscInputMap['tgtSpeed'],
tgtAngle=miscInputMap['tgtAngle'],
tgtSigRadius=tgtSigRadius)
dmg = self._aggregate(dmgMap=dmgMap, applicationMap=applicationMap).total
xs.append(distance)
ys.append(dmg)
return xs, ys
def _xTgtSpeedGetter(self, mainInput, miscInputs, fit, tgt, dmgFunc, timeCacheFunc):
xs = []
ys = []
tgtSigRadius = tgt.ship.getModifiedItemAttr('signatureRadius')
# Process inputs into more convenient form
miscInputMap = dict(miscInputs)
# Get all data we need for all target speeds into maps/caches
timeCacheFunc(fit, miscInputMap['time'])
dmgMap = dmgFunc(fit=fit, time=miscInputMap['time'])
# Go through target speeds and calculate distance-dependent data
for tgtSpeed in self._iterLinear(mainInput[1]):
applicationMap = self._getApplicationPerKey(
fit=fit,
tgt=tgt,
atkSpeed=miscInputMap['atkSpeed'],
atkAngle=miscInputMap['atkAngle'],
distance=miscInputMap['distance'],
tgtSpeed=tgtSpeed,
tgtAngle=miscInputMap['tgtAngle'],
tgtSigRadius=tgtSigRadius)
dmg = self._aggregate(dmgMap=dmgMap, applicationMap=applicationMap).total
xs.append(tgtSpeed)
ys.append(dmg)
return xs, ys
def _xTgtSigRadiusGetter(self, mainInput, miscInputs, fit, tgt, dmgFunc, timeCacheFunc):
xs = []
ys = []
# Process inputs into more convenient form
miscInputMap = dict(miscInputs)
# Get all data we need for all target speeds into maps/caches
timeCacheFunc(fit, miscInputMap['time'])
dmgMap = dmgFunc(fit=fit, time=miscInputMap['time'])
# Go through target speeds and calculate distance-dependent data
for tgtSigRadius in self._iterLinear(mainInput[1]):
applicationMap = self._getApplicationPerKey(
fit=fit,
tgt=tgt,
atkSpeed=miscInputMap['atkSpeed'],
atkAngle=miscInputMap['atkAngle'],
distance=miscInputMap['distance'],
tgtSpeed=miscInputMap['tgtSpeed'],
tgtAngle=miscInputMap['tgtAngle'],
tgtSigRadius=tgtSigRadius)
dmg = self._aggregate(dmgMap=dmgMap, applicationMap=applicationMap).total
xs.append(tgtSigRadius)
ys.append(dmg)
return xs, ys
# Damage data per key getters
def _getDpsPerKey(self, fit, time):
if time is not None:
return self._timeCache.getDpsDataPoint(fit, time)
dpsMap = {}
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
for mod in fit.modules:
if not mod.isDealingDamage():
continue
dpsMap[mod] = mod.getDps(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
for drone in fit.drones:
if not drone.isDealingDamage():
continue
dpsMap[drone] = drone.getDps()
for fighter in fit.fighters:
if not fighter.isDealingDamage():
continue
for effectID, effectDps in fighter.getDpsPerEffect().items():
dpsMap[(fighter, effectID)] = effectDps
return dpsMap
def _getVolleyPerKey(self, fit, time):
if time is not None:
return self._timeCache.getVolleyDataPoint(fit, time)
volleyMap = {}
defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage']
for mod in fit.modules:
if not mod.isDealingDamage():
continue
volleyMap[mod] = mod.getVolley(spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False))
for drone in fit.drones:
if not drone.isDealingDamage():
continue
volleyMap[drone] = drone.getVolley()
for fighter in fit.fighters:
if not fighter.isDealingDamage():
continue
for effectID, effectVolley in fighter.getVolleyPerEffect().items():
volleyMap[(fighter, effectID)] = effectVolley
return volleyMap
def _getDmgPerKey(self, fit, time):
# Damage inflicted makes no sense without time specified
if time is None:
raise ValueError
return self._timeCache.getDmgDataPoint(fit, time)
# Application getter
def _getApplicationPerKey(self, fit, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius):
applicationMap = {}
for mod in fit.modules:
if not mod.isDealingDamage():
continue
if mod.hardpoint == FittingHardpoint.TURRET:
applicationMap[mod] = getTurretMult(
mod=mod,
fit=fit,
tgt=tgt,
atkSpeed=atkSpeed,
atkAngle=atkAngle,
distance=distance,
tgtSpeed=tgtSpeed,
tgtAngle=tgtAngle,
tgtSigRadius=tgtSigRadius)
elif mod.hardpoint == FittingHardpoint.MISSILE:
applicationMap[mod] = getLauncherMult(
mod=mod,
fit=fit,
distance=distance,
tgtSpeed=tgtSpeed,
tgtSigRadius=tgtSigRadius)
for drone in fit.drones:
if not drone.isDealingDamage():
continue
applicationMap[drone] = getDroneMult(
drone=drone,
fit=fit,
tgt=tgt,
atkSpeed=atkSpeed,
atkAngle=atkAngle,
distance=distance,
tgtSpeed=tgtSpeed,
tgtAngle=tgtAngle,
tgtSigRadius=tgtSigRadius)
for fighter in fit.fighters:
if not fighter.isDealingDamage():
continue
for ability in fighter.abilities:
if not ability.dealsDamage or not ability.active:
continue
applicationMap[(fighter, ability.effectID)] = getFighterAbilityMult(
fighter=fighter,
ability=ability,
fit=fit,
distance=distance,
tgtSpeed=tgtSpeed,
tgtSigRadius=tgtSigRadius)
return applicationMap
# Calculate damage from maps
def _aggregate(self, dmgMap, applicationMap):
total = DmgTypes(0, 0, 0, 0)
for key, dmg in dmgMap.items():
total += dmg * applicationMap.get(key, 1)
return total
############# TO REFACTOR: time graph stuff
def _composeTimeGraph(self, mainInput, fit, cacheFunc, calcFunc):
xs = []
ys = []

View File

@@ -28,15 +28,33 @@ from gui.builtinGraphs.base import FitDataCache
class TimeCache(FitDataCache):
# Whole data getters
def getDpsData(self, fit):
"""Return DPS data in {time: {key: dps}} format."""
return self._data[fit.ID]['finalDps']
def getVolleyData(self, fit):
"""Return volley data in {time: {key: volley}} format."""
return self._data[fit.ID]['finalVolley']
def getDmgData(self, fit):
"""Return inflicted damage data in {time: {key: damage}} format."""
return self._data[fit.ID]['finalDmg']
# Specific data point getters
def getDpsDataPoint(self, fit, time):
"""Get DPS data by specified time in {key: dps} format."""
return self._getDataPoint(fit, time, self.getDpsData)
def getVolleyDataPoint(self, fit, time):
"""Get volley data by specified time in {key: volley} format."""
return self._getDataPoint(fit, time, self.getVolleyData)
def getDmgDataPoint(self, fit, time):
"""Get inflicted damage data by specified time in {key: dmg} format."""
return self._getDataPoint(fit, time, self.getDmgData)
# Preparation functions
def prepareDpsData(self, fit, maxTime):
self._prepareDpsVolleyData(fit, maxTime)
@@ -74,6 +92,7 @@ class TimeCache(FitDataCache):
# We do not need internal cache once we have final
del fitCache['internalDmg']
# Private stuff
def _prepareDpsVolleyData(self, fit, maxTime):
# Time is none means that time parameter has to be ignored,
# we do not need cache for that
@@ -223,3 +242,13 @@ class TimeCache(FitDataCache):
except KeyError:
return False
return maxTime <= cacheMaxTime
def _getDataPoint(self, fit, time, dataFunc):
data = dataFunc(fit)
timesBefore = [t for t in data if floatUnerr(t) <= floatUnerr(time)]
try:
time = max(timesBefore)
except ValueError:
return {}
else:
return data[time]