Exemplo n.º 1
0
 def build(self):
     from eos import db
     self.__extraDrains = []
     self.__ehp = None
     self.__weaponDPS = None
     self.__minerYield = None
     self.__weaponVolley = None
     self.__droneDPS = None
     self.__droneVolley = None
     self.__droneYield = None
     self.__sustainableTank = None
     self.__effectiveSustainableTank = None
     self.__effectiveTank = None
     self.__calculated = False
     self.__capStable = None
     self.__capState = None
     self.__capUsed = None
     self.__capRecharge = None
     self.__calculatedTargets = []
     self.factorReload = False
     self.fleet = None
     self.boostsFits = set()
     self.gangBoosts = None
     self.ecmProjectedStr = 1
     self.extraAttributes = ModifiedAttributeDict(self)
     self.extraAttributes.original = self.EXTRA_ATTRIBUTES
     self.ship = Ship(db.getItem(
         self.shipID)) if self.shipID is not None else None
     if self.ship is not None:
         self.mode = self.ship.checkModeItem(
             db.getItem(self.modeID) if self.modeID else None)
     else:
         self.mode = None
Exemplo n.º 2
0
    def build(self):
        """ Build object. Assumes proper and valid item already set """
        self.__charge = None
        self.__dps = None
        self.__volley = None
        self.__miningyield = None
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__chargeModifiedAttributes = ModifiedAttributeDict()

        if len(self.abilities) != len(self.item.effects):
            self.__abilities = []
            for ability in self.__getAbilities():
                self.__abilities.append(ability)

        if self.__item:
            self.__itemModifiedAttributes.original = self.__item.attributes
            self.__itemModifiedAttributes.overrides = self.__item.overrides
            self.__slot = self.__calculateSlot(self.__item)

            chargeID = self.getModifiedItemAttr("fighterAbilityLaunchBombType")
            if chargeID:
                charge = eos.db.getItem(int(chargeID))
                self.__charge = charge
                self.__chargeModifiedAttributes.original = charge.attributes
                self.__chargeModifiedAttributes.overrides = charge.overrides
Exemplo n.º 3
0
 def build(self):
     self.__extraDrains = []
     self.__ehp = None
     self.__weaponDPS = None
     self.__minerYield = None
     self.__weaponVolley = None
     self.__droneDPS = None
     self.__droneVolley = None
     self.__droneYield = None
     self.__sustainableTank = None
     self.__effectiveSustainableTank = None
     self.__effectiveTank = None
     self.__calculated = False
     self.__capStable = None
     self.__capState = None
     self.__capUsed = None
     self.__capRecharge = None
     self.__calculatedTargets = []
     self.factorReload = False
     self.fleet = None
     self.boostsFits = set()
     self.gangBoosts = None
     self.ecmProjectedStr = 1
     self.extraAttributes = ModifiedAttributeDict(self)
     self.extraAttributes.original = self.EXTRA_ATTRIBUTES
Exemplo n.º 4
0
    def build(self):
        """ Builds internal module variables from both init's """

        if self.__charge and self.__charge.category.name != "Charge":
            self.__charge = None

        self.__dps = None
        self.__miningyield = None
        self.__volley = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.__hardpoint = Hardpoint.NONE
        self.__itemModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__chargeModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__slot = self.dummySlot  # defaults to None

        if self.__item:
            self.__itemModifiedAttributes.original = self.__item.attributes
            self.__itemModifiedAttributes.overrides = self.__item.overrides
            self.__hardpoint = self.__calculateHardpoint(self.__item)
            self.__slot = self.__calculateSlot(self.__item)
        if self.__charge:
            self.__chargeModifiedAttributes.original = self.__charge.attributes
            self.__chargeModifiedAttributes.overrides = self.__charge.overrides
Exemplo n.º 5
0
 def __fetchItemInfo(self):
     import eos.db
     item = eos.db.getItem(self.itemID)
     self.__item = item
     self.__itemModifiedAttributes = ModifiedAttributeDict()
     self.__itemModifiedAttributes.original = item.attributes
     self.__hardpoint = self.__calculateHardpoint(item)
     self.__slot = self.__calculateSlot(item)
Exemplo n.º 6
0
    def buildRack(cls, slot):
        empty = Rack(None)
        empty.__slot = slot
        empty.__hardpoint = Hardpoint.NONE
        empty.__item = 0
        empty.__charge = 0
        empty.dummySlot = slot
        empty.__itemModifiedAttributes = ModifiedAttributeDict()
        empty.__chargeModifiedAttributes = ModifiedAttributeDict()

        return empty
Exemplo n.º 7
0
    def build(self):
        """ Build object. Assumes proper and valid item already set """
        self.__charge = None
        self.__dps = None
        self.__volley = None
        self.__miningyield = None
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__itemModifiedAttributes.original = self.__item.attributes

        self.__chargeModifiedAttributes = ModifiedAttributeDict()
        chargeID = self.getModifiedItemAttr("entityMissileTypeID")
        if chargeID is not None:
            charge = eos.db.getItem(int(chargeID))
            self.__charge = charge
            self.__chargeModifiedAttributes.original = charge.attributes
Exemplo n.º 8
0
Arquivo: fit.py Projeto: mickoes/Pyfa
 def build(self):
     from eos import db
     self.__extraDrains = []
     self.__ehp = None
     self.__weaponDPS = None
     self.__minerYield = None
     self.__weaponVolley = None
     self.__droneDPS = None
     self.__droneVolley = None
     self.__droneYield = None
     self.__sustainableTank = None
     self.__effectiveSustainableTank = None
     self.__effectiveTank = None
     self.__calculated = False
     self.__capStable = None
     self.__capState = None
     self.__capUsed = None
     self.__capRecharge = None
     self.__calculatedTargets = []
     self.factorReload = False
     self.fleet = None
     self.boostsFits = set()
     self.gangBoosts = None
     self.ecmProjectedStr = 1
     self.extraAttributes = ModifiedAttributeDict(self)
     self.extraAttributes.original = self.EXTRA_ATTRIBUTES
     self.ship = Ship(db.getItem(self.shipID)) if self.shipID is not None else None
     if self.ship is not None:
         self.mode = self.ship.checkModeItem(db.getItem(self.modeID) if self.modeID else None)
     else:
         self.mode = None
Exemplo n.º 9
0
 def build(self):
     self.__extraDrains = []
     self.__ehp = None
     self.__weaponDPS = None
     self.__minerYield = None
     self.__weaponVolley = None
     self.__droneDPS = None
     self.__droneVolley = None
     self.__droneYield = None
     self.__sustainableTank = None
     self.__effectiveSustainableTank = None
     self.__effectiveTank = None
     self.__calculated = False
     self.__capStable = None
     self.__capState = None
     self.__capUsed = None
     self.__capRecharge = None
     self.__calculatedTargets = []
     self.factorReload = False
     self.fleet = None
     self.boostsFits = set()
     self.gangBoosts = None
     self.ecmProjectedStr = 1
     self.extraAttributes = ModifiedAttributeDict(self)
     self.extraAttributes.original = self.EXTRA_ATTRIBUTES
Exemplo n.º 10
0
 def __init__(self, item):
     self.__slot = self.__calculateSlot(item)
     self.__item = item
     self.itemID = item.ID
     self.active = True
     self.__itemModifiedAttributes = ModifiedAttributeDict()
     self.__itemModifiedAttributes.original = self.item.attributes
Exemplo n.º 11
0
    def build(self):
        """ Builds internal module variables from both init's """

        if self.__charge and self.__charge.category.name != "Charge":
            self.__charge = None

        self.__dps = None
        self.__miningyield = None
        self.__volley = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.__hardpoint = Hardpoint.NONE
        self.__itemModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__chargeModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__slot = self.dummySlot  # defaults to None

        if self.__item:
            self.__itemModifiedAttributes.original = self.__item.attributes
            self.__itemModifiedAttributes.overrides = self.__item.overrides
            self.__hardpoint = self.__calculateHardpoint(self.__item)
            self.__slot = self.__calculateSlot(self.__item)
        if self.__charge:
            self.__chargeModifiedAttributes.original = self.__charge.attributes
            self.__chargeModifiedAttributes.overrides = self.__charge.overrides
Exemplo n.º 12
0
 def __init__(self, item):
     self.__item = item
     self.itemID = item.ID
     self.active = True
     self.amount = 0
     self.__itemModifiedAttributes = ModifiedAttributeDict()
     self.__itemModifiedAttributes.original = self.item.attributes
Exemplo n.º 13
0
 def __init__(self, item):
     """Initialize cargo from the program"""
     self.__item = item
     self.itemID = item.ID if item is not None else None
     self.amount = 0
     self.__itemModifiedAttributes = ModifiedAttributeDict()
     self.__itemModifiedAttributes.original = item.attributes
Exemplo n.º 14
0
 def __fetchItemInfo(self):
     import eos.db
     item = eos.db.getItem(self.itemID)
     self.__item = item
     self.__itemModifiedAttributes = ModifiedAttributeDict()
     self.__itemModifiedAttributes.original = item.attributes
     self.__hardpoint = self.__calculateHardpoint(item)
     self.__slot = self.__calculateSlot(item)
Exemplo n.º 15
0
    def build(self):
        """ Build object. Assumes proper and valid item already set """
        self.__charge = None
        self.__dps = None
        self.__volley = None
        self.__miningyield = None
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__itemModifiedAttributes.original = self.__item.attributes
        self.__itemModifiedAttributes.overrides = self.__item.overrides

        self.__chargeModifiedAttributes = ModifiedAttributeDict()
        # pheonix todo: check the attribute itself, not the modified. this will always return 0 now.
        chargeID = self.getModifiedItemAttr("entityMissileTypeID", None)
        if chargeID is not None:
            charge = eos.db.getItem(int(chargeID))
            self.__charge = charge
            self.__chargeModifiedAttributes.original = charge.attributes
            self.__chargeModifiedAttributes.overrides = charge.overrides
Exemplo n.º 16
0
def handler(fit, module, context, **kwargs):
    if "projected" in context:
        # jam formula: 1 - (1- (jammer str/ship str))^(# of jam mods with same str))
        strModifier = 1 - module.getModifiedItemAttr("scan{0}StrengthBonus".format(fit.scanType)) / fit.scanStrength

        if 'effect' in kwargs:
            strModifier *= ModifiedAttributeDict.getResistance(fit, kwargs['effect'])

        fit.ecmProjectedStr *= strModifier
def handler(fit, src, context, **kwargs):
    if "projected" in context:
        amount = src.getModifiedItemAttr("powerTransferAmount")
        duration = src.getModifiedItemAttr("duration")

        if 'effect' in kwargs:
            amount *= ModifiedAttributeDict.getResistance(fit, kwargs['effect'])

        fit.addDrain(src, duration, -amount, 0)
Exemplo n.º 18
0
def handler(fit, src, context, **kwargs):
    if "projected" in context:
        amount = src.getModifiedItemAttr("{}Amount".format(prefix)) * src.amountActive
        time = src.getModifiedItemAttr("{}Duration".format(prefix))

        if 'effect' in kwargs:
            amount *= ModifiedAttributeDict.getResistance(fit, kwargs['effect'])

        fit.addDrain(src, time, amount, 0)
Exemplo n.º 19
0
 def __fetchChargeInfo(self):
     self.__chargeModifiedAttributes = ModifiedAttributeDict()
     if self.chargeID is not None:
         import eos.db
         charge = eos.db.getItem(self.chargeID)
         self.__charge = charge
         self.__chargeModifiedAttributes.original = charge.attributes
     else:
         self.__charge = 0
Exemplo n.º 20
0
def handler(fit, module, context, **kwargs):
    if "projected" in context:
        # jam formula: 1 - (1- (jammer str/ship str))^(# of jam mods with same str))
        strModifier = 1 - module.getModifiedItemAttr("scan{0}StrengthBonus".format(fit.scanType)) / fit.scanStrength

        if 'effect' in kwargs:
            strModifier *= ModifiedAttributeDict.getResistance(fit, kwargs['effect'])

        fit.ecmProjectedStr *= strModifier
def handler(fit, src, context, **kwargs):
    if "projected" in context and ((hasattr(src, "state") and src.state >= FittingModuleState.ACTIVE) or
                                   hasattr(src, "amountActive")):
        amount = src.getModifiedItemAttr("energyNeutralizerAmount")
        time = src.getModifiedItemAttr("energyNeutralizerDuration")

        if 'effect' in kwargs:
            amount *= ModifiedAttributeDict.getResistance(fit, kwargs['effect'])

        fit.addDrain(src, time, amount, 0)
Exemplo n.º 22
0
 def __init__(self, item, owner=None):
     if item.group.name != "Ship Modifiers":
         raise ValueError(
             'Passed item "%s" (category: (%s)) is not a Ship Modifier' %
             (item.name, item.category.name))
     self.owner = owner
     self.__item = item
     self.__itemModifiedAttributes = ModifiedAttributeDict()
     self.__itemModifiedAttributes.original = self.item.attributes
     self.__itemModifiedAttributes.overrides = self.item.overrides
def handler(fit, src, context, **kwargs):
    if "projected" in context:
        amount = src.getModifiedItemAttr("powerTransferAmount")
        duration = src.getModifiedItemAttr("duration")

        if 'effect' in kwargs:
            amount *= ModifiedAttributeDict.getResistance(
                fit, kwargs['effect'])

        fit.addDrain(src, duration, -amount, 0)
Exemplo n.º 24
0
    def build(self):
        """ Build object. Assumes proper and valid item already set """
        self.__sideEffects = []
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__itemModifiedAttributes.original = self.__item.attributes
        self.__itemModifiedAttributes.overrides = self.__item.overrides
        self.__slot = self.__calculateSlot(self.__item)

        # Legacy booster side effect code, disabling as not currently implemented
        '''
def handler(fit, src, context, **kwargs):
    if "projected" in context and ((hasattr(src, "state") and src.state >= State.ACTIVE) or
                                    hasattr(src, "amountActive")):
        amount = src.getModifiedItemAttr("energyNeutralizerAmount")
        time = src.getModifiedItemAttr("energyNeutralizerDuration")

        if 'effect' in kwargs:
            amount *= ModifiedAttributeDict.getResistance(fit, kwargs['effect'])

        fit.addDrain(src, time, amount, 0)
Exemplo n.º 26
0
def handler(fit, src, context, **kwargs):
    if "projected" in context:
        amount = src.getModifiedItemAttr("{}Amount".format(prefix))
        time = src.getModifiedItemAttr("{}Duration".format(prefix))

        if 'effect' in kwargs:
            amount *= ModifiedAttributeDict.getResistance(
                fit, kwargs['effect'])

        fit.addDrain(src, time, amount, 0)
Exemplo n.º 27
0
def handler(fit, module, context, **kwargs):
    if "projected" not in context:
        return
    # jam formula: 1 - (1- (jammer str/ship str))^(# of jam mods with same str))
    strModifier = 1 - (module.getModifiedItemAttr("{}Strength{}".format(prefix, fit.scanType)) * module.amountActive) / fit.scanStrength

    if 'effect' in kwargs:
        strModifier *= ModifiedAttributeDict.getResistance(fit, kwargs['effect'])

    fit.ecmProjectedStr *= strModifier
Exemplo n.º 28
0
 def build(self):
     self.__itemModifiedAttributes = ModifiedAttributeDict()
     self.__itemModifiedAttributes.original = self.__item.attributes
     self.__sideEffects = []
     for effect in self.item.effects.itervalues():
         if effect.isType("boosterSideEffect"):
             s = SideEffect(self)
             s.effect = effect
             s.active = effect.ID in self.__activeSideEffectIDs
             self.__sideEffects.append(s)
Exemplo n.º 29
0
def handler(fit, src, context, **kwargs):
    amount = src.getModifiedItemAttr("powerTransferAmount")
    time = src.getModifiedItemAttr("duration")

    if 'effect' in kwargs:
        amount *= ModifiedAttributeDict.getResistance(fit, kwargs['effect'])

    if "projected" in context:
        fit.addDrain(src, time, amount, 0)
    elif "module" in context:
        src.itemModifiedAttributes.force("capacitorNeed", -amount)
Exemplo n.º 30
0
    def build(self):
        """ Build object. Assumes proper and valid item already set """
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__itemModifiedAttributes.original = self.__item.attributes
        self.__itemModifiedAttributes.overrides = self.__item.overrides
        self.__slot = self.__calculateSlot(self.__item)

        if len(self.sideEffects) != len(self.__getSideEffects()):
            self.__sideEffects = []
            for ability in self.__getSideEffects():
                self.__sideEffects.append(ability)
Exemplo n.º 31
0
    def __init__(self, item):

        if item.category.name != "Ship":
            raise ValueError('Passed item "%s" (category: (%s)) is not under Ship category'%(item.name, item.category.name))

        self.__item = item
        self.__modeItems = self.__getModeItems()
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__itemModifiedAttributes.original = self.item.attributes

        self.commandBonus = 0
Exemplo n.º 32
0
    def __fetchChargeInfo(self):
        chargeID = self.getModifiedItemAttr("entityMissileTypeID")
        self.__chargeModifiedAttributes = ModifiedAttributeDict()
        if chargeID is not None:
            import eos.db
            charge = eos.db.getItem(int(chargeID))
            self.__charge = charge

            self.chargeModifiedAttributes.original = charge.attributes
        else:
            self.__charge = 0
Exemplo n.º 33
0
    def __init__(self, item):
        self.__item = item if item != None else 0
        self.itemID = item.ID if item is not None else None
        self.__charge = 0
        self.projected = False
        self.state = State.ONLINE
        self.__dps = None
        self.__volley = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__slot = None

        if item != None:
            self.__itemModifiedAttributes.original = item.attributes
            self.__hardpoint = self.__calculateHardpoint(item)
            self.__slot = self.__calculateSlot(item)

        self.__chargeModifiedAttributes = ModifiedAttributeDict()
Exemplo n.º 34
0
def handler(fit, src, context, **kwargs):
    amount = src.getModifiedItemAttr("powerTransferAmount")
    time = src.getModifiedItemAttr("duration")

    if 'effect' in kwargs and "projected" in context:
        amount *= ModifiedAttributeDict.getResistance(fit, kwargs['effect'])

    if "projected" in context:
        fit.addDrain(src, time, amount, 0)
    elif "module" in context:
        src.itemModifiedAttributes.force("capacitorNeed", -amount)
Exemplo n.º 35
0
 def init(self):
     if self.dummySlot is None:
         self.__item = None
         self.__charge = None
         self.__volley = None
         self.__dps = None
         self.__reloadTime = None
         self.__reloadForce = None
         self.__chargeCycles = None
     else:
         self.__slot = self.dummySlot
         self.__item = 0
         self.__charge = 0
         self.__dps = 0
         self.__volley = 0
         self.__reloadTime = 0
         self.__reloadForce = None
         self.__chargeCycles = 0
         self.__hardpoint = Hardpoint.NONE
         self.__itemModifiedAttributes = ModifiedAttributeDict()
         self.__chargeModifiedAttributes = ModifiedAttributeDict()
Exemplo n.º 36
0
    def init(self):
        """Initialize cargo from the database and validate"""
        self.__item = None

        if self.itemID:
            self.__item = eos.db.getItem(self.itemID)
            if self.__item is None:
                logger.error("Item (id: %d) does not exist", self.itemID)
                return

        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__itemModifiedAttributes.original = self.__item.attributes
Exemplo n.º 37
0
    def build(self):
        """ Build object. Assumes proper and valid item already set """
        self.__charge = None
        self.__baseVolley = None
        self.__baseRRAmount = None
        self.__miningYield = None
        self.__miningWaste = None
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__itemModifiedAttributes.original = self._item.attributes
        self.__itemModifiedAttributes.overrides = self._item.overrides
        self.__chargeModifiedAttributes = ModifiedAttributeDict()

        self._mutaLoadMutators(mutatorClass=MutatorDrone)
        self.__itemModifiedAttributes.mutators = self.mutators

        chargeID = self.getModifiedItemAttr("entityMissileTypeID", None)
        if chargeID:
            charge = eos.db.getItem(int(chargeID))
            self.__charge = charge
            self.__chargeModifiedAttributes.original = charge.attributes
            self.__chargeModifiedAttributes.overrides = charge.overrides
Exemplo n.º 38
0
    def __init__(self, item):
        if item.category.name != "Drone":
            raise ValueError("Passed item is not a drone")

        self.__item = item
        self.__charge = None
        self.itemID = item.ID
        self.amount = 0
        self.amountActive = 0
        self.__dps = None
        self.projected = False
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.itemModifiedAttributes.original = self.item.attributes
Exemplo n.º 39
0
    def build(self):
        """ Builds internal module variables from both init's """

        if self.__charge and self.__charge.category.name != "Charge":
            self.__charge = None

        self.__dps = None
        self.__dpsSpool = None
        self.__volley = None
        self.__volleySpool = None
        self.__miningyield = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.__hardpoint = Hardpoint.NONE
        self.__itemModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__chargeModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__slot = self.dummySlot  # defaults to None

        if self.__item:
            self.__itemModifiedAttributes.original = self.__item.attributes
            self.__itemModifiedAttributes.overrides = self.__item.overrides
            self.__hardpoint = self.__calculateHardpoint(self.__item)
            self.__slot = self.__calculateSlot(self.__item)

            # Instantiate / remove mutators if this is a mutated module
            if self.__baseItem:
                for x in self.mutaplasmid.attributes:
                    attr = self.item.attributes[x.name]
                    id = attr.ID
                    if id not in self.mutators:  # create the mutator
                        Mutator(self, attr, attr.value)
                # @todo: remove attributes that are no longer part of the mutaplasmid.

            self.__itemModifiedAttributes.mutators = self.mutators

        if self.__charge:
            self.__chargeModifiedAttributes.original = self.__charge.attributes
            self.__chargeModifiedAttributes.overrides = self.__charge.overrides
Exemplo n.º 40
0
    def __init__(self, item):
        self.__item = item if item != None else 0
        self.itemID = item.ID if item is not None else None
        self.__charge = 0
        self.projected = False
        self.state = State.ONLINE
        self.__dps = None
        self.__volley = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__slot = None

        if item != None:
            self.__itemModifiedAttributes.original = item.attributes
            self.__hardpoint = self.__calculateHardpoint(item)
            self.__slot = self.__calculateSlot(item)

        self.__chargeModifiedAttributes = ModifiedAttributeDict()
Exemplo n.º 41
0
 def init(self):
     if self.dummySlot is None:
         self.__item = None
         self.__charge = None
         self.__volley = None
         self.__dps = None
         self.__reloadTime = None
         self.__reloadForce = None
         self.__chargeCycles = None
     else:
         self.__slot = self.dummySlot
         self.__item = 0
         self.__charge = 0
         self.__dps = 0
         self.__volley = 0
         self.__reloadTime = 0
         self.__reloadForce = None
         self.__chargeCycles = 0
         self.__hardpoint = Hardpoint.NONE
         self.__itemModifiedAttributes = ModifiedAttributeDict()
         self.__chargeModifiedAttributes = ModifiedAttributeDict()
Exemplo n.º 42
0
    def build(self):
        """ Builds internal module variables from both init's """

        if self.__charge and self.__charge.category.name != "Charge":
            self.__charge = None

        self.__dps = None
        self.__dpsSpool = None
        self.__volley = None
        self.__volleySpool = None
        self.__miningyield = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.__hardpoint = Hardpoint.NONE
        self.__itemModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__chargeModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__slot = self.dummySlot  # defaults to None

        if self.__item:
            self.__itemModifiedAttributes.original = self.__item.attributes
            self.__itemModifiedAttributes.overrides = self.__item.overrides
            self.__hardpoint = self.__calculateHardpoint(self.__item)
            self.__slot = self.__calculateSlot(self.__item)

            # Instantiate / remove mutators if this is a mutated module
            if self.__baseItem:
                for x in self.mutaplasmid.attributes:
                    attr = self.item.attributes[x.name]
                    id = attr.ID
                    if id not in self.mutators:  # create the mutator
                        Mutator(self, attr, attr.value)
                # @todo: remove attributes that are no longer part of the mutaplasmid.

            self.__itemModifiedAttributes.mutators = self.mutators

        if self.__charge:
            self.__chargeModifiedAttributes.original = self.__charge.attributes
            self.__chargeModifiedAttributes.overrides = self.__charge.overrides
Exemplo n.º 43
0
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
    """An instance of this class represents a module together with its charge and modified attributes"""
    DAMAGE_ATTRIBUTES = ("emDamage", "kineticDamage", "explosiveDamage", "thermalDamage")

    def __init__(self, item):
        self.__item = item if item != None else 0
        self.itemID = item.ID if item is not None else None
        self.__charge = 0
        self.projected = False
        self.state = State.ONLINE
        self.__dps = None
        self.__volley = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__slot = None

        if item != None:
            self.__itemModifiedAttributes.original = item.attributes
            self.__hardpoint = self.__calculateHardpoint(item)
            self.__slot = self.__calculateSlot(item)

        self.__chargeModifiedAttributes = ModifiedAttributeDict()

    @reconstructor
    def init(self):
        if self.dummySlot is None:
            self.__item = None
            self.__charge = None
            self.__volley = None
            self.__dps = None
            self.__reloadTime = None
            self.__reloadForce = None
            self.__chargeCycles = None
        else:
            self.__slot = self.dummySlot
            self.__item = 0
            self.__charge = 0
            self.__dps = 0
            self.__volley = 0
            self.__reloadTime = 0
            self.__reloadForce = None
            self.__chargeCycles = 0
            self.__hardpoint = Hardpoint.NONE
            self.__itemModifiedAttributes = ModifiedAttributeDict()
            self.__chargeModifiedAttributes = ModifiedAttributeDict()

    def __fetchItemInfo(self):
        import eos.db
        item = eos.db.getItem(self.itemID)
        self.__item = item
        self.__itemModifiedAttributes = ModifiedAttributeDict()
        self.__itemModifiedAttributes.original = item.attributes
        self.__hardpoint = self.__calculateHardpoint(item)
        self.__slot = self.__calculateSlot(item)

    def __fetchChargeInfo(self):
        self.__chargeModifiedAttributes = ModifiedAttributeDict()
        if self.chargeID is not None:
            import eos.db
            charge = eos.db.getItem(self.chargeID)
            self.__charge = charge
            self.__chargeModifiedAttributes.original = charge.attributes
        else:
            self.__charge = 0

    @classmethod
    def buildEmpty(cls, slot):
        empty = Module(None)
        empty.__slot = slot
        empty.__hardpoint = Hardpoint.NONE
        empty.__item = 0
        empty.__charge = 0
        empty.dummySlot = slot
        empty.__itemModifiedAttributes = ModifiedAttributeDict()
        empty.__chargeModifiedAttributes = ModifiedAttributeDict()

        return empty

    @classmethod
    def buildRack(cls, slot):
        empty = Rack(None)
        empty.__slot = slot
        empty.__hardpoint = Hardpoint.NONE
        empty.__item = 0
        empty.__charge = 0
        empty.dummySlot = slot
        empty.__itemModifiedAttributes = ModifiedAttributeDict()
        empty.__chargeModifiedAttributes = ModifiedAttributeDict()

        return empty

    @property
    def isEmpty(self):
        return self.dummySlot is not None

    @property
    def hardpoint(self):
        if self.__item is None:
            self.__fetchItemInfo()

        return self.__hardpoint

    @property
    def numCharges(self):
        if self.charge is None:
            charges = 0
        else:
            chargeVolume = self.charge.volume
            containerCapacity = self.item.capacity
            if chargeVolume is None or containerCapacity is None:
                charges = 0
            else:
                charges = floorFloat(float(containerCapacity) / chargeVolume)
        return charges

    @property
    def numShots(self):
        if self.charge is None:
            return None
        if self.__chargeCycles is None and self.charge:
            numCharges = self.numCharges
            # Usual ammo like projectiles and missiles
            if numCharges > 0 and "chargeRate" in self.itemModifiedAttributes:
                self.__chargeCycles = self.__calculateAmmoShots()
            # Frequency crystals (combat and mining lasers)
            elif numCharges > 0 and "crystalsGetDamaged" in self.chargeModifiedAttributes:
                self.__chargeCycles = self.__calculateCrystalShots()
            # Scripts and stuff
            else:
                self.__chargeCycles = 0
            return self.__chargeCycles
        else:
            return self.__chargeCycles

    @property
    def hpBeforeReload(self):
        """
        If item is some kind of repairer with charges, calculate
        HP it reps before going into reload.
        """
        cycles = self.numShots
        armorRep = self.getModifiedItemAttr("armorDamageAmount") or 0
        shieldRep = self.getModifiedItemAttr("shieldBonus") or 0
        if not cycles or (not armorRep and not shieldRep):
            return None
        hp = round((armorRep + shieldRep) * cycles)
        return hp

    def __calculateAmmoShots(self):
        if self.charge is not None:
            # Set number of cycles before reload is needed
            chargeRate = self.getModifiedItemAttr("chargeRate")
            numCharges = self.numCharges
            numShots = floorFloat(float(numCharges) / chargeRate)
        else:
            numShots = None
        return numShots

    def __calculateCrystalShots(self):
        if self.charge is not None:
            if self.getModifiedChargeAttr("crystalsGetDamaged") == 1:
                # For depletable crystals, calculate average amount of shots before it's destroyed
                hp = self.getModifiedChargeAttr("hp")
                chance = self.getModifiedChargeAttr("crystalVolatilityChance")
                damage = self.getModifiedChargeAttr("crystalVolatilityDamage")
                crystals = self.numCharges
                numShots = floorFloat(float(crystals * hp) / (damage * chance))
            else:
                # Set 0 (infinite) for permanent crystals like t1 laser crystals
                numShots = 0
        else:
            numShots = None
        return numShots

    @property
    def maxRange(self):
        attrs = ("maxRange", "shieldTransferRange", "powerTransferRange",
                 "energyDestabilizationRange", "empFieldRange",
                 "ecmBurstRange", "warpScrambleRange", "cargoScanRange",
                 "shipScanRange", "surveyScanRange")
        for attr in attrs:
            maxRange = self.getModifiedItemAttr(attr)
            if maxRange is not None: return maxRange
        if self.charge is not None:
            try:
                chargeName = self.charge.group.name
            except AttributeError:
                pass
            else:
                if chargeName in ("Scanner Probe", "Survey Probe"):
                    return None
            # Source: http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1307419&page=1#15
            # D_m = V_m * (T_m + T_0*[exp(- T_m/T_0)-1])
            maxVelocity = self.getModifiedChargeAttr("maxVelocity")
            flightTime = self.getModifiedChargeAttr("explosionDelay") / 1000.0
            mass = self.getModifiedChargeAttr("mass")
            agility = self.getModifiedChargeAttr("agility")
            if maxVelocity and flightTime and mass and agility:
                accelTime =  min(flightTime, mass*agility/1000000)
                # Average distance done during acceleration
                duringAcceleration = maxVelocity / 2 * accelTime
                # Distance done after being at full speed
                fullSpeed = maxVelocity * (flightTime - accelTime)
                return duringAcceleration + fullSpeed

    @property
    def falloff(self):
        attrs = ("falloff", "shipScanFalloff")
        for attr in attrs:
            falloff = self.getModifiedItemAttr(attr)
            if falloff is not None: return falloff

    @property
    def slot(self):
        if self.__item is None:
            self.__fetchItemInfo()

        return self.__slot


    @property
    def itemModifiedAttributes(self):
        if self.__item is None:
            self.__fetchItemInfo()

        return self.__itemModifiedAttributes

    @property
    def chargeModifiedAttributes(self):
        if self.__charge is None:
            self.__fetchChargeInfo()

        return self.__chargeModifiedAttributes

    @property
    def item(self):
        if self.__item is None:
            self.__fetchItemInfo()

        return self.__item if self.__item != 0 else None

    @property
    def charge(self):
        if self.__charge is None:
            self.__fetchChargeInfo()

        return self.__charge if self.__charge != 0 else None

    @charge.setter
    def charge(self, charge):
        self.__charge = charge
        if charge is not None:
            self.chargeID = charge.ID
            self.__chargeModifiedAttributes.original = charge.attributes
        else:
            self.chargeID = None
            self.__chargeModifiedAttributes.original = None

        self.__itemModifiedAttributes.clear()

    @property
    def damageStats(self):
        if self.__dps == None:
            if self.isEmpty:
                self.__dps = 0
                self.__volley = 0
            else:
                if self.state >= State.ACTIVE:
                    if self.charge:
                        volley = sum(map(lambda attr: self.getModifiedChargeAttr(attr) or 0, self.DAMAGE_ATTRIBUTES))
                    else:
                        volley = sum(map(lambda attr: self.getModifiedItemAttr(attr) or 0, self.DAMAGE_ATTRIBUTES))
                    volley *= self.getModifiedItemAttr("damageMultiplier") or 1
                    if volley:
                        cycleTime = self.cycleTime
                        self.__volley = volley
                        self.__dps = volley / (cycleTime / 1000.0)
                    else:
                        self.__volley = 0
                        self.__dps = 0
                else:
                    self.__volley = 0
                    self.__dps = 0

        return self.__dps, self.__volley

    @property
    def dps(self):
        return self.damageStats[0]

    @property
    def volley(self):
        return self.damageStats[1]

    @property
    def reloadTime(self):
        # Get reload time from attrs first, then use
        # custom value specified otherwise (e.g. in effects)
        moduleReloadTime = self.getModifiedItemAttr("reloadTime")
        if moduleReloadTime is None:
            moduleReloadTime = self.__reloadTime
        return moduleReloadTime

    @reloadTime.setter
    def reloadTime(self, milliseconds):
        self.__reloadTime = milliseconds

    @property
    def forceReload(self):
        return self.__reloadForce

    @forceReload.setter
    def forceReload(self, type):
        self.__reloadForce = type

    def fits(self, fit, hardpointLimit=True):
        slot = self.slot
        if fit.getSlotsFree(slot) <= (0 if self.owner != fit else -1):
            return False

        # Check ship type restrictions
        fitsOnType = set()
        fitsOnGroup = set()

        shipType = self.getModifiedItemAttr("fitsToShipType")
        if shipType is not None:
            fitsOnType.add(shipType)

        for i in xrange(1, 6):
            shipType = self.getModifiedItemAttr("canFitShipType%d" % i)
            if shipType is not None:
                fitsOnType.add(shipType)


        # Check ship group restrictions
        for i in xrange(1, 10):
            shipGroup = self.getModifiedItemAttr("canFitShipGroup%d" % i)
            if shipGroup is not None:
                fitsOnGroup.add(shipGroup)

        if (len(fitsOnGroup) > 0 or len(fitsOnType) > 0) and fit.ship.item.group.ID not in fitsOnGroup and fit.ship.item.ID not in fitsOnType:
            return False


        # If the mod is a subsystem, don't let two subs in the same slot fit
        if self.slot == Slot.SUBSYSTEM:
            subSlot = self.getModifiedItemAttr("subSystemSlot")
            for mod in fit.modules:
                if mod.getModifiedItemAttr("subSystemSlot") == subSlot:
                    return False

        # Check rig sizes
        if self.slot == Slot.RIG:
            if self.getModifiedItemAttr("rigSize") != fit.ship.getModifiedItemAttr("rigSize"):
                return False

        # Check max group fitted
        max = self.getModifiedItemAttr("maxGroupFitted")
        if max is not None:
            current = 0 if self.owner != fit else -1
            for mod in fit.modules:
                if mod.item and mod.item.groupID == self.item.groupID:
                    current += 1

            if current >= max:
                return False

        # Check this only if we're told to do so
        if hardpointLimit:
            if self.hardpoint == Hardpoint.TURRET:
                if fit.ship.getModifiedItemAttr('turretSlotsLeft') - fit.getHardpointsUsed(Hardpoint.TURRET) < 1:
                    return False
            elif self.hardpoint == Hardpoint.MISSILE:
                if fit.ship.getModifiedItemAttr('launcherSlotsLeft') - fit.getHardpointsUsed(Hardpoint.MISSILE) < 1:
                    return False

        return True

    def isValidState(self, state):
        """
        Check if the state is valid for this module, without considering other modules at all
        """
        #Check if we're within bounds
        if state < -1 or state > 2:
            return False
        elif state >= State.ACTIVE and not self.item.isType("active"):
            return False
        elif state == State.OVERHEATED and not self.item.isType("overheat"):
            return False
        else:
            return True

    def canHaveState(self, state=None, projectedOnto=None):
        """
        Check with other modules if there are restrictions that might not allow this module to be activated
        """
        # If we're going to set module to offline or online for local modules or offline for projected,
        # it should be fine for all cases
        item = self.item
        if (state <= State.ONLINE and projectedOnto is None) or (state <= State.OFFLINE):
            return True

        # Check if the local module is over it's max limit; if it's not, we're fine
        maxGroupActive = self.getModifiedItemAttr("maxGroupActive")
        if maxGroupActive is None and projectedOnto is None:
            return True

        # Following is applicable only to local modules, we do not want to limit projected
        if projectedOnto is None:
            currActive = 0
            group = item.group.name
            for mod in self.owner.modules:
                currItem = getattr(mod, "item", None)
                if mod.state >= State.ACTIVE and currItem is not None and currItem.group.name == group:
                    currActive += 1
                if currActive > maxGroupActive:
                    break
            return currActive <= maxGroupActive
        # For projected, we're checking if ship is vulnerable to given item
        else:
            # Do not allow to apply offensive modules on ship with offensive module immunite, with few exceptions
            # (all effects which apply instant modification are exception, generally speaking)
            if item.offensive and projectedOnto.ship.getModifiedItemAttr("disallowOffensiveModifiers") == 1:
                offensiveNonModifiers = set(("energyDestabilizationNew", "leech"))
                if not offensiveNonModifiers.intersection(set(item.effects)):
                    return False
            # If assistive modules are not allowed, do not let to apply these altogether
            if item.assistive and projectedOnto.ship.getModifiedItemAttr("disallowAssistance") == 1:
                return False
            return True

    def isValidCharge(self, charge):
        #Check sizes, if 'charge size > module volume' it won't fit
        if charge is None: return True
        chargeVolume = charge.volume
        moduleCapacity = self.item.capacity
        if chargeVolume is not None and moduleCapacity is not None and chargeVolume > moduleCapacity:
            return False

        itemChargeSize = self.getModifiedItemAttr("chargeSize")
        if itemChargeSize is not None:
            chargeSize = charge.getAttribute('chargeSize')
            if itemChargeSize != chargeSize:
                return False

        chargeGroup = charge.groupID
        for i in range(5):
            itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i))
            if itemChargeGroup is None: continue
            if itemChargeGroup == chargeGroup: return True

        return False

    def getValidCharges(self):
        validCharges = set()
        import eos.db
        for i in range(5):
            itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i))
            if itemChargeGroup is not None:
                g = eos.db.getGroup(int(itemChargeGroup), eager=("items.icon", "items.attributes"))
                if g is None:
                    continue
                for i in g.items:
                    if i.published and self.isValidCharge(i):
                        validCharges.add(i)

        return validCharges

    def __calculateHardpoint(self, item):
        effectHardpointMap = {"turretFitted" : Hardpoint.TURRET,
                              "launcherFitted": Hardpoint.MISSILE}

        if item is None:
            return Hardpoint.NONE

        for effectName, slot in effectHardpointMap.iteritems():
            if effectName in item.effects:
                return slot

        return Hardpoint.NONE

    def __calculateSlot(self, item):
        effectSlotMap = {"rigSlot" : Slot.RIG,
                         "loPower" : Slot.LOW,
                         "medPower" : Slot.MED,
                         "hiPower" : Slot.HIGH,
                         "subSystem" : Slot.SUBSYSTEM}
        if item is None:
            return None
        for effectName, slot in effectSlotMap.iteritems():
            if effectName in item.effects:
                return slot
        if item.group.name == "Effect Beacon":
            return Slot.RIG

        raise ValueError("Passed item does not fit in any known slot")

    @validates("ID", "itemID", "ammoID")
    def validator(self, key, val):
        map = {"ID": lambda val: isinstance(val, int),
               "itemID" : lambda val: val is None or isinstance(val, int),
               "ammoID" : lambda val: isinstance(val, int)}

        if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key)
        else: return val

    def clear(self):
        self.__dps = None
        self.__volley = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.itemModifiedAttributes.clear()
        self.chargeModifiedAttributes.clear()

    def calculateModifiedAttributes(self, fit, runTime, forceProjected = False):
        #We will run the effect when two conditions are met:
        #1: It makes sense to run the effect
        #    The effect is either offline
        #    or the effect is passive and the module is in the online state (or higher)

        #    or the effect is active and the module is in the active state (or higher)
        #    or the effect is overheat and the module is in the overheated state (or higher)
        #2: the runtimes match

        if self.projected or forceProjected:
            context = "projected", "module"
            projected = True
        else:
            context = ("module",)
            projected = False

        if self.charge is not None:
            # fix for #82 and it's regression #106
            if not projected or (self.projected and not forceProjected):
                for effect in self.charge.effects.itervalues():
                    if effect.runTime == runTime:
                        effect.handler(fit, self, ("moduleCharge",))

        if self.item:
            if self.state >= State.OVERHEATED:
                for effect in self.item.effects.itervalues():
                    if effect.runTime == runTime and effect.isType("overheat"):
                        effect.handler(fit, self, context)

            for effect in self.item.effects.itervalues():
                if effect.runTime == runTime and \
                (effect.isType("offline") or
                (effect.isType("passive") and self.state >= State.ONLINE) or \
                (effect.isType("active") and self.state >= State.ACTIVE)) and \
                ((projected and effect.isType("projected")) or not projected):
                        effect.handler(fit, self, context)

    @property
    def cycleTime(self):
        reactivation = (self.getModifiedItemAttr("moduleReactivationDelay") or 0)
        # Reactivation time starts counting after end of module cycle
        speed = self.rawCycleTime + reactivation
        if self.charge:
            reload = self.reloadTime
        else:
            reload = 0.0
        # Determine if we'll take into account reload time or not
        factorReload = self.owner.factorReload if self.forceReload is None else self.forceReload
        # If reactivation is longer than 10 seconds then module can be reloaded
        # during reactivation time, thus we may ignore reload
        if factorReload and reactivation < reload:
            numShots = self.numShots
            # Time it takes to reload module after end of reactivation time,
            # given that we started when module cycle has just over
            additionalReloadTime = (reload - reactivation)
            # Speed here already takes into consideration reactivation time
            speed = (speed * numShots + additionalReloadTime) / numShots if numShots > 0 else speed

        return speed

    @property
    def rawCycleTime(self):
        speed =  self.getModifiedItemAttr("speed") or self.getModifiedItemAttr("duration")
        return speed

    @property
    def capUse(self):
        capNeed = self.getModifiedItemAttr("capacitorNeed")
        if capNeed and self.state >= State.ACTIVE:
            cycleTime = self.cycleTime
            capUsed = capNeed / (cycleTime / 1000.0)
            return capUsed
        else:
            return 0

    def __deepcopy__(self, memo):
        item = self.item
        if item is None:
            copy = Module.buildEmpty(self.slot)
        else:
            copy = Module(self.item)
        copy.charge = self.charge
        copy.state = self.state
        return copy
Exemplo n.º 44
0
class Fit(object):
    """Represents a fitting, with modules, ship, implants, etc."""
    EXTRA_ATTRIBUTES = {"armorRepair": 0,
                        "hullRepair": 0,
                        "shieldRepair": 0,
                        "maxActiveDrones": 0,
                        "maxTargetsLockedFromSkills": 2,
                        "droneControlRange": 20000,
                        "cloaked": False,
                        "siege": False}

    PEAK_RECHARGE = 0.25

    def __init__(self):
        self.__modules = HandledModuleList()
        self.__drones = HandledDroneList()
        self.__implants = HandledImplantBoosterList()
        self.__boosters = HandledImplantBoosterList()
        self.__projectedFits = HandledProjectedFitList()
        self.__projectedModules = HandledProjectedModList()
        self.__projectedDrones = HandledProjectedDroneList()
        self.__character = None
        self.__owner = None
        self.shipID = None
        self.projected = False
        self.name = ""
        self.fleet = None
        self.boostsFits = set()
        self.gangBoosts = None
        self.timestamp = time.time()
        self.build()

    @classmethod
    def importAuto(cls, string, sourceFileName=None):
        # Get first line and strip space symbols of it
        # to avoid possible detection errors
        firstLine = re.split("[\n\r]+", string, maxsplit=1)[0]
        firstLine = firstLine.strip()
        # If XML-style start of tag encountered, detect as XML
        if re.match("<", firstLine):
            return "XML", cls.importXml(string)
        # If we've got source file name which is used to describe ship name
        # and first line contains something like [setup name], detect as eft config file
        elif re.match("\[.*\]", firstLine) and sourceFileName is not None:
            shipName = sourceFileName.rsplit('.')[0]
            return "EFT Config", cls.importEftCfg(shipName, string)
        # If no file is specified and there's comma between brackets,
        # consider that we have [ship, setup name] and detect like eft export format
        elif re.match("\[.*,.*\]", firstLine):
            return "EFT", (cls.importEft(string),)
        # Use DNA format for all other cases
        else:
            return "DNA", (cls.importDna(string),)

    @classmethod
    def importDna(cls, string):
        from eos import db
        info = string.split(":")
        f = Fit()
        f.ship = Ship(db.getItem(int(info[0])))
        f.name = "{0} - DNA Imported".format(f.ship.item.name)
        for itemInfo in info[1:]:
            if itemInfo:
                itemID, amount = itemInfo.split(";")
                item = db.getItem(int(itemID), eager="group.category")

                if item.category.name == "Drone":
                    d = Drone(item)
                    d.amount = int(amount)
                    f.drones.append(d)
                elif item.category.name == "Charge":
                    for i in xrange(int(amount)):
                        for mod in f.modules:
                            if (mod.isValidCharge(item) and mod.charge == None):
                                mod.charge = item
                                break;
                else:
                    for i in xrange(int(amount)):
                        try:
                            m = Module(item)
                            f.modules.append(m)
                        except:
                            pass
                        if m.isValidState(State.ACTIVE):
                            m.state = State.ACTIVE

        return f

    @classmethod
    def importEft(cls, eftString):
        from eos import db
        offineSuffix = " /OFFLINE"
        fit = cls()
        eftString = eftString.strip()
        lines = re.split('[\n\r]+', eftString)
        info = lines[0][1:-1].split(",", 1)
        if len(info) == 2:
            shipType = info[0].strip()
            fitName = info[1].strip()
        else:
            shipType = info[0].strip()
            fitName = "Imported %s" % shipType

        try:
            fit.ship = Ship(db.getItem(shipType))
            fit.name = fitName
        except:
            return
        droneMap = {}
        for i in range(1, len(lines)):
            line = lines[i]
            setOffline = line.endswith(offineSuffix)
            if setOffline == True:
                line = line[:len(line) - len(offineSuffix)]
            modAmmo = line.split(",")
            modDrone = modAmmo[0].split(" x")
            if len(modAmmo) == 2: ammoName = modAmmo[1].strip()
            else: ammoName = None
            modName = modDrone[0].strip()
            if len(modDrone) == 2: droneAmount = modDrone[1].strip()
            else: droneAmount = None
            try:
                item = db.getItem(modName, eager="group.category")
            except:
                try:
                    item = db.getItem(modAmmo[0], eager="group.category")
                except:
                    continue

            if item.category.name == "Drone":
                droneAmount = int(droneAmount) if droneAmount is not None else 1
                if not modName in droneMap:
                    droneMap[modName] = 0
                droneMap[modName] += droneAmount
            elif item.category.name == "Implant":
                fit.implants.append(Implant(item))
            else:
                m = Module(item)
                if ammoName:
                    try:
                        m.charge = db.getItem(ammoName)
                    except:
                        pass

                if setOffline == True and m.isValidState(State.OFFLINE):
                    m.state = State.OFFLINE
                elif m.isValidState(State.ACTIVE):
                    m.state = State.ACTIVE

                fit.modules.append(m)

        for droneName in droneMap:
            d = Drone(db.getItem(droneName))
            d.amount = droneMap[droneName]
            fit.drones.append(d)

        return fit

    @classmethod
    def importEftCfg(cls, shipname, contents):
        """Handle import from EFT config store file"""
        # Check if we have such ship in database, bail if we don't
        from eos import db
        try:
            db.getItem(shipname)
        except:
            return
        # If client didn't take care of encoding file contents into Unicode,
        # do it using fallback encoding ourselves
        if isinstance(contents, str):
            contents = unicode(contents, "cp1252")
        # List for fits
        fits = []
        # List for starting line numbers for each fit
        fitIndices = []
        # Separate string into lines
        lines = re.split('[\n\r]+', contents)
        for line in lines:
            # Detect fit header
            if line[:1] == "[" and line[-1:] == "]":
                # Line index where current fit starts
                startPos = lines.index(line)
                fitIndices.append(startPos)

        for i, startPos in enumerate(fitIndices):
            # End position is last file line if we're trying to get it for last fit,
            # or start position of next fit minus 1
            endPos = len(lines) if i == len(fitIndices) - 1 else fitIndices[i + 1]
            # Finally, get lines for current fitting
            fitLines = lines[startPos:endPos]
            try:
                # Create fit object
                f = Fit()
                # Strip square brackets and pull out a fit name
                f.name = fitLines[0][1:-1]
                # Assign ship to fitting
                f.ship = Ship(db.getItem(shipname))
                for i in range(1, len(fitLines)):
                    line = fitLines[i]
                    # Parse line into some data we will need
                    misc = re.match("(Drones|Implant|Booster)_(Active|Inactive)=(.+)",line)
                    if misc:
                        entityType = misc.group(1)
                        entityState = misc.group(2)
                        entityData = misc.group(3)
                        if entityType == "Drones":
                            droneData = re.match("(.+),([0-9]+)", entityData)
                            # Get drone name and attempt to detect drone number
                            droneName = droneData.group(1) if droneData else entityData
                            droneAmount = int(droneData.group(2)) if droneData else 1
                            # Bail if we can't get item or it's not from drone category
                            try:
                                droneItem = db.getItem(droneName, eager="group.category")
                            except:
                                continue
                            if droneItem.category.name != "Drone":
                                continue
                            # Add drone to the fitting
                            d = Drone(droneItem)
                            d.amount = droneAmount
                            if entityState == "Active":
                                d.amountActive = droneAmount
                            elif entityState == "Inactive":
                                d.amountActive = 0
                            f.drones.append(d)
                        elif entityType == "Implant":
                            # Bail if we can't get item or it's not from implant category
                            try:
                                implantItem = db.getItem(entityData, eager="group.category")
                            except:
                                continue
                            if implantItem.category.name != "Implant":
                                continue
                            # Add implant to the fitting
                            imp = Implant(implantItem)
                            if entityState == "Active":
                                imp.active = True
                            elif entityState == "Inactive":
                                imp.active = False
                            f.implants.append(imp)
                        elif entityType == "Booster":
                            # Bail if we can't get item or it's not from implant category
                            try:
                                boosterItem = db.getItem(entityData, eager="group.category")
                            except:
                                continue
                            # All boosters have implant category
                            if boosterItem.category.name != "Implant":
                                continue
                            # Add booster to the fitting
                            b = Booster(boosterItem)
                            if entityState == "Active":
                                b.active = True
                            elif entityState == "Inactive":
                                b.active = False
                            f.boosters.append(b)
                    # If we don't have any prefixes, then it's a module
                    else:
                        withCharge = re.match("(.+),(.+)", line)
                        modName = withCharge.group(1) if withCharge else line
                        chargeName = withCharge.group(2) if withCharge else None
                        # If we can't get module item, skip it
                        try:
                            modItem = db.getItem(modName)
                        except:
                            continue
                        # Create module and activate it if it's activable
                        m = Module(modItem)
                        if m.isValidState(State.ACTIVE):
                            m.state = State.ACTIVE
                        # Add charge to mod if applicable, on any errors just don't add anything
                        if chargeName:
                            try:
                                chargeItem = db.getItem(chargeName, eager="group.category")
                                if chargeItem.category.name == "Charge":
                                    m.charge = chargeItem
                            except:
                                pass
                        # Append module to fit
                        f.modules.append(m)
                # Append fit to list of fits
                fits.append(f)
            # Skip fit silently if we get an exception
            except Exception:
                pass

        return fits

    @classmethod
    def importXml(cls, text):
        doc = xml.dom.minidom.parseString(text.encode("utf-8"))
        fittings = doc.getElementsByTagName("fittings").item(0)
        fittings = fittings.getElementsByTagName("fitting")
        fits = []
        from eos import db
        for fitting in fittings:

                f = Fit()
                f.name = fitting.getAttribute("name")
                shipType = fitting.getElementsByTagName("shipType").item(0).getAttribute("value")
                f.ship = Ship(db.getItem(shipType))
                hardwares = fitting.getElementsByTagName("hardware")
                for hardware in hardwares:
                    try:
                        moduleName = hardware.getAttribute("type")
                        item = db.getItem(moduleName, eager="group.category")
                        if item:
                            if item.category.name == "Drone":
                                d = Drone(item)
                                d.amount = int(hardware.getAttribute("qty"))
                                f.drones.append(d)
                            else:
                                m = Module(item)
                                if m.isValidState(State.ACTIVE):
                                    m.state = State.ACTIVE

                                f.modules.append(m)
                    except Exception:
                        continue

                fits.append(f)


        return fits

    EXPORT_ORDER_EFT = [Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM]
    def exportEft(self):
        offineSuffix = " /OFFLINE"
        export = "[%s, %s]\n" % (self.ship.item.name, self.name)
        stuff = {}
        for module in self.modules:
            slot = module.slot
            if not slot in stuff: stuff[slot] = []
            curr = module.item.name if module.item else ("[Empty %s slot]" % Slot.getName(slot).capitalize() if slot is not None else "")
            if module.charge:
                curr += ", %s" % module.charge.name
            if module.state == State.OFFLINE:
                curr += offineSuffix
            curr += "\n"
            stuff[slot].append(curr)

        for slotType in self.EXPORT_ORDER_EFT:
            data = stuff.get(slotType)
            if data is not None:
                export += "\n"
                for curr in data:
                    export += curr

        if len(self.drones) > 0:
            export += "\n\n"
            for drone in self.drones:
                export += "%s x%s\n" % (drone.item.name, drone.amount)

        if export[-1] == "\n":
            export = export[:-1]

        return export

    def exportEftImps(self):
        export = self.exportEft()
        if len(self.implants) > 0:
            export += "\n\n\n"
            for implant in self.implants:
                export += "%s\n" % (implant.item.name)

        if export[-1] == "\n":
            export = export[:-1]

        return export

    def exportDna(self):
        dna = str(self.shipID)
        mods = OrderedDict()
        for mod in self.modules:
            if not mod.isEmpty:
                if not mod.itemID in mods:
                    mods[mod.itemID] = 0
                mods[mod.itemID] += 1

        for mod in mods:
            dna += ":{0};{1}".format(mod, mods[mod])

        for drone in self.drones:
            dna += ":{0};{1}".format(drone.itemID, drone.amount)

        return dna + "::"

    @classmethod
    def exportXml(cls, *fits):
        doc = xml.dom.minidom.Document()
        fittings = doc.createElement("fittings")
        doc.appendChild(fittings)

        for fit in fits:
            fitting = doc.createElement("fitting")
            fitting.setAttribute("name", fit.name)
            fittings.appendChild(fitting)
            description = doc.createElement("description")
            description.setAttribute("value", "")
            fitting.appendChild(description)
            shipType = doc.createElement("shipType")
            shipType.setAttribute("value", fit.ship.item.name)
            fitting.appendChild(shipType)

            slotNum = {}
            for module in fit.modules:
                if module.isEmpty:
                    continue

                slot = module.slot
                if not slot in slotNum: slotNum[slot] = 0
                slotId = slotNum[slot]
                slotNum[slot] += 1
                hardware = doc.createElement("hardware")
                hardware.setAttribute("type", module.item.name)
                slotName = Slot.getName(slot).lower()
                slotName = slotName if slotName != "high" else "hi"
                hardware.setAttribute("slot", "%s slot %d" % (slotName, slotId))
                fitting.appendChild(hardware)

            for drone in fit.drones:
                hardware = doc.createElement("hardware")
                hardware.setAttribute("qty", "%d" % drone.amount)
                hardware.setAttribute("slot", "drone bay")
                hardware.setAttribute("type", drone.item.name)
                fitting.appendChild(hardware)

        return doc.toprettyxml()

    @reconstructor
    def init(self):
        self.build()

    def build(self):
        from eos import db
        self.__extraDrains = []
        self.__ehp = None
        self.__weaponDPS = None
        self.__weaponVolley = None
        self.__droneDPS = None
        self.__sustainableTank = None
        self.__effectiveSustainableTank = None
        self.__effectiveTank = None
        self.__calculated = False
        self.__capStable = None
        self.__capState = None
        self.__capUsed = None
        self.__capRecharge = None
        self.__calculatedTargets = []
        self.factorReload = False
        self.fleet = None
        self.boostsFits = set()
        self.gangBoosts = None
        self.extraAttributes = ModifiedAttributeDict(self)
        self.extraAttributes.original = self.EXTRA_ATTRIBUTES
        self.ship = Ship(db.getItem(self.shipID)) if self.shipID is not None else None

    @property
    def damagePattern(self):
        return self.__damagePattern

    @damagePattern.setter
    def damagePattern(self, damagePattern):
        self.__damagePattern = damagePattern
        self.__ehp = None
        self.__effectiveTank = None

    @property
    def character(self):
        return self.__character if self.__character is not None else Character.getAll0()

    @character.setter
    def character(self, char):
        self.__character = char

    @property
    def ship(self):
        return self.__ship

    @ship.setter
    def ship(self, ship):
        self.__ship = ship
        self.shipID = ship.item.ID if ship is not None else None

    @property
    def drones(self):
        return self.__drones

    @property
    def modules(self):
        return self.__modules

    @property
    def implants(self):
        return self.__implants

    @property
    def boosters(self):
        return self.__boosters

    @property
    def projectedModules(self):
        return self.__projectedModules

    @property
    def projectedFits(self):
        return self.__projectedFits

    @property
    def projectedDrones(self):
        return self.__projectedDrones

    @property
    def weaponDPS(self):
        if self.__weaponDPS is None:
            self.calculateWeaponStats()

        return self.__weaponDPS

    @property
    def weaponVolley(self):
        if self.__weaponVolley is None:
            self.calculateWeaponStats()

        return self.__weaponVolley

    @property
    def droneDPS(self):
        if self.__droneDPS is None:
            self.calculateWeaponStats()

        return self.__droneDPS

    @property
    def totalDPS(self):
        return self.droneDPS + self.weaponDPS

    @property
    def maxTargets(self):
        return min(self.extraAttributes["maxTargetsLockedFromSkills"], self.ship.getModifiedItemAttr("maxLockedTargets"))

    @property
    def maxTargetRange(self):
        return min(self.ship.getModifiedItemAttr("maxTargetRange"), 250000)

    @property
    def scanStrength(self):
        return max([self.ship.getModifiedItemAttr("scan%sStrength" % scanType)
                    for scanType in ("Magnetometric", "Ladar", "Radar", "Gravimetric")])

    @property
    def scanType(self):
        maxStr = -1
        type = None
        for scanType in ("Magnetometric", "Ladar", "Radar", "Gravimetric"):
            currStr = self.ship.getModifiedItemAttr("scan%sStrength" % scanType)
            if currStr > maxStr:
                maxStr = currStr
                type = scanType
            elif currStr == maxStr:
                type = "Multispectral"

        return type

    @property
    def alignTime(self):
        agility = self.ship.getModifiedItemAttr("agility")
        mass = self.ship.getModifiedItemAttr("mass")

        return -log(0.25) * agility * mass / 1000000

    @property
    def appliedImplants(self):
        implantsBySlot = {}
        if self.character:
            for implant in self.character.implants:
                implantsBySlot[implant.slot] = implant

        for implant in self.implants:
            implantsBySlot[implant.slot] = implant

        return implantsBySlot.values()

    @validates("ID", "ownerID", "shipID")
    def validator(self, key, val):
        map = {"ID": lambda val: isinstance(val, int),
               "ownerID" : lambda val: isinstance(val, int),
               "shipID" : lambda val: isinstance(val, int) or val is None}

        if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key)
        else: return val

    def clear(self):
        self.__effectiveTank = None
        self.__weaponDPS = None
        self.__weaponVolley = None
        self.__effectiveSustainableTank = None
        self.__sustainableTank = None
        self.__droneDPS = None
        self.__ehp = None
        self.__calculated = False
        self.__capStable = None
        self.__capState = None
        self.__capUsed = None
        self.__capRecharge = None
        del self.__calculatedTargets[:]
        del self.__extraDrains[:]

        if self.ship is not None: self.ship.clear()
        c = chain(self.modules, self.drones, self.boosters, self.implants, self.projectedDrones, self.projectedModules, self.projectedFits, (self.character, self.extraAttributes))
        for stuff in c:
            if stuff is not None and stuff != self: stuff.clear()

    #Methods to register and get the thing currently affecting the fit,
    #so we can correctly map "Affected By"
    def register(self, currModifier):
        self.__modifier = currModifier
        if hasattr(currModifier, "itemModifiedAttributes"):
            currModifier.itemModifiedAttributes.fit = self
        if hasattr(currModifier, "chargeModifiedAttributes"):
            currModifier.chargeModifiedAttributes.fit = self

    def getModifier(self):
        return self.__modifier

    def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None):
        refreshBoosts = False
        if withBoosters is True:
            refreshBoosts = True
        if dirtyStorage is not None and self.ID in dirtyStorage:
            refreshBoosts = True
        if dirtyStorage is not None:
            dirtyStorage.update(self.boostsFits)
        if self.fleet is not None and refreshBoosts is True:
            self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters, dirtyStorage=dirtyStorage)
        elif self.fleet is None:
            self.gangBoosts = None
        if dirtyStorage is not None:
            try:
                dirtyStorage.remove(self.ID)
            except KeyError:
                pass
        # If we're not explicitly asked to project fit onto something,
        # set self as target fit
        if targetFit is None:
            targetFit = self
            forceProjected = False
        # Else, we're checking all target projectee fits
        elif targetFit not in self.__calculatedTargets:
            self.__calculatedTargets.append(targetFit)
            targetFit.calculateModifiedAttributes(dirtyStorage=dirtyStorage)
            forceProjected = True
        # Or do nothing if target fit is calculated
        else:
            return

        # If fit is calculated and we have nothing to do here, get out
        if self.__calculated == True and forceProjected == False:
            return

        # Mark fit as calculated
        self.__calculated = True

        # There's a few things to keep in mind here
        # 1: Early effects first, then regular ones, then late ones, regardless of anything else
        # 2: Some effects aren't implemented
        # 3: Some effects are implemented poorly and will just explode on us
        # 4: Errors should be handled gracefully and preferably without crashing unless serious
        for runTime in ("early", "normal", "late"):
            # Build a little chain of stuff
            # Avoid adding projected drones and modules when fit is projected onto self
            # TODO: remove this workaround when proper self-projection using virtual duplicate fits is implemented
            if targetFit == self and forceProjected is True:
                c = chain((self.character, self.ship), self.drones, self.boosters, self.appliedImplants, self.modules)
            else:
                c = chain((self.character, self.ship), self.drones, self.boosters, self.appliedImplants, self.modules,
                          self.projectedDrones, self.projectedModules)

            for item in c:
                # Registering the item about to affect the fit allows us to track "Affected By" relations correctly
                if item is not None:
                    self.register(item)
                    item.calculateModifiedAttributes(self, runTime, False)
                    if forceProjected is True:
                        targetFit.register(item)
                        item.calculateModifiedAttributes(targetFit, runTime, True)
            if self.gangBoosts is not None:
                #print self.gangBoosts
                contextMap = {Skill: "skill",
                              Ship: "ship",
                              Module: "module",
                              Implant: "implant"}
                for name, info in self.gangBoosts.iteritems():
                    # Unpack all data required to run effect properly
                    effect, thing = info[1]
                    if effect.runTime == runTime:
                        context = ("gang", contextMap[type(thing)])
                        if isinstance(thing, Module):
                            if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \
                            (effect.isType("active") and thing.state >= State.ACTIVE):
                                # Run effect, and get proper bonuses applied
                                try:
                                    effect.handler(targetFit, thing, context)
                                except:
                                    pass
                        else:
                            # Run effect, and get proper bonuses applied
                            try:
                                effect.handler(targetFit, thing, context)
                            except:
                                pass
        for fit in self.projectedFits:
            fit.calculateModifiedAttributes(self, dirtyStorage=dirtyStorage)

    def fill(self):
        """
        Fill this fit's module slots with enough dummy slots so that all slots are used.
        This is mostly for making the life of gui's easier.
        GUI's can call fill() and then stop caring about empty slots completely.
        """
        if self.ship is None:
            return

        for slotType in (Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM):
            amount = self.getSlotsFree(slotType, True)
            if amount > 0:
                for _ in xrange(int(amount)):
                    self.modules.append(Module.buildEmpty(slotType))

            if amount < 0:
                #Look for any dummies of that type to remove
                toRemove = []
                for mod in self.modules:
                    if mod.isEmpty and mod.slot == slotType:
                        toRemove.append(mod)
                        amount += 1
                        if amount == 0:
                            break
                for mod in toRemove:
                    self.modules.remove(mod)

    def unfill(self):
        for i in xrange(len(self.modules) - 1, -1, -1):
            mod = self.modules[i]
            if mod.isEmpty:
                del self.modules[i]

    def getItemAttrSum(self, dict, attr):
        amount = 0
        for mod in dict:
            add = mod.getModifiedItemAttr(attr)
            if add is not None:
                amount += add

        return amount

    def getItemAttrOnlineSum(self, dict, attr):
        amount = 0
        for mod in dict:
            add = mod.getModifiedItemAttr(attr) if mod.state >= State.ONLINE else None
            if add is not None:
                amount += add

        return amount

    def getHardpointsUsed(self, type):
        amount = 0
        for mod in self.modules:
            if mod.hardpoint is type and not mod.isEmpty:
                amount += 1

        return amount

    def getSlotsUsed(self, type, countDummies=False):
        amount = 0
        for mod in self.modules:
            if mod.slot is type and (not mod.isEmpty or countDummies):
                amount += 1

        return amount

    def getSlotsFree(self, type, countDummies=False):
        slots = {Slot.LOW: "lowSlots",
                 Slot.MED: "medSlots",
                 Slot.HIGH: "hiSlots",
                 Slot.RIG: "rigSlots",
                 Slot.SUBSYSTEM: "maxSubSystems"}

        slotsUsed = self.getSlotsUsed(type, countDummies)
        totalSlots = self.ship.getModifiedItemAttr(slots[type]) or 0
        return int(totalSlots - slotsUsed)

    @property
    def calibrationUsed(self):
        return self.getItemAttrSum(self.modules, 'upgradeCost')

    @property
    def pgUsed(self):
        return self.getItemAttrOnlineSum(self.modules, "power")

    @property
    def cpuUsed(self):
        return self.getItemAttrOnlineSum(self.modules, "cpu")

    @property
    def droneBandwidthUsed(self):
        amount = 0
        for d in self.drones:
            amount += d.getModifiedItemAttr("droneBandwidthUsed") * d.amountActive

        return amount

    @property
    def droneBayUsed(self):
        amount = 0
        for d in self.drones:
            amount += d.item.volume * d.amount

        return amount

    @property
    def activeDrones(self):
        amount = 0
        for d in self.drones:
            amount +=d.amountActive

        return amount

    # Expresses how difficult a target is to probe down with scan probes
    # If this is <1.08, the ship is unproabeable
    @property
    def probeSize(self):
        sigRad = self.ship.getModifiedItemAttr("signatureRadius")
        sensorStr = float(self.scanStrength)
        probeSize = sigRad / sensorStr if sensorStr != 0 else None
        # http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1532170&page=2#42
        if probeSize is not None:
            # http://forum.eve-ru.com/index.php?showtopic=74195&view=findpost&p=1333691
            # http://forum.eve-ru.com/index.php?showtopic=74195&view=findpost&p=1333763
            # Tests by tester128 and several conclusions by me, prove that cap is in range
            # from 1.1 to 1.12, we're picking average value
            probeSize = max(probeSize, 1.11)
        return probeSize

    @property
    def warpSpeed(self):
        base = self.ship.getModifiedItemAttr("baseWarpSpeed") or 1
        multiplier = self.ship.getModifiedItemAttr("warpSpeedMultiplier") or 1
        return 3 * base * multiplier

    @property
    def maxWarpDistance(self):
        capacity = self.ship.getModifiedItemAttr("capacitorCapacity")
        mass = self.ship.getModifiedItemAttr("mass")
        warpCapNeed = self.ship.getModifiedItemAttr("warpCapacitorNeed")
        return capacity / (mass * warpCapNeed)

    @property
    def capStable(self):
        if self.__capStable is None:
            self.simulateCap()

        return self.__capStable

    @property
    def capState(self):
        """
        If the cap is stable, the capacitor state is the % at which it is stable.
        If the cap is unstable, this is the amount of time before it runs out
        """
        if self.__capState is None:
            self.simulateCap()

        return self.__capState

    @property
    def capUsed(self):
        if self.__capUsed is None:
            self.simulateCap()

        return self.__capUsed

    @property
    def capRecharge(self):
        if self.__capRecharge is None:
            self.simulateCap()

        return self.__capRecharge


    @property
    def sustainableTank(self):
        if self.__sustainableTank is None:
            self.calculateSustainableTank()

        return self.__sustainableTank

    def calculateSustainableTank(self, effective=True):
        if self.__sustainableTank is None:
            if self.capStable:
                sustainable = {}
                sustainable["armorRepair"] = self.extraAttributes["armorRepair"]
                sustainable["shieldRepair"] = self.extraAttributes["shieldRepair"]
                sustainable["hullRepair"] = self.extraAttributes["hullRepair"]
            else:
                sustainable = {}

                repairers = []
                #Map a repairer type to the attribute it uses
                groupAttrMap = {"Armor Repair Unit": "armorDamageAmount",
                     "Fueled Armor Repairer": "armorDamageAmount",
                     "Hull Repair Unit": "structureDamageAmount",
                     "Shield Booster": "shieldBonus",
                     "Fueled Shield Booster": "shieldBonus",
                     "Remote Armor Repairer": "armorDamageAmount",
                     "Remote Shield Booster": "shieldBonus"}
                #Map repairer type to attribute
                groupStoreMap = {"Armor Repair Unit": "armorRepair",
                                 "Hull Repair Unit": "hullRepair",
                                 "Shield Booster": "shieldRepair",
                                 "Fueled Shield Booster": "shieldRepair",
                                 "Remote Armor Repairer": "armorRepair",
                                 "Remote Shield Booster": "shieldRepair",
                                 "Fueled Armor Repairer": "armorRepair",}

                capUsed = self.capUsed
                for attr in ("shieldRepair", "armorRepair", "hullRepair"):
                    sustainable[attr] = self.extraAttributes[attr]
                    dict = self.extraAttributes.getAfflictions(attr)
                    if self in dict:
                        for mod, _, amount in dict[self]:
                            if mod.projected is False:
                                usesCap = True
                                try:
                                    if mod.capUse:
                                        capUsed -= mod.capUse
                                    else:
                                        usesCap = False
                                except AttributeError:
                                    usesCap = False
                                # Modules which do not use cap are not penalized based on cap use
                                if usesCap:
                                    cycleTime = mod.getModifiedItemAttr("duration")
                                    amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
                                    sustainable[attr] -= amount / (cycleTime / 1000.0)
                                    repairers.append(mod)


                #Sort repairers by efficiency. We want to use the most efficient repairers first
                repairers.sort(key=lambda mod: mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name]) / mod.getModifiedItemAttr("capacitorNeed"), reverse = True)

                #Loop through every module until we're above peak recharge
                #Most efficient first, as we sorted earlier.
                #calculate how much the repper can rep stability & add to total
                totalPeakRecharge = self.capRecharge
                for mod in repairers:
                    if capUsed > totalPeakRecharge: break
                    cycleTime = mod.cycleTime
                    capPerSec = mod.capUse
                    if capPerSec is not None and cycleTime is not None:
                        #Check how much this repper can work
                        sustainability = min(1, (totalPeakRecharge - capUsed) / capPerSec)

                        #Add the sustainable amount
                        amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
                        sustainable[groupStoreMap[mod.item.group.name]] += sustainability * (amount / (cycleTime / 1000.0))
                        capUsed += capPerSec

            sustainable["passiveShield"] = self.calculateShieldRecharge()
            self.__sustainableTank = sustainable

        return self.__sustainableTank

    def calculateCapRecharge(self, percent = PEAK_RECHARGE):
        capacity = self.ship.getModifiedItemAttr("capacitorCapacity")
        rechargeRate = self.ship.getModifiedItemAttr("rechargeRate") / 1000.0
        return 10 / rechargeRate * sqrt(percent) * (1 - sqrt(percent)) * capacity

    def calculateShieldRecharge(self, percent = PEAK_RECHARGE):
        capacity = self.ship.getModifiedItemAttr("shieldCapacity")
        rechargeRate = self.ship.getModifiedItemAttr("shieldRechargeRate") / 1000.0
        return 10 / rechargeRate * sqrt(percent) * (1 - sqrt(percent)) * capacity

    def addDrain(self, cycleTime, capNeed, clipSize=0):
        self.__extraDrains.append((cycleTime, capNeed, clipSize))

    def removeDrain(self, i):
        del self.__extraDrains[i]

    def iterDrains(self):
        return self.__extraDrains.__iter__()

    def __generateDrain(self):
        drains = []
        capUsed = 0
        capAdded = 0
        for mod in self.modules:
            if mod.state >= State.ACTIVE:
                    cycleTime = mod.rawCycleTime or 0
                    reactivationTime = mod.getModifiedItemAttr("moduleReactivationDelay") or 0
                    fullCycleTime = cycleTime + reactivationTime
                    if fullCycleTime > 0:
                        capNeed = mod.capUse
                        if capNeed > 0:
                            capUsed += capNeed
                        else:
                            capAdded -= capNeed

                        drains.append((int(fullCycleTime), mod.getModifiedItemAttr("capacitorNeed") or 0, mod.numShots or 0))

        for fullCycleTime, capNeed, clipSize in self.iterDrains():
            drains.append((int(fullCycleTime), capNeed, clipSize))
            if capNeed > 0:
                capUsed += capNeed / (fullCycleTime / 1000.0)
            else:
                capAdded += -capNeed / (fullCycleTime / 1000.0)

        return drains, capUsed, capAdded

    def simulateCap(self):
        drains, self.__capUsed, self.__capRecharge = self.__generateDrain()
        self.__capRecharge += self.calculateCapRecharge()
        if len(drains) > 0:
            sim = capSim.CapSimulator()
            sim.init(drains)
            sim.capacitorCapacity = self.ship.getModifiedItemAttr("capacitorCapacity")
            sim.capacitorRecharge = self.ship.getModifiedItemAttr("rechargeRate")
            sim.stagger = True
            sim.scale = False
            sim.t_max = 6 * 60 * 60 * 1000
            sim.reload = self.factorReload
            sim.run()

            capState = (sim.cap_stable_low + sim.cap_stable_high) / (2 * sim.capacitorCapacity)
            self.__capStable = capState > 0
            self.__capState = min(100, capState * 100) if self.__capStable else sim.t / 1000.0
        else:
            self.__capStable = True
            self.__capState = 100

    @property
    def hp(self):
        hp = {}
        for (type, attr) in (('shield', 'shieldCapacity'), ('armor', 'armorHP'), ('hull', 'hp')):
            hp[type] = self.ship.getModifiedItemAttr(attr)

        return hp

    @property
    def ehp(self):
        if self.__ehp is None:
            if self.damagePattern is None:
                ehp = self.hp
            else:
                ehp = self.damagePattern.calculateEhp(self)
            self.__ehp = ehp

        return self.__ehp

    @property
    def tank(self):
        hps = {"passiveShield" : self.calculateShieldRecharge()}
        for type in ("shield", "armor", "hull"):
            hps["%sRepair" % type] = self.extraAttributes["%sRepair" % type]

        return hps

    @property
    def effectiveTank(self):
        if self.__effectiveTank is None:
            if self.damagePattern is None:
                ehps = self.tank
            else:
                ehps = self.damagePattern.calculateEffectiveTank(self, self.extraAttributes)

            self.__effectiveTank = ehps

        return self.__effectiveTank

    @property
    def effectiveSustainableTank(self):
        if self.__effectiveSustainableTank is None:
            if self.damagePattern is None:
                eshps = self.sustainableTank
            else:
                eshps = self.damagePattern.calculateEffectiveTank(self, self.sustainableTank)

            self.__effectiveSustainableTank = eshps

        return self.__effectiveSustainableTank


    def calculateLockTime(self, radius):
        scanRes = self.ship.getModifiedItemAttr("scanResolution")
        if scanRes is not None and scanRes > 0:
            # Yes, this function returns time in seconds, not miliseconds.
            # 40,000 is indeed the correct constant here.
            return min(40000 / scanRes / asinh(radius)**2, 30*60)
        else:
            return self.ship.getModifiedItemAttr("scanSpeed") / 1000.0

    def calculateWeaponStats(self):
        weaponDPS = 0
        droneDPS = 0
        weaponVolley = 0

        for mod in self.modules:
            dps, volley = mod.damageStats
            weaponDPS += dps
            weaponVolley += volley

        for drone in self.drones:
            droneDPS += drone.dps

        self.__weaponDPS = weaponDPS
        self.__weaponVolley = weaponVolley
        self.__droneDPS = droneDPS

    @property
    def fits(self):
        for mod in self.modules:
            if not mod.fits(self):
                return False

        return True

    def __deepcopy__(self, memo):
        copy = Fit()
        #Character and owner are not copied
        copy.character = self.__character
        copy.owner = self.owner
        copy.ship = deepcopy(self.ship, memo)
        copy.name = "%s copy" % self.name
        copy.damagePattern = self.damagePattern

        toCopy = ("modules", "drones", "implants", "boosters", "projectedModules", "projectedDrones")
        for name in toCopy:
            orig = getattr(self, name)
            c = getattr(copy, name)
            for i in orig:
                c.append(deepcopy(i, memo))

        for fit in self.projectedFits:
            copy.projectedFits.append(fit)

        return copy
Exemplo n.º 45
0
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
    """An instance of this class represents a module together with its charge and modified attributes"""
    MINING_ATTRIBUTES = ("miningAmount",)
    SYSTEM_GROUPS = ("Effect Beacon", "MassiveEnvironments", "Abyssal Hazards", "Non-Interactable Object")

    def __init__(self, item, baseItem=None, mutaplasmid=None):
        """Initialize a module from the program"""

        self.itemID = item.ID if item is not None else None
        self.baseItemID = baseItem.ID if baseItem is not None else None
        self.mutaplasmidID = mutaplasmid.ID if mutaplasmid is not None else None

        if baseItem is not None:
            # we're working with a mutated module, need to get abyssal module loaded with the base attributes
            # Note: there may be a better way of doing this, such as a metho on this classe to convert(mutaplamid). This
            # will require a bit more research though, considering there has never been a need to "swap" out the item of a Module
            # before, and there may be assumptions taken with regards to the item never changing (pre-calculated / cached results, for example)
            self.__item = eos.db.getItemWithBaseItemAttribute(self.itemID, self.baseItemID)
            self.__baseItem = baseItem
            self.__mutaplasmid = mutaplasmid
        else:
            self.__item = item
            self.__baseItem = baseItem
            self.__mutaplasmid = mutaplasmid

        if item is not None and self.isInvalid:
            raise ValueError("Passed item is not a Module")

        self.__charge = None

        self.projected = False
        self.state = FittingModuleState.ONLINE
        self.build()

    @reconstructor
    def init(self):
        """Initialize a module from the database and validate"""
        self.__item = None
        self.__baseItem = None
        self.__charge = None
        self.__mutaplasmid = None

        # we need this early if module is invalid and returns early
        self.__slot = self.dummySlot

        if self.itemID:
            self.__item = eos.db.getItem(self.itemID)
            if self.__item is None:
                pyfalog.error("Item (id: {0}) does not exist", self.itemID)
                return

        if self.baseItemID:
            self.__item = eos.db.getItemWithBaseItemAttribute(self.itemID, self.baseItemID)
            self.__baseItem = eos.db.getItem(self.baseItemID)
            self.__mutaplasmid = eos.db.getMutaplasmid(self.mutaplasmidID)
            if self.__baseItem is None:
                pyfalog.error("Base Item (id: {0}) does not exist", self.itemID)
                return

        if self.isInvalid:
            pyfalog.error("Item (id: {0}) is not a Module", self.itemID)
            return

        if self.chargeID:
            self.__charge = eos.db.getItem(self.chargeID)

        self.build()

    def build(self):
        """ Builds internal module variables from both init's """

        if self.__charge and self.__charge.category.name != "Charge":
            self.__charge = None

        self.__baseVolley = None
        self.__baseRemoteReps = None
        self.__miningyield = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.__hardpoint = FittingHardpoint.NONE
        self.__itemModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__chargeModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__slot = self.dummySlot  # defaults to None

        if self.__item:
            self.__itemModifiedAttributes.original = self.__item.attributes
            self.__itemModifiedAttributes.overrides = self.__item.overrides
            self.__hardpoint = self.__calculateHardpoint(self.__item)
            self.__slot = self.calculateSlot(self.__item)

            # Instantiate / remove mutators if this is a mutated module
            if self.__baseItem:
                for x in self.mutaplasmid.attributes:
                    attr = self.item.attributes[x.name]
                    id = attr.ID
                    if id not in self.mutators:  # create the mutator
                        Mutator(self, attr, attr.value)
                # @todo: remove attributes that are no longer part of the mutaplasmid.

            self.__itemModifiedAttributes.mutators = self.mutators

        if self.__charge:
            self.__chargeModifiedAttributes.original = self.__charge.attributes
            self.__chargeModifiedAttributes.overrides = self.__charge.overrides

    @classmethod
    def buildEmpty(cls, slot):
        empty = Module(None)
        empty.__slot = slot
        empty.dummySlot = slot
        return empty

    @classmethod
    def buildRack(cls, slot, num=None):
        empty = Rack(None)
        empty.__slot = slot
        empty.dummySlot = slot
        empty.num = num
        return empty

    @property
    def isEmpty(self):
        return self.dummySlot is not None

    @property
    def hardpoint(self):
        return self.__hardpoint

    @property
    def isInvalid(self):
        # todo: validate baseItem as well if it's set.
        if self.isEmpty:
            return False
        return (
            self.__item is None or (
                self.__item.category.name not in ("Module", "Subsystem", "Structure Module") and
                self.__item.group.name not in self.SYSTEM_GROUPS) or
            (self.item.isAbyssal and not self.isMutated))

    @property
    def isMutated(self):
        return self.baseItemID and self.mutaplasmidID

    @property
    def numCharges(self):
        return self.getNumCharges(self.charge)

    def getNumCharges(self, charge):
        if charge is None:
            charges = 0
        else:
            chargeVolume = charge.volume
            containerCapacity = self.item.capacity
            if chargeVolume is None or containerCapacity is None:
                charges = 0
            else:
                charges = int(floatUnerr(containerCapacity / chargeVolume))
        return charges

    @property
    def numShots(self):
        if self.charge is None:
            return 0
        if self.__chargeCycles is None and self.charge:
            numCharges = self.numCharges
            # Usual ammo like projectiles and missiles
            if numCharges > 0 and "chargeRate" in self.itemModifiedAttributes:
                self.__chargeCycles = self.__calculateAmmoShots()
            # Frequency crystals (combat and mining lasers)
            elif numCharges > 0 and "crystalsGetDamaged" in self.chargeModifiedAttributes:
                self.__chargeCycles = self.__calculateCrystalShots()
            # Scripts and stuff
            else:
                self.__chargeCycles = 0
            return self.__chargeCycles
        else:
            return self.__chargeCycles

    @property
    def modPosition(self):
        return self.getModPosition()

    def getModPosition(self, fit=None):
        # Pass in fit for reliability. When it's not passed, we rely on owner and owner
        # is set by sqlalchemy during flush
        fit = fit if fit is not None else self.owner
        if fit:
            container = fit.projectedModules if self.isProjected else fit.modules
            try:
                return container.index(self)
            except ValueError:
                return None
        return None

    @property
    def isProjected(self):
        if self.owner:
            return self in self.owner.projectedModules
        return None

    @property
    def isExclusiveSystemEffect(self):
        return self.item.group.name in ("Effect Beacon", "Non-Interactable Object", "MassiveEnvironments")

    @property
    def isCapitalSize(self):
        return self.getModifiedItemAttr("volume", 0) >= 4000

    @property
    def hpBeforeReload(self):
        """
        If item is some kind of repairer with charges, calculate
        HP it reps before going into reload.
        """
        cycles = self.numShots
        armorRep = self.getModifiedItemAttr("armorDamageAmount") or 0
        shieldRep = self.getModifiedItemAttr("shieldBonus") or 0
        if not cycles or (not armorRep and not shieldRep):
            return 0
        hp = round((armorRep + shieldRep) * cycles)
        return hp

    def __calculateAmmoShots(self):
        if self.charge is not None:
            # Set number of cycles before reload is needed
            # numcycles = math.floor(module_capacity / (module_volume * module_chargerate))
            chargeRate = self.getModifiedItemAttr("chargeRate")
            numCharges = self.numCharges
            numShots = math.floor(numCharges / chargeRate)
        else:
            numShots = None
        return numShots

    def __calculateCrystalShots(self):
        if self.charge is not None:
            if self.getModifiedChargeAttr("crystalsGetDamaged") == 1:
                # For depletable crystals, calculate average amount of shots before it's destroyed
                hp = self.getModifiedChargeAttr("hp")
                chance = self.getModifiedChargeAttr("crystalVolatilityChance")
                damage = self.getModifiedChargeAttr("crystalVolatilityDamage")
                crystals = self.numCharges
                numShots = math.floor((crystals * hp) / (damage * chance))
            else:
                # Set 0 (infinite) for permanent crystals like t1 laser crystals
                numShots = 0
        else:
            numShots = None
        return numShots

    @property
    def maxRange(self):
        attrs = ("maxRange", "shieldTransferRange", "powerTransferRange",
                 "energyDestabilizationRange", "empFieldRange",
                 "ecmBurstRange", "warpScrambleRange", "cargoScanRange",
                 "shipScanRange", "surveyScanRange")
        for attr in attrs:
            maxRange = self.getModifiedItemAttr(attr, None)
            if maxRange is not None:
                return maxRange
        if self.charge is not None:
            try:
                chargeName = self.charge.group.name
            except AttributeError:
                pass
            else:
                if chargeName in ("Scanner Probe", "Survey Probe"):
                    return None
            # Source: http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1307419&page=1#15
            # D_m = V_m * (T_m + T_0*[exp(- T_m/T_0)-1])
            maxVelocity = self.getModifiedChargeAttr("maxVelocity")
            flightTime = self.getModifiedChargeAttr("explosionDelay") / 1000.0
            mass = self.getModifiedChargeAttr("mass")
            agility = self.getModifiedChargeAttr("agility")
            if maxVelocity and (flightTime or mass or agility):
                accelTime = min(flightTime, mass * agility / 1000000)
                # Average distance done during acceleration
                duringAcceleration = maxVelocity / 2 * accelTime
                # Distance done after being at full speed
                fullSpeed = maxVelocity * (flightTime - accelTime)
                return duringAcceleration + fullSpeed

    @property
    def falloff(self):
        attrs = ("falloffEffectiveness", "falloff", "shipScanFalloff")
        for attr in attrs:
            falloff = self.getModifiedItemAttr(attr, None)
            if falloff is not None:
                return falloff

    @property
    def slot(self):
        return self.__slot

    @property
    def itemModifiedAttributes(self):
        return self.__itemModifiedAttributes

    @property
    def chargeModifiedAttributes(self):
        return self.__chargeModifiedAttributes

    @property
    def item(self):
        return self.__item if self.__item != 0 else None

    @property
    def baseItem(self):
        return self.__baseItem

    @property
    def mutaplasmid(self):
        return self.__mutaplasmid

    @property
    def charge(self):
        return self.__charge if self.__charge != 0 else None

    @charge.setter
    def charge(self, charge):
        self.__charge = charge
        if charge is not None:
            self.chargeID = charge.ID
            self.__chargeModifiedAttributes.original = charge.attributes
            self.__chargeModifiedAttributes.overrides = charge.overrides
        else:
            self.chargeID = None
            self.__chargeModifiedAttributes.original = None
            self.__chargeModifiedAttributes.overrides = {}

        self.__itemModifiedAttributes.clear()

    @property
    def miningStats(self):
        if self.__miningyield is None:
            if self.isEmpty:
                self.__miningyield = 0
            else:
                if self.state >= FittingModuleState.ACTIVE:
                    volley = self.getModifiedItemAttr("specialtyMiningAmount") or self.getModifiedItemAttr(
                            "miningAmount") or 0
                    if volley:
                        cycleParams = self.getCycleParameters()
                        if cycleParams is None:
                            self.__miningyield = 0
                        else:
                            cycleTime = cycleParams.averageTime
                            self.__miningyield = volley / (cycleTime / 1000.0)
                    else:
                        self.__miningyield = 0
                else:
                    self.__miningyield = 0

        return self.__miningyield

    def isDealingDamage(self, ignoreState=False):
        volleyParams = self.getVolleyParameters(ignoreState=ignoreState)
        for volley in volleyParams.values():
            if volley.total > 0:
                return True
        return False

    def getVolleyParameters(self, spoolOptions=None, targetResists=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)
            if dmgDuration != 0 and dmgSubcycle != 0:
                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(targetResists, "emAmount", 0)),
                thermal=volleyValue.thermal * spoolMultiplier * (1 - getattr(targetResists, "thermalAmount", 0)),
                kinetic=volleyValue.kinetic * spoolMultiplier * (1 - getattr(targetResists, "kineticAmount", 0)),
                explosive=volleyValue.explosive * spoolMultiplier * (1 - getattr(targetResists, "explosiveAmount", 0)))
        return adjustedVolley

    def getVolley(self, spoolOptions=None, targetResists=None, ignoreState=False):
        volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetResists=targetResists, ignoreState=ignoreState)
        if len(volleyParams) == 0:
            return DmgTypes(0, 0, 0, 0)
        return volleyParams[min(volleyParams)]

    def getDps(self, spoolOptions=None, targetResists=None, ignoreState=False):
        dmgDuringCycle = DmgTypes(0, 0, 0, 0)
        cycleParams = self.getCycleParameters()
        if cycleParams is None:
            return dmgDuringCycle
        volleyParams = self.getVolleyParameters(spoolOptions=spoolOptions, targetResists=targetResists, 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 getRemoteReps(self, spoolOptions=None, ignoreState=False):
        if self.isEmpty or (self.state < FittingModuleState.ACTIVE and not ignoreState):
            return None, 0

        def getBaseRemoteReps(module):
            remoteModuleGroups = {
                "Remote Armor Repairer": "Armor",
                "Ancillary Remote Armor Repairer": "Armor",
                "Mutadaptive Remote Armor Repairer": "Armor",
                "Remote Hull Repairer": "Hull",
                "Remote Shield Booster": "Shield",
                "Ancillary Remote Shield Booster": "Shield",
                "Remote Capacitor Transmitter": "Capacitor"}
            rrType = remoteModuleGroups.get(module.item.group.name, None)
            if not rrType:
                return None, 0
            if rrType == "Hull":
                rrAmount = module.getModifiedItemAttr("structureDamageAmount", 0)
            elif rrType == "Armor":
                rrAmount = module.getModifiedItemAttr("armorDamageAmount", 0)
            elif rrType == "Shield":
                rrAmount = module.getModifiedItemAttr("shieldBonus", 0)
            elif rrType == "Capacitor":
                rrAmount = module.getModifiedItemAttr("powerTransferAmount", 0)
            else:
                return None, 0
            if rrAmount:
                cycleParams = self.getCycleParameters()
                if cycleParams is None:
                    return None, 0
                rrAmount *= 1 / (cycleParams.averageTime / 1000)
                if module.item.group.name == "Ancillary Remote Armor Repairer" and module.charge:
                    rrAmount *= module.getModifiedItemAttr("chargedArmorDamageMultiplier", 1)

            return rrType, rrAmount

        if self.__baseRemoteReps is None:
            self.__baseRemoteReps = getBaseRemoteReps(self)

        rrType, rrAmount = self.__baseRemoteReps

        if rrType and rrAmount and self.item.group.name == "Mutadaptive Remote Armor Repairer":
            spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
            spoolBoost = calculateSpoolup(
                self.getModifiedItemAttr("repairMultiplierBonusMax", 0),
                self.getModifiedItemAttr("repairMultiplierBonusPerCycle", 0),
                self.rawCycleTime / 1000, spoolType, spoolAmount)[0]
            rrAmount *= (1 + spoolBoost)

        return rrType, rrAmount

    def getSpoolData(self, spoolOptions=None):
        weaponMultMax = self.getModifiedItemAttr("damageMultiplierBonusMax", 0)
        weaponMultPerCycle = self.getModifiedItemAttr("damageMultiplierBonusPerCycle", 0)
        if weaponMultMax and weaponMultPerCycle:
            spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
            _, spoolCycles, spoolTime = calculateSpoolup(
                weaponMultMax, weaponMultPerCycle,
                self.rawCycleTime / 1000, spoolType, spoolAmount)
            return spoolCycles, spoolTime
        rrMultMax = self.getModifiedItemAttr("repairMultiplierBonusMax", 0)
        rrMultPerCycle = self.getModifiedItemAttr("repairMultiplierBonusPerCycle", 0)
        if rrMultMax and rrMultPerCycle:
            spoolType, spoolAmount = resolveSpoolOptions(spoolOptions, self)
            _, spoolCycles, spoolTime = calculateSpoolup(
                rrMultMax, rrMultPerCycle,
                self.rawCycleTime / 1000, spoolType, spoolAmount)
            return spoolCycles, spoolTime
        return 0, 0

    @property
    def reloadTime(self):
        # Get reload time from attrs first, then use
        # custom value specified otherwise (e.g. in effects)
        moduleReloadTime = self.getModifiedItemAttr("reloadTime")
        if moduleReloadTime is None:
            moduleReloadTime = self.__reloadTime
        return moduleReloadTime or 0.0

    @reloadTime.setter
    def reloadTime(self, milliseconds):
        self.__reloadTime = milliseconds

    @property
    def forceReload(self):
        return self.__reloadForce

    @forceReload.setter
    def forceReload(self, type):
        self.__reloadForce = type

    def fits(self, fit, hardpointLimit=True):
        """
        Function that determines if a module can be fit to the ship. We always apply slot restrictions no matter what
        (too many assumptions made on this), however all other fitting restrictions are optional
        """

        slot = self.slot
        if fit.getSlotsFree(slot) <= (0 if self.owner != fit else -1):
            return False

        fits = self.__fitRestrictions(fit, hardpointLimit)

        if not fits and fit.ignoreRestrictions:
            self.restrictionOverridden = True
            fits = True
        elif fits and fit.ignoreRestrictions:
            self.restrictionOverridden = False

        return fits

    def __fitRestrictions(self, fit, hardpointLimit=True):

        if not fit.canFit(self.item):
            return False

        # EVE doesn't let capital modules be fit onto subcapital hulls. Confirmed by CCP Larrikin that this is dictated
        # by the modules volume. See GH issue #1096
        if not isinstance(fit.ship, Citadel) and fit.ship.getModifiedItemAttr("isCapitalSize", 0) != 1 and self.isCapitalSize:
            return False

        # If the mod is a subsystem, don't let two subs in the same slot fit
        if self.slot == FittingSlot.SUBSYSTEM:
            subSlot = self.getModifiedItemAttr("subSystemSlot")
            for mod in fit.modules:
                if mod is self:
                    continue
                if mod.getModifiedItemAttr("subSystemSlot") == subSlot:
                    return False

        # Check rig sizes
        if self.slot == FittingSlot.RIG:
            if self.getModifiedItemAttr("rigSize") != fit.ship.getModifiedItemAttr("rigSize"):
                return False

        # Check max group fitted
        max = self.getModifiedItemAttr("maxGroupFitted", None)
        if max is not None:
            current = 0  # if self.owner != fit else -1  # Disabled, see #1278
            for mod in fit.modules:
                if (mod.item and mod.item.groupID == self.item.groupID and
                        self.getModPosition(fit) != mod.getModPosition(fit)):
                    current += 1

            if current >= max:
                return False

        # Check this only if we're told to do so
        if hardpointLimit:
            if fit.getHardpointsFree(self.hardpoint) < 1:
                return False

        return True

    def isValidState(self, state):
        """
        Check if the state is valid for this module, without considering other modules at all
        """
        # Check if we're within bounds
        if state < -1 or state > 2:
            return False
        elif state >= FittingModuleState.ACTIVE and (not self.item.isType("active") or self.getModifiedItemAttr('activationBlocked') > 0):
            return False
        elif state == FittingModuleState.OVERHEATED and not self.item.isType("overheat"):
            return False
        else:
            return True

    def getMaxState(self, proposedState=None):
        states = sorted((s for s in FittingModuleState if proposedState is None or s <= proposedState), reverse=True)
        for state in states:
            if self.isValidState(state):
                return state

    def canHaveState(self, state=None, projectedOnto=None):
        """
        Check with other modules if there are restrictions that might not allow this module to be activated
        """
        # If we're going to set module to offline or online for local modules or offline for projected,
        # it should be fine for all cases
        item = self.item
        if (state <= FittingModuleState.ONLINE and projectedOnto is None) or (state <= FittingModuleState.OFFLINE):
            return True

        # Check if the local module is over it's max limit; if it's not, we're fine
        maxGroupActive = self.getModifiedItemAttr("maxGroupActive", None)
        if maxGroupActive is None and projectedOnto is None:
            return True

        # Following is applicable only to local modules, we do not want to limit projected
        if projectedOnto is None:
            currActive = 0
            group = item.group.name
            for mod in self.owner.modules:
                currItem = getattr(mod, "item", None)
                if mod.state >= FittingModuleState.ACTIVE and currItem is not None and currItem.group.name == group:
                    currActive += 1
                if currActive > maxGroupActive:
                    break
            return currActive <= maxGroupActive
        # For projected, we're checking if ship is vulnerable to given item
        else:
            # Do not allow to apply offensive modules on ship with offensive module immunite, with few exceptions
            # (all effects which apply instant modification are exception, generally speaking)
            if item.offensive and projectedOnto.ship.getModifiedItemAttr("disallowOffensiveModifiers") == 1:
                offensiveNonModifiers = {"energyDestabilizationNew",
                                         "leech",
                                         "energyNosferatuFalloff",
                                         "energyNeutralizerFalloff"}
                if not offensiveNonModifiers.intersection(set(item.effects)):
                    return False
            # If assistive modules are not allowed, do not let to apply these altogether
            if item.assistive and projectedOnto.ship.getModifiedItemAttr("disallowAssistance") == 1:
                return False
            return True

    def isValidCharge(self, charge):
        # Check sizes, if 'charge size > module volume' it won't fit
        if charge is None:
            return True
        chargeVolume = charge.volume
        moduleCapacity = self.item.capacity
        if chargeVolume is not None and moduleCapacity is not None and chargeVolume > moduleCapacity:
            return False

        itemChargeSize = self.getModifiedItemAttr("chargeSize")
        if itemChargeSize > 0:
            chargeSize = charge.getAttribute('chargeSize')
            if itemChargeSize != chargeSize:
                return False

        chargeGroup = charge.groupID
        for i in range(5):
            itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None)
            if itemChargeGroup is None:
                continue
            if itemChargeGroup == chargeGroup:
                return True

        return False

    def getValidCharges(self):
        validCharges = set()
        for i in range(5):
            itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i), None)
            if itemChargeGroup is not None:
                g = eos.db.getGroup(int(itemChargeGroup), eager="items.attributes")
                if g is None:
                    continue
                for singleItem in g.items:
                    if singleItem.published and self.isValidCharge(singleItem):
                        validCharges.add(singleItem)

        return validCharges

    @staticmethod
    def __calculateHardpoint(item):
        effectHardpointMap = {
            "turretFitted"  : FittingHardpoint.TURRET,
            "launcherFitted": FittingHardpoint.MISSILE
        }

        if item is None:
            return FittingHardpoint.NONE

        for effectName, slot in effectHardpointMap.items():
            if effectName in item.effects:
                return slot

        return FittingHardpoint.NONE

    @staticmethod
    def calculateSlot(item):
        effectSlotMap = {
            "rigSlot"    : FittingSlot.RIG.value,
            "loPower"    : FittingSlot.LOW.value,
            "medPower"   : FittingSlot.MED.value,
            "hiPower"    : FittingSlot.HIGH.value,
            "subSystem"  : FittingSlot.SUBSYSTEM.value,
            "serviceSlot": FittingSlot.SERVICE.value
        }
        if item is None:
            return None
        for effectName, slot in effectSlotMap.items():
            if effectName in item.effects:
                return slot
        if item.group.name in Module.SYSTEM_GROUPS:
            return FittingSlot.SYSTEM

        return None

    @validates("ID", "itemID", "ammoID")
    def validator(self, key, val):
        map = {
            "ID"    : lambda _val: isinstance(_val, int),
            "itemID": lambda _val: _val is None or isinstance(_val, int),
            "ammoID": lambda _val: isinstance(_val, int)
        }

        if not map[key](val):
            raise ValueError(str(val) + " is not a valid value for " + key)
        else:
            return val

    def clear(self):
        self.__baseVolley = None
        self.__baseRemoteReps = None
        self.__miningyield = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.itemModifiedAttributes.clear()
        self.chargeModifiedAttributes.clear()

    def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, gang=False):
        # We will run the effect when two conditions are met:
        # 1: It makes sense to run the effect
        #    The effect is either offline
        #    or the effect is passive and the module is in the online state (or higher)

        #    or the effect is active and the module is in the active state (or higher)
        #    or the effect is overheat and the module is in the overheated state (or higher)
        # 2: the runtimes match

        if self.projected or forceProjected:
            context = "projected", "module"
            projected = True
        else:
            context = ("module",)
            projected = False

        if self.charge is not None:
            # fix for #82 and it's regression #106
            if not projected or (self.projected and not forceProjected) or gang:
                for effect in self.charge.effects.values():
                    if (
                        effect.runTime == runTime and
                        effect.activeByDefault and (
                            effect.isType("offline") or
                            (effect.isType("passive") and self.state >= FittingModuleState.ONLINE) or
                            (effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) and
                        (not gang or (gang and effect.isType("gang")))
                    ):
                        contexts = ("moduleCharge",)
                        # For gang effects, we pass in the effect itself as an argument. However, to avoid going through all
                        # the effect definitions and defining this argument, do a simple try/catch here and be done with it.
                        # @todo: possibly fix this
                        try:
                            effect.handler(fit, self, contexts, effect=effect)
                        except:
                            effect.handler(fit, self, contexts)

        if self.item:
            if self.state >= FittingModuleState.OVERHEATED:
                for effect in self.item.effects.values():
                    if effect.runTime == runTime and \
                            effect.isType("overheat") \
                            and not forceProjected \
                            and effect.activeByDefault \
                            and ((gang and effect.isType("gang")) or not gang):
                        effect.handler(fit, self, context)

            for effect in self.item.effects.values():
                if effect.runTime == runTime and \
                        effect.activeByDefault and \
                        (effect.isType("offline") or
                         (effect.isType("passive") and self.state >= FittingModuleState.ONLINE) or
                         (effect.isType("active") and self.state >= FittingModuleState.ACTIVE)) \
                        and ((projected and effect.isType("projected")) or not projected) \
                        and ((gang and effect.isType("gang")) or not gang):
                    try:
                        effect.handler(fit, self, context, effect=effect)
                    except:
                        effect.handler(fit, self, context)

    def getCycleParameters(self, reloadOverride=None):
        """Copied from new eos as well"""
        # Determine if we'll take into account reload time or not
        if reloadOverride is not None:
            factorReload = reloadOverride
        else:
            factorReload = self.owner.factorReload if self.forceReload is None else self.forceReload

        cycles_until_reload = self.numShots
        if cycles_until_reload == 0:
            cycles_until_reload = math.inf

        active_time = self.rawCycleTime
        if active_time == 0:
            return None
        forced_inactive_time = self.reactivationDelay
        reload_time = self.reloadTime
        # Effects which cannot be reloaded have the same processing whether
        # caller wants to take reload time into account or not
        if reload_time is None and cycles_until_reload < math.inf:
            final_cycles = 1
            early_cycles = cycles_until_reload - final_cycles
            # Single cycle until effect cannot run anymore
            if early_cycles == 0:
                return CycleInfo(active_time, 0, 1)
            # Multiple cycles with the same parameters
            if forced_inactive_time == 0:
                return CycleInfo(active_time, 0, cycles_until_reload)
            # Multiple cycles with different parameters
            return CycleSequence((
                CycleInfo(active_time, forced_inactive_time, early_cycles),
                CycleInfo(active_time, 0, final_cycles)
            ), 1)
        # Module cycles the same way all the time in 3 cases:
        # 1) caller doesn't want to take into account reload time
        # 2) effect does not have to reload anything to keep running
        # 3) effect has enough time to reload during inactivity periods
        if (
            not factorReload or
            cycles_until_reload == math.inf or
            forced_inactive_time >= reload_time
        ):
            return CycleInfo(active_time, forced_inactive_time, math.inf)
        # We've got to take reload into consideration
        else:
            final_cycles = 1
            early_cycles = cycles_until_reload - final_cycles
            # If effect has to reload after each its cycle, then its parameters
            # are the same all the time
            if early_cycles == 0:
                return CycleInfo(active_time, reload_time, math.inf)
            return CycleSequence((
                CycleInfo(active_time, forced_inactive_time, early_cycles),
                CycleInfo(active_time, reload_time, final_cycles)
            ), math.inf)

    @property
    def rawCycleTime(self):
        speed = max(
                self.getModifiedItemAttr("speed", 0),  # Most weapons
                self.getModifiedItemAttr("duration", 0),  # Most average modules
                self.getModifiedItemAttr("durationSensorDampeningBurstProjector", 0),
                self.getModifiedItemAttr("durationTargetIlluminationBurstProjector", 0),
                self.getModifiedItemAttr("durationECMJammerBurstProjector", 0),
                self.getModifiedItemAttr("durationWeaponDisruptionBurstProjector", 0)
        )
        return speed

    @property
    def disallowRepeatingAction(self):
        return self.getModifiedItemAttr("disallowRepeatingActivation", 0)

    @property
    def reactivationDelay(self):
        return self.getModifiedItemAttr("moduleReactivationDelay", 0)

    @property
    def capUse(self):
        capNeed = self.getModifiedItemAttr("capacitorNeed")
        if capNeed and self.state >= FittingModuleState.ACTIVE:
            cycleParams = self.getCycleParameters()
            if cycleParams is None:
                return 0
            cycleTime = cycleParams.averageTime
            if cycleTime > 0:
                capUsed = capNeed / (cycleTime / 1000.0)
                return capUsed
        else:
            return 0

    @staticmethod
    def getProposedState(mod, click, proposedState=None):
        pyfalog.debug("Get proposed state for module.")
        if mod.slot == FittingSlot.SUBSYSTEM or mod.isEmpty:
            return FittingModuleState.ONLINE

        if mod.slot == FittingSlot.SYSTEM:
            transitionMap = ProjectedSystem
        else:
            transitionMap = ProjectedMap if mod.projected else LocalMap

        currState = mod.state

        if proposedState is not None:
            state = proposedState
        elif click == "right":
            state = FittingModuleState.OVERHEATED
        elif click == "ctrl":
            state = FittingModuleState.OFFLINE
        else:
            state = transitionMap[currState]
            # If passive module tries to transition into online and fails,
            # put it to passive instead
            if not mod.isValidState(state) and currState == FittingModuleState.ONLINE:
                state = FittingModuleState.OFFLINE

        return mod.getMaxState(proposedState=state)

    def __deepcopy__(self, memo):
        item = self.item
        if item is None:
            copy = Module.buildEmpty(self.slot)
        else:
            copy = Module(self.item, self.baseItem, self.mutaplasmid)
        copy.charge = self.charge
        copy.state = self.state
        copy.spoolType = self.spoolType
        copy.spoolAmount = self.spoolAmount

        for x in self.mutators.values():
            Mutator(copy, x.attribute, x.value)

        return copy

    def rebase(self, item):
        state = self.state
        charge = self.charge
        Module.__init__(self, item, self.baseItem, self.mutaplasmid)
        self.state = state
        if self.isValidCharge(charge):
            self.charge = charge
        for x in self.mutators.values():
            Mutator(self, x.attribute, x.value)

    def __repr__(self):
        if self.item:
            return "Module(ID={}, name={}) at {}".format(
                    self.item.ID, self.item.name, hex(id(self))
            )
        else:
            return "EmptyModule() at {}".format(hex(id(self)))
Exemplo n.º 46
0
class Module(HandledItem, HandledCharge, ItemAttrShortcut, ChargeAttrShortcut):
    """An instance of this class represents a module together with its charge and modified attributes"""
    DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
    MINING_ATTRIBUTES = ("miningAmount",)

    def __init__(self, item):
        """Initialize a module from the program"""
        self.__item = item

        if item is not None and self.isInvalid:
            raise ValueError("Passed item is not a Module")

        self.__charge = None
        self.itemID = item.ID if item is not None else None
        self.projected = False
        self.state = State.ONLINE
        self.build()

    @reconstructor
    def init(self):
        """Initialize a module from the database and validate"""
        self.__item = None
        self.__charge = None

        # we need this early if module is invalid and returns early
        self.__slot = self.dummySlot

        if self.itemID:
            self.__item = eos.db.getItem(self.itemID)
            if self.__item is None:
                pyfalog.error("Item (id: {0}) does not exist", self.itemID)
                return

        if self.isInvalid:
            pyfalog.error("Item (id: {0}) is not a Module", self.itemID)
            return

        if self.chargeID:
            self.__charge = eos.db.getItem(self.chargeID)

        self.build()

    def build(self):
        """ Builds internal module variables from both init's """

        if self.__charge and self.__charge.category.name != "Charge":
            self.__charge = None

        self.__dps = None
        self.__miningyield = None
        self.__volley = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.__hardpoint = Hardpoint.NONE
        self.__itemModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__chargeModifiedAttributes = ModifiedAttributeDict(parent=self)
        self.__slot = self.dummySlot  # defaults to None

        if self.__item:
            self.__itemModifiedAttributes.original = self.__item.attributes
            self.__itemModifiedAttributes.overrides = self.__item.overrides
            self.__hardpoint = self.__calculateHardpoint(self.__item)
            self.__slot = self.__calculateSlot(self.__item)
        if self.__charge:
            self.__chargeModifiedAttributes.original = self.__charge.attributes
            self.__chargeModifiedAttributes.overrides = self.__charge.overrides

    @classmethod
    def buildEmpty(cls, slot):
        empty = Module(None)
        empty.__slot = slot
        empty.dummySlot = slot
        return empty

    @classmethod
    def buildRack(cls, slot):
        empty = Rack(None)
        empty.__slot = slot
        empty.dummySlot = slot
        return empty

    @property
    def isEmpty(self):
        return self.dummySlot is not None

    @property
    def hardpoint(self):
        return self.__hardpoint

    @property
    def isInvalid(self):
        if self.isEmpty:
            return False
        return self.__item is None or \
               (self.__item.category.name not in ("Module", "Subsystem", "Structure Module") and
                self.__item.group.name != "Effect Beacon")

    @property
    def numCharges(self):
        if self.charge is None:
            charges = 0
        else:
            chargeVolume = self.charge.volume
            containerCapacity = self.item.capacity
            if chargeVolume is None or containerCapacity is None:
                charges = 0
            else:
                charges = floor(containerCapacity / chargeVolume)
        return int(charges)

    @property
    def numShots(self):
        if self.charge is None:
            return None
        if self.__chargeCycles is None and self.charge:
            numCharges = self.numCharges
            # Usual ammo like projectiles and missiles
            if numCharges > 0 and "chargeRate" in self.itemModifiedAttributes:
                self.__chargeCycles = self.__calculateAmmoShots()
            # Frequency crystals (combat and mining lasers)
            elif numCharges > 0 and "crystalsGetDamaged" in self.chargeModifiedAttributes:
                self.__chargeCycles = self.__calculateCrystalShots()
            # Scripts and stuff
            else:
                self.__chargeCycles = 0
            return self.__chargeCycles
        else:
            return self.__chargeCycles

    @property
    def modPosition(self):
        if self.owner:
            return self.owner.modules.index(self)

    @property
    def isCapitalSize(self):
        return self.getModifiedItemAttr("volume", 0) >= 4000

    @property
    def hpBeforeReload(self):
        """
        If item is some kind of repairer with charges, calculate
        HP it reps before going into reload.
        """
        cycles = self.numShots
        armorRep = self.getModifiedItemAttr("armorDamageAmount") or 0
        shieldRep = self.getModifiedItemAttr("shieldBonus") or 0
        if not cycles or (not armorRep and not shieldRep):
            return None
        hp = round((armorRep + shieldRep) * cycles)
        return hp

    def __calculateAmmoShots(self):
        if self.charge is not None:
            # Set number of cycles before reload is needed
            # numcycles = math.floor(module_capacity / (module_volume * module_chargerate))
            chargeRate = self.getModifiedItemAttr("chargeRate")
            numCharges = self.numCharges
            numShots = floor(numCharges / chargeRate)
        else:
            numShots = None
        return numShots

    def __calculateCrystalShots(self):
        if self.charge is not None:
            if self.getModifiedChargeAttr("crystalsGetDamaged") == 1:
                # For depletable crystals, calculate average amount of shots before it's destroyed
                hp = self.getModifiedChargeAttr("hp")
                chance = self.getModifiedChargeAttr("crystalVolatilityChance")
                damage = self.getModifiedChargeAttr("crystalVolatilityDamage")
                crystals = self.numCharges
                numShots = floor((crystals * hp) / (damage * chance))
            else:
                # Set 0 (infinite) for permanent crystals like t1 laser crystals
                numShots = 0
        else:
            numShots = None
        return numShots

    @property
    def maxRange(self):
        attrs = ("maxRange", "shieldTransferRange", "powerTransferRange",
                 "energyDestabilizationRange", "empFieldRange",
                 "ecmBurstRange", "warpScrambleRange", "cargoScanRange",
                 "shipScanRange", "surveyScanRange")
        for attr in attrs:
            maxRange = self.getModifiedItemAttr(attr)
            if maxRange is not None:
                return maxRange
        if self.charge is not None:
            try:
                chargeName = self.charge.group.name
            except AttributeError:
                pass
            else:
                if chargeName in ("Scanner Probe", "Survey Probe"):
                    return None
            # Source: http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1307419&page=1#15
            # D_m = V_m * (T_m + T_0*[exp(- T_m/T_0)-1])
            maxVelocity = self.getModifiedChargeAttr("maxVelocity")
            flightTime = self.getModifiedChargeAttr("explosionDelay") / 1000.0
            mass = self.getModifiedChargeAttr("mass")
            agility = self.getModifiedChargeAttr("agility")
            if maxVelocity and (flightTime or mass or agility):
                accelTime = min(flightTime, mass * agility / 1000000)
                # Average distance done during acceleration
                duringAcceleration = maxVelocity / 2 * accelTime
                # Distance done after being at full speed
                fullSpeed = maxVelocity * (flightTime - accelTime)
                return duringAcceleration + fullSpeed

    @property
    def falloff(self):
        attrs = ("falloffEffectiveness", "falloff", "shipScanFalloff")
        for attr in attrs:
            falloff = self.getModifiedItemAttr(attr)
            if falloff is not None:
                return falloff

    @property
    def slot(self):
        return self.__slot

    @property
    def itemModifiedAttributes(self):
        return self.__itemModifiedAttributes

    @property
    def chargeModifiedAttributes(self):
        return self.__chargeModifiedAttributes

    @property
    def item(self):
        return self.__item if self.__item != 0 else None

    @property
    def charge(self):
        return self.__charge if self.__charge != 0 else None

    @charge.setter
    def charge(self, charge):
        self.__charge = charge
        if charge is not None:
            self.chargeID = charge.ID
            self.__chargeModifiedAttributes.original = charge.attributes
            self.__chargeModifiedAttributes.overrides = charge.overrides
        else:
            self.chargeID = None
            self.__chargeModifiedAttributes.original = None
            self.__chargeModifiedAttributes.overrides = {}

        self.__itemModifiedAttributes.clear()

    def damageStats(self, targetResists):
        if self.__dps is None:
            self.__dps = 0
            self.__volley = 0

            if not self.isEmpty and self.state >= State.ACTIVE:
                if self.charge:
                    func = self.getModifiedChargeAttr
                else:
                    func = self.getModifiedItemAttr

                volley = sum(map(
                        lambda attr: (func("%sDamage" % attr) or 0) * (1 - getattr(targetResists, "%sAmount" % attr, 0)),
                        self.DAMAGE_TYPES))
                volley *= self.getModifiedItemAttr("damageMultiplier") or 1
                if volley:
                    cycleTime = self.cycleTime
                    # Some weapons repeat multiple times in one cycle (think doomsdays)
                    # Get the number of times it fires off
                    weaponDoT = max(
                            self.getModifiedItemAttr("doomsdayDamageDuration", 1) / self.getModifiedItemAttr("doomsdayDamageCycleTime", 1),
                            1
                    )

                    self.__volley = volley
                    self.__dps = (volley * weaponDoT) / (cycleTime / 1000.0)

        return self.__dps, self.__volley

    @property
    def miningStats(self):
        if self.__miningyield is None:
            if self.isEmpty:
                self.__miningyield = 0
            else:
                if self.state >= State.ACTIVE:
                    volley = self.getModifiedItemAttr("specialtyMiningAmount") or self.getModifiedItemAttr(
                            "miningAmount") or 0
                    if volley:
                        cycleTime = self.cycleTime
                        self.__miningyield = volley / (cycleTime / 1000.0)
                    else:
                        self.__miningyield = 0
                else:
                    self.__miningyield = 0

        return self.__miningyield

    @property
    def dps(self):
        return self.damageStats(None)[0]

    @property
    def volley(self):
        return self.damageStats(None)[1]

    @property
    def reloadTime(self):
        # Get reload time from attrs first, then use
        # custom value specified otherwise (e.g. in effects)
        moduleReloadTime = self.getModifiedItemAttr("reloadTime")
        if moduleReloadTime is None:
            moduleReloadTime = self.__reloadTime
        return moduleReloadTime or 0.0

    @reloadTime.setter
    def reloadTime(self, milliseconds):
        self.__reloadTime = milliseconds

    @property
    def forceReload(self):
        return self.__reloadForce

    @forceReload.setter
    def forceReload(self, type):
        self.__reloadForce = type

    def fits(self, fit, hardpointLimit=True):
        """
        Function that determines if a module can be fit to the ship. We always apply slot restrictions no matter what
        (too many assumptions made on this), however all other fitting restrictions are optional
        """

        slot = self.slot
        if fit.getSlotsFree(slot) <= (0 if self.owner != fit else -1):
            return False

        fits = self.__fitRestrictions(fit, hardpointLimit)

        if not fits and fit.ignoreRestrictions:
            self.restrictionOverridden = True
            fits = True

        return fits

    def __fitRestrictions(self, fit, hardpointLimit=True):
        # Check ship type restrictions
        fitsOnType = set()
        fitsOnGroup = set()

        shipType = self.getModifiedItemAttr("fitsToShipType")
        if shipType is not None:
            fitsOnType.add(shipType)

        for attr in self.itemModifiedAttributes.keys():
            if attr.startswith("canFitShipType"):
                shipType = self.getModifiedItemAttr(attr)
                if shipType is not None:
                    fitsOnType.add(shipType)

        for attr in self.itemModifiedAttributes.keys():
            if attr.startswith("canFitShipGroup"):
                shipGroup = self.getModifiedItemAttr(attr)
                if shipGroup is not None:
                    fitsOnGroup.add(shipGroup)

        if (len(fitsOnGroup) > 0 or len(fitsOnType) > 0) \
                and fit.ship.item.group.ID not in fitsOnGroup \
                and fit.ship.item.ID not in fitsOnType:
            return False

        # Citadel modules are now under a new category, so we can check this to ensure only structure modules can fit on a citadel
        if isinstance(fit.ship, Citadel) and self.item.category.name != "Structure Module" or \
                not isinstance(fit.ship, Citadel) and self.item.category.name == "Structure Module":
            return False

        # EVE doesn't let capital modules be fit onto subcapital hulls. Confirmed by CCP Larrikin that this is dictated
        # by the modules volume. See GH issue #1096
        if not isinstance(fit.ship, Citadel) and fit.ship.getModifiedItemAttr("isCapitalSize", 0) != 1 and self.isCapitalSize:
            return False

        # If the mod is a subsystem, don't let two subs in the same slot fit
        if self.slot == Slot.SUBSYSTEM:
            subSlot = self.getModifiedItemAttr("subSystemSlot")
            for mod in fit.modules:
                if mod.getModifiedItemAttr("subSystemSlot") == subSlot:
                    return False

        # Check rig sizes
        if self.slot == Slot.RIG:
            if self.getModifiedItemAttr("rigSize") != fit.ship.getModifiedItemAttr("rigSize"):
                return False

        # Check max group fitted
        max = self.getModifiedItemAttr("maxGroupFitted")
        if max is not None:
            current = 0  # if self.owner != fit else -1  # Disabled, see #1278
            for mod in fit.modules:
                if mod.item and mod.item.groupID == self.item.groupID:
                    current += 1

            if current >= max:
                return False

        # Check this only if we're told to do so
        if hardpointLimit:
            if self.hardpoint == Hardpoint.TURRET:
                if (fit.ship.getModifiedItemAttr('turretSlotsLeft') or 0) - fit.getHardpointsUsed(Hardpoint.TURRET) < 1:
                    return False
            elif self.hardpoint == Hardpoint.MISSILE:
                if (fit.ship.getModifiedItemAttr('launcherSlotsLeft') or 0) - fit.getHardpointsUsed(
                        Hardpoint.MISSILE) < 1:
                    return False

        return True

    def isValidState(self, state):
        """
        Check if the state is valid for this module, without considering other modules at all
        """
        # Check if we're within bounds
        if state < -1 or state > 2:
            return False
        elif state >= State.ACTIVE and not self.item.isType("active"):
            return False
        elif state == State.OVERHEATED and not self.item.isType("overheat"):
            return False
        else:
            return True

    def canHaveState(self, state=None, projectedOnto=None):
        """
        Check with other modules if there are restrictions that might not allow this module to be activated
        """
        # If we're going to set module to offline or online for local modules or offline for projected,
        # it should be fine for all cases
        item = self.item
        if (state <= State.ONLINE and projectedOnto is None) or (state <= State.OFFLINE):
            return True

        # Check if the local module is over it's max limit; if it's not, we're fine
        maxGroupActive = self.getModifiedItemAttr("maxGroupActive")
        if maxGroupActive is None and projectedOnto is None:
            return True

        # Following is applicable only to local modules, we do not want to limit projected
        if projectedOnto is None:
            currActive = 0
            group = item.group.name
            for mod in self.owner.modules:
                currItem = getattr(mod, "item", None)
                if mod.state >= State.ACTIVE and currItem is not None and currItem.group.name == group:
                    currActive += 1
                if currActive > maxGroupActive:
                    break
            return currActive <= maxGroupActive
        # For projected, we're checking if ship is vulnerable to given item
        else:
            # Do not allow to apply offensive modules on ship with offensive module immunite, with few exceptions
            # (all effects which apply instant modification are exception, generally speaking)
            if item.offensive and projectedOnto.ship.getModifiedItemAttr("disallowOffensiveModifiers") == 1:
                offensiveNonModifiers = {"energyDestabilizationNew",
                                         "leech",
                                         "energyNosferatuFalloff",
                                         "energyNeutralizerFalloff"}
                if not offensiveNonModifiers.intersection(set(item.effects)):
                    return False
            # If assistive modules are not allowed, do not let to apply these altogether
            if item.assistive and projectedOnto.ship.getModifiedItemAttr("disallowAssistance") == 1:
                return False
            return True

    def isValidCharge(self, charge):
        # Check sizes, if 'charge size > module volume' it won't fit
        if charge is None:
            return True
        chargeVolume = charge.volume
        moduleCapacity = self.item.capacity
        if chargeVolume is not None and moduleCapacity is not None and chargeVolume > moduleCapacity:
            return False

        itemChargeSize = self.getModifiedItemAttr("chargeSize")
        if itemChargeSize > 0:
            chargeSize = charge.getAttribute('chargeSize')
            if itemChargeSize != chargeSize:
                return False

        chargeGroup = charge.groupID
        for i in range(5):
            itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i))
            if itemChargeGroup is None:
                continue
            if itemChargeGroup == chargeGroup:
                return True

        return False

    def getValidCharges(self):
        validCharges = set()
        for i in range(5):
            itemChargeGroup = self.getModifiedItemAttr('chargeGroup' + str(i))
            if itemChargeGroup is not None:
                g = eos.db.getGroup(int(itemChargeGroup), eager=("items.icon", "items.attributes"))
                if g is None:
                    continue
                for singleItem in g.items:
                    if singleItem.published and self.isValidCharge(singleItem):
                        validCharges.add(singleItem)

        return validCharges

    @staticmethod
    def __calculateHardpoint(item):
        effectHardpointMap = {
            "turretFitted"  : Hardpoint.TURRET,
            "launcherFitted": Hardpoint.MISSILE
        }

        if item is None:
            return Hardpoint.NONE

        for effectName, slot in effectHardpointMap.iteritems():
            if effectName in item.effects:
                return slot

        return Hardpoint.NONE

    @staticmethod
    def __calculateSlot(item):
        effectSlotMap = {
            "rigSlot"    : Slot.RIG,
            "loPower"    : Slot.LOW,
            "medPower"   : Slot.MED,
            "hiPower"    : Slot.HIGH,
            "subSystem"  : Slot.SUBSYSTEM,
            "serviceSlot": Slot.SERVICE
        }
        if item is None:
            return None
        for effectName, slot in effectSlotMap.iteritems():
            if effectName in item.effects:
                return slot
        if item.group.name == "Effect Beacon":
            return Slot.SYSTEM

        raise ValueError("Passed item does not fit in any known slot")

    @validates("ID", "itemID", "ammoID")
    def validator(self, key, val):
        map = {
            "ID"    : lambda _val: isinstance(_val, int),
            "itemID": lambda _val: _val is None or isinstance(_val, int),
            "ammoID": lambda _val: isinstance(_val, int)
        }

        if not map[key](val):
            raise ValueError(str(val) + " is not a valid value for " + key)
        else:
            return val

    def clear(self):
        self.__dps = None
        self.__miningyield = None
        self.__volley = None
        self.__reloadTime = None
        self.__reloadForce = None
        self.__chargeCycles = None
        self.itemModifiedAttributes.clear()
        self.chargeModifiedAttributes.clear()

    def calculateModifiedAttributes(self, fit, runTime, forceProjected=False, gang=False):
        # We will run the effect when two conditions are met:
        # 1: It makes sense to run the effect
        #    The effect is either offline
        #    or the effect is passive and the module is in the online state (or higher)

        #    or the effect is active and the module is in the active state (or higher)
        #    or the effect is overheat and the module is in the overheated state (or higher)
        # 2: the runtimes match

        if self.projected or forceProjected:
            context = "projected", "module"
            projected = True
        else:
            context = ("module",)
            projected = False

        # if gang:
        #     context += ("commandRun",)

        if self.charge is not None:
            # fix for #82 and it's regression #106
            if not projected or (self.projected and not forceProjected) or gang:
                for effect in self.charge.effects.itervalues():
                    if effect.runTime == runTime and \
                            effect.activeByDefault and \
                            (effect.isType("offline") or
                                 (effect.isType("passive") and self.state >= State.ONLINE) or
                                 (effect.isType("active") and self.state >= State.ACTIVE)) and \
                            (not gang or (gang and effect.isType("gang"))):

                        chargeContext = ("moduleCharge",)
                        # For gang effects, we pass in the effect itself as an argument. However, to avoid going through
                        # all the effect files and defining this argument, do a simple try/catch here and be done with it.
                        # @todo: possibly fix this
                        try:
                            effect.handler(fit, self, chargeContext, effect=effect)
                        except:
                            effect.handler(fit, self, chargeContext)

        if self.item:
            if self.state >= State.OVERHEATED:
                for effect in self.item.effects.itervalues():
                    if effect.runTime == runTime and \
                            effect.isType("overheat") \
                            and not forceProjected \
                            and effect.activeByDefault \
                            and ((gang and effect.isType("gang")) or not gang):
                        effect.handler(fit, self, context)

            for effect in self.item.effects.itervalues():
                if effect.runTime == runTime and \
                        effect.activeByDefault and \
                        (effect.isType("offline") or
                             (effect.isType("passive") and self.state >= State.ONLINE) or
                             (effect.isType("active") and self.state >= State.ACTIVE)) \
                        and ((projected and effect.isType("projected")) or not projected) \
                        and ((gang and effect.isType("gang")) or not gang):
                    try:
                        effect.handler(fit, self, context, effect=effect)
                    except:
                        effect.handler(fit, self, context)

    @property
    def cycleTime(self):
        # Determine if we'll take into account reload time or not
        factorReload = self.owner.factorReload if self.forceReload is None else self.forceReload

        numShots = self.numShots
        speed = self.rawCycleTime

        if factorReload and self.charge:
            raw_reload_time = self.reloadTime
        else:
            raw_reload_time = 0.0

        # Module can only fire one shot at a time, think bomb launchers or defender launchers
        if self.disallowRepeatingAction:
            if numShots > 0:
                """
                The actual mechanics behind this is complex.  Behavior will be (for 3 ammo):
                    fire, reactivation delay, fire, reactivation delay, fire, max(reactivation delay, reload)
                so your effective reload time depends on where you are at in the cycle.

                We can't do that, so instead we'll average it out.

                Currently would apply to bomb launchers and defender missiles
                """
                effective_reload_time = ((self.reactivationDelay * (numShots - 1)) + max(raw_reload_time, self.reactivationDelay, 0))
            else:
                """
                Applies to MJD/MJFG
                """
                effective_reload_time = max(raw_reload_time, self.reactivationDelay, 0)
                speed = speed + effective_reload_time
        else:
            """
            Currently no other modules would have a reactivation delay, so for sanities sake don't try and account for it.
            Okay, technically cloaks do, but they also have 0 cycle time and cap usage so why do you care?
            """
            effective_reload_time = raw_reload_time

        if numShots > 0 and self.charge:
            speed = (speed * numShots + effective_reload_time) / numShots

        return speed

    @property
    def rawCycleTime(self):
        speed = max(
                self.getModifiedItemAttr("speed"),  # Most weapons
                self.getModifiedItemAttr("duration"),  # Most average modules
                self.getModifiedItemAttr("durationSensorDampeningBurstProjector"),
                self.getModifiedItemAttr("durationTargetIlluminationBurstProjector"),
                self.getModifiedItemAttr("durationECMJammerBurstProjector"),
                self.getModifiedItemAttr("durationWeaponDisruptionBurstProjector"),
                0,  # Return 0 if none of the above are valid
        )
        return speed

    @property
    def disallowRepeatingAction(self):
        return self.getModifiedItemAttr("disallowRepeatingActivation", 0)

    @property
    def reactivationDelay(self):
        return self.getModifiedItemAttr("moduleReactivationDelay", 0)

    @property
    def capUse(self):
        capNeed = self.getModifiedItemAttr("capacitorNeed")
        if capNeed and self.state >= State.ACTIVE:
            cycleTime = self.cycleTime
            if cycleTime > 0:
                capUsed = capNeed / (cycleTime / 1000.0)
                return capUsed
        else:
            return 0

    def __deepcopy__(self, memo):
        item = self.item
        if item is None:
            copy = Module.buildEmpty(self.slot)
        else:
            copy = Module(self.item)
        copy.charge = self.charge
        copy.state = self.state
        return copy

    def __repr__(self):
        if self.item:
            return u"Module(ID={}, name={}) at {}".format(
                    self.item.ID, self.item.name, hex(id(self))
            )
        else:
            return "EmptyModule() at {}".format(hex(id(self)))
Exemplo n.º 47
0
Arquivo: fit.py Projeto: mickoes/Pyfa
class Fit(object):
    """Represents a fitting, with modules, ship, implants, etc."""
    EXTRA_ATTRIBUTES = {"armorRepair": 0,
                        "hullRepair": 0,
                        "shieldRepair": 0,
                        "maxActiveDrones": 0,
                        "maxTargetsLockedFromSkills": 2,
                        "droneControlRange": 20000,
                        "cloaked": False,
                        "siege": False}

    PEAK_RECHARGE = 0.25

    def __init__(self):
        self.__modules = HandledModuleList()
        self.__drones = HandledDroneList()
        self.__cargo = HandledCargoList()
        self.__implants = HandledImplantBoosterList()
        self.__boosters = HandledImplantBoosterList()
        self.__projectedFits = HandledProjectedFitList()
        self.__projectedModules = HandledProjectedModList()
        self.__projectedDrones = HandledProjectedDroneList()
        self.__character = None
        self.__owner = None
        self.shipID = None
        self.projected = False
        self.name = ""
        self.fleet = None
        self.boostsFits = set()
        self.gangBoosts = None
        self.timestamp = time.time()
        self.ecmProjectedStr = 1
        self.modeID = None
        self.build()

    @reconstructor
    def init(self):
        self.build()

    def build(self):
        from eos import db
        self.__extraDrains = []
        self.__ehp = None
        self.__weaponDPS = None
        self.__minerYield = None
        self.__weaponVolley = None
        self.__droneDPS = None
        self.__droneVolley = None
        self.__droneYield = None
        self.__sustainableTank = None
        self.__effectiveSustainableTank = None
        self.__effectiveTank = None
        self.__calculated = False
        self.__capStable = None
        self.__capState = None
        self.__capUsed = None
        self.__capRecharge = None
        self.__calculatedTargets = []
        self.factorReload = False
        self.fleet = None
        self.boostsFits = set()
        self.gangBoosts = None
        self.ecmProjectedStr = 1
        self.extraAttributes = ModifiedAttributeDict(self)
        self.extraAttributes.original = self.EXTRA_ATTRIBUTES
        self.ship = Ship(db.getItem(self.shipID)) if self.shipID is not None else None
        if self.ship is not None:
            self.mode = self.ship.checkModeItem(db.getItem(self.modeID) if self.modeID else None)
        else:
            self.mode = None

    @property
    def targetResists(self):
        return self.__targetResists

    @targetResists.setter
    def targetResists(self, targetResists):
        self.__targetResists = targetResists
        self.__weaponDPS = None
        self.__weaponVolley = None
        self.__droneDPS = None
        self.__droneVolley = None

    @property
    def damagePattern(self):
        return self.__damagePattern

    @damagePattern.setter
    def damagePattern(self, damagePattern):
        self.__damagePattern = damagePattern
        self.__ehp = None
        self.__effectiveTank = None

    @property
    def mode(self):
        return self._mode

    @mode.setter
    def mode(self, mode):
        self._mode = mode
        self.modeID = mode.item.ID if mode is not None else None

    @property
    def character(self):
        return self.__character if self.__character is not None else Character.getAll0()

    @character.setter
    def character(self, char):
        self.__character = char

    @property
    def ship(self):
        return self.__ship

    @ship.setter
    def ship(self, ship):
        self.__ship = ship
        self.shipID = ship.item.ID if ship is not None else None
        #  set mode of new ship
        self.mode = self.ship.checkModeItem(None) if ship is not None else None

    @property
    def drones(self):
        return self.__drones

    @property
    def cargo(self):
        return self.__cargo

    @property
    def modules(self):
        return self.__modules

    @property
    def implants(self):
        return self.__implants

    @property
    def boosters(self):
        return self.__boosters

    @property
    def projectedModules(self):
        return self.__projectedModules

    @property
    def projectedFits(self):
        return self.__projectedFits

    @property
    def projectedDrones(self):
        return self.__projectedDrones

    @property
    def weaponDPS(self):
        if self.__weaponDPS is None:
            self.calculateWeaponStats()

        return self.__weaponDPS

    @property
    def weaponVolley(self):
        if self.__weaponVolley is None:
            self.calculateWeaponStats()

        return self.__weaponVolley

    @property
    def droneDPS(self):
        if self.__droneDPS is None:
            self.calculateWeaponStats()

        return self.__droneDPS

    @property
    def droneVolley(self):
        if self.__droneVolley is None:
            self.calculateWeaponStats()

        return self.__droneVolley

    @property
    def totalDPS(self):
        return self.droneDPS + self.weaponDPS

    @property
    def totalVolley(self):
        return self.droneVolley + self.weaponVolley

    @property
    def minerYield(self):
        if self.__minerYield is None:
            self.calculateMiningStats()

        return self.__minerYield

    @property
    def droneYield(self):
        if self.__droneYield is None:
            self.calculateMiningStats()

        return self.__droneYield

    @property
    def totalYield(self):
        return self.droneYield + self.minerYield

    @property
    def maxTargets(self):
        return min(self.extraAttributes["maxTargetsLockedFromSkills"], self.ship.getModifiedItemAttr("maxLockedTargets"))

    @property
    def maxTargetRange(self):
        return self.ship.getModifiedItemAttr("maxTargetRange")

    @property
    def scanStrength(self):
        return max([self.ship.getModifiedItemAttr("scan%sStrength" % scanType)
                    for scanType in ("Magnetometric", "Ladar", "Radar", "Gravimetric")])

    @property
    def scanType(self):
        maxStr = -1
        type = None
        for scanType in ("Magnetometric", "Ladar", "Radar", "Gravimetric"):
            currStr = self.ship.getModifiedItemAttr("scan%sStrength" % scanType)
            if currStr > maxStr:
                maxStr = currStr
                type = scanType
            elif currStr == maxStr:
                type = "Multispectral"

        return type

    @property
    def jamChance(self):
        return (1-self.ecmProjectedStr)*100

    @property
    def alignTime(self):
        agility = self.ship.getModifiedItemAttr("agility")
        mass = self.ship.getModifiedItemAttr("mass")

        return -log(0.25) * agility * mass / 1000000

    @property
    def appliedImplants(self):
        implantsBySlot = {}
        if self.character:
            for implant in self.character.implants:
                implantsBySlot[implant.slot] = implant

        for implant in self.implants:
            implantsBySlot[implant.slot] = implant

        return implantsBySlot.values()

    @validates("ID", "ownerID", "shipID")
    def validator(self, key, val):
        map = {"ID": lambda val: isinstance(val, int),
               "ownerID" : lambda val: isinstance(val, int),
               "shipID" : lambda val: isinstance(val, int) or val is None}

        if map[key](val) == False: raise ValueError(str(val) + " is not a valid value for " + key)
        else: return val

    def clear(self):
        self.__effectiveTank = None
        self.__weaponDPS = None
        self.__minerYield = None
        self.__weaponVolley = None
        self.__effectiveSustainableTank = None
        self.__sustainableTank = None
        self.__droneDPS = None
        self.__droneVolley = None
        self.__droneYield = None
        self.__ehp = None
        self.__calculated = False
        self.__capStable = None
        self.__capState = None
        self.__capUsed = None
        self.__capRecharge = None
        self.ecmProjectedStr = 1
        del self.__calculatedTargets[:]
        del self.__extraDrains[:]

        if self.ship is not None: self.ship.clear()
        c = chain(self.modules, self.drones, self.boosters, self.implants, self.projectedDrones, self.projectedModules, self.projectedFits, (self.character, self.extraAttributes))
        for stuff in c:
            if stuff is not None and stuff != self: stuff.clear()

    #Methods to register and get the thing currently affecting the fit,
    #so we can correctly map "Affected By"
    def register(self, currModifier):
        self.__modifier = currModifier
        if hasattr(currModifier, "itemModifiedAttributes"):
            currModifier.itemModifiedAttributes.fit = self
        if hasattr(currModifier, "chargeModifiedAttributes"):
            currModifier.chargeModifiedAttributes.fit = self

    def getModifier(self):
        return self.__modifier

    def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None):
        refreshBoosts = False
        if withBoosters is True:
            refreshBoosts = True
        if dirtyStorage is not None and self.ID in dirtyStorage:
            refreshBoosts = True
        if dirtyStorage is not None:
            dirtyStorage.update(self.boostsFits)
        if self.fleet is not None and refreshBoosts is True:
            self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters, dirtyStorage=dirtyStorage)
        elif self.fleet is None:
            self.gangBoosts = None
        if dirtyStorage is not None:
            try:
                dirtyStorage.remove(self.ID)
            except KeyError:
                pass
        # If we're not explicitly asked to project fit onto something,
        # set self as target fit
        if targetFit is None:
            targetFit = self
            forceProjected = False
        # Else, we're checking all target projectee fits
        elif targetFit not in self.__calculatedTargets:
            self.__calculatedTargets.append(targetFit)
            targetFit.calculateModifiedAttributes(dirtyStorage=dirtyStorage)
            forceProjected = True
        # Or do nothing if target fit is calculated
        else:
            return

        # If fit is calculated and we have nothing to do here, get out
        if self.__calculated == True and forceProjected == False:
            return

        # Mark fit as calculated
        self.__calculated = True

        # There's a few things to keep in mind here
        # 1: Early effects first, then regular ones, then late ones, regardless of anything else
        # 2: Some effects aren't implemented
        # 3: Some effects are implemented poorly and will just explode on us
        # 4: Errors should be handled gracefully and preferably without crashing unless serious
        for runTime in ("early", "normal", "late"):
            # Build a little chain of stuff
            # Avoid adding projected drones and modules when fit is projected onto self
            # TODO: remove this workaround when proper self-projection using virtual duplicate fits is implemented
            if forceProjected is True:
                c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules)
            else:
                c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules,
                          self.projectedDrones, self.projectedModules)

            if self.gangBoosts is not None:
                contextMap = {Skill: "skill",
                              Ship: "ship",
                              Module: "module",
                              Implant: "implant"}
                for name, info in self.gangBoosts.iteritems():
                    # Unpack all data required to run effect properly
                    effect, thing = info[1]
                    if effect.runTime == runTime:
                        context = ("gang", contextMap[type(thing)])
                        if isinstance(thing, Module):
                            if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \
                            (effect.isType("active") and thing.state >= State.ACTIVE):
                                # Run effect, and get proper bonuses applied
                                try:
                                    self.register(thing)
                                    effect.handler(self, thing, context)
                                except:
                                    pass
                        else:
                            # Run effect, and get proper bonuses applied
                            try:
                                self.register(thing)
                                effect.handler(self, thing, context)
                            except:
                                pass

            for item in c:
                # Registering the item about to affect the fit allows us to track "Affected By" relations correctly
                if item is not None:
                    self.register(item)
                    item.calculateModifiedAttributes(self, runTime, False)
                    if forceProjected is True:
                        targetFit.register(item)
                        item.calculateModifiedAttributes(targetFit, runTime, True)

        for fit in self.projectedFits:
            fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage)

    def fill(self):
        """
        Fill this fit's module slots with enough dummy slots so that all slots are used.
        This is mostly for making the life of gui's easier.
        GUI's can call fill() and then stop caring about empty slots completely.
        """
        if self.ship is None:
            return

        for slotType in (Slot.LOW, Slot.MED, Slot.HIGH, Slot.RIG, Slot.SUBSYSTEM):
            amount = self.getSlotsFree(slotType, True)
            if amount > 0:
                for _ in xrange(int(amount)):
                    self.modules.append(Module.buildEmpty(slotType))

            if amount < 0:
                #Look for any dummies of that type to remove
                toRemove = []
                for mod in self.modules:
                    if mod.isEmpty and mod.slot == slotType:
                        toRemove.append(mod)
                        amount += 1
                        if amount == 0:
                            break
                for mod in toRemove:
                    self.modules.remove(mod)

    def unfill(self):
        for i in xrange(len(self.modules) - 1, -1, -1):
            mod = self.modules[i]
            if mod.isEmpty:
                del self.modules[i]

    @property
    def modCount(self):
        x=0
        for i in xrange(len(self.modules) - 1, -1, -1):
            mod = self.modules[i]
            if not mod.isEmpty:
                x += 1
        return x

    def getItemAttrSum(self, dict, attr):
        amount = 0
        for mod in dict:
            add = mod.getModifiedItemAttr(attr)
            if add is not None:
                amount += add

        return amount

    def getItemAttrOnlineSum(self, dict, attr):
        amount = 0
        for mod in dict:
            add = mod.getModifiedItemAttr(attr) if mod.state >= State.ONLINE else None
            if add is not None:
                amount += add

        return amount

    def getHardpointsUsed(self, type):
        amount = 0
        for mod in self.modules:
            if mod.hardpoint is type and not mod.isEmpty:
                amount += 1

        return amount

    def getSlotsUsed(self, type, countDummies=False):
        amount = 0
        for mod in self.modules:
            if mod.slot is type and (not mod.isEmpty or countDummies):
                amount += 1

        return amount

    def getSlotsFree(self, type, countDummies=False):
        slots = {Slot.LOW: "lowSlots",
                 Slot.MED: "medSlots",
                 Slot.HIGH: "hiSlots",
                 Slot.RIG: "rigSlots",
                 Slot.SUBSYSTEM: "maxSubSystems"}

        if type == Slot.MODE:
            # Mode slot doesn't really exist, return default 0
            return 0

        slotsUsed = self.getSlotsUsed(type, countDummies)
        totalSlots = self.ship.getModifiedItemAttr(slots[type]) or 0
        return int(totalSlots - slotsUsed)

    @property
    def calibrationUsed(self):
        return self.getItemAttrSum(self.modules, 'upgradeCost')

    @property
    def pgUsed(self):
        return self.getItemAttrOnlineSum(self.modules, "power")

    @property
    def cpuUsed(self):
        return self.getItemAttrOnlineSum(self.modules, "cpu")

    @property
    def droneBandwidthUsed(self):
        amount = 0
        for d in self.drones:
            amount += d.getModifiedItemAttr("droneBandwidthUsed") * d.amountActive

        return amount

    @property
    def droneBayUsed(self):
        amount = 0
        for d in self.drones:
            amount += d.item.volume * d.amount

        return amount

    @property
    def cargoBayUsed(self):
        amount = 0
        for c in self.cargo:
            amount += c.getModifiedItemAttr("volume") * c.amount

        return amount

    @property
    def activeDrones(self):
        amount = 0
        for d in self.drones:
            amount +=d.amountActive

        return amount

    # Expresses how difficult a target is to probe down with scan probes
    # If this is <1.08, the ship is unproabeable
    @property
    def probeSize(self):
        sigRad = self.ship.getModifiedItemAttr("signatureRadius")
        sensorStr = float(self.scanStrength)
        probeSize = sigRad / sensorStr if sensorStr != 0 else None
        # http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1532170&page=2#42
        if probeSize is not None:
            # http://forum.eve-ru.com/index.php?showtopic=74195&view=findpost&p=1333691
            # http://forum.eve-ru.com/index.php?showtopic=74195&view=findpost&p=1333763
            # Tests by tester128 and several conclusions by me, prove that cap is in range
            # from 1.1 to 1.12, we're picking average value
            probeSize = max(probeSize, 1.11)
        return probeSize

    @property
    def warpSpeed(self):
        base = self.ship.getModifiedItemAttr("baseWarpSpeed") or 1
        multiplier = self.ship.getModifiedItemAttr("warpSpeedMultiplier") or 1
        return base * multiplier

    @property
    def maxWarpDistance(self):
        capacity = self.ship.getModifiedItemAttr("capacitorCapacity")
        mass = self.ship.getModifiedItemAttr("mass")
        warpCapNeed = self.ship.getModifiedItemAttr("warpCapacitorNeed")
        return capacity / (mass * warpCapNeed)

    @property
    def capStable(self):
        if self.__capStable is None:
            self.simulateCap()

        return self.__capStable

    @property
    def capState(self):
        """
        If the cap is stable, the capacitor state is the % at which it is stable.
        If the cap is unstable, this is the amount of time before it runs out
        """
        if self.__capState is None:
            self.simulateCap()

        return self.__capState

    @property
    def capUsed(self):
        if self.__capUsed is None:
            self.simulateCap()

        return self.__capUsed

    @property
    def capRecharge(self):
        if self.__capRecharge is None:
            self.simulateCap()

        return self.__capRecharge


    @property
    def sustainableTank(self):
        if self.__sustainableTank is None:
            self.calculateSustainableTank()

        return self.__sustainableTank

    def calculateSustainableTank(self, effective=True):
        if self.__sustainableTank is None:
            if self.capStable:
                sustainable = {}
                sustainable["armorRepair"] = self.extraAttributes["armorRepair"]
                sustainable["shieldRepair"] = self.extraAttributes["shieldRepair"]
                sustainable["hullRepair"] = self.extraAttributes["hullRepair"]
            else:
                sustainable = {}

                repairers = []
                #Map a repairer type to the attribute it uses
                groupAttrMap = {"Armor Repair Unit": "armorDamageAmount",
                     "Fueled Armor Repairer": "armorDamageAmount",
                     "Hull Repair Unit": "structureDamageAmount",
                     "Shield Booster": "shieldBonus",
                     "Fueled Shield Booster": "shieldBonus",
                     "Remote Armor Repairer": "armorDamageAmount",
                     "Remote Shield Booster": "shieldBonus"}
                #Map repairer type to attribute
                groupStoreMap = {"Armor Repair Unit": "armorRepair",
                                 "Hull Repair Unit": "hullRepair",
                                 "Shield Booster": "shieldRepair",
                                 "Fueled Shield Booster": "shieldRepair",
                                 "Remote Armor Repairer": "armorRepair",
                                 "Remote Shield Booster": "shieldRepair",
                                 "Fueled Armor Repairer": "armorRepair",}

                capUsed = self.capUsed
                for attr in ("shieldRepair", "armorRepair", "hullRepair"):
                    sustainable[attr] = self.extraAttributes[attr]
                    dict = self.extraAttributes.getAfflictions(attr)
                    if self in dict:
                        for mod, _, amount, used in dict[self]:
                            if not used:
                                continue
                            if mod.projected is False:
                                usesCap = True
                                try:
                                    if mod.capUse:
                                        capUsed -= mod.capUse
                                    else:
                                        usesCap = False
                                except AttributeError:
                                    usesCap = False
                                # Modules which do not use cap are not penalized based on cap use
                                if usesCap:
                                    cycleTime = mod.getModifiedItemAttr("duration")
                                    amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
                                    sustainable[attr] -= amount / (cycleTime / 1000.0)
                                    repairers.append(mod)


                #Sort repairers by efficiency. We want to use the most efficient repairers first
                repairers.sort(key=lambda mod: mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name]) / mod.getModifiedItemAttr("capacitorNeed"), reverse = True)

                #Loop through every module until we're above peak recharge
                #Most efficient first, as we sorted earlier.
                #calculate how much the repper can rep stability & add to total
                totalPeakRecharge = self.capRecharge
                for mod in repairers:
                    if capUsed > totalPeakRecharge: break
                    cycleTime = mod.cycleTime
                    capPerSec = mod.capUse
                    if capPerSec is not None and cycleTime is not None:
                        #Check how much this repper can work
                        sustainability = min(1, (totalPeakRecharge - capUsed) / capPerSec)

                        #Add the sustainable amount
                        amount = mod.getModifiedItemAttr(groupAttrMap[mod.item.group.name])
                        sustainable[groupStoreMap[mod.item.group.name]] += sustainability * (amount / (cycleTime / 1000.0))
                        capUsed += capPerSec

            sustainable["passiveShield"] = self.calculateShieldRecharge()
            self.__sustainableTank = sustainable

        return self.__sustainableTank

    def calculateCapRecharge(self, percent = PEAK_RECHARGE):
        capacity = self.ship.getModifiedItemAttr("capacitorCapacity")
        rechargeRate = self.ship.getModifiedItemAttr("rechargeRate") / 1000.0
        return 10 / rechargeRate * sqrt(percent) * (1 - sqrt(percent)) * capacity

    def calculateShieldRecharge(self, percent = PEAK_RECHARGE):
        capacity = self.ship.getModifiedItemAttr("shieldCapacity")
        rechargeRate = self.ship.getModifiedItemAttr("shieldRechargeRate") / 1000.0
        return 10 / rechargeRate * sqrt(percent) * (1 - sqrt(percent)) * capacity

    def addDrain(self, cycleTime, capNeed, clipSize=0):
        self.__extraDrains.append((cycleTime, capNeed, clipSize))

    def removeDrain(self, i):
        del self.__extraDrains[i]

    def iterDrains(self):
        return self.__extraDrains.__iter__()

    def __generateDrain(self):
        drains = []
        capUsed = 0
        capAdded = 0
        for mod in self.modules:
            if mod.state >= State.ACTIVE:
                if mod.getModifiedItemAttr("capacitorNeed") != 0:
                    cycleTime = mod.rawCycleTime or 0
                    reactivationTime = mod.getModifiedItemAttr("moduleReactivationDelay") or 0
                    fullCycleTime = cycleTime + reactivationTime
                    if fullCycleTime > 0:
                        capNeed = mod.capUse
                        if capNeed > 0:
                            capUsed += capNeed
                        else:
                            capAdded -= capNeed

                        drains.append((int(fullCycleTime), mod.getModifiedItemAttr("capacitorNeed") or 0, mod.numShots or 0))

        for fullCycleTime, capNeed, clipSize in self.iterDrains():
            drains.append((int(fullCycleTime), capNeed, clipSize))
            if capNeed > 0:
                capUsed += capNeed / (fullCycleTime / 1000.0)
            else:
                capAdded += -capNeed / (fullCycleTime / 1000.0)

        return drains, capUsed, capAdded

    def simulateCap(self):
        drains, self.__capUsed, self.__capRecharge = self.__generateDrain()
        self.__capRecharge += self.calculateCapRecharge()
        if len(drains) > 0:
            sim = capSim.CapSimulator()
            sim.init(drains)
            sim.capacitorCapacity = self.ship.getModifiedItemAttr("capacitorCapacity")
            sim.capacitorRecharge = self.ship.getModifiedItemAttr("rechargeRate")
            sim.stagger = True
            sim.scale = False
            sim.t_max = 6 * 60 * 60 * 1000
            sim.reload = self.factorReload
            sim.run()

            capState = (sim.cap_stable_low + sim.cap_stable_high) / (2 * sim.capacitorCapacity)
            self.__capStable = capState > 0
            self.__capState = min(100, capState * 100) if self.__capStable else sim.t / 1000.0
        else:
            self.__capStable = True
            self.__capState = 100

    @property
    def hp(self):
        hp = {}
        for (type, attr) in (('shield', 'shieldCapacity'), ('armor', 'armorHP'), ('hull', 'hp')):
            hp[type] = self.ship.getModifiedItemAttr(attr)

        return hp

    @property
    def ehp(self):
        if self.__ehp is None:
            if self.damagePattern is None:
                ehp = self.hp
            else:
                ehp = self.damagePattern.calculateEhp(self)
            self.__ehp = ehp

        return self.__ehp

    @property
    def tank(self):
        hps = {"passiveShield" : self.calculateShieldRecharge()}
        for type in ("shield", "armor", "hull"):
            hps["%sRepair" % type] = self.extraAttributes["%sRepair" % type]

        return hps

    @property
    def effectiveTank(self):
        if self.__effectiveTank is None:
            if self.damagePattern is None:
                ehps = self.tank
            else:
                ehps = self.damagePattern.calculateEffectiveTank(self, self.extraAttributes)

            self.__effectiveTank = ehps

        return self.__effectiveTank

    @property
    def effectiveSustainableTank(self):
        if self.__effectiveSustainableTank is None:
            if self.damagePattern is None:
                eshps = self.sustainableTank
            else:
                eshps = self.damagePattern.calculateEffectiveTank(self, self.sustainableTank)

            self.__effectiveSustainableTank = eshps

        return self.__effectiveSustainableTank


    def calculateLockTime(self, radius):
        scanRes = self.ship.getModifiedItemAttr("scanResolution")
        if scanRes is not None and scanRes > 0:
            # Yes, this function returns time in seconds, not miliseconds.
            # 40,000 is indeed the correct constant here.
            return min(40000 / scanRes / asinh(radius)**2, 30*60)
        else:
            return self.ship.getModifiedItemAttr("scanSpeed") / 1000.0

    def calculateMiningStats(self):
        minerYield = 0
        droneYield = 0

        for mod in self.modules:
            minerYield += mod.miningStats

        for drone in self.drones:
            droneYield += drone.miningStats

        self.__minerYield = minerYield
        self.__droneYield = droneYield

    def calculateWeaponStats(self):
        weaponDPS = 0
        droneDPS = 0
        weaponVolley = 0
        droneVolley = 0

        for mod in self.modules:
            dps, volley = mod.damageStats(self.targetResists)
            weaponDPS += dps
            weaponVolley += volley

        for drone in self.drones:
            dps, volley = drone.damageStats(self.targetResists)
            droneDPS += dps
            droneVolley += volley

        self.__weaponDPS = weaponDPS
        self.__weaponVolley = weaponVolley
        self.__droneDPS = droneDPS
        self.__droneVolley = droneVolley

    @property
    def fits(self):
        for mod in self.modules:
            if not mod.fits(self):
                return False

        return True

    def __deepcopy__(self, memo):
        copy = Fit()
        #Character and owner are not copied
        copy.character = self.__character
        copy.owner = self.owner
        copy.ship = deepcopy(self.ship, memo)
        copy.name = "%s copy" % self.name
        copy.damagePattern = self.damagePattern
        copy.targetResists = self.targetResists

        toCopy = ("modules", "drones", "cargo", "implants", "boosters", "projectedModules", "projectedDrones")
        for name in toCopy:
            orig = getattr(self, name)
            c = getattr(copy, name)
            for i in orig:
                c.append(deepcopy(i, memo))

        for fit in self.projectedFits:
            copy.projectedFits.append(fit)

        return copy