def OnGraphSwitched(self, event): view = self.getView() GraphSettings.getInstance().set('selectedGraph', view.internalName) self.clearCache(reason=GraphCacheCleanupReason.graphSwitched) self.ctrlPanel.updateControls() self.draw() event.Skip()
def _prepareApplicationMap(self, miscParams, src, tgt): tgtSpeed = miscParams['tgtSpeed'] tgtSigRadius = tgt.getSigRadius() if GraphSettings.getInstance().get('applyProjected'): webMods, tpMods = self.graph._projectedCache.getProjModData(src) webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src) webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src) tgtSpeed = getWebbedSpeed( src=src, tgt=tgt, currentUnwebbedSpeed=tgtSpeed, webMods=webMods, webDrones=webDrones, webFighters=webFighters, distance=miscParams['distance']) tgtSigRadius = tgtSigRadius * getTpMult( src=src, tgt=tgt, tgtSpeed=tgtSpeed, tpMods=tpMods, tpDrones=tpDrones, tpFighters=tpFighters, distance=miscParams['distance']) # Get all data we need for all times into maps/caches applicationMap = getApplicationPerKey( src=src, tgt=tgt, atkSpeed=miscParams['atkSpeed'], atkAngle=miscParams['atkAngle'], distance=miscParams['distance'], tgtSpeed=tgtSpeed, tgtAngle=miscParams['tgtAngle'], tgtSigRadius=tgtSigRadius) return applicationMap
def _getCommonData(self, miscParams, src, tgt): tgtSpeed = miscParams['tgtSpeed'] tgtSigMult = 1 if GraphSettings.getInstance().get('applyProjected'): webMods, tpMods = self.graph._projectedCache.getProjModData(src) webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src) webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src) tgtSpeed = getWebbedSpeed( src=src, tgt=tgt, currentUnwebbedSpeed=tgtSpeed, webMods=webMods, webDrones=webDrones, webFighters=webFighters, distance=miscParams['distance']) tgtSigMult = getTpMult( src=src, tgt=tgt, tgtSpeed=tgtSpeed, tpMods=tpMods, tpDrones=tpDrones, tpFighters=tpFighters, distance=miscParams['distance']) # Prepare time cache here because we need to do it only once, # and this function is called once per point info fetch self._prepareTimeCache(src=src, maxTime=miscParams['time']) return { 'tgtSpeed': tgtSpeed, 'tgtSigMult': tgtSigMult, 'dmgMap': self._getDamagePerKey(src=src, time=miscParams['time']), 'tgtResists': tgt.getResists()}
def _getCommonData(self, miscParams, src, tgt): # Prepare time cache here because we need to do it only once, # and this function is called once per point info fetch self._prepareTimeCache(src=src, maxTime=miscParams['time']) return { 'applyProjected': GraphSettings.getInstance().get('applyProjected'), 'dmgMap': self._getDamagePerKey(src=src, time=miscParams['time']), 'tgtResists': tgt.getResists()}
def getSigRadiusMult(src, tgt, tgtSpeed, srcScramRange, tgtScrammables, tpMods, tpDrones, tpFighters, distance): # 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 not inLockRange or srcScramRange is None or (distance is not None and distance > srcScramRange): tgtScrammables = () # TPing modules appliedMultipliers = {} 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 = [] if inLockRange: mobileTps.extend(tpFighters) if inLockRange and inDroneRange: mobileTps.extend(tpDrones) droneOpt = GraphSettings.getInstance().get('mobileDroneMode') atkRadius = src.getRadius() for mtpData in mobileTps: # Faster than target or set to follow it - apply full TP if (droneOpt == GraphDpsDroneMode.auto and mtpData.speed >= tgtSpeed ) or droneOpt == GraphDpsDroneMode.followTarget: appliedMtpBoost = mtpData.boost # Otherwise project from the center of the ship else: if distance is None: rangeFactorDistance = None else: rangeFactorDistance = distance + atkRadius - mtpData.radius appliedMtpBoost = mtpData.boost * calculateRangeFactor( srcOptimalRange=mtpData.optimal, srcFalloffRange=mtpData.falloff, distance=rangeFactorDistance) appliedMultipliers.setdefault(mtpData.stackingGroup, []).append( (1 + appliedMtpBoost / 100, mtpData.resAttrID)) modifiedSig = tgt.getSigRadius(extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables) if modifiedSig == math.inf and initSig == math.inf: return 1 mult = modifiedSig / initSig # Ensure consistent results - round off a little to avoid float errors return floatUnerr(mult)
def display(self, callingWindow, srcContext, mainItem, selection): if srcContext != 'graphTgtList': return False if GraphSettings.getInstance().get('ignoreResists'): return False if not isinstance(mainItem, TargetWrapper) or not mainItem.isFit: return False self.callingWindow = callingWindow self.selection = selection return True
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']) or (not GraphSettings.getInstance().get('ignoreLockRange') and distance > src.item.maxTargetRange)) ): return 0 droneSpeed = drone.getModifiedItemAttr('maxVelocity') # Hard to simulate drone behavior, so assume chance to hit is 1 for mobile drones # which catch up with target droneOpt = GraphSettings.getInstance().get('mobileDroneMode') if ( droneSpeed > 1 and ( (droneOpt == GraphDpsDroneMode.auto and droneSpeed >= tgtSpeed) or droneOpt == GraphDpsDroneMode.followTarget) ): cth = 1 # Otherwise put the drone into center of the ship, move it at its max speed or ship's speed # (whichever is lower) towards direction of attacking ship and see how well it projects else: droneRadius = drone.getModifiedItemAttr('radius') if distance is None: cthDistance = None else: # As distance is ship surface to ship surface, we adjust it according # to attacker ship's radiuses to have drone surface to ship surface distance cthDistance = distance + src.getRadius() - droneRadius cth = _calcTurretChanceToHit( atkSpeed=min(atkSpeed, droneSpeed), atkAngle=atkAngle, atkRadius=droneRadius, atkOptimalRange=drone.maxRange or 0, atkFalloffRange=drone.falloff or 0, atkTracking=drone.getModifiedItemAttr('trackingSpeed'), atkOptimalSigRadius=drone.getModifiedItemAttr('optimalSigRadius'), distance=cthDistance, tgtSpeed=tgtSpeed, tgtAngle=tgtAngle, tgtRadius=tgt.getRadius(), tgtSigRadius=tgtSigRadius) mult = _calcTurretMult(cth) return mult
def applyDamage(dmgMap, applicationMap, tgtResists): total = DmgTypes(em=0, thermal=0, kinetic=0, explosive=0) for key, dmg in dmgMap.items(): total += dmg * applicationMap.get(key, 0) if not GraphSettings.getInstance().get('ignoreResists'): emRes, thermRes, kinRes, exploRes = tgtResists total = DmgTypes(em=total.em * (1 - emRes), thermal=total.thermal * (1 - thermRes), kinetic=total.kinetic * (1 - kinRes), explosive=total.explosive * (1 - exploRes)) return total
def getFighterAbilityMult(fighter, ability, src, distance, tgtSpeed, tgtSigRadius): fighterSpeed = fighter.getModifiedItemAttr('maxVelocity') attrPrefix = ability.attrPrefix # It's bomb attack if attrPrefix == 'fighterAbilityLaunchBomb': # Just assume we can land bomb anywhere return _calcBombFactor( atkEr=fighter.getModifiedChargeAttr('aoeCloudSize'), tgtSigRadius=tgtSigRadius) droneOpt = GraphSettings.getInstance().get('mobileDroneMode') # It's regular missile-based attack if (droneOpt == GraphDpsDroneMode.auto and fighterSpeed >= tgtSpeed ) or droneOpt == GraphDpsDroneMode.followTarget: rangeFactor = 1 # Same as with drones, if fighters are slower - put them to center of # the ship and see how they apply else: if distance is None: rangeFactorDistance = None else: rangeFactorDistance = distance + src.getRadius( ) - fighter.getModifiedItemAttr('radius') rangeFactor = _calcRangeFactor( atkOptimalRange=fighter.getModifiedItemAttr( '{}RangeOptimal'.format(attrPrefix)) or fighter.getModifiedItemAttr('{}Range'.format(attrPrefix)), atkFalloffRange=fighter.getModifiedItemAttr( '{}RangeFalloff'.format(attrPrefix)), distance=rangeFactorDistance) drf = fighter.getModifiedItemAttr('{}ReductionFactor'.format(attrPrefix), None) if drf is None: drf = fighter.getModifiedItemAttr( '{}DamageReductionFactor'.format(attrPrefix)) drs = fighter.getModifiedItemAttr( '{}ReductionSensitivity'.format(attrPrefix), None) if drs is None: drs = fighter.getModifiedItemAttr( '{}DamageReductionSensitivity'.format(attrPrefix)) missileFactor = _calcMissileFactor( atkEr=fighter.getModifiedItemAttr( '{}ExplosionRadius'.format(attrPrefix)), atkEv=fighter.getModifiedItemAttr( '{}ExplosionVelocity'.format(attrPrefix)), atkDrf=_calcAggregatedDrf(reductionFactor=drf, reductionSensitivity=drs), tgtSpeed=tgtSpeed, tgtSigRadius=tgtSigRadius) mult = rangeFactor * missileFactor return mult
def yDefs(self): ignoreResists = GraphSettings.getInstance().get('ignoreResists') return [ YDef(handle='dps', unit=None, label='DPS' if ignoreResists else 'Effective DPS'), YDef(handle='volley', unit=None, label='Volley' if ignoreResists else 'Effective volley'), YDef(handle='damage', unit=None, label='Damage inflicted' if ignoreResists else 'Effective damage inflicted') ]
def getTpMult(src, tgt, tgtSpeed, tpMods, tpDrones, tpFighters, distance): # Can blow non-immune ships and target profiles if tgt.isFit and tgt.item.ship.getModifiedItemAttr( 'disallowOffensiveModifiers'): return 1 untpedSig = tgt.getSigRadius() # 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)) # Drones and fighters mobileTps = [] mobileTps.extend(tpFighters) # Drones have range limit if distance is None or distance <= src.item.extraAttributes[ 'droneControlRange']: mobileTps.extend(tpDrones) droneOpt = GraphSettings.getInstance().get('mobileDroneMode') atkRadius = src.getRadius() for mtpData in mobileTps: # Faster than target or set to follow it - apply full TP if (droneOpt == GraphDpsDroneMode.auto and mtpData.speed >= tgtSpeed ) or droneOpt == GraphDpsDroneMode.followTarget: appliedMtpBoost = mtpData.boost # Otherwise project from the center of the ship else: if distance is None: rangeFactorDistance = None else: rangeFactorDistance = distance + atkRadius - mtpData.radius appliedMtpBoost = mtpData.boost * calculateRangeFactor( srcOptimalRange=mtpData.optimal, srcFalloffRange=mtpData.falloff, distance=rangeFactorDistance) appliedMultipliers.setdefault(mtpData.stackingGroup, []).append( (1 + appliedMtpBoost / 100, mtpData.resAttrID)) tpedSig = tgt.getSigRadius(extraMultipliers=appliedMultipliers) if tpedSig == math.inf and untpedSig == math.inf: return 1 mult = tpedSig / untpedSig # Ensure consistent results - round off a little to avoid float errors return floatUnerr(mult)
def getTackledSpeed(src, tgt, currentUntackledSpeed, srcScramRange, tgtScrammables, webMods, webDrones, webFighters, distance): # Can slow down non-immune ships and target profiles if tgt.isFit and tgt.item.ship.getModifiedItemAttr( 'disallowOffensiveModifiers'): return currentUntackledSpeed maxUntackledSpeed = tgt.getMaxVelocity() # 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 not inLockRange or srcScramRange is None or (distance is not None and distance > srcScramRange): tgtScrammables = () appliedMultipliers = {} # 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 = [] 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, # if target is within mobile web optimal - it can be applied unconditionally longEnoughMws = [ mw for mw in mobileWebs if distance is None or distance <= mw.optimal - atkRadius + mw.radius ] if longEnoughMws: for mwData in longEnoughMws: appliedMultipliers.setdefault(mwData.stackingGroup, []).append( (1 + mwData.boost / 100, mwData.resAttrID)) mobileWebs.remove(mwData) maxTackledSpeed = tgt.getMaxVelocity( extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables) currentTackledSpeed = maxTackledSpeed * speedRatio # Apply remaining webs, from fastest to slowest droneOpt = GraphSettings.getInstance().get('mobileDroneMode') while mobileWebs: # Process in batches unified by speed to save up resources fastestMwSpeed = max(mobileWebs, key=lambda mw: mw.speed).speed fastestMws = [mw for mw in mobileWebs if mw.speed == fastestMwSpeed] for mwData in fastestMws: # Faster than target or set to follow it - apply full slowdown if (droneOpt == GraphDpsDroneMode.auto and mwData.speed >= currentTackledSpeed ) or droneOpt == GraphDpsDroneMode.followTarget: appliedMwBoost = mwData.boost # Otherwise project from the center of the ship else: if distance is None: rangeFactorDistance = None else: rangeFactorDistance = distance + atkRadius - mwData.radius appliedMwBoost = mwData.boost * calculateRangeFactor( srcOptimalRange=mwData.optimal, srcFalloffRange=mwData.falloff, distance=rangeFactorDistance) appliedMultipliers.setdefault(mwData.stackingGroup, []).append( (1 + appliedMwBoost / 100, mwData.resAttrID)) mobileWebs.remove(mwData) maxTackledSpeed = tgt.getMaxVelocity( extraMultipliers=appliedMultipliers, ignoreAfflictors=tgtScrammables) currentTackledSpeed = maxTackledSpeed * speedRatio # Ensure consistent results - round off a little to avoid float errors return floatUnerr(currentTackledSpeed)
def checkDroneControlRange(src, distance): if distance is None: return True if GraphSettings.getInstance().get('ignoreDCR'): return True return distance <= src.item.extraAttributes['droneControlRange']
def checkLockRange(src, distance): if distance is None: return True if GraphSettings.getInstance().get('ignoreLockRange'): return True return distance <= src.item.maxTargetRange
def tgtExtraCols(self): cols = [] if not GraphSettings.getInstance().get('ignoreResists'): cols.append('Target Resists') cols.extend(('Speed', 'SigRadius', 'Radius')) return cols
def __init__(self): self.mainFrame = gui.mainFrame.MainFrame.getInstance() self.settings = GraphSettings.getInstance()
def getWebbedSpeed(src, tgt, currentUnwebbedSpeed, webMods, webDrones, webFighters, distance): # Can slow down non-immune ships and target profiles if tgt.isFit and tgt.item.ship.getModifiedItemAttr( 'disallowOffensiveModifiers'): return currentUnwebbedSpeed maxUnwebbedSpeed = tgt.getMaxVelocity() try: speedRatio = currentUnwebbedSpeed / maxUnwebbedSpeed except ZeroDivisionError: currentWebbedSpeed = 0 else: appliedMultipliers = {} # Modules first, they are applied always the same way for wData in webMods: appliedBoost = wData.boost * _calcRangeFactor( atkOptimalRange=wData.optimal, atkFalloffRange=wData.falloff, distance=distance) if appliedBoost: appliedMultipliers.setdefault(wData.stackingGroup, []).append( (1 + appliedBoost / 100, wData.resAttrID)) maxWebbedSpeed = tgt.getMaxVelocity( extraMultipliers=appliedMultipliers) currentWebbedSpeed = maxWebbedSpeed * speedRatio # Drones and fighters mobileWebs = [] mobileWebs.extend(webFighters) # Drones have range limit if distance is None or distance <= src.item.extraAttributes[ 'droneControlRange']: mobileWebs.extend(webDrones) atkRadius = src.getRadius() # As mobile webs either follow the target or stick to the attacking ship, # if target is within mobile web optimal - it can be applied unconditionally longEnoughMws = [ mw for mw in mobileWebs if distance is None or distance <= mw.optimal - atkRadius + mw.radius ] if longEnoughMws: for mwData in longEnoughMws: appliedMultipliers.setdefault(mwData.stackingGroup, []).append( (1 + mwData.boost / 100, mwData.resAttrID)) mobileWebs.remove(mwData) maxWebbedSpeed = tgt.getMaxVelocity( extraMultipliers=appliedMultipliers) currentWebbedSpeed = maxWebbedSpeed * speedRatio # Apply remaining webs, from fastest to slowest droneOpt = GraphSettings.getInstance().get('mobileDroneMode') while mobileWebs: # Process in batches unified by speed to save up resources fastestMwSpeed = max(mobileWebs, key=lambda mw: mw.speed).speed fastestMws = [ mw for mw in mobileWebs if mw.speed == fastestMwSpeed ] for mwData in fastestMws: # Faster than target or set to follow it - apply full slowdown if (droneOpt == GraphDpsDroneMode.auto and mwData.speed >= currentWebbedSpeed ) or droneOpt == GraphDpsDroneMode.followTarget: appliedMwBoost = mwData.boost # Otherwise project from the center of the ship else: if distance is None: rangeFactorDistance = None else: rangeFactorDistance = distance + atkRadius - mwData.radius appliedMwBoost = mwData.boost * _calcRangeFactor( atkOptimalRange=mwData.optimal, atkFalloffRange=mwData.falloff, distance=rangeFactorDistance) appliedMultipliers.setdefault(mwData.stackingGroup, []).append( (1 + appliedMwBoost / 100, mwData.resAttrID)) mobileWebs.remove(mwData) maxWebbedSpeed = tgt.getMaxVelocity( extraMultipliers=appliedMultipliers) currentWebbedSpeed = maxWebbedSpeed * speedRatio # Ensure consistent results - round off a little to avoid float errors return floatUnerr(currentWebbedSpeed)