def _prepareDpsVolleyData(self, src, maxTime): # Time is none means that time parameter has to be ignored, # we do not need cache for that if maxTime is None: return True self._generateInternalForm(src=src, maxTime=maxTime) fitCache = self._data[src.item.ID] # Final cache has been generated already, don't do anything if 'finalDps' in fitCache and 'finalVolley' in fitCache: return # Convert cache from segments with assigned values into points # which are located at times when dps/volley values change pointCache = {} for key, dmgList in fitCache['internalDpsVolley'].items(): pointData = pointCache[key] = {} prevDps = None prevVolley = None prevTimeEnd = None for timeStart, timeEnd, dps, volley in dmgList: # First item if not pointData: pointData[timeStart] = (dps, volley) # Gap between items elif floatUnerr(prevTimeEnd) < floatUnerr(timeStart): pointData[prevTimeEnd] = (DmgTypes(0, 0, 0, 0), DmgTypes(0, 0, 0, 0)) pointData[timeStart] = (dps, volley) # Changed value elif dps != prevDps or volley != prevVolley: pointData[timeStart] = (dps, volley) prevDps = dps prevVolley = volley prevTimeEnd = timeEnd # We have data in another form, do not need old one any longer del fitCache['internalDpsVolley'] changesByTime = {} for key, dmgMap in pointCache.items(): for time in dmgMap: changesByTime.setdefault(time, []).append(key) # Here we convert cache to following format: # {time: {key: (dps, volley}} finalDpsCache = fitCache['finalDps'] = {} finalVolleyCache = fitCache['finalVolley'] = {} timeDpsData = {} timeVolleyData = {} for time in sorted(changesByTime): timeDpsData = copy(timeDpsData) timeVolleyData = copy(timeVolleyData) for key in changesByTime[time]: dps, volley = pointCache[key][time] timeDpsData[key] = dps timeVolleyData[key] = volley finalDpsCache[time] = timeDpsData finalVolleyCache[time] = timeVolleyData
def getDps(self, targetResists=None, cycleTimeOverride=None): volley = self.getVolley(targetResists=targetResists) if not volley: return DmgTypes(0, 0, 0, 0) cycleTime = cycleTimeOverride if cycleTimeOverride is not None else self.cycleTime dpsFactor = 1 / (cycleTime / 1000) dps = DmgTypes(em=volley.em * dpsFactor, thermal=volley.thermal * dpsFactor, kinetic=volley.kinetic * dpsFactor, explosive=volley.explosive * dpsFactor) return dps
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 getDps(self, targetResists=None): volley = self.getVolley(targetResists=targetResists) if not volley: return DmgTypes(0, 0, 0, 0) cycleAttr = "missileLaunchDuration" if self.hasAmmo else "speed" cycleTime = self.getModifiedItemAttr(cycleAttr) dpsFactor = 1 / (cycleTime / 1000) dps = DmgTypes(em=volley.em * dpsFactor, thermal=volley.thermal * dpsFactor, kinetic=volley.kinetic * dpsFactor, explosive=volley.explosive * dpsFactor) return dps
def getDps(self, targetProfile=None): volley = self.getVolley(targetProfile=targetProfile) if not volley: return DmgTypes(0, 0, 0, 0) cycleParams = self.getCycleParameters() if cycleParams is None: return DmgTypes(0, 0, 0, 0) dpsFactor = 1 / (cycleParams.averageTime / 1000) dps = DmgTypes(em=volley.em * dpsFactor, thermal=volley.thermal * dpsFactor, kinetic=volley.kinetic * dpsFactor, explosive=volley.explosive * dpsFactor) return dps
def getDps(self, spoolOptions=None, targetResists=None, ignoreState=False): volley = self.getVolley(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState) if not volley: return DmgTypes(0, 0, 0, 0) # Some weapons repeat multiple times in one cycle (bosonic doomsdays). Get the number of times it fires off volleysPerCycle = max(self.getModifiedItemAttr("doomsdayDamageDuration", 1) / self.getModifiedItemAttr("doomsdayDamageCycleTime", 1), 1) dpsFactor = volleysPerCycle / (self.cycleTime / 1000) dps = DmgTypes( em=volley.em * dpsFactor, thermal=volley.thermal * dpsFactor, kinetic=volley.kinetic * dpsFactor, explosive=volley.explosive * dpsFactor) return dps
def getDpsPerEffect(self, targetResists=None): if not self.active or self.amountActive <= 0: return {} uptime = self.getUptime() if uptime == 1: return { a.effectID: a.getDps(targetResists=targetResists) for a in self.abilities } # Decide if it's better to keep steady dps up and never reload or reload from time to time dpsMapSteady = {} dpsMapPeakAdjusted = {} for ability in self.abilities: abilityDps = ability.getDps(targetResists=targetResists) dpsMapPeakAdjusted[ability.effectID] = DmgTypes( em=abilityDps.em * uptime, thermal=abilityDps.thermal * uptime, kinetic=abilityDps.kinetic * uptime, explosive=abilityDps.explosive * uptime) # Infinite use - add to steady dps if ability.numShots == 0: dpsMapSteady[ability.effectID] = abilityDps totalSteady = sum(i.total for i in dpsMapSteady.values()) totalPeakAdjusted = sum(i.total for i in dpsMapPeakAdjusted.values()) return dpsMapSteady if totalSteady >= totalPeakAdjusted else dpsMapPeakAdjusted
def getVolleyParameters(self, spoolOptions=None, targetProfile=None, ignoreState=False): if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState): return {0: DmgTypes(0, 0, 0, 0)} if self.__baseVolley is None: self.__baseVolley = {} dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr dmgMult = self.getModifiedItemAttr("damageMultiplier", 1) dmgDelay = self.getModifiedItemAttr( "damageDelayDuration", 0) or self.getModifiedItemAttr( "doomsdayWarningDuration", 0) dmgDuration = self.getModifiedItemAttr("doomsdayDamageDuration", 0) dmgSubcycle = self.getModifiedItemAttr("doomsdayDamageCycleTime", 0) # Reaper DD can damage each target only once if dmgDuration != 0 and dmgSubcycle != 0 and 'doomsdaySlash' not in self.item.effects: subcycles = math.floor(floatUnerr(dmgDuration / dmgSubcycle)) else: subcycles = 1 for i in range(subcycles): self.__baseVolley[dmgDelay + dmgSubcycle * i] = DmgTypes( em=(dmgGetter("emDamage", 0)) * dmgMult, thermal=(dmgGetter("thermalDamage", 0)) * dmgMult, kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult, explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult) spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self) spoolBoost = calculateSpoolup( self.getModifiedItemAttr("damageMultiplierBonusMax", 0), self.getModifiedItemAttr("damageMultiplierBonusPerCycle", 0), self.rawCycleTime / 1000, spoolType, spoolAmount)[0] spoolMultiplier = 1 + spoolBoost adjustedVolley = {} for volleyTime, volleyValue in self.__baseVolley.items(): adjustedVolley[volleyTime] = DmgTypes( em=volleyValue.em * spoolMultiplier * (1 - getattr(targetProfile, "emAmount", 0)), thermal=volleyValue.thermal * spoolMultiplier * (1 - getattr(targetProfile, "thermalAmount", 0)), kinetic=volleyValue.kinetic * spoolMultiplier * (1 - getattr(targetProfile, "kineticAmount", 0)), explosive=volleyValue.explosive * spoolMultiplier * (1 - getattr(targetProfile, "explosiveAmount", 0))) return adjustedVolley
def getVolleyParameters(self, targetResists=None): if not self.dealsDamage or self.amountActive <= 0: return {0: DmgTypes(0, 0, 0, 0)} if self.__baseVolley is None: dmgGetter = self.getModifiedChargeAttr if self.hasAmmo else self.getModifiedItemAttr dmgMult = self.amountActive * (self.getModifiedItemAttr("damageMultiplier", 1)) self.__baseVolley = DmgTypes( em=(dmgGetter("emDamage", 0)) * dmgMult, thermal=(dmgGetter("thermalDamage", 0)) * dmgMult, kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult, explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult) volley = DmgTypes( em=self.__baseVolley.em * (1 - getattr(targetResists, "emAmount", 0)), thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)), kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)), explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0))) return {0: volley}
def getDps(self, spoolOptions=None, targetProfile=None, ignoreState=False): dmgDuringCycle = DmgTypes(0, 0, 0, 0) cycleParams = self.getCycleParameters() if cycleParams is None: return dmgDuringCycle volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetProfile=targetProfile, ignoreState=ignoreState) avgCycleTime = cycleParams.averageTime if len(volleyParams) == 0 or avgCycleTime == 0: return dmgDuringCycle for volleyValue in volleyParams.values(): dmgDuringCycle += volleyValue dpsFactor = 1 / (avgCycleTime / 1000) dps = DmgTypes( em=dmgDuringCycle.em * dpsFactor, thermal=dmgDuringCycle.thermal * dpsFactor, kinetic=dmgDuringCycle.kinetic * dpsFactor, explosive=dmgDuringCycle.explosive * dpsFactor) return dps
def getVolley(self, spoolOptions=None, targetProfile=None, ignoreState=False): volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetProfile=targetProfile, ignoreState=ignoreState) if len(volleyParams) == 0: return DmgTypes(0, 0, 0, 0) return volleyParams[min(volleyParams)]
def getVolley(self, targetProfile=None): if not self.dealsDamage or not self.active: return DmgTypes(0, 0, 0, 0) if self.attrPrefix == "fighterAbilityLaunchBomb": em = self.fighter.getModifiedChargeAttr("emDamage", 0) therm = self.fighter.getModifiedChargeAttr("thermalDamage", 0) kin = self.fighter.getModifiedChargeAttr("kineticDamage", 0) exp = self.fighter.getModifiedChargeAttr("explosiveDamage", 0) else: em = self.fighter.getModifiedItemAttr("{}DamageEM".format(self.attrPrefix), 0) therm = self.fighter.getModifiedItemAttr("{}DamageTherm".format(self.attrPrefix), 0) kin = self.fighter.getModifiedItemAttr("{}DamageKin".format(self.attrPrefix), 0) exp = self.fighter.getModifiedItemAttr("{}DamageExp".format(self.attrPrefix), 0) dmgMult = self.fighter.amountActive * self.fighter.getModifiedItemAttr("{}DamageMultiplier".format(self.attrPrefix), 1) volley = DmgTypes( em=em * dmgMult * (1 - getattr(targetProfile, "emAmount", 0)), thermal=therm * dmgMult * (1 - getattr(targetProfile, "thermalAmount", 0)), kinetic=kin * dmgMult * (1 - getattr(targetProfile, "kineticAmount", 0)), explosive=exp * dmgMult * (1 - getattr(targetProfile, "explosiveAmount", 0))) return volley
def getDps(self, targetProfile=None): em = 0 thermal = 0 kinetic = 0 explosive = 0 for dps in self.getDpsPerEffect(targetProfile=targetProfile).values(): em += dps.em thermal += dps.thermal kinetic += dps.kinetic explosive += dps.explosive return DmgTypes(em=em, thermal=thermal, kinetic=kinetic, explosive=explosive)
def getVolley(self, targetProfile=None): volleyParams = self.getVolleyParametersPerEffect(targetProfile=targetProfile) em = 0 therm = 0 kin = 0 exp = 0 for volleyData in volleyParams.values(): em += volleyData[0].em therm += volleyData[0].thermal kin += volleyData[0].kinetic exp += volleyData[0].explosive return DmgTypes(em, therm, kin, exp)
def addDpsVolley(ddKey, addedTimeStart, addedTimeFinish, addedVolleys): if not addedVolleys: return volleySum = sum(addedVolleys, DmgTypes(0, 0, 0, 0)) if volleySum.total > 0: addedDps = volleySum / (addedTimeFinish - addedTimeStart) # We can take "just best" volley, no matter target resistances, because all # known items have the same damage type ratio throughout their cycle - and # applying resistances doesn't change final outcome bestVolley = max(addedVolleys, key=lambda v: v.total) ddCacheDps = intCacheDpsVolley.setdefault(ddKey, []) ddCacheDps.append( (addedTimeStart, addedTimeFinish, addedDps, bestVolley))
def getVolley(self, targetResists=None): if not self.active or self.amountActive <= 0: return DmgTypes(0, 0, 0, 0) if self.__baseVolley is None: em = 0 therm = 0 kin = 0 exp = 0 for ability in self.abilities: # Not passing resists here as we want to calculate and store base volley abilityVolley = ability.getVolley() em += abilityVolley.em therm += abilityVolley.thermal kin += abilityVolley.kinetic exp += abilityVolley.explosive self.__baseVolley = DmgTypes(em, therm, kin, exp) volley = DmgTypes( em=self.__baseVolley.em * (1 - getattr(targetResists, "emAmount", 0)), thermal=self.__baseVolley.thermal * (1 - getattr(targetResists, "thermalAmount", 0)), kinetic=self.__baseVolley.kinetic * (1 - getattr(targetResists, "kineticAmount", 0)), explosive=self.__baseVolley.explosive * (1 - getattr(targetResists, "explosiveAmount", 0))) return volley
def getVolley(self, spoolOptions=None, targetResists=None, ignoreState=False): if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState): return DmgTypes(0, 0, 0, 0) if self.__baseVolley is None: dmgGetter = self.getModifiedChargeAttr if self.charge else self.getModifiedItemAttr dmgMult = self.getModifiedItemAttr("damageMultiplier", 1) self.__baseVolley = DmgTypes( em=(dmgGetter("emDamage", 0)) * dmgMult, thermal=(dmgGetter("thermalDamage", 0)) * dmgMult, kinetic=(dmgGetter("kineticDamage", 0)) * dmgMult, explosive=(dmgGetter("explosiveDamage", 0)) * dmgMult) spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self) spoolBoost = calculateSpoolup( self.getModifiedItemAttr("damageMultiplierBonusMax", 0), self.getModifiedItemAttr("damageMultiplierBonusPerCycle", 0), self.rawCycleTime / 1000, spoolType, spoolAmount)[0] spoolMultiplier = 1 + spoolBoost volley = DmgTypes( em=self.__baseVolley.em * spoolMultiplier * (1 - getattr(targetResists, "emAmount", 0)), thermal=self.__baseVolley.thermal * spoolMultiplier * (1 - getattr(targetResists, "thermalAmount", 0)), kinetic=self.__baseVolley.kinetic * spoolMultiplier * (1 - getattr(targetResists, "kineticAmount", 0)), explosive=self.__baseVolley.explosive * spoolMultiplier * (1 - getattr(targetResists, "explosiveAmount", 0))) return volley
def getVolleyParametersPerEffect(self, targetProfile=None): if not self.active or self.amountActive <= 0: return {} if self.__baseVolley is None: self.__baseVolley = {} for ability in self.abilities: # Not passing resists here as we want to calculate and store base volley self.__baseVolley[ability.effectID] = {0: ability.getVolley()} adjustedVolley = {} for effectID, effectData in self.__baseVolley.items(): adjustedVolley[effectID] = {} for volleyTime, volleyValue in effectData.items(): adjustedVolley[effectID][volleyTime] = DmgTypes( em=volleyValue.em * (1 - getattr(targetProfile, "emAmount", 0)), thermal=volleyValue.thermal * (1 - getattr(targetProfile, "thermalAmount", 0)), kinetic=volleyValue.kinetic * (1 - getattr(targetProfile, "kineticAmount", 0)), explosive=volleyValue.explosive * (1 - getattr(targetProfile, "explosiveAmount", 0))) return adjustedVolley
def setup_damage_types(): return DmgTypes(10, 20, 30, 40)