Files
pyfa/graphs/data/fitEwarStats/getter.py

411 lines
21 KiB
Python

# =============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa. If not, see <http://www.gnu.org/licenses/>.
# =============================================================================
import math
from eos.calc import calculateMultiplier, calculateRangeFactor
from graphs.calc import checkLockRange, checkDroneControlRange
from graphs.data.base import SmoothPointGetter
class Distance2NeutingStrGetter(SmoothPointGetter):
_baseResolution = 50
_extraDepth = 2
def _getCommonData(self, miscParams, src, tgt):
resonance = 1 - (miscParams['resist'] or 0)
neuts = []
for mod in src.item.activeModulesIter():
for effectName in ('energyNeutralizerFalloff', 'structureEnergyNeutralizerFalloff'):
if effectName in mod.item.effects:
neuts.append((
mod.getModifiedItemAttr('energyNeutralizerAmount') / self.__getDuration(mod) * resonance,
mod.maxRange or 0, mod.falloff or 0, True, False))
if 'energyNosferatuFalloff' in mod.item.effects and mod.getModifiedItemAttr('nosOverride'):
neuts.append((
mod.getModifiedItemAttr('powerTransferAmount') / self.__getDuration(mod) * resonance,
mod.maxRange or 0, mod.falloff or 0, True, False))
if 'doomsdayAOENeut' in mod.item.effects:
neuts.append((
mod.getModifiedItemAttr('energyNeutralizerAmount') / self.__getDuration(mod) * resonance,
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange')),
mod.falloff or 0, False, False))
for drone in src.item.activeDronesIter():
if 'entityEnergyNeutralizerFalloff' in drone.item.effects:
neuts.extend(drone.amountActive * ((
drone.getModifiedItemAttr('energyNeutralizerAmount') / (drone.getModifiedItemAttr('energyNeutralizerDuration') / 1000) * resonance,
math.inf, 0, True, True),))
for fighter, ability in src.item.activeFighterAbilityIter():
if ability.effect.name == 'fighterAbilityEnergyNeutralizer':
nps = fighter.getModifiedItemAttr('fighterAbilityEnergyNeutralizerAmount') / (ability.cycleTime / 1000)
neuts.append((
nps * fighter.amount * resonance,
math.inf, 0, True, False))
return {'neuts': neuts}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
distance = x
inLockRange = checkLockRange(src=src, distance=distance)
inDroneRange = checkDroneControlRange(src=src, distance=distance)
combinedStr = 0
for strength, optimal, falloff, needsLock, needsDcr in commonData['neuts']:
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
continue
combinedStr += strength * calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
return combinedStr
def __getDuration(self, mod):
return getattr(mod.getCycleParameters(), 'averageTime', math.inf) / 1000
class Distance2WebbingStrGetter(SmoothPointGetter):
_baseResolution = 50
_extraDepth = 2
def _getCommonData(self, miscParams, src, tgt):
resonance = 1 - (miscParams['resist'] or 0)
webs = []
for mod in src.item.activeModulesIter():
for effectName in ('remoteWebifierFalloff', 'structureModuleEffectStasisWebifier'):
if effectName in mod.item.effects:
webs.append((
mod.getModifiedItemAttr('speedFactor') * resonance,
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
if 'doomsdayAOEWeb' in mod.item.effects:
webs.append((
mod.getModifiedItemAttr('speedFactor') * resonance,
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange')),
mod.falloff or 0, 'default', False, False))
for drone in src.item.activeDronesIter():
if 'remoteWebifierEntity' in drone.item.effects:
webs.extend(drone.amountActive * ((
drone.getModifiedItemAttr('speedFactor') * resonance,
math.inf, 0, 'default', True, True),))
for fighter, ability in src.item.activeFighterAbilityIter():
if ability.effect.name == 'fighterAbilityStasisWebifier':
webs.append((
fighter.getModifiedItemAttr('fighterAbilityStasisWebifierSpeedPenalty') * fighter.amount * resonance,
math.inf, 0, 'default', True, False))
return {'webs': webs}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
distance = x
inLockRange = checkLockRange(src=src, distance=distance)
inDroneRange = checkDroneControlRange(src=src, distance=distance)
strMults = {}
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['webs']:
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
continue
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
strMult = calculateMultiplier(strMults)
strength = (1 - strMult) * 100
return strength
class Distance2EcmStrMaxGetter(SmoothPointGetter):
_baseResolution = 50
_extraDepth = 2
ECM_ATTRS_GENERAL = ('scanGravimetricStrengthBonus', 'scanLadarStrengthBonus', 'scanMagnetometricStrengthBonus', 'scanRadarStrengthBonus')
ECM_ATTRS_FIGHTERS = ('fighterAbilityECMStrengthGravimetric', 'fighterAbilityECMStrengthLadar', 'fighterAbilityECMStrengthMagnetometric', 'fighterAbilityECMStrengthRadar')
def _getCommonData(self, miscParams, src, tgt):
resonance = 1 - (miscParams['resist'] or 0)
ecms = []
for mod in src.item.activeModulesIter():
for effectName in ('remoteECMFalloff', 'structureModuleEffectECM'):
if effectName in mod.item.effects:
ecms.append((
max(mod.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
mod.maxRange or 0, mod.falloff or 0, True, False))
if 'doomsdayAOEECM' in mod.item.effects:
ecms.append((
max(mod.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange')),
mod.falloff or 0, False, False))
for drone in src.item.activeDronesIter():
if 'entityECMFalloff' in drone.item.effects:
ecms.extend(drone.amountActive * ((
max(drone.getModifiedItemAttr(a) for a in self.ECM_ATTRS_GENERAL) * resonance,
math.inf, 0, True, True),))
for fighter, ability in src.item.activeFighterAbilityIter():
if ability.effect.name == 'fighterAbilityECM':
ecms.append((
max(fighter.getModifiedItemAttr(a) for a in self.ECM_ATTRS_FIGHTERS) * fighter.amount * resonance,
math.inf, 0, True, False))
return {'ecms': ecms}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
distance = x
inLockRange = checkLockRange(src=src, distance=distance)
inDroneRange = checkDroneControlRange(src=src, distance=distance)
combinedStr = 0
for strength, optimal, falloff, needsLock, needsDcr in commonData['ecms']:
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
continue
combinedStr += strength * calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
return combinedStr
class Distance2DampStrLockRangeGetter(SmoothPointGetter):
_baseResolution = 50
_extraDepth = 2
def _getCommonData(self, miscParams, src, tgt):
resonance = 1 - (miscParams['resist'] or 0)
damps = []
for mod in src.item.activeModulesIter():
for effectName in ('remoteSensorDampFalloff', 'structureModuleEffectRemoteSensorDampener'):
if effectName in mod.item.effects:
damps.append((
mod.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
if 'doomsdayAOEDamp' in mod.item.effects:
damps.append((
mod.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange')),
mod.falloff or 0, 'default', False, False))
for drone in src.item.activeDronesIter():
if 'remoteSensorDampEntity' in drone.item.effects:
damps.extend(drone.amountActive * ((
drone.getModifiedItemAttr('maxTargetRangeBonus') * resonance,
math.inf, 0, 'default', True, True),))
return {'damps': damps}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
distance = x
inLockRange = checkLockRange(src=src, distance=distance)
inDroneRange = checkDroneControlRange(src=src, distance=distance)
strMults = {}
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['damps']:
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
continue
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
strMult = calculateMultiplier(strMults)
strength = (1 - strMult) * 100
return strength
class Distance2TdStrOptimalGetter(SmoothPointGetter):
_baseResolution = 50
_extraDepth = 2
def _getCommonData(self, miscParams, src, tgt):
resonance = 1 - (miscParams['resist'] or 0)
tds = []
for mod in src.item.activeModulesIter():
for effectName in ('shipModuleTrackingDisruptor', 'structureModuleEffectWeaponDisruption'):
if effectName in mod.item.effects:
tds.append((
mod.getModifiedItemAttr('maxRangeBonus') * resonance,
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
if 'doomsdayAOETrack' in mod.item.effects:
tds.append((
mod.getModifiedItemAttr('maxRangeBonus') * resonance,
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange')),
mod.falloff or 0, 'default', False, False))
for drone in src.item.activeDronesIter():
if 'npcEntityWeaponDisruptor' in drone.item.effects:
tds.extend(drone.amountActive * ((
drone.getModifiedItemAttr('maxRangeBonus') * resonance,
math.inf, 0, 'default', True, True),))
return {'tds': tds}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
distance = x
inLockRange = checkLockRange(src=src, distance=distance)
inDroneRange = checkDroneControlRange(src=src, distance=distance)
strMults = {}
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['tds']:
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
continue
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
strMult = calculateMultiplier(strMults)
strength = (1 - strMult) * 100
return strength
class Distance2GdStrRangeGetter(SmoothPointGetter):
_baseResolution = 50
_extraDepth = 2
def _getCommonData(self, miscParams, src, tgt):
resonance = 1 - (miscParams['resist'] or 0)
gds = []
for mod in src.item.activeModulesIter():
for effectName in ('shipModuleGuidanceDisruptor', 'structureModuleEffectWeaponDisruption'):
if effectName in mod.item.effects:
gds.append((
mod.getModifiedItemAttr('missileVelocityBonus') * resonance,
mod.getModifiedItemAttr('explosionDelayBonus') * resonance,
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
if 'doomsdayAOETrack' in mod.item.effects:
gds.append((
mod.getModifiedItemAttr('missileVelocityBonus') * resonance,
mod.getModifiedItemAttr('explosionDelayBonus') * resonance,
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange')),
mod.falloff or 0, 'default', False, False))
return {'gds': gds}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
distance = x
inLockRange = checkLockRange(src=src, distance=distance)
inDroneRange = checkDroneControlRange(src=src, distance=distance)
velocityStrMults = {}
timeStrMults = {}
for velocityStr, timeStr, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['gds']:
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
continue
rangeFactor = calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
velocityStr *= rangeFactor
timeStr *= rangeFactor
velocityStrMults.setdefault(stackingGroup, []).append((1 + velocityStr / 100, None))
timeStrMults.setdefault(stackingGroup, []).append((1 + timeStr / 100, None))
velocityStrMult = calculateMultiplier(velocityStrMults)
timeStrMult = calculateMultiplier(timeStrMults)
strength = (1 - velocityStrMult * timeStrMult) * 100
return strength
class Distance2TpStrGetter(SmoothPointGetter):
_baseResolution = 50
_extraDepth = 2
def _getCommonData(self, miscParams, src, tgt):
resonance = 1 - (miscParams['resist'] or 0)
tps = []
for mod in src.item.activeModulesIter():
for effectName in ('remoteTargetPaintFalloff', 'structureModuleEffectTargetPainter'):
if effectName in mod.item.effects:
tps.append((
mod.getModifiedItemAttr('signatureRadiusBonus') * resonance,
mod.maxRange or 0, mod.falloff or 0, 'default', True, False))
if 'doomsdayAOEPaint' in mod.item.effects:
tps.append((
mod.getModifiedItemAttr('signatureRadiusBonus') * resonance,
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange')),
mod.falloff or 0, 'default', False, False))
for drone in src.item.activeDronesIter():
if 'remoteTargetPaintEntity' in drone.item.effects:
tps.extend(drone.amountActive * ((
drone.getModifiedItemAttr('signatureRadiusBonus') * resonance,
math.inf, 0, 'default', True, True),))
return {'tps': tps}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
distance = x
inLockRange = checkLockRange(src=src, distance=distance)
inDroneRange = checkDroneControlRange(src=src, distance=distance)
strMults = {}
for strength, optimal, falloff, stackingGroup, needsLock, needsDcr in commonData['tps']:
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
continue
strength *= calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
strMults.setdefault(stackingGroup, []).append((1 + strength / 100, None))
strMult = calculateMultiplier(strMults)
strength = (strMult - 1) * 100
return strength
class Distance2JamChanceGetter(SmoothPointGetter):
_baseResolution = 50
_extraDepth = 2
ECM_ATTRS_GENERAL = ('scanGravimetricStrengthBonus', 'scanLadarStrengthBonus', 'scanMagnetometricStrengthBonus', 'scanRadarStrengthBonus')
ECM_ATTRS_FIGHTERS = ('fighterAbilityECMStrengthGravimetric', 'fighterAbilityECMStrengthLadar', 'fighterAbilityECMStrengthMagnetometric', 'fighterAbilityECMStrengthRadar')
SCAN_TYPES = ('Gravimetric', 'Ladar', 'Magnetometric', 'Radar')
def _getCommonData(self, miscParams, src, tgt):
resonance = 1 - (miscParams['resist'] or 0)
ecms = []
for mod in src.item.activeModulesIter():
for effectName in ('remoteECMFalloff', 'structureModuleEffectECM'):
if effectName in mod.item.effects:
ecms.append((
tuple(mod.getModifiedItemAttr(a) or 0 for a in self.ECM_ATTRS_GENERAL),
mod.maxRange or 0, mod.falloff or 0, True, False))
if 'doomsdayAOEECM' in mod.item.effects:
ecms.append((
tuple(mod.getModifiedItemAttr(a) or 0 for a in self.ECM_ATTRS_GENERAL),
max(0, (mod.maxRange or 0) + mod.getModifiedItemAttr('doomsdayAOERange')),
mod.falloff or 0, False, False))
for drone in src.item.activeDronesIter():
if 'entityECMFalloff' in drone.item.effects:
ecms.extend(drone.amountActive * ((
tuple(drone.getModifiedItemAttr(a) or 0 for a in self.ECM_ATTRS_GENERAL),
math.inf, 0, True, True),))
for fighter, ability in src.item.activeFighterAbilityIter():
if ability.effect.name == 'fighterAbilityECM':
ecms.append((
tuple(fighter.getModifiedItemAttr(a) or 0 for a in self.ECM_ATTRS_FIGHTERS),
math.inf, 0, True, False))
# Determine target's strongest sensor type if target is available
targetScanTypeIndex = None
if tgt is not None:
maxStr = -1
for i, scanType in enumerate(self.SCAN_TYPES):
currStr = tgt.item.ship.getModifiedItemAttr('scan%sStrength' % scanType) or 0
if currStr > maxStr:
maxStr = currStr
targetScanTypeIndex = i
return {'ecms': ecms, 'targetScanTypeIndex': targetScanTypeIndex}
def _calculatePoint(self, x, miscParams, src, tgt, commonData):
distance = x
inLockRange = checkLockRange(src=src, distance=distance)
inDroneRange = checkDroneControlRange(src=src, distance=distance)
jamStrengths = []
targetScanTypeIndex = commonData['targetScanTypeIndex']
for strengths, optimal, falloff, needsLock, needsDcr in commonData['ecms']:
if (needsLock and not inLockRange) or (needsDcr and not inDroneRange):
continue
rangeFactor = calculateRangeFactor(srcOptimalRange=optimal, srcFalloffRange=falloff, distance=distance)
# Use the strength matching the target's sensor type
if targetScanTypeIndex is not None and targetScanTypeIndex < len(strengths):
strength = strengths[targetScanTypeIndex]
effectiveStrength = strength * rangeFactor
if effectiveStrength > 0:
jamStrengths.append(effectiveStrength)
if not jamStrengths:
return 0
# Get sensor strength from target
if tgt is None:
return 0
sensorStrength = max([tgt.item.ship.getModifiedItemAttr('scan%sStrength' % scanType)
for scanType in self.SCAN_TYPES]) or 0
if sensorStrength <= 0:
return 100 # If target has no sensor strength, 100% jam chance
# Calculate jam chance: 1 - (1 - (ecmStrength / sensorStrength)) ^ numJammers
retainLockChance = 1
for jamStrength in jamStrengths:
retainLockChance *= 1 - min(1, jamStrength / sensorStrength)
return (1 - retainLockChance) * 100