diff --git a/graphs/data/fitEwarStats/getter.py b/graphs/data/fitEwarStats/getter.py index 1aa74d172..df7b07ff6 100644 --- a/graphs/data/fitEwarStats/getter.py +++ b/graphs/data/fitEwarStats/getter.py @@ -332,3 +332,79 @@ class Distance2TpStrGetter(SmoothPointGetter): 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 diff --git a/graphs/data/fitEwarStats/graph.py b/graphs/data/fitEwarStats/graph.py index d83969dd0..588ed8671 100644 --- a/graphs/data/fitEwarStats/graph.py +++ b/graphs/data/fitEwarStats/graph.py @@ -21,8 +21,8 @@ import wx from graphs.data.base import FitGraph, Input, XDef, YDef -from .getter import (Distance2DampStrLockRangeGetter, Distance2EcmStrMaxGetter, Distance2GdStrRangeGetter, Distance2NeutingStrGetter, Distance2TdStrOptimalGetter, - Distance2TpStrGetter, Distance2WebbingStrGetter) +from .getter import (Distance2DampStrLockRangeGetter, Distance2EcmStrMaxGetter, Distance2GdStrRangeGetter, Distance2JamChanceGetter, Distance2NeutingStrGetter, + Distance2TdStrOptimalGetter, Distance2TpStrGetter, Distance2WebbingStrGetter) _t = wx.GetTranslation @@ -31,11 +31,13 @@ class FitEwarStatsGraph(FitGraph): # UI stuff internalName = 'ewarStatsGraph' name = _t('Electronic Warfare Stats') + hasTargets = True xDefs = [XDef(handle='distance', unit='km', label=_t('Distance'), mainInput=('distance', 'km'))] yDefs = [ YDef(handle='neutStr', unit=None, label=_t('Cap neutralized per second'), selectorLabel=_t('Neuts: cap per second')), YDef(handle='webStr', unit='%', label=_t('Speed reduction'), selectorLabel=_t('Webs: speed reduction')), YDef(handle='ecmStrMax', unit=None, label=_t('Combined ECM strength'), selectorLabel=_t('ECM: combined strength')), + YDef(handle='jamChance', unit='%', label=_t('Jam chance'), selectorLabel=_t('ECM: jam chance')), YDef(handle='dampStrLockRange', unit='%', label=_t('Lock range reduction'), selectorLabel=_t('Damps: lock range reduction')), YDef(handle='tdStrOptimal', unit='%', label=_t('Turret optimal range reduction'), selectorLabel=_t('TDs: turret optimal range reduction')), YDef(handle='gdStrRange', unit='%', label=_t('Missile flight range reduction'), selectorLabel=_t('GDs: missile flight range reduction')), @@ -53,6 +55,7 @@ class FitEwarStatsGraph(FitGraph): ('distance', 'neutStr'): Distance2NeutingStrGetter, ('distance', 'webStr'): Distance2WebbingStrGetter, ('distance', 'ecmStrMax'): Distance2EcmStrMaxGetter, + ('distance', 'jamChance'): Distance2JamChanceGetter, ('distance', 'dampStrLockRange'): Distance2DampStrLockRangeGetter, ('distance', 'tdStrOptimal'): Distance2TdStrOptimalGetter, ('distance', 'gdStrRange'): Distance2GdStrRangeGetter,