diff --git a/graphs/calc.py b/graphs/calc.py index 651f5e10d..82d4a2bc1 100644 --- a/graphs/calc.py +++ b/graphs/calc.py @@ -20,6 +20,8 @@ import math +from service.settings import GraphSettings + def calculateRangeFactor(srcOptimalRange, srcFalloffRange, distance, restrictedRange=True): """Range strength/chance factor, applicable to guns, ewar, RRs, etc.""" @@ -63,3 +65,19 @@ def calculateMultiplier(multipliers): bonus = l[i] val *= 1 + (bonus - 1) * math.exp(- i ** 2 / 7.1289) return val + + +def checkLockRange(src, distance): + if distance is None: + return True + if GraphSettings.getInstance().get('ignoreLockRange'): + return True + return distance <= src.item.maxTargetRange + + +def checkDroneControlRange(src, distance): + if distance is None: + return True + if GraphSettings.getInstance().get('ignoreDCR'): + return True + return distance <= src.item.extraAttributes['droneControlRange'] diff --git a/graphs/data/fitDamageStats/calc/application.py b/graphs/data/fitDamageStats/calc/application.py index 88bc6bef6..daa91d0e9 100644 --- a/graphs/data/fitDamageStats/calc/application.py +++ b/graphs/data/fitDamageStats/calc/application.py @@ -23,35 +23,44 @@ from functools import lru_cache from eos.const import FittingHardpoint from eos.utils.float import floatUnerr -from graphs.calc import calculateRangeFactor +from graphs.calc import calculateRangeFactor, checkLockRange, checkDroneControlRange from service.attribute import Attribute from service.const import GraphDpsDroneMode from service.settings import GraphSettings def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius): + inLockRange = checkLockRange(src=src, distance=distance) + inDroneRange = checkDroneControlRange(src=src, distance=distance) applicationMap = {} for mod in src.item.activeModulesIter(): if not mod.isDealingDamage(): continue if mod.hardpoint == FittingHardpoint.TURRET: - applicationMap[mod] = getTurretMult( - mod=mod, - src=src, - tgt=tgt, - atkSpeed=atkSpeed, - atkAngle=atkAngle, - distance=distance, - tgtSpeed=tgtSpeed, - tgtAngle=tgtAngle, - tgtSigRadius=tgtSigRadius) + if inLockRange: + applicationMap[mod] = getTurretMult( + mod=mod, + src=src, + tgt=tgt, + atkSpeed=atkSpeed, + atkAngle=atkAngle, + distance=distance, + tgtSpeed=tgtSpeed, + tgtAngle=tgtAngle, + tgtSigRadius=tgtSigRadius) + else: + applicationMap[mod] = 0 elif mod.hardpoint == FittingHardpoint.MISSILE: - applicationMap[mod] = getLauncherMult( - mod=mod, - src=src, - distance=distance, - tgtSpeed=tgtSpeed, - tgtSigRadius=tgtSigRadius) + # FoF missiles can shoot beyond lock range + if inLockRange or (mod.charge is not None and 'fofMissileLaunching' in mod.charge.effects): + applicationMap[mod] = getLauncherMult( + mod=mod, + src=src, + distance=distance, + tgtSpeed=tgtSpeed, + tgtSigRadius=tgtSigRadius) + else: + applicationMap[mod] = 0 elif mod.item.group.name in ('Smart Bomb', 'Structure Area Denial Module'): applicationMap[mod] = getSmartbombMult( mod=mod, @@ -64,44 +73,58 @@ def getApplicationPerKey(src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAn distance=distance, tgtSigRadius=tgtSigRadius) elif mod.item.group.name == 'Structure Guided Bomb Launcher': - applicationMap[mod] = getGuidedBombMult( - mod=mod, - src=src, - distance=distance, - tgtSigRadius=tgtSigRadius) + if inLockRange: + applicationMap[mod] = getGuidedBombMult( + mod=mod, + src=src, + distance=distance, + tgtSigRadius=tgtSigRadius) + else: + applicationMap[mod] = 0 elif mod.item.group.name in ('Super Weapon', 'Structure Doomsday Weapon'): - applicationMap[mod] = getDoomsdayMult( - mod=mod, - tgt=tgt, - distance=distance, - tgtSigRadius=tgtSigRadius) + # Only single-target DDs need locks + if not inLockRange and {'superWeaponAmarr', 'superWeaponCaldari', 'superWeaponGallente', 'superWeaponMinmatar', 'lightningWeapon'}.intersection(mod.item.effects): + applicationMap[mod] = 0 + else: + applicationMap[mod] = getDoomsdayMult( + mod=mod, + tgt=tgt, + distance=distance, + tgtSigRadius=tgtSigRadius) for drone in src.item.activeDronesIter(): if not drone.isDealingDamage(): continue - applicationMap[drone] = getDroneMult( - drone=drone, - src=src, - tgt=tgt, - atkSpeed=atkSpeed, - atkAngle=atkAngle, - distance=distance, - tgtSpeed=tgtSpeed, - tgtAngle=tgtAngle, - tgtSigRadius=tgtSigRadius) + if inLockRange and inDroneRange: + applicationMap[drone] = getDroneMult( + drone=drone, + src=src, + tgt=tgt, + atkSpeed=atkSpeed, + atkAngle=atkAngle, + distance=distance, + tgtSpeed=tgtSpeed, + tgtAngle=tgtAngle, + tgtSigRadius=tgtSigRadius) + else: + applicationMap[drone] = 0 for fighter in src.item.activeFightersIter(): 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, - src=src, - tgt=tgt, - distance=distance, - tgtSpeed=tgtSpeed, - tgtSigRadius=tgtSigRadius) + # Bomb launching doesn't need locks + if inLockRange or ability.effect.name == 'fighterAbilityLaunchBomb': + applicationMap[(fighter, ability.effectID)] = getFighterAbilityMult( + fighter=fighter, + ability=ability, + src=src, + tgt=tgt, + distance=distance, + tgtSpeed=tgtSpeed, + tgtSigRadius=tgtSigRadius) + else: + applicationMap[(fighter, ability.effectID)] = 0 # Ensure consistent results - round off a little to avoid float errors for k, v in applicationMap.items(): applicationMap[k] = floatUnerr(v) @@ -201,9 +224,9 @@ def getGuidedBombMult(mod, src, distance, tgtSigRadius): def getDroneMult(drone, src, tgt, atkSpeed, atkAngle, distance, tgtSpeed, tgtAngle, tgtSigRadius): if ( - distance is not None and - not GraphSettings.getInstance().get('ignoreDCR') and - distance > src.item.extraAttributes['droneControlRange'] + distance is not None and ( + (not GraphSettings.getInstance().get('ignoreDCR') and distance > src.item.extraAttributes['droneControlRange']) or + (not GraphSettings.getInstance().get('ignoreLockRange') and distance > src.item.maxTargetRange)) ): return 0 droneSpeed = drone.getModifiedItemAttr('maxVelocity') diff --git a/graphs/data/fitDamageStats/calc/projected.py b/graphs/data/fitDamageStats/calc/projected.py index be50fe061..a38ebd36d 100644 --- a/graphs/data/fitDamageStats/calc/projected.py +++ b/graphs/data/fitDamageStats/calc/projected.py @@ -21,7 +21,7 @@ import math from eos.utils.float import floatUnerr -from graphs.calc import calculateRangeFactor +from graphs.calc import calculateRangeFactor, checkLockRange, checkDroneControlRange from service.const import GraphDpsDroneMode from service.settings import GraphSettings @@ -75,30 +75,29 @@ def getTackledSpeed(src, tgt, currentUntackledSpeed, srcScramRange, tgtScrammabl # What's immobile cannot be slowed if maxUntackledSpeed == 0: return maxUntackledSpeed + inLockRange = checkLockRange(src=src, distance=distance) + inDroneRange = checkDroneControlRange(src=src, distance=distance) speedRatio = currentUntackledSpeed / maxUntackledSpeed # No scrams or distance is longer than longest scram - nullify scrammables list - if srcScramRange is None or (distance is not None and distance > srcScramRange): + if not inLockRange or srcScramRange is None or (distance is not None and distance > srcScramRange): tgtScrammables = () appliedMultipliers = {} - # Modules first, they are applied always the same way - for wData in webMods: - appliedBoost = wData.boost * calculateRangeFactor( - srcOptimalRange=wData.optimal, - srcFalloffRange=wData.falloff, - distance=distance) - if appliedBoost: - appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID)) + # Modules first, they are always applied the same way + if inLockRange: + for wData in webMods: + appliedBoost = wData.boost * calculateRangeFactor( + srcOptimalRange=wData.optimal, + srcFalloffRange=wData.falloff, + distance=distance) + if appliedBoost: + appliedMultipliers.setdefault(wData.stackingGroup, []).append((1 + appliedBoost / 100, wData.resAttrID)) maxTackledSpeed = tgt.getMaxVelocity(extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables) currentTackledSpeed = maxTackledSpeed * speedRatio # Drones and fighters mobileWebs = [] - mobileWebs.extend(webFighters) - # Drones have range limit - if ( - distance is None or - GraphSettings.getInstance().get('ignoreDCR') or - distance <= src.item.extraAttributes['droneControlRange'] - ): + if inLockRange: + mobileWebs.extend(webFighters) + if inLockRange and inDroneRange: mobileWebs.extend(webDrones) atkRadius = src.getRadius() # As mobile webs either follow the target or stick to the attacking ship, @@ -142,28 +141,27 @@ def getSigRadiusMult(src, tgt, tgtSpeed, srcScramRange, tgtScrammables, tpMods, # Can blow non-immune ships and target profiles if tgt.isFit and tgt.item.ship.getModifiedItemAttr('disallowOffensiveModifiers'): return 1 + inLockRange = checkLockRange(src=src, distance=distance) + inDroneRange = checkDroneControlRange(src=src, distance=distance) initSig = tgt.getSigRadius() # No scrams or distance is longer than longest scram - nullify scrammables list - if srcScramRange is None or (distance is not None and distance > srcScramRange): + if not inLockRange or srcScramRange is None or (distance is not None and distance > srcScramRange): tgtScrammables = () # TPing modules appliedMultipliers = {} - for tpData in tpMods: - appliedBoost = tpData.boost * calculateRangeFactor( - srcOptimalRange=tpData.optimal, - srcFalloffRange=tpData.falloff, - distance=distance) - if appliedBoost: - appliedMultipliers.setdefault(tpData.stackingGroup, []).append((1 + appliedBoost / 100, tpData.resAttrID)) + if inLockRange: + for tpData in tpMods: + appliedBoost = tpData.boost * calculateRangeFactor( + srcOptimalRange=tpData.optimal, + srcFalloffRange=tpData.falloff, + distance=distance) + if appliedBoost: + appliedMultipliers.setdefault(tpData.stackingGroup, []).append((1 + appliedBoost / 100, tpData.resAttrID)) # TPing drones mobileTps = [] - mobileTps.extend(tpFighters) - # Drones have range limit - if ( - distance is None or - GraphSettings.getInstance().get('ignoreDCR') or - distance <= src.item.extraAttributes['droneControlRange'] - ): + if inLockRange: + mobileTps.extend(tpFighters) + if inLockRange and inDroneRange: mobileTps.extend(tpDrones) droneOpt = GraphSettings.getInstance().get('mobileDroneMode') atkRadius = src.getRadius() diff --git a/graphs/data/fitRemoteReps/calc.py b/graphs/data/fitRemoteReps/calc.py index 2473682e5..f0177855d 100644 --- a/graphs/data/fitRemoteReps/calc.py +++ b/graphs/data/fitRemoteReps/calc.py @@ -19,18 +19,17 @@ from eos.utils.float import floatUnerr -from graphs.calc import calculateRangeFactor -from service.settings import GraphSettings +from graphs.calc import calculateRangeFactor, checkLockRange, checkDroneControlRange def getApplicationPerKey(src, distance): + inLockRange = checkLockRange(src=src, distance=distance) + inDroneRange = checkDroneControlRange(src=src, distance=distance) applicationMap = {} for mod in src.item.activeModulesIter(): if not mod.isRemoteRepping(): continue - if distance is None: - applicationMap[mod] = 1 - elif not GraphSettings.getInstance().get('ignoreLockRange') and distance > src.item.maxTargetRange: + if not inLockRange: applicationMap[mod] = 0 else: applicationMap[mod] = calculateRangeFactor( @@ -40,11 +39,7 @@ def getApplicationPerKey(src, distance): for drone in src.item.activeDronesIter(): if not drone.isRemoteRepping(): continue - if distance is None: - applicationMap[drone] = 1 - elif not GraphSettings.getInstance().get('ignoreDCR') and distance > src.item.extraAttributes['droneControlRange']: - applicationMap[drone] = 0 - elif not GraphSettings.getInstance().get('ignoreLockRange') and distance > src.item.maxTargetRange: + if not inLockRange or not inDroneRange: applicationMap[drone] = 0 else: applicationMap[drone] = 1