# ============================================================================= # 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 . # ============================================================================= import eos.config from eos.saveddata.targetProfile import TargetProfile from eos.utils.spoolSupport import SpoolOptions, SpoolType from eos.utils.stats import DmgTypes from graphs.data.base import PointGetter, SmoothPointGetter from service.settings import GraphSettings from .calc.application import getApplicationPerKey from .calc.projected import getScramRange, getScrammables, getTackledSpeed, getSigRadiusMult def applyDamage(dmgMap, applicationMap, tgtResists, tgtFullHp): total = DmgTypes.default() for key, dmg in dmgMap.items(): total += dmg * applicationMap.get(key, 0) if not GraphSettings.getInstance().get('ignoreResists'): emRes, thermRes, kinRes, exploRes = tgtResists else: emRes = thermRes = kinRes = exploRes = 0 total.profile = TargetProfile( emAmount=emRes, thermalAmount=thermRes, kineticAmount=kinRes, explosiveAmount=exploRes, hp=tgtFullHp) return total # Y mixins class YDpsMixin: def _getDamagePerKey(self, src, time): # Use data from time cache if time was not specified if time is not None: return self._getTimeCacheDataPoint(src=src, time=time) # Compose map ourselves using current fit settings if time is not specified dpsMap = {} defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage'] for mod in src.item.activeModulesIter(): if not mod.isDealingDamage(): continue dpsMap[mod] = mod.getDps(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)) for drone in src.item.activeDronesIter(): if not drone.isDealingDamage(): continue dpsMap[drone] = drone.getDps() for fighter in src.item.activeFightersIter(): if not fighter.isDealingDamage(): continue for effectID, effectDps in fighter.getDpsPerEffect().items(): dpsMap[(fighter, effectID)] = effectDps return dpsMap def _prepareTimeCache(self, src, maxTime): self.graph._timeCache.prepareDpsData(src=src, maxTime=maxTime) def _getTimeCacheData(self, src): return self.graph._timeCache.getDpsData(src=src) def _getTimeCacheDataPoint(self, src, time): return self.graph._timeCache.getDpsDataPoint(src=src, time=time) class YVolleyMixin: def _getDamagePerKey(self, src, time): # Use data from time cache if time was not specified if time is not None: return self._getTimeCacheDataPoint(src=src, time=time) # Compose map ourselves using current fit settings if time is not specified volleyMap = {} defaultSpoolValue = eos.config.settings['globalDefaultSpoolupPercentage'] for mod in src.item.activeModulesIter(): if not mod.isDealingDamage(): continue volleyMap[mod] = mod.getVolley(spoolOptions=SpoolOptions(SpoolType.SPOOL_SCALE, defaultSpoolValue, False)) for drone in src.item.activeDronesIter(): if not drone.isDealingDamage(): continue volleyMap[drone] = drone.getVolley() for fighter in src.item.activeFightersIter(): if not fighter.isDealingDamage(): continue for effectID, effectVolley in fighter.getVolleyPerEffect().items(): volleyMap[(fighter, effectID)] = effectVolley return volleyMap def _prepareTimeCache(self, src, maxTime): self.graph._timeCache.prepareVolleyData(src=src, maxTime=maxTime) def _getTimeCacheData(self, src): return self.graph._timeCache.getVolleyData(src=src) def _getTimeCacheDataPoint(self, src, time): return self.graph._timeCache.getVolleyDataPoint(src=src, time=time) class YInflictedDamageMixin: def _getDamagePerKey(self, src, time): # Damage inflicted makes no sense without time specified if time is None: raise ValueError return self._getTimeCacheDataPoint(src=src, time=time) def _prepareTimeCache(self, src, maxTime): self.graph._timeCache.prepareDmgData(src=src, maxTime=maxTime) def _getTimeCacheData(self, src): return self.graph._timeCache.getDmgData(src=src) def _getTimeCacheDataPoint(self, src, time): return self.graph._timeCache.getDmgDataPoint(src=src, time=time) # X mixins class XDistanceMixin(SmoothPointGetter): _baseResolution = 50 _extraDepth = 2 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']) applyProjected = GraphSettings.getInstance().get('applyProjected') return { 'applyProjected': applyProjected, 'srcScramRange': getScramRange(src=src) if applyProjected else None, 'tgtScrammables': getScrammables(tgt=tgt) if applyProjected else (), 'dmgMap': self._getDamagePerKey(src=src, time=miscParams['time']), 'tgtResists': tgt.getResists(), 'tgtFullHp': tgt.getFullHp()} def _calculatePoint(self, x, miscParams, src, tgt, commonData): distance = x tgtSpeed = miscParams['tgtSpeed'] tgtSigRadius = tgt.getSigRadius() if commonData['applyProjected']: webMods, tpMods = self.graph._projectedCache.getProjModData(src) webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src) webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src) tgtSpeed = getTackledSpeed( src=src, tgt=tgt, currentUntackledSpeed=tgtSpeed, srcScramRange=commonData['srcScramRange'], tgtScrammables=commonData['tgtScrammables'], webMods=webMods, webDrones=webDrones, webFighters=webFighters, distance=distance) tgtSigRadius = tgtSigRadius * getSigRadiusMult( src=src, tgt=tgt, tgtSpeed=tgtSpeed, srcScramRange=commonData['srcScramRange'], tgtScrammables=commonData['tgtScrammables'], tpMods=tpMods, tpDrones=tpDrones, tpFighters=tpFighters, distance=distance) applicationMap = getApplicationPerKey( src=src, tgt=tgt, atkSpeed=miscParams['atkSpeed'], atkAngle=miscParams['atkAngle'], distance=distance, tgtSpeed=tgtSpeed, tgtAngle=miscParams['tgtAngle'], tgtSigRadius=tgtSigRadius) y = applyDamage( dmgMap=commonData['dmgMap'], applicationMap=applicationMap, tgtResists=commonData['tgtResists'], tgtFullHp=commonData['tgtFullHp']).total return y class XTimeMixin(PointGetter): def _prepareApplicationMap(self, miscParams, src, tgt): tgtSpeed = miscParams['tgtSpeed'] tgtSigRadius = tgt.getSigRadius() if GraphSettings.getInstance().get('applyProjected'): srcScramRange = getScramRange(src=src) tgtScrammables = getScrammables(tgt=tgt) webMods, tpMods = self.graph._projectedCache.getProjModData(src) webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src) webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src) tgtSpeed = getTackledSpeed( src=src, tgt=tgt, currentUntackledSpeed=tgtSpeed, srcScramRange=srcScramRange, tgtScrammables=tgtScrammables, webMods=webMods, webDrones=webDrones, webFighters=webFighters, distance=miscParams['distance']) tgtSigRadius = tgtSigRadius * getSigRadiusMult( src=src, tgt=tgt, tgtSpeed=tgtSpeed, srcScramRange=srcScramRange, tgtScrammables=tgtScrammables, 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 getRange(self, xRange, miscParams, src, tgt): xs = [] ys = [] minTime, maxTime = xRange # Prepare time cache and various shared data self._prepareTimeCache(src=src, maxTime=maxTime) timeCache = self._getTimeCacheData(src=src) applicationMap = self._prepareApplicationMap(miscParams=miscParams, src=src, tgt=tgt) # Custom iteration for time graph to show all data points currentDmg = None currentTime = None for currentTime in sorted(timeCache): prevDmg = currentDmg currentDmgData = timeCache[currentTime] currentDmg = applyDamage( dmgMap=currentDmgData, applicationMap=applicationMap, tgtResists=tgt.getResists(), tgtFullHp=tgt.getFullHp()).total if currentTime < minTime: continue # First set of data points if not xs: # Start at exactly requested time, at last known value initialDmg = prevDmg or 0 xs.append(minTime) ys.append(initialDmg) # If current time is bigger then starting, extend plot to that time with old value if currentTime > minTime: xs.append(currentTime) ys.append(initialDmg) # If new value is different, extend it with new point to the new value if currentDmg != prevDmg: xs.append(currentTime) ys.append(currentDmg) continue # Last data point if currentTime >= maxTime: xs.append(maxTime) ys.append(prevDmg) break # Anything in-between if currentDmg != prevDmg: if prevDmg is not None: xs.append(currentTime) ys.append(prevDmg) xs.append(currentTime) ys.append(currentDmg) # Special case - there are no damage dealers if currentDmg is None and currentTime is None: xs.append(minTime) ys.append(0) # Make sure that last data point is always at max time if maxTime > (currentTime or 0): xs.append(maxTime) ys.append(currentDmg or 0) return xs, ys def getPoint(self, x, miscParams, src, tgt): time = x # Prepare time cache and various data self._prepareTimeCache(src=src, maxTime=time) dmgData = self._getTimeCacheDataPoint(src=src, time=time) applicationMap = self._prepareApplicationMap(miscParams=miscParams, src=src, tgt=tgt) y = applyDamage( dmgMap=dmgData, applicationMap=applicationMap, tgtResists=tgt.getResists(), tgtFullHp=tgt.getFullHp()).total return y class XTgtSpeedMixin(SmoothPointGetter): _baseResolution = 50 _extraDepth = 2 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(), 'tgtFullHp': tgt.getFullHp()} def _calculatePoint(self, x, miscParams, src, tgt, commonData): tgtSpeed = x tgtSigRadius = tgt.getSigRadius() if commonData['applyProjected']: srcScramRange = getScramRange(src=src) tgtScrammables = getScrammables(tgt=tgt) webMods, tpMods = self.graph._projectedCache.getProjModData(src) webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src) webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src) tgtSpeed = getTackledSpeed( src=src, tgt=tgt, currentUntackledSpeed=tgtSpeed, srcScramRange=srcScramRange, tgtScrammables=tgtScrammables, webMods=webMods, webDrones=webDrones, webFighters=webFighters, distance=miscParams['distance']) tgtSigRadius = tgtSigRadius * getSigRadiusMult( src=src, tgt=tgt, tgtSpeed=tgtSpeed, srcScramRange=srcScramRange, tgtScrammables=tgtScrammables, tpMods=tpMods, tpDrones=tpDrones, tpFighters=tpFighters, distance=miscParams['distance']) applicationMap = getApplicationPerKey( src=src, tgt=tgt, atkSpeed=miscParams['atkSpeed'], atkAngle=miscParams['atkAngle'], distance=miscParams['distance'], tgtSpeed=tgtSpeed, tgtAngle=miscParams['tgtAngle'], tgtSigRadius=tgtSigRadius) y = applyDamage( dmgMap=commonData['dmgMap'], applicationMap=applicationMap, tgtResists=commonData['tgtResists'], tgtFullHp=commonData['tgtFullHp']).total return y class XTgtSigRadiusMixin(SmoothPointGetter): _baseResolution = 50 _extraDepth = 2 def _getCommonData(self, miscParams, src, tgt): tgtSpeed = miscParams['tgtSpeed'] tgtSigMult = 1 if GraphSettings.getInstance().get('applyProjected'): srcScramRange = getScramRange(src=src) tgtScrammables = getScrammables(tgt=tgt) webMods, tpMods = self.graph._projectedCache.getProjModData(src) webDrones, tpDrones = self.graph._projectedCache.getProjDroneData(src) webFighters, tpFighters = self.graph._projectedCache.getProjFighterData(src) tgtSpeed = getTackledSpeed( src=src, tgt=tgt, currentUntackledSpeed=tgtSpeed, srcScramRange=srcScramRange, tgtScrammables=tgtScrammables, webMods=webMods, webDrones=webDrones, webFighters=webFighters, distance=miscParams['distance']) tgtSigMult = getSigRadiusMult( src=src, tgt=tgt, tgtSpeed=tgtSpeed, srcScramRange=srcScramRange, tgtScrammables=tgtScrammables, 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(), 'tgtFullHp': tgt.getFullHp()} def _calculatePoint(self, x, miscParams, src, tgt, commonData): tgtSigRadius = x applicationMap = getApplicationPerKey( src=src, tgt=tgt, atkSpeed=miscParams['atkSpeed'], atkAngle=miscParams['atkAngle'], distance=miscParams['distance'], tgtSpeed=commonData['tgtSpeed'], tgtAngle=miscParams['tgtAngle'], tgtSigRadius=tgtSigRadius * commonData['tgtSigMult']) y = applyDamage( dmgMap=commonData['dmgMap'], applicationMap=applicationMap, tgtResists=commonData['tgtResists'], tgtFullHp=commonData['tgtFullHp']).total return y # Final getters class Distance2DpsGetter(XDistanceMixin, YDpsMixin): pass class Distance2VolleyGetter(XDistanceMixin, YVolleyMixin): pass class Distance2InflictedDamageGetter(XDistanceMixin, YInflictedDamageMixin): pass class Time2DpsGetter(XTimeMixin, YDpsMixin): pass class Time2VolleyGetter(XTimeMixin, YVolleyMixin): pass class Time2InflictedDamageGetter(XTimeMixin, YInflictedDamageMixin): pass class TgtSpeed2DpsGetter(XTgtSpeedMixin, YDpsMixin): pass class TgtSpeed2VolleyGetter(XTgtSpeedMixin, YVolleyMixin): pass class TgtSpeed2InflictedDamageGetter(XTgtSpeedMixin, YInflictedDamageMixin): pass class TgtSigRadius2DpsGetter(XTgtSigRadiusMixin, YDpsMixin): pass class TgtSigRadius2VolleyGetter(XTgtSigRadiusMixin, YVolleyMixin): pass class TgtSigRadius2InflictedDamageGetter(XTgtSigRadiusMixin, YInflictedDamageMixin): pass