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
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
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
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
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 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
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
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
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
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
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
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
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
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 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
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)
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)
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
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)
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)
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)
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)
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
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)
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)
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)
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
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
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()
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)
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 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
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
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
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
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()
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 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
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
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
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)))
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)))
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