# =============================================================================== # Copyright (C) 2010 Diego Duclos # # This file is part of eos. # # eos is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # eos 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with eos. If not, see . # =============================================================================== from math import exp, log, radians, sin, inf from logbook import Logger import eos.config from eos.const import FittingHardpoint, FittingModuleState from eos.utils.spoolSupport import SpoolType, SpoolOptions from .base import SmoothGraph pyfalog = Logger(__name__) class FitDpsVsRangeGraph(SmoothGraph): def getYForX(self, fit, extraData, distance): tgtSpeed = extraData['speed'] tgtSigRad = extraData['signatureRadius'] if extraData['signatureRadius'] is not None else inf angle = extraData['angle'] tgtSigRadMods = [] tgtSpeedMods = [] total = 0 distance = distance * 1000 for mod in fit.modules: if not mod.isEmpty and mod.state >= FittingModuleState.ACTIVE: if "remoteTargetPaintFalloff" in mod.item.effects or "structureModuleEffectTargetPainter" in mod.item.effects: tgtSigRadMods.append( 1 + (mod.getModifiedItemAttr("signatureRadiusBonus") / 100) * self.calculateModuleMultiplier(mod, distance)) if "remoteWebifierFalloff" in mod.item.effects or "structureModuleEffectStasisWebifier" in mod.item.effects: if distance <= mod.getModifiedItemAttr("maxRange"): tgtSpeedMods.append(1 + (mod.getModifiedItemAttr("speedFactor") / 100)) elif mod.getModifiedItemAttr("falloffEffectiveness") > 0: # I am affected by falloff tgtSpeedMods.append( 1 + (mod.getModifiedItemAttr("speedFactor") / 100) * self.calculateModuleMultiplier(mod, distance)) tgtSpeed = self.penalizeModChain(tgtSpeed, tgtSpeedMods) tgtSigRad = self.penalizeModChain(tgtSigRad, tgtSigRadMods) attRad = fit.ship.getModifiedItemAttr('radius', 0) defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage'] for mod in fit.modules: dps = mod.getDps(targetResists=fit.targetResists, spoolOptions=SpoolOptions(SpoolType.SCALE, defaultSpoolValue, False)).total if mod.hardpoint == FittingHardpoint.TURRET: if mod.state >= FittingModuleState.ACTIVE: total += dps * self.calculateTurretMultiplier(fit, mod, distance, angle, tgtSpeed, tgtSigRad) elif mod.hardpoint == FittingHardpoint.MISSILE: if mod.state >= FittingModuleState.ACTIVE and mod.maxRange is not None and (mod.maxRange - attRad) >= distance: total += dps * self.calculateMissileMultiplier(mod, tgtSpeed, tgtSigRad) if distance <= fit.extraAttributes['droneControlRange']: for drone in fit.drones: multiplier = 1 if drone.getModifiedItemAttr('maxVelocity') > 1 else self.calculateTurretMultiplier( fit, drone, distance, angle, tgtSpeed, tgtSigRad) dps = drone.getDps(targetResists=fit.targetResists).total total += dps * multiplier # this is janky as fuck for fighter in fit.fighters: if not fighter.active: continue fighterDpsMap = fighter.getDpsPerEffect(targetResists=fit.targetResists) for ability in fighter.abilities: if ability.dealsDamage and ability.active: if ability.effectID not in fighterDpsMap: continue multiplier = self.calculateFighterMissileMultiplier(tgtSpeed, tgtSigRad, ability) dps = fighterDpsMap[ability.effectID].total total += dps * multiplier return total @staticmethod def calculateMissileMultiplier(mod, tgtSpeed, tgtSigRad): explosionRadius = mod.getModifiedChargeAttr('aoeCloudSize') explosionVelocity = mod.getModifiedChargeAttr('aoeVelocity') damageReductionFactor = mod.getModifiedChargeAttr('aoeDamageReductionFactor') sigRadiusFactor = tgtSigRad / explosionRadius if tgtSpeed: velocityFactor = (explosionVelocity / explosionRadius * tgtSigRad / tgtSpeed) ** damageReductionFactor else: velocityFactor = 1 return min(sigRadiusFactor, velocityFactor, 1) @staticmethod def calculateFighterMissileMultiplier(tgtSpeed, tgtSigRad, ability): prefix = ability.attrPrefix explosionRadius = ability.fighter.getModifiedItemAttr('{}ExplosionRadius'.format(prefix)) explosionVelocity = ability.fighter.getModifiedItemAttr('{}ExplosionVelocity'.format(prefix)) damageReductionFactor = ability.fighter.getModifiedItemAttr('{}ReductionFactor'.format(prefix), None) # the following conditionals are because CCP can't keep a decent naming convention, as if fighter implementation # wasn't already fucked. if damageReductionFactor is None: damageReductionFactor = ability.fighter.getModifiedItemAttr('{}DamageReductionFactor'.format(prefix)) damageReductionSensitivity = ability.fighter.getModifiedItemAttr('{}ReductionSensitivity'.format(prefix), None) if damageReductionSensitivity is None: damageReductionSensitivity = ability.fighter.getModifiedItemAttr('{}DamageReductionSensitivity'.format(prefix)) sigRadiusFactor = tgtSigRad / explosionRadius if tgtSpeed: velocityFactor = (explosionVelocity / explosionRadius * tgtSigRad / tgtSpeed) ** ( log(damageReductionFactor) / log(damageReductionSensitivity)) else: velocityFactor = 1 return min(sigRadiusFactor, velocityFactor, 1) @staticmethod def penalizeModChain(value, mods): mods.sort(key=lambda v: -abs(v - 1)) try: for i in range(len(mods)): bonus = mods[i] value *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289) return value except Exception as e: pyfalog.critical('Caught exception when penalizing modifier chain.') pyfalog.critical(e) return value