diff --git a/eos/saveddata/module.py b/eos/saveddata/module.py index a08ed235a..e6e1bb1bb 100644 --- a/eos/saveddata/module.py +++ b/eos/saveddata/module.py @@ -28,6 +28,8 @@ from eos.enum import Enum from eos.modifiedAttributeDict import ChargeAttrShortcut, ItemAttrShortcut, ModifiedAttributeDict from eos.saveddata.citadel import Citadel from eos.saveddata.mutator import Mutator +from eos.utils.spoolSupport import SpoolType, calculateSpoolup +from eos.utils.float import floatUnerr pyfalog = Logger(__name__) @@ -169,9 +171,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): self.__charge = None self.__dps = None - self.__dpsSpool = None + self.__dpsSpoolZero = None + self.__dpsSpoolFull = None self.__volley = None - self.__volleySpool = None + self.__volleySpoolZero = None + self.__volleySpoolFull = None self.__miningyield = None self.__reloadTime = None self.__reloadForce = None @@ -249,8 +253,8 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): if chargeVolume is None or containerCapacity is None: charges = 0 else: - charges = floor(containerCapacity / chargeVolume) - return int(charges) + charges = int(floatUnerr(containerCapacity / chargeVolume)) + return charges @property def numShots(self): @@ -416,9 +420,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): def damageStats(self, targetResists): if self.__dps is None: self.__dps = 0 - self.__dpsSpool = 0 + self.__dpsSpoolZero = 0 + self.__dpsSpoolFull = 0 self.__volley = 0 - self.__volleySpool = 0 + self.__volleySpoolZero = 0 + self.__volleySpoolFull = 0 if not self.isEmpty and self.state >= State.ACTIVE: if self.charge: @@ -426,12 +432,24 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): else: func = self.getModifiedItemAttr - volley = sum([(func("%sDamage" % attr) or 0) * (1 - getattr(targetResists, "%sAmount" % attr, 0)) for attr in self.DAMAGE_TYPES]) - volley *= self.getModifiedItemAttr("damageMultiplier") or 1 - # Disintegrator-specific ramp-up multiplier - volleySpool = volley * ((self.getModifiedItemAttr("damageMultiplierBonusMax") or 0) + 1) - if volley: - cycleTime = self.cycleTime + cycleTime = self.cycleTime + + # Base volley + volleyBase = sum([(func("%sDamage" % attr) or 0) * (1 - getattr(targetResists, "%sAmount" % attr, 0)) for attr in self.DAMAGE_TYPES]) + volleyBase *= self.getModifiedItemAttr("damageMultiplier") or 1 + spoolMultMax = self.getModifiedItemAttr("damageMultiplierBonusMax") or 0 + spoolMultPerCycle = self.getModifiedItemAttr("damageMultiplierBonusPerCycle") or 0 + # Volley affected by module-specific spoolup + if spoolMultMax: + volley = volleyBase * (1 + calculateSpoolup(spoolMultMax, spoolMultPerCycle, cycleTime, self.spoolType, self.spoolAmount)) + else: + volley = volleyBase + # Full spoolup + if spoolMultMax: + volleySpoolFull = volleyBase * (1 + calculateSpoolup(spoolMultMax, spoolMultPerCycle, cycleTime, SpoolType.SCALE, 1)) + else: + volleySpoolFull = volleyBase + if volleyBase: # Some weapons repeat multiple times in one cycle (think doomsdays) # Get the number of times it fires off weaponDoT = max( @@ -440,12 +458,12 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): ) self.__volley = volley - self.__volleySpool = volleySpool + self.__volleySpoolFull = volleySpoolFull dpsFactor = weaponDoT / (cycleTime / 1000.0) self.__dps = volley * dpsFactor - self.__dpsSpool = volleySpool * dpsFactor + self.__dpsSpoolFull = volleySpoolFull * dpsFactor - return self.__dps, self.__dpsSpool, self.__volley, self.__volleySpool + return self.__dps, self.__dpsSpoolFull, self.__volley, self.__volleySpoolFull @property def miningStats(self): @@ -730,6 +748,11 @@ class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut): def clear(self): self.__dps = None + self.__dpsSpoolZero = None + self.__dpsSpoolFull = None + self.__volley = None + self.__volleySpoolZero = None + self.__volleySpoolFull = None self.__miningyield = None self.__volley = None self.__reloadTime = None diff --git a/eos/utils/__init__.py b/eos/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/eos/utils/float.py b/eos/utils/float.py new file mode 100644 index 000000000..b7bd6cc61 --- /dev/null +++ b/eos/utils/float.py @@ -0,0 +1,26 @@ +""" +Sometimes use of floats may lead to undesirable results, e.g. + int(2.3 / 0.1) = 22. +We cannot afford to use different number representations (e.g. representations +provided by decimal or fraction modules), thus consequences are worked around by +this module. +""" + +import math +import sys + + +# As we will be rounding numbers after operations (which introduce higher error +# than base float representation error), we need to keep less significant +# numbers than for single float number w/o operations +keepDigits = int(sys.float_info.dig / 2) + + +def floatUnerr(value): + """Round possible float number error, killing some precision in process.""" + if value == 0: + return value + # Find round factor, taking into consideration that we want to keep at least + # predefined amount of significant digits + roundFactor = int(keepDigits - math.ceil(math.log10(abs(value)))) + return round(value, roundFactor) diff --git a/eos/utils/spoolSupport.py b/eos/utils/spoolSupport.py new file mode 100644 index 000000000..48d1c937d --- /dev/null +++ b/eos/utils/spoolSupport.py @@ -0,0 +1,43 @@ +# =============================================================================== +# 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 enum import IntEnum, unique + +from eos.utils.float import floatUnerr + + +@unique +class SpoolType(IntEnum): + SCALE = 0 # [0..1] + TIME = 1 # Expressed via time in seconds since spoolup started + CYCLES = 2 # Expressed in amount of cycles since spoolup started + + +def calculateSpoolup(modMaxValue, modStepValue, modCycleTime, spoolType, spoolAmount): + if spoolType == SpoolType.SCALE: + return (floatUnerr(spoolAmount * modMaxValue) // modStepValue) * modStepValue + elif spoolType == SpoolType.TIME: + # Stub + return 0 + elif spoolType == SpoolType.CYCLES: + cycles = round(spoolAmount) + return min(modMaxValue, cycles * modStepValue) + else: + return 0