NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

moduleButtonTooltip.py (58195B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\inflight\shipModuleButton\moduleButtonTooltip.py
      2 import appConst
      3 import carbonui.const as uiconst
      4 import dogma.attributes.groups as dogma_attr_groups
      5 import dogma.data as dogma_data
      6 import gametime
      7 from carbonui.text.styles import TextBody
      8 from carbonui.primitives.container import Container
      9 from carbon.common.script.util.timerstuff import AutoTimer
     10 from dogma import units
     11 from dogma.const import attributeSpeedBoostFactor, attributeMassAddition, attributeSpeedFactor, attributeMaxVelocity
     12 from dogma.const import attributeMass
     13 from eve.client.script.ui.control.gauge import Gauge
     14 from eve.client.script.ui.control.tooltips import TooltipPanel, ShortcutHint
     15 from eve.client.script.ui.crimewatch.crimewatchConst import Colors as CrimeWatchColors
     16 import math
     17 from carbon.common.script.util.format import FmtDist, FmtDate
     18 from eve.client.script.ui.control.eveIcon import Icon
     19 from eve.client.script.ui.eveThemeColor import THEME_ACCENT
     20 from eve.client.script.ui.inflight import shipHud
     21 from eve.client.script.ui.inflight.shipModuleButton.attributeValueRowContainer import AttributeValueRowContainer
     22 from eve.client.script.ui.shared.fittingScreen import ACTIVE, OVERHEATED
     23 from eve.client.script.ui.tooltips.tooltipsWrappers import TooltipBaseWrapper
     24 from eve.client.script.ui.util.uix import GetTechLevelIcon
     25 from eve.common.lib import appConst as const
     26 from eve.common.script.sys.eveCfg import GetActiveShip
     27 import evetypes
     28 from inventorycommon.const import groupPropulsionModule
     29 from localization.formatters import FormatTimeIntervalShort, FormatTimeIntervalShortWritten
     30 from localization.formatters.timeIntervalFormatters import FormatSeconds
     31 from shipfitting.fittingDogmaLocationUtil import GetTurretDps, GetLauncherDps, GetMissleDamageMultiplier, GetDotDps, GetTurretDpsWithMax
     32 import localization
     33 from carbonui.uicore import uicore
     34 import blue
     35 from utillib import KeyVal
     36 RESISTANCE_BONUS_ATTRIBUTES = [const.attributeEmDamageResistanceBonus,
     37  const.attributeExplosiveDamageResistanceBonus,
     38  const.attributeKineticDamageResistanceBonus,
     39  const.attributeThermalDamageResistanceBonus]
     40 DL_NAME_FITTING = 'FittingDogmaLocation'
     41 
     42 class TooltipModuleWrapper(TooltipBaseWrapper):
     43 
     44     def CreateTooltip(self, parent, owner, idx):
     45         self.tooltipPanel = ModuleButtonTooltip(parent=parent, owner=owner, idx=idx)
     46         return self.tooltipPanel
     47 
     48 
     49 class ModuleButtonTooltip(TooltipPanel):
     50     infoFunctionNamesForCategories = {}
     51     infoFunctionNames = {const.groupCompressors: 'AddCompressorInfo',
     52      const.groupMiningLaser: 'AddMiningLaserInfo',
     53      const.groupStripMiner: 'AddMiningLaserInfo',
     54      const.groupFrequencyMiningLaser: 'AddMiningLaserInfo',
     55      const.groupGasCloudHarvester: 'AddMiningLaserInfo',
     56      const.groupGasCloudHoarder: 'AddMiningLaserInfo',
     57      const.groupEnergyVampire: 'AddEnergyVampireInfo',
     58      const.groupEnergyDestabilizer: 'AddEnergyDestabilizerInfo',
     59      const.groupArmorRepairUnit: 'AddArmorRepairersInfo',
     60      const.groupFueledArmorRepairer: 'AddArmorRepairersInfo',
     61      const.groupFueledRemoteArmorRepairer: 'AddArmorRepairersInfo',
     62      const.groupPrecursorRepairer: 'AddArmorRepairersInfo',
     63      const.groupHullRepairUnit: 'AddHullRepairersInfo',
     64      const.groupShieldBooster: 'AddShieldBoosterInfo',
     65      const.groupFueledShieldBooster: 'AddShieldBoosterInfo',
     66      const.groupTrackingComputer: 'AddTrackingComputerInfo',
     67      const.groupTrackingLink: 'AddTrackingComputerInfo',
     68      const.groupSmartBomb: 'AddSmartBombInfo',
     69      const.groupPropulsionModule: 'AddPropulsionModuleInfo',
     70      const.groupStasisWeb: 'AddStasisWebInfo',
     71      const.groupWarpScrambler: 'AddWarpScramblerInfo',
     72      const.groupCapacitorBooster: 'AddCapacitorBoosterInfo',
     73      const.groupEnergyTransferArray: 'AddEnergyTransferArrayInfo',
     74      const.groupShieldTransporter: 'AddShieldTransporterInfo',
     75      const.groupArmorRepairProjector: 'AddArmorRepairProjectorInfo',
     76      const.groupRemoteHullRepairer: 'AddRemoteHullRepairInfo',
     77      const.groupArmorHardener: 'AddArmorHardenerInfo',
     78      const.groupArmorCoating: 'AddArmorHardenerInfo',
     79      const.groupShieldHardener: 'AddArmorHardenerInfo',
     80      const.groupShieldAmplifier: 'AddArmorHardenerInfo',
     81      const.groupArmorPlatingEnergized: 'AddArmorHardenerInfo',
     82      const.groupElectronicCounterMeasureBurst: 'AddECMInfo',
     83      const.groupElectronicCounterMeasures: 'AddECMInfo',
     84      const.groupElectronicCounterCounterMeasures: 'AddECCMInfo',
     85      const.groupProjectedElectronicCounterCounterMeasures: 'AddECCMInfo',
     86      const.groupRemoteSensorDamper: 'AddSensorDamperInfo',
     87      const.groupRemoteSensorBooster: 'AddSensorDamperInfo',
     88      const.groupSensorBooster: 'AddSensorDamperInfo',
     89      const.groupTargetBreaker: 'AddTargetBreakerInfo',
     90      const.groupTargetPainter: 'AddTargetPainterInfo',
     91      const.groupTrackingDisruptor: 'AddTrackingDisruptorInfo',
     92      const.groupCloakingDevice: 'AddCloakingDeviceInfo',
     93      const.groupTractorBeam: 'AddTractorBeamInfo',
     94      const.groupDamageControl: 'AddDamageControlInfo',
     95      const.groupArmorResistanceShiftHardener: 'AddArmorResistanceShiftHardenerInfo',
     96      const.groupSuperWeapon: 'AddSuperWeaponInfo',
     97      const.groupGangCoordinator: 'AddGangCoordinatorInfo',
     98      const.groupScanProbeLauncher: 'AddScanProbeLauncherInfo',
     99      const.groupCynosuralField: 'AddCynoInfo',
    100      const.groupSiegeModule: 'AddSiegeInfo',
    101      const.groupStructureStasisWebifier: 'AddStasisWebInfo',
    102      const.groupStructureECM: 'AddECMInfo',
    103      const.groupStructureWarpDisruptor: 'AddWarpScramblerInfo',
    104      const.groupStructureEnergyNeutralizer: 'AddEnergyDestabilizerInfo',
    105      const.groupStructureDefenseBattery: 'AddStructureDefenseBatteryInfo',
    106      const.groupStructureDoomsdayWeapon: 'AddSuperWeaponInfo',
    107      const.groupMissileLauncherDot: 'AddDotWeaponInfo',
    108      const.groupPrecursorTurret: 'AddPrecursorTurretInfo'}
    109     infoFunctionNamesForTypes = {const.typeStandupRemoteSensorDampener: 'AddSensorDamperInfo',
    110      const.typeStandupWeaponDisruptor: 'AddTrackingDisruptorInfo',
    111      const.typeStandupTargetPainter: 'AddTargetPainterInfo'}
    112 
    113     def ApplyAttributes(self, attributes):
    114         TooltipPanel.ApplyAttributes(self, attributes)
    115         self.stateManager = sm.StartService('godma').GetStateManager()
    116         self.dogmaLocation = sm.GetService('clientDogmaIM').GetDogmaLocation()
    117         self.columns = 4
    118         self.margin = 16
    119         self.labelPadding = (4, 8, 4, 8)
    120         self.cellSpacing = 0
    121         self.cellPadding = 0
    122         self.SetBackgroundAlpha(0.75)
    123         self.controller = getattr(self.owner, 'controller', None)
    124 
    125     def LoadTooltip(self):
    126         if not self.owner:
    127             return
    128         self.ownerGuid = self.owner.__guid__
    129         if self.ownerGuid == 'xtriui.ModuleButton':
    130             self.moduleItemID = self.owner.moduleinfo.itemID
    131         else:
    132             self.moduleItemID = self.controller.GetModuleID()
    133         if not self.moduleItemID:
    134             return
    135         if self.ownerGuid in ('xtriui.FittingSlot', 'FittingServiceSlot', 'Drones'):
    136             self.dogmaLocation = sm.GetService('fittingSvc').GetCurrentDogmaLocation()
    137         else:
    138             self.dogmaLocation = sm.GetService('clientDogmaIM').GetDogmaLocation()
    139         self.moduleInfoItem = self.dogmaLocation.GetDogmaItem(self.moduleItemID)
    140         self.moduleGroupID = evetypes.GetGroupID(self.moduleInfoItem.typeID)
    141         self.moduleCategoryID = evetypes.GetCategoryID(self.moduleInfoItem.typeID)
    142         self.numSlaves = self.GetNumberOfSlaves(self.moduleInfoItem, self.ownerGuid)
    143         if self.stateManager.GetDefaultEffect(self.moduleInfoItem.typeID):
    144             self.moduleShortcut = self.GetModuleShortcut(self.moduleInfoItem)
    145         else:
    146             self.moduleShortcut = None
    147         self.typeName = evetypes.GetName(self.moduleInfoItem.typeID)
    148         self.onHUDModuleButton = self.ownerGuid == 'xtriui.ModuleButton'
    149         self.UpdateToolTips()
    150         self._toolTooltipUpdateTimer = AutoTimer(1000, self.UpdateToolTips)
    151 
    152     def UpdateToolTips(self):
    153         if self.destroyed or self.beingDestroyed or self.owner is None:
    154             self._toolTooltipUpdateTimer = None
    155             return
    156         self.Flush()
    157         if getattr(self.owner, 'charge', None):
    158             chargeItemID = self.owner.charge.itemID
    159         elif self.controller and hasattr(self.controller, 'GetCharge') and self.controller.GetCharge():
    160             chargeItemID = self.controller.GetCharge().itemID
    161         else:
    162             chargeItemID = None
    163         if chargeItemID is None:
    164             chargeInfoItem = None
    165         else:
    166             chargeInfoItem = self.dogmaLocation.GetDogmaItem(chargeItemID)
    167         moduleDamageInfo = self.GetModuleDamage(self.ownerGuid, self.moduleItemID)
    168         chargesType, chargesQty = self.GetChargeTypeAndQty(self.moduleInfoItem, chargeInfoItem)
    169         typeText = self.GetTypeText()
    170         self.AddTypeAndIcon(label=typeText, typeID=self.moduleInfoItem.typeID, moduleShortcut=self.moduleShortcut, moduleDamageInfo=moduleDamageInfo)
    171         self.UpdateChargesCont(chargeInfoItem, chargesQty)
    172         maxRange, falloffDist, bombRadius, _ = self.GetRanges(chargeInfoItem)
    173         if maxRange > 0:
    174             self.AddRangeInfo(self.moduleInfoItem.typeID, optimalRange=maxRange, falloff=falloffDist)
    175         if self.ShouldAddDps(chargesQty):
    176             self.AddDpsAndDamgeTypeInfo(self.moduleItemID, self.moduleInfoItem.typeID, self.moduleGroupID, chargeInfoItem, self.numSlaves)
    177         self.AddGroupAndCategoryExtraInfo(chargeInfoItem)
    178         self.AddTrackingSpeed(self.moduleItemID)
    179         self.AddCountDownTimes(self.moduleItemID)
    180         if moduleDamageInfo.repairTimeLeft > 0:
    181             self.GetTimeLeftRepairingCountDown(moduleDamageInfo.repairTimeLeft)
    182         if self.onHUDModuleButton:
    183             safetyLevel = self.owner.GetSafetyWarning()
    184             if safetyLevel is not None:
    185                 self.AddSafetyLevelWarning(safetyLevel)
    186         self.AddActivityStatus()
    187 
    188     def AddGroupAndCategoryExtraInfo(self, chargeInfoItem):
    189         categoryInfoFuncName = self.infoFunctionNamesForCategories.get(self.moduleCategoryID, None)
    190         goupInfoFuncName = self.infoFunctionNames.get(self.moduleGroupID, None)
    191         typeInfoFuncName = self.infoFunctionNamesForTypes.get(self.moduleInfoItem.typeID)
    192         for funcName in (categoryInfoFuncName, goupInfoFuncName, typeInfoFuncName):
    193             if funcName is None:
    194                 continue
    195             myInfoFunction = getattr(self, funcName)
    196             myInfoFunction(self.moduleItemID, chargeInfoItem)
    197 
    198     def GetTypeText(self):
    199         if self.numSlaves:
    200             typeText = localization.GetByLabel('UI/Inflight/ModuleRacks/TypeNameWithNumInGroup', numInGroup=self.numSlaves, typeName=self.typeName)
    201         else:
    202             typeText = self.typeName
    203         typeText = '<b>%s</b>' % typeText
    204         return typeText
    205 
    206     def GetRanges(self, chargeInfoItem):
    207         return sm.GetService('tactical').FindMaxRange(self.moduleInfoItem, chargeInfoItem, self.dogmaLocation)
    208 
    209     def ShouldAddDps(self, chargesQty):
    210         shouldAddDps = chargesQty is not None
    211         return shouldAddDps
    212 
    213     def UpdateChargesCont(self, chargeInfoItem, chargesQty):
    214         if chargeInfoItem and chargesQty:
    215             chargeText = self.GetChargeText(chargeInfoItem, chargesQty)
    216             self.AddTypeAndIcon(label=chargeText, typeID=chargeInfoItem.typeID)
    217 
    218     def GetChargeText(self, chargeInfoItem, chargesQty):
    219         if chargeInfoItem.groupID in cfg.GetCrystalGroups():
    220             crystalDamageAmount = self.GetCrystalDamage(chargeInfoItem)
    221             chargeText = '<b>%s</b>' % evetypes.GetName(chargeInfoItem.typeID)
    222             if crystalDamageAmount > 0.0:
    223                 damagedText = localization.GetByLabel('UI/Inflight/ModuleRacks/ModuleDamaged', color='<color=red>', percentageNum=crystalDamageAmount * 100)
    224                 chargeText += '<br>' + damagedText
    225         else:
    226             chargeText = localization.GetByLabel('UI/Inflight/ModuleRacks/AmmoNameWithQty', qty=chargesQty, ammoTypeID=chargeInfoItem.typeID)
    227         return chargeText
    228 
    229     def GetModuleDamage(self, ownerGuid, moduleItemID):
    230         moduleDamageInfo = KeyVal(moduleDamage=None, repairingModuleDamage=None, repairTimeLeft=None)
    231         myShipID = GetActiveShip()
    232         if myShipID != self.dogmaLocation.GetCurrentShipID():
    233             return moduleDamageInfo
    234         showSingleModuleReadout = ownerGuid in ('xtriui.FittingSlot', 'FittingServiceSlot')
    235         if self.stateManager.IsBeingRepaired(moduleItemID):
    236             repairingModuleDamage, repairTimeLeft = self.GetTotalDamageWhileRepairing(moduleItemID, showSingleModuleReadout)
    237             moduleDamageInfo.repairingModuleDamage = int(math.ceil(repairingModuleDamage / self.dogmaLocation.GetAttributeValue(moduleItemID, const.attributeHp) * 100))
    238             moduleDamageInfo.repairTimeLeft = repairTimeLeft
    239         else:
    240             if showSingleModuleReadout:
    241                 damage = self.dogmaLocation.GetAccurateAttributeValue(moduleItemID, const.attributeDamage)
    242             else:
    243                 damage = uicore.layer.shipui.controller.GetModuleGroupDamage(moduleItemID)
    244             if damage:
    245                 moduleDamageAmount = int(math.ceil(damage / self.dogmaLocation.GetAttributeValue(moduleItemID, const.attributeHp) * 100))
    246                 moduleDamageInfo.moduleDamage = moduleDamageAmount
    247         return moduleDamageInfo
    248 
    249     def GetCrystalDamage(self, chargeInfoItem):
    250         crystalDamageAmount = None
    251         if chargeInfoItem is not None:
    252             if self.ownerGuid == 'xtriui.FittingSlot':
    253                 dogmaLocation = sm.GetService('fittingSvc').GetCurrentDogmaLocation()
    254             else:
    255                 dogmaLocation = sm.GetService('clientDogmaIM').GetDogmaLocation()
    256             crystalDamageAmount = dogmaLocation.GetAccurateAttributeValue(chargeInfoItem.itemID, const.attributeDamage)
    257         return crystalDamageAmount
    258 
    259     def GetNumberOfSlaves(self, moduleInfoItem, ownerGuid):
    260         myShip = GetActiveShip()
    261         if ownerGuid == 'xtriui.FittingSlot':
    262             return 0
    263         else:
    264             slaves = self.dogmaLocation.GetSlaveModules(moduleInfoItem.itemID, myShip)
    265             if slaves:
    266                 numSlaves = len(slaves) + 1
    267             else:
    268                 numSlaves = 0
    269             return numSlaves
    270 
    271     def GetModuleShortcut(self, moduleInfoItem):
    272         moduleShortcut = None
    273         masterModuleID = self.dogmaLocation.GetMasterModuleID(GetActiveShip(), moduleInfoItem.itemID)
    274         if masterModuleID is not None:
    275             masterModuleInfoItem = self.dogmaLocation.GetDogmaItem(masterModuleID)
    276             flagID = masterModuleInfoItem.flagID
    277         else:
    278             flagID = moduleInfoItem.flagID
    279         slotOrder = shipHud.GetSlotOrder()
    280         if flagID not in slotOrder:
    281             return
    282         pos = slotOrder.index(flagID)
    283         if pos is not None:
    284             row = pos / 8
    285             hiMedLo = ('High', 'Medium', 'Low')[row]
    286             loc = pos % 8
    287             slotno = loc + 1
    288             shortcut = uicore.cmd.GetShortcutStringByFuncName('CmdActivate%sPowerSlot%i' % (hiMedLo, slotno))
    289             if shortcut:
    290                 moduleShortcut = shortcut
    291         return moduleShortcut
    292 
    293     def GetChargeTypeAndQty(self, moduleInfoItem, chargeInfoItem):
    294         chargesQty = None
    295         chargesType = None
    296         if self.IsChargeCompatible(moduleInfoItem):
    297             if chargeInfoItem and chargeInfoItem.typeID:
    298                 chargesQty = self.dogmaLocation.GetQuantity(chargeInfoItem.itemID)
    299                 chargesType = chargeInfoItem.typeID
    300             else:
    301                 chargesQty = 0
    302         return (chargesType, chargesQty)
    303 
    304     def IsChargeCompatible(self, moduleInfoItem):
    305         return moduleInfoItem.groupID in cfg.__chargecompatiblegroups__
    306 
    307     def GetEffectiveAttributeValue(self, itemID, attributeID):
    308         attributeValue = self.dogmaLocation.GetAccurateAttributeValue(itemID, attributeID)
    309         unitID = dogma_data.get_attribute_unit_id(attributeID)
    310         if unitID in (const.unitInverseAbsolutePercent, const.unitInversedModifierPercent):
    311             attributeValue = (1.0 - attributeValue) * 100.0
    312         elif unitID == const.unitModifierPercent:
    313             attributeValue = -(1.0 - attributeValue) * 100.0
    314         return attributeValue
    315 
    316     def GetDuration(self, itemID, durationAttributeID = None):
    317         if durationAttributeID is None:
    318             durationAttributeID = const.attributeDuration
    319         duration = self.GetEffectiveAttributeValue(itemID, durationAttributeID)
    320         durationInSec = duration / 1000.0
    321         return FormatSeconds(durationInSec)
    322 
    323     def AddSafetyLevelWarning(self, safetyLevel):
    324         if safetyLevel == const.shipSafetyLevelNone:
    325             iconColor = CrimeWatchColors.Criminal.GetRGBA()
    326             text = localization.GetByLabel('UI/Crimewatch/SafetyLevel/ModuleRestrictionTooltip', color=CrimeWatchColors.Criminal.GetHex())
    327         else:
    328             iconColor = CrimeWatchColors.Suspect.GetRGBA()
    329             text = localization.GetByLabel('UI/Crimewatch/SafetyLevel/ModuleRestrictionTooltip', color=CrimeWatchColors.Suspect.GetHex())
    330         texturePath = 'res:/UI/Texture/Crimewatch/Crimewatch_SuspectCriminal.png'
    331         icon, label = self.AddRowWithIconAndText(text, texturePath, iconSize=16)
    332         icon.SetRGBA(*iconColor)
    333 
    334     def AddActivityStatus(self):
    335         text, color, activityState = self.dogmaLocation.GetActivityState(self.moduleItemID)
    336         if text:
    337             self.FillRow()
    338             self.AddCell(colSpan=2)
    339             label = self.AddLabelLarge(text=text, colSpan=self.columns - 2, align=uiconst.CENTERLEFT, cellPadding=self.labelPadding, bold=True)
    340             label.SetRGBA(*color)
    341 
    342     def AddTypeAndIcon(self, label, typeID, moduleShortcut = None, moduleDamageInfo = None, iconSize = 26, minRowSize = 30):
    343         self.FillRow()
    344         self.AddSpacer(height=minRowSize, width=0)
    345         iconCont = Container(pos=(0,
    346          0,
    347          iconSize,
    348          iconSize), align=uiconst.CENTER)
    349         iconObj = Icon(parent=iconCont, pos=(0,
    350          0,
    351          iconSize,
    352          iconSize), align=uiconst.TOPLEFT, ignoreSize=True)
    353         iconObj.LoadIconByTypeID(typeID, size=iconSize, ignoreSize=True)
    354         techIcon = Icon(parent=iconCont, width=16, height=16, align=uiconst.TOPLEFT, idx=0, top=0)
    355         GetTechLevelIcon(techIcon, typeID=typeID)
    356         self.AddCell(iconCont, cellPadding=2)
    357         if moduleShortcut:
    358             nameColSpan = self.columns - 3
    359         else:
    360             nameColSpan = self.columns - 2
    361         if moduleDamageInfo:
    362             repairingText = ''
    363             if moduleDamageInfo.repairingModuleDamage:
    364                 repairingText = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/ModuleRepairing')
    365             damageToShow = moduleDamageInfo.repairingModuleDamage or moduleDamageInfo.moduleDamage
    366             if damageToShow > 0:
    367                 damagedText = localization.GetByLabel('UI/Inflight/ModuleRacks/ModuleDamaged', color='<color=red>', percentageNum=damageToShow)
    368                 label += '<br>%s%s' % (damagedText, repairingText)
    369         labelObj = self.AddLabelMedium(text=label, align=uiconst.CENTERLEFT, cellPadding=self.labelPadding, colSpan=nameColSpan)
    370         if moduleShortcut:
    371             shortcutObj = ShortcutHint(text=moduleShortcut)
    372             shortcutObj.width += 10
    373             shortcutObj.padLeft = 10
    374             self.AddCell(shortcutObj)
    375         return (iconObj, labelObj)
    376 
    377     def AddAttributeRow(self, texturePath, attributeValues, minRowSize = 30, spacerWidth = 100):
    378         self.AddCell()
    379         self.AddCell()
    380         self.AddSpacer(colSpan=self.columns - 2, width=spacerWidth, height=0)
    381         self.FillRow()
    382         self.AddSpacer(width=0, height=minRowSize)
    383         self.AddIconCell(texturePath)
    384         container = AttributeValueRowContainer(attributeValues=attributeValues)
    385         self.AddCell(container, colSpan=self.columns - 2)
    386         self.FillRow()
    387 
    388     def AddRangeInfo(self, typeID, optimalRange, falloff):
    389         text, formattedOptimalRange = self.GetOptimalRangeText(typeID, optimalRange, falloff)
    390         if not text:
    391             return
    392         texturePath = 'res:/UI/Texture/Icons/22_32_15.png'
    393         self.AddRowWithIconAndText(text, texturePath)
    394 
    395     def GetOptimalRangeText(self, typeID, optimalRange, falloff):
    396         if optimalRange <= 0:
    397             return ('', '')
    398         formattedOptimalRAnge = FmtDist(optimalRange)
    399         if sm.GetService('clientDogmaStaticSvc').TypeHasEffect(typeID, const.effectLauncherFitted):
    400             rangeText = localization.GetByLabel('UI/Inflight/ModuleRacks/MaxRange', maxRange=formattedOptimalRAnge)
    401         elif sm.GetService('clientDogmaStaticSvc').TypeHasEffect(typeID, const.effectTurretFitted) or evetypes.GetCategoryID(typeID) == const.categoryFighter:
    402             if falloff > 1:
    403                 rangeText = localization.GetByLabel('UI/Inflight/ModuleRacks/OptimalRangeAndFalloff', optimalRange=formattedOptimalRAnge, falloffPlusOptimal=FmtDist(falloff + optimalRange))
    404             else:
    405                 rangeText = localization.GetByLabel('UI/Inflight/ModuleRacks/OptimalRange', optimalRange=formattedOptimalRAnge)
    406         elif evetypes.GetGroupID(typeID) == const.groupSmartBomb:
    407             rangeText = localization.GetByLabel('UI/Inflight/ModuleRacks/AreaOfEffect', range=formattedOptimalRAnge)
    408         elif falloff > 1:
    409             rangeText = localization.GetByLabel('UI/Inflight/ModuleRacks/RangeWithFalloff', optimalRange=formattedOptimalRAnge, falloffPlusOptimal=FmtDist(falloff + optimalRange))
    410         else:
    411             rangeText = localization.GetByLabel('UI/Inflight/ModuleRacks/Range', optimalRange=formattedOptimalRAnge)
    412         return (rangeText, formattedOptimalRAnge)
    413 
    414     def GetAmountPerTimeInfo(self, itemID, attributeID, labelPath, chargeMultiplier = 1.0):
    415         duration = self.GetDuration(itemID)
    416         amount = self.GetEffectiveAttributeValue(itemID, attributeID)
    417         amount = amount * chargeMultiplier
    418         text = localization.GetByLabel(labelPath, duration=duration, amount=amount)
    419         return (text, duration, amount)
    420 
    421     def AddRowWithIconAndText(self, text, texturePath = None, iconID = None, iconSize = 24, minRowSize = 30):
    422         self.FillRow()
    423         self.AddSpacer(height=minRowSize, width=0)
    424         icon = self.AddIconCell(texturePath or iconID, iconSize=iconSize)
    425         label = self.AddLabelMedium(text=text, colSpan=self.columns - 2, align=uiconst.CENTERLEFT, cellPadding=self.labelPadding)
    426         self.FillRow()
    427         return (icon, label)
    428 
    429     def AddRowWithIconAndTextAndValue(self, text, valueText, texturePath, iconSize = 24, minRowSize = 30):
    430         self.FillRow()
    431         self.AddSpacer(height=minRowSize, width=0)
    432         self.AddIconCell(texturePath, iconSize=iconSize)
    433         self.AddLabelMedium(text=text, colSpan=1, align=uiconst.CENTERLEFT, cellPadding=self.labelPadding)
    434         self.AddLabelMedium(text=valueText, align=uiconst.CENTERRIGHT, left=2)
    435         self.FillRow()
    436 
    437     def AddRowWithIconAndContainer(self, texturePath, container, iconSize = 24, minRowSize = 30):
    438         self.FillRow()
    439         self.AddSpacer(height=minRowSize, width=0)
    440         self.AddIconCell(texturePath, iconSize=iconSize)
    441         self.AddCell(container, colSpan=self.columns - 1)
    442 
    443     def AddIconCell(self, texturePath = None, iconID = None, iconSize = 24):
    444         icon = Icon(pos=(0,
    445          0,
    446          iconSize,
    447          iconSize), align=uiconst.CENTER, ignoreSize=True, state=uiconst.UI_DISABLED, icon=texturePath or iconID)
    448         self.AddCell(icon)
    449         return icon
    450 
    451     def AddRowForInfoWithOneOrMoreAttributes(self, attributeValues, oneAttributeText, manyAttributesText, headerText, texturePath = ''):
    452         if len(attributeValues) == 1:
    453             self.AddRowForSingleAttribute(attributeValues=attributeValues, labelPath=oneAttributeText)
    454         elif attributeValues:
    455             self.AddRowAndHeaderForManyAttributes(attributeValues, manyAttributesText, headerText, texturePath)
    456 
    457     def AddRowForSingleAttribute(self, attributeValues, labelPath):
    458         attributeID, value = attributeValues[0]
    459         iconID = dogma_data.get_attribute_icon_id(attributeID)
    460         displayName = dogma_data.get_attribute_display_name(attributeID)
    461         text = localization.GetByLabel(labelPath, activeName=displayName, activeValue=value)
    462         self.AddRowWithIconAndText(text=text, iconID=iconID)
    463 
    464     def AddRowAndHeaderForManyAttributes(self, attributeValues, manyAttributesText, headerText, texturePath = ''):
    465         text = localization.GetByLabel(headerText)
    466         icon, label = self.AddRowWithIconAndText(text=text, texturePath='', minRowSize=24)
    467         label.SetAlign(uiconst.BOTTOMLEFT)
    468         formattedAttributeValues = self.GetFormattedAttributeValues(attributeValues=attributeValues, valueText=manyAttributesText)
    469         self.AddAttributeRow(texturePath, formattedAttributeValues, minRowSize=0, spacerWidth=65 * len(formattedAttributeValues))
    470 
    471     def GetFormattedAttributeValues(self, attributeValues, valueText):
    472         attributeValuesText = []
    473         for attributeID, attributeValue in attributeValues:
    474             attributeValueText = localization.GetByLabel(valueText, activeValue=attributeValue)
    475             attributeValuesText.append((attributeID, attributeValueText))
    476 
    477         return attributeValuesText
    478 
    479     def AddDpsAndDamgeTypeInfo(self, itemID, typeID, groupID, charge, numSlaves):
    480         dmgInfo = self.GetDpsDamageTypeInfo(itemID, typeID, groupID, charge, numSlaves)
    481         if not dmgInfo:
    482             return
    483         if dmgInfo.totalMaxDpsDamage:
    484             text = localization.GetByLabel('UI/Inflight/ModuleRacks/DamagePerSecondRange', dps=dmgInfo.totalDpsDamage, maxDps=dmgInfo.totalMaxDpsDamage)
    485         else:
    486             text = localization.GetByLabel('UI/Inflight/ModuleRacks/DamagePerSecond', dps=dmgInfo.totalDpsDamage)
    487         self.AddRowWithIconAndText(text=text, texturePath=dmgInfo.texturePath, iconID=dmgInfo.iconID)
    488         self.AddDamageTypes(itemID, charge, dmgInfo.damageMultiplier)
    489 
    490     def GetDpsDamageTypeInfo(self, itemID, typeID, groupID, charge, numSlaves):
    491         isBomb = groupID == const.groupMissileLauncherBomb
    492         dogmaStaticSvc = sm.GetService('clientDogmaStaticSvc')
    493         isLauncher = dogmaStaticSvc.TypeHasEffect(typeID, const.effectLauncherFitted) or dogmaStaticSvc.TypeHasEffect(typeID, const.effectUseMissiles)
    494         isTurret = dogmaStaticSvc.TypeHasEffect(typeID, const.effectTurretFitted)
    495         if not isLauncher and not isTurret and not isBomb:
    496             return
    497         GAV = self.dogmaLocation.GetAccurateAttributeValue
    498         texturePath = None
    499         iconID = None
    500         totalDpsDamage = 0
    501         totalDpsDamageWithReload = 0
    502         totalMaxDpsDamage = None
    503         totalMaxDpsDamageWithReload = None
    504         damageMultiplier = 1
    505         if (isLauncher or isBomb) and charge:
    506             chargeKey = charge.itemID
    507             damageMultiplier = GetMissleDamageMultiplier(self.dogmaLocation, itemID, session.charid, GAV)
    508             totalDpsDamage, totalDpsDamageWithReload = GetLauncherDps(self.dogmaLocation, chargeKey, itemID, session.charid, GAV)
    509             if isLauncher:
    510                 texturePath = 'res:/UI/Texture/Icons/81_64_16.png'
    511             else:
    512                 iconID = evetypes.GetIconID(typeID)
    513         elif isTurret:
    514             if charge:
    515                 chargeKey = charge.itemID
    516             else:
    517                 chargeKey = None
    518             totalDpsDamage, totalDpsDamageWithReload, totalMaxDpsDamage, totalMaxDpsDamageWithReload = GetTurretDpsWithMax(self.dogmaLocation, chargeKey, itemID, GAV, False)
    519             damageMultiplier = GAV(itemID, const.attributeDamageMultiplier)
    520             damageMultiplier *= self.dogmaLocation.GetMaxDamageMultiplier(itemID)
    521             texturePath = 'res:/UI/Texture/Icons/26_64_1.png'
    522         if totalDpsDamage == 0:
    523             return KeyVal(totalDpsDamage=totalDpsDamage, totalDpsDamageWithReload=totalDpsDamageWithReload, texturePath=texturePath, iconID=iconID, damageMultiplier=damageMultiplier, totalMaxDpsDamage=totalMaxDpsDamage, totalMaxDpsDamageWithReload=totalMaxDpsDamageWithReload)
    524         if numSlaves:
    525             totalDpsDamage = numSlaves * totalDpsDamage
    526             damageMultiplier = numSlaves * damageMultiplier
    527             totalDpsDamageWithReload = numSlaves * totalDpsDamageWithReload
    528             if totalMaxDpsDamage:
    529                 totalMaxDpsDamage = numSlaves * totalMaxDpsDamage
    530                 totalMaxDpsDamageWithReload = numSlaves * totalMaxDpsDamageWithReload
    531         return KeyVal(totalDpsDamage=totalDpsDamage, totalDpsDamageWithReload=totalDpsDamageWithReload, texturePath=texturePath, iconID=iconID, damageMultiplier=damageMultiplier, totalMaxDpsDamage=totalMaxDpsDamage, totalMaxDpsDamageWithReload=totalMaxDpsDamageWithReload)
    532 
    533     def AddDamageTypes(self, itemID, charge, multiplier):
    534         if charge:
    535             dmgCausingItemID = charge.itemID
    536         else:
    537             dmgCausingItemID = itemID
    538         damageAttributeValues = self.GetAttributesValues(dmgCausingItemID, multiplier, dogma_attr_groups.damage_type_attribute_ids, includeZeros=False)
    539         if not damageAttributeValues:
    540             return
    541         self.AddRowForInfoWithOneOrMoreAttributes(attributeValues=damageAttributeValues, oneAttributeText='UI/Inflight/ModuleRacks/Tooltips/OneDamageTypeText', manyAttributesText='UI/Inflight/ModuleRacks/Tooltips/DamageHitpoints', headerText='UI/Inflight/ModuleRacks/Tooltips/DamageTypesHeader')
    542 
    543     def GetAttributesValues(self, itemID, multiplier, attributeList, includeZeros = True):
    544         attributeValues = []
    545         for eachAttributeID in attributeList:
    546             attributeValue = self.GetEffectiveAttributeValue(itemID, eachAttributeID)
    547             if not attributeValue and not includeZeros:
    548                 continue
    549             attributeValue = attributeValue * multiplier
    550             attributeValues.append((eachAttributeID, attributeValue))
    551 
    552         return attributeValues
    553 
    554     def AddAttributePerTimeInfo(self, itemID, attributeID, labelPath, chargeMultiplier = 1.0):
    555         text, duration, amount = self.GetAmountPerTimeInfo(itemID=itemID, attributeID=attributeID, labelPath=labelPath, chargeMultiplier=chargeMultiplier)
    556         iconID = dogma_data.get_attribute_icon_id(attributeID)
    557         self.AddRowWithIconAndText(text=text, iconID=iconID)
    558 
    559     def AddAttributeInfo(self, itemID, attributeID, labelPath, labelKeyword, skipZeroValue = False):
    560         value = self.GetEffectiveAttributeValue(itemID, attributeID)
    561         if value == 0 and skipZeroValue:
    562             return
    563         tokenDict = {labelKeyword: value}
    564         text = localization.GetByLabel(labelPath, **tokenDict)
    565         iconID = dogma_data.get_attribute_icon_id(attributeID)
    566         self.AddRowWithIconAndText(text=text, iconID=iconID)
    567 
    568     def AddAttributeInfoWithAttributeName(self, itemID, attributeID, labelPath, skipZeroValue = False):
    569         activeValue = self.GetEffectiveAttributeValue(itemID, attributeID)
    570         if activeValue == 0 and skipZeroValue:
    571             return
    572         iconID = dogma_data.get_attribute_icon_id(attributeID)
    573         displayName = dogma_data.get_attribute_display_name(attributeID)
    574         text = localization.GetByLabel(labelPath, activeValue=activeValue, activeName=displayName)
    575         self.AddRowWithIconAndText(text=text, iconID=iconID)
    576 
    577     def AddCompressorInfo(self, itemID, *args):
    578         self.FillRow()
    579         self.AddSpacer()
    580         texturePath = 'res:/UI/Texture/Classes/SkillPlan/infoIcon.png'
    581         range = self.GetEffectiveAttributeValue(itemID, const.attributeMaxRange)
    582         text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/CompressorModuleInfo', range=FmtDist(range))
    583         self.AddIconCell(texturePath, iconSize=34)
    584         self.AddLabelMedium(text=text, colSpan=2, align=uiconst.CENTERLEFT, cellPadding=self.labelPadding, wrapWidth=250)
    585         self.FillRow()
    586 
    587     def AddMiningLaserInfo(self, itemID, chargeInfoItem, *args):
    588         duration = self.GetDuration(itemID)
    589         amount = self.GetEffectiveAttributeValue(itemID, const.attributeMiningAmount)
    590         durationInSec = self.GetEffectiveAttributeValue(itemID, const.attributeDuration) / 1000.0
    591         amountPerSec = amount / durationInSec
    592         text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/MiningAmountPerTime2', duration=duration, amount=amount, amountPerSec=amountPerSec)
    593         self.AddRowWithIconAndText(text=text, texturePath='res:/ui/texture/icons/23_64_5.png')
    594 
    595     def AddEnergyVampireInfo(self, itemID, chargeInfoItem):
    596         self.AddAttributePerTimeInfo(itemID=itemID, attributeID=const.attributePowerTransferAmount, labelPath='UI/Inflight/ModuleRacks/Tooltips/EnergyVampireAmountPerTime')
    597 
    598     def AddEnergyDestabilizerInfo(self, itemID, chargeInfoItem):
    599         self.AddAttributePerTimeInfo(itemID=itemID, attributeID=const.attributeEnergyNeutralizerAmount, labelPath='UI/Inflight/ModuleRacks/Tooltips/EnergDestabilizedPerTime')
    600 
    601     def AddArmorRepairersInfo(self, itemID, chargeInfoItem):
    602         if chargeInfoItem:
    603             chargeMultiplier = self.GetEffectiveAttributeValue(itemID, const.attributeChargedArmorDamageMultiplier)
    604         else:
    605             chargeMultiplier = 1.0
    606         self.AddAttributePerTimeInfo(itemID=itemID, attributeID=const.attributeArmorDamageAmount, labelPath='UI/Inflight/ModuleRacks/Tooltips/ArmorRepairedPerTime', chargeMultiplier=chargeMultiplier)
    607 
    608     def AddHullRepairersInfo(self, itemID, chargeInfoItem):
    609         self.AddAttributePerTimeInfo(itemID=itemID, attributeID=const.attributeStructureDamageAmount, labelPath='UI/Inflight/ModuleRacks/Tooltips/HullRepairedPerTime')
    610 
    611     def AddShieldBoosterInfo(self, itemID, chargeInfoItem):
    612         self.AddAttributePerTimeInfo(itemID=itemID, attributeID=const.attributeShieldBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ShieldBonusPerTime')
    613 
    614     def AddTrackingComputerInfo(self, itemID, chargeInfoItem):
    615         self.AddAttributeInfo(itemID=itemID, attributeID=const.attributeFalloffBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/TrackingComputerFalloffBonus', labelKeyword='falloffBonus')
    616         self.AddAttributeInfo(itemID=itemID, attributeID=const.attributeMaxRangeBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/TrackingComputerRangeBonus', labelKeyword='optimalRangeBonus')
    617         self.AddAttributeInfo(itemID=itemID, attributeID=const.attributeTrackingSpeedBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/TrackingComputerTrackingBonus', labelKeyword='trackingSpeedBonus')
    618 
    619     def AddSmartBombInfo(self, itemID, chargeInfoItem):
    620         attrID = None
    621         damage = 0
    622         for attributeID in dogma_attr_groups.damage_type_attribute_ids:
    623             damage = self.GetEffectiveAttributeValue(itemID, attributeID)
    624             if damage > 0:
    625                 attrID = attributeID
    626                 break
    627 
    628         attributeInfo = dogma_data.get_attribute(attrID)
    629         damageType = dogma_data.get_attribute_display_name(attrID)
    630         iconID = attributeInfo.iconID
    631         text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/SmartBombDamage', amount=damage, damageType=damageType)
    632         self.AddRowWithIconAndText(text=text, iconID=iconID)
    633 
    634     def AddPropulsionModuleInfo(self, itemID, chargeInfoItem):
    635         myShipID = self.dogmaLocation.GetActiveShipID()
    636         if self.dogmaLocation.locationName == DL_NAME_FITTING:
    637             _, _, activityState = self.dogmaLocation.GetActivityState(self.moduleItemID)
    638             maxVelocity = self.dogmaLocation.GetAccurateAttributeValue(myShipID, attributeMaxVelocity)
    639             if activityState in (ACTIVE, OVERHEATED):
    640                 text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/MaxVelocityWith', maxVelocity=maxVelocity)
    641             else:
    642                 text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/MaxVelocityWithout', maxVelocity=maxVelocity)
    643         else:
    644             speedFactor, speedBoostFactor = self._GetSpeedBoostForPropulsionModule(itemID)
    645             currentMass = self.dogmaLocation.GetAccurateAttributeValue(myShipID, attributeMass)
    646             godmaEffect = self._GetDefaultDogmaEffect(itemID, self.moduleInfoItem.typeID)
    647             if godmaEffect.isActive:
    648                 maxVelocityWithBonus = self.dogmaLocation.GetAccurateAttributeValue(myShipID, attributeMaxVelocity)
    649                 myMaxVelocity = maxVelocityWithBonus / self._GetVelocityRatio(speedBoostFactor, speedFactor, currentMass)
    650             else:
    651                 myMaxVelocity, activatedMass = self._GetMyMaxVelocityAndMassOnActivation(itemID, myShipID, currentMass)
    652                 maxVelocityWithBonus = myMaxVelocity * self._GetVelocityRatio(speedBoostFactor, speedFactor, activatedMass)
    653             text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/MaxVelocityWithAndWithoutPropulsion', maxVelocity=myMaxVelocity, maxVelocityWithBonus=maxVelocityWithBonus)
    654         iconID = dogma_data.get_attribute_icon_id(attributeMaxVelocity)
    655         self.AddRowWithIconAndText(text=text, iconID=iconID)
    656 
    657     def _GetMyMaxVelocityAndMassOnActivation(self, propulsionItemID, myShipID, currentMass):
    658         myMaxVelocity = self.dogmaLocation.GetAccurateAttributeValue(myShipID, attributeMaxVelocity)
    659         typeBaseMass = self.dogmaLocation.GetAttributeValue(myShipID, attributeMass)
    660         massAddition = self.dogmaLocation.GetAttributeValue(propulsionItemID, attributeMassAddition)
    661         massFactor = currentMass / typeBaseMass
    662         massOnActivation = massFactor * (typeBaseMass + massAddition)
    663         shipItem = self.dogmaLocation.GetDogmaItem(myShipID)
    664         fittedItems = shipItem.GetFittedItems()
    665         for fittedModuleItemID, moduleItem in fittedItems.iteritems():
    666             if propulsionItemID == fittedModuleItemID:
    667                 continue
    668             if moduleItem.groupID == groupPropulsionModule:
    669                 godmaEffect = self._GetDefaultDogmaEffect(fittedModuleItemID, moduleItem.typeID)
    670                 if godmaEffect.isActive:
    671                     speedFactor, speedBoostFactor = self._GetSpeedBoostForPropulsionModule(fittedModuleItemID)
    672                     massAddition = self.dogmaLocation.GetAttributeValue(fittedModuleItemID, attributeMassAddition)
    673                     massFactor = currentMass / typeBaseMass
    674                     massOnActivation = massFactor * (currentMass - massAddition)
    675                     myMaxVelocity = myMaxVelocity / self._GetVelocityRatio(speedBoostFactor, speedFactor, currentMass)
    676                     break
    677 
    678         return (myMaxVelocity, massOnActivation)
    679 
    680     def _GetDefaultDogmaEffect(self, itemID, typeID):
    681         effectName = self.stateManager.GetDefaultEffect(typeID).effectName
    682         godmaItem = self.dogmaLocation.godma.GetItem(itemID)
    683         return godmaItem.effects[effectName]
    684 
    685     def _GetVelocityRatio(self, speedBoostFactor, speedFactor, mass):
    686         return 1 + speedBoostFactor * speedFactor * 0.01 / mass
    687 
    688     def _GetSpeedBoostForPropulsionModule(self, moduleItemID):
    689         speedFactor = self.GetEffectiveAttributeValue(moduleItemID, attributeSpeedFactor)
    690         speedBoostFactor = self.GetEffectiveAttributeValue(moduleItemID, attributeSpeedBoostFactor)
    691         return (speedFactor, speedBoostFactor)
    692 
    693     def AddStasisWebInfo(self, itemID, chargeInfoItem):
    694         attributeID = const.attributeSpeedFactor
    695         amount = self.GetEffectiveAttributeValue(itemID, attributeID)
    696         text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/VelocityReductionFromWeb', percentage=abs(amount))
    697         iconID = dogma_data.get_attribute_icon_id(attributeID)
    698         self.AddRowWithIconAndText(text=text, iconID=iconID)
    699 
    700     def AddWarpScramblerInfo(self, itemID, chargeInfoItem):
    701         self.AddAttributeInfo(itemID=itemID, attributeID=const.attributeWarpScrambleStrength, labelPath='UI/Inflight/ModuleRacks/Tooltips/WarpScramblerStrength', labelKeyword='strength')
    702 
    703     def AddCapacitorBoosterInfo(self, itemID, chargeInfoItem):
    704         attributeID = const.attributeCapacitorBonus
    705         duration = self.GetDuration(itemID)
    706         if chargeInfoItem is None:
    707             return
    708         amount = self.GetEffectiveAttributeValue(chargeInfoItem.itemID, attributeID)
    709         text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/CapacitorBoostPerTime', boostAmount=amount, duration=duration)
    710         iconID = dogma_data.get_attribute_icon_id(attributeID)
    711         self.AddRowWithIconAndText(text=text, iconID=iconID)
    712 
    713     def AddEnergyTransferArrayInfo(self, itemID, chargeInfoItem):
    714         self.AddAttributePerTimeInfo(itemID=itemID, attributeID=const.attributePowerTransferAmount, labelPath='UI/Inflight/ModuleRacks/Tooltips/EnergyTransferredPerTime')
    715 
    716     def AddShieldTransporterInfo(self, itemID, chargeInfoItem):
    717         self.AddAttributePerTimeInfo(itemID=itemID, attributeID=const.attributeShieldBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ShieldTransportedPerTime')
    718 
    719     def AddArmorRepairProjectorInfo(self, itemID, chargeInfoItem):
    720         self.AddAttributePerTimeInfo(itemID=itemID, attributeID=const.attributeArmorDamageAmount, labelPath='UI/Inflight/ModuleRacks/Tooltips/ArmorTransferredPerTime')
    721 
    722     def AddRemoteHullRepairInfo(self, itemID, chargeInfoItem):
    723         self.AddAttributePerTimeInfo(itemID=itemID, attributeID=const.attributeStructureDamageAmount, labelPath='UI/Inflight/ModuleRacks/Tooltips/HullRemoteRepairedPerTime')
    724 
    725     def AddArmorHardenerInfo(self, itemID, chargeInfoItem):
    726         damageAttributeValues = self.GetAttributesValues(itemID, 1, RESISTANCE_BONUS_ATTRIBUTES, includeZeros=False)
    727         self.AddRowForInfoWithOneOrMoreAttributes(attributeValues=damageAttributeValues, oneAttributeText='UI/Inflight/ModuleRacks/Tooltips/ResistanceActiveBonusText', manyAttributesText='UI/Inflight/ModuleRacks/Tooltips/ResistanceActiveBonusValues', headerText='UI/Inflight/ModuleRacks/Tooltips/ResistanceActiveBonusesHeader')
    728 
    729     def AddECMInfo(self, itemID, chargeInfoItem):
    730         bonusAttributes = [const.attributeScanGravimetricStrengthBonus,
    731          const.attributeScanLadarStrengthBonus,
    732          const.attributeScanMagnetometricStrengthBonus,
    733          const.attributeScanRadarStrengthBonus]
    734         rows = []
    735         for attrID in bonusAttributes:
    736             strength = self.GetEffectiveAttributeValue(itemID, attrID)
    737             if strength is not None and strength != 0:
    738                 attributeName = dogma_data.get_attribute_display_name(attrID)
    739                 rows.append(localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/ECMStrengthBonus', strength=strength, attributeName=attributeName))
    740 
    741         text = '<br>'.join(rows)
    742         self.AddRowWithIconAndText(text=text, texturePath='res:/UI/Texture/Icons/4_64_12.png')
    743 
    744     def AddECCMInfo(self, itemID, chargeInfoItem):
    745         damageTypeAttributes = [const.attributeScanGravimetricStrengthPercent,
    746          const.attributeScanLadarStrengthPercent,
    747          const.attributeScanMagnetometricStrengthPercent,
    748          const.attributeScanRadarStrengthPercent]
    749         rows = []
    750         for attrID in damageTypeAttributes:
    751             strength = self.GetEffectiveAttributeValue(itemID, attrID)
    752             if strength is not None and strength != 0:
    753                 attributeName = dogma_data.get_attribute_display_name(attrID)
    754                 rows.append(localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName', activeValue=strength, activeName=attributeName))
    755 
    756         text = '<br>'.join(rows)
    757         if text:
    758             self.AddRowWithIconAndText(text=text, texturePath='res:/UI/Texture/Icons/4_64_12.png')
    759 
    760     def AddSensorDamperInfo(self, itemID, chargeInfoItem):
    761         bonus = self.GetEffectiveAttributeValue(itemID, const.attributeScanResolutionBonus)
    762         if bonus != 0:
    763             self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeScanResolutionBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName')
    764         bonus = self.GetEffectiveAttributeValue(itemID, const.attributeMaxTargetRangeBonus)
    765         if bonus != 0:
    766             self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeMaxTargetRangeBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName')
    767         self.AddECCMInfo(itemID, chargeInfoItem)
    768 
    769     def AddTargetBreakerInfo(self, itemID, chargeInfoItem):
    770         self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeSignatureSuppressorSignatureRadiusBonusPassive, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName')
    771 
    772     def AddTargetPainterInfo(self, itemID, chargeInfoItem):
    773         self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeSignatureRadiusBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName')
    774 
    775     def AddTrackingDisruptorInfo(self, itemID, chargeInfoItem):
    776         self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeFalloffBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName', skipZeroValue=True)
    777         self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeMaxRangeBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName', skipZeroValue=True)
    778         self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeTrackingSpeedBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName', skipZeroValue=True)
    779         self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeMissileVelocityBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName', skipZeroValue=True)
    780         self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeExplosionDelayBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName', skipZeroValue=True)
    781         self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeAoeVelocityBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName', skipZeroValue=True)
    782         self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeAoeCloudSizeBonus, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName', skipZeroValue=True)
    783 
    784     def AddCloakingDeviceInfo(self, itemID, chargeInfoItem):
    785         self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeMaxVelocityModifier, labelPath='UI/Inflight/ModuleRacks/Tooltips/ValuePercentageWithAttributeName')
    786 
    787     def AddTractorBeamInfo(self, itemID, chargeInfoItem):
    788         attributeID = const.attributeMaxTractorVelocity
    789         maxTractorVel = self.GetEffectiveAttributeValue(itemID, attributeID)
    790         iconID = dogma_data.get_attribute_icon_id(attributeID)
    791         displayName = dogma_data.get_attribute_display_name(attributeID)
    792         text = (localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/TractorBeamTractorVelocity', maxTractorVel=maxTractorVel, attributeName=displayName),)
    793         self.AddRowWithIconAndText(text=text, iconID=iconID)
    794 
    795     def AddDamageControlInfo(self, itemID, chargeInfoItem):
    796         shieldDamageTypeAttributes = [const.attributeShieldEmDamageResonance,
    797          const.attributeShieldExplosiveDamageResonance,
    798          const.attributeShieldKineticDamageResonance,
    799          const.attributeShieldThermalDamageResonance]
    800         armorDamageTypeAttributes = [const.attributeArmorEmDamageResonance,
    801          const.attributeArmorExplosiveDamageResonance,
    802          const.attributeArmorKineticDamageResonance,
    803          const.attributeArmorThermalDamageResonance]
    804         hullDamageTypeAttributes = [const.attributeHullEmDamageResonance,
    805          const.attributeHullExplosiveDamageResonance,
    806          const.attributeHullKineticDamageResonance,
    807          const.attributeHullThermalDamageResonance]
    808         for damageTypeAttributes, texturePath, headerText in ((shieldDamageTypeAttributes, 'res:/UI/Texture/Icons/1_64_13.png', 'UI/Inflight/ModuleRacks/Tooltips/ShieldDamageResistanceHeader'), (armorDamageTypeAttributes, 'res:/UI/Texture/Icons/1_64_9.png', 'UI/Inflight/ModuleRacks/Tooltips/ArmorDamageResistanceHeader'), (hullDamageTypeAttributes, 'res:/UI/Texture/Icons/2_64_12.png', 'UI/Inflight/ModuleRacks/Tooltips/HullDamageResistanceHeader')):
    809             attributeValues = self.GetAttributesValues(itemID, 1, damageTypeAttributes, includeZeros=True)
    810             self.AddRowAndHeaderForManyAttributes(attributeValues=attributeValues, manyAttributesText='UI/Inflight/ModuleRacks/Tooltips/ResistanceActiveBonusValues', headerText=headerText, texturePath=texturePath)
    811 
    812     def AddArmorResistanceShiftHardenerInfo(self, itemID, chargeInfoItem):
    813         damageTypeAttributes = [const.attributeArmorEmDamageResonance,
    814          const.attributeArmorExplosiveDamageResonance,
    815          const.attributeArmorKineticDamageResonance,
    816          const.attributeArmorThermalDamageResonance]
    817         attributeValues = self.GetAttributesValues(itemID, 1, damageTypeAttributes, includeZeros=True)
    818         self.AddRowAndHeaderForManyAttributes(attributeValues=attributeValues, manyAttributesText='UI/Inflight/ModuleRacks/Tooltips/ResistanceActiveBonusValues', headerText='UI/Inflight/ModuleRacks/Tooltips/ArmorDamageResistanceHeader', texturePath='')
    819 
    820     def AddSuperWeaponInfo(self, itemID, chargeInfoItem):
    821         for attributeID in dogma_attr_groups.damage_type_attribute_ids:
    822             damage = self.GetEffectiveAttributeValue(itemID, attributeID)
    823             if damage == 0:
    824                 continue
    825             self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=attributeID, labelPath='UI/Inflight/ModuleRacks/Tooltips/SuperWeaponDamage')
    826 
    827         self.AddDurationCountdownTimes(itemID)
    828 
    829     def AddGangCoordinatorInfo(self, itemID, chargeInfoItem):
    830         commandBonus = self.GetEffectiveAttributeValue(itemID, const.attributeCommandbonus)
    831         if commandBonus != 0:
    832             displayName = dogma_data.get_attribute_display_name(const.attributeCommandbonus)
    833             text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/GangCoordinatorCommandBonus', commandBonus=commandBonus, attributeName=displayName)
    834             self.AddRowWithIconAndText(text=text, texturePath='')
    835         maxGangModulesAttrName = dogma_data.get_attribute_name(const.attributeMaxGangModules)
    836         if maxGangModulesAttrName in self.stateManager.GetAttributes(itemID):
    837             maxGangModules = self.GetEffectiveAttributeValue(itemID, const.attributeMaxGangModules)
    838             displayName = dogma_data.get_attribute_display_name(const.attributeMaxGangModules)
    839             text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/GangCoordinatorMaxCommandRelays', maxGangModules=maxGangModules, attributeName=displayName)
    840             self.AddRowWithIconAndText(text=text, texturePath='')
    841 
    842     def AddStructureDefenseBatteryInfo(self, itemID, chargeInfoItem):
    843         self.AddDamageTypes(itemID, chargeInfoItem, 1)
    844 
    845     def AddScanProbeLauncherInfo(self, itemID, chargeInfoItem):
    846         if chargeInfoItem:
    847             self.AddAttributeInfoWithAttributeName(itemID=chargeInfoItem.itemID, attributeID=const.attributeBaseSensorStrength, labelPath='UI/Inflight/ModuleRacks/Tooltips/ScanProbeSensorStrength')
    848 
    849     def AddCynoInfo(self, itemID, chargInfoItem):
    850         self.AddDurationCountdownTimes(itemID)
    851 
    852     def AddSiegeInfo(self, itemID, chargInfoItem):
    853         self.AddDurationCountdownTimes(itemID)
    854 
    855     def AddDotWeaponInfo(self, itemID, chargeInfoItem):
    856         if chargeInfoItem:
    857             GAV = self.dogmaLocation.GetAccurateAttributeValue
    858             dotMaxDamagePerTick = GetDotDps(chargeInfoItem.itemID, itemID, GAV)
    859             text = localization.GetByLabel('UI/Inflight/ModuleRacks/DamagePerSecond', dps=dotMaxDamagePerTick)
    860             self.AddRowWithIconAndText(text=text, iconID=appConst.iconBreacherPod)
    861             attributeID = const.attributeDotDuration
    862             durationText = self.GetDuration(chargeInfoItem.itemID, attributeID)
    863             displayName = dogma_data.get_attribute_display_name(attributeID)
    864             text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/DotEffectDuration', attributeName=displayName, duration=durationText)
    865             self.AddRowWithIconAndText(text=text, iconID=dogma_data.get_attribute_icon_id(attributeID))
    866 
    867     def AddTrackingSpeed(self, itemID):
    868         self.AddAttributeInfoWithAttributeName(itemID=itemID, attributeID=const.attributeTrackingSpeed, labelPath='UI/Inflight/ModuleRacks/Tooltips/WeaponAccuracyScore', skipZeroValue=True)
    869 
    870     def AddPrecursorTurretInfo(self, itemID, chargeInfoItem):
    871         if self.dogmaLocation.locationName == DL_NAME_FITTING:
    872             return
    873         godmaEffect = self._GetDefaultDogmaEffect(itemID, self.moduleInfoItem.typeID)
    874         if not godmaEffect.isActive:
    875             return
    876         damageMultiplierBonusMax = self.dogmaLocation.GetAccurateAttributeValue(itemID, const.attributeDamageMultiplierBonusMax)
    877         current = self.dogmaLocation.GetAccurateAttributeValue(itemID, const.attributeDamageMultiplierBonusCurrent)
    878         ratio = current / float(damageMultiplierBonusMax)
    879         maxTimestamp = self.dogmaLocation.GetAccurateAttributeValue(itemID, const.attributeDamageMultiplierBonusMaxTimestamp)
    880         if maxTimestamp:
    881             timeUntilFullDmg = int(maxTimestamp - gametime.GetSimTime())
    882             self.FillRow()
    883             if timeUntilFullDmg > 0:
    884                 timeText = FormatTimeIntervalShortWritten(timeUntilFullDmg, showFrom='minute')
    885                 timeUntilText = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/TimeUntilMaxDamage', time=timeText)
    886             else:
    887                 timeUntilText = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/MaxDamage')
    888             self.AddRowWithIconAndText(text=timeUntilText)
    889         self.FillRow()
    890         cont = Container(align=uiconst.TOTOP, height=30)
    891         ratioText = localization.GetByLabel('UI/Common/Percentage', percentage=int(round(100 * ratio)))
    892         ratioLabel = TextBody(parent=cont, text=ratioText, align=uiconst.CENTERRIGHT)
    893         rowSize = max(ratioLabel.height + 4, 20)
    894         gaugeHeight = 6
    895         gaugeTop = (rowSize - gaugeHeight) / 2
    896         cont.height = rowSize
    897         gauge = Gauge(parent=cont, top=gaugeTop, gaugeHeight=gaugeHeight, align=uiconst.TOTOP, padLeft=3, padRight=max(ratioLabel.width + 8, 36), glowBrightness=0.1, color=THEME_ACCENT)
    898         gauge.SetValue(ratio, animate=False)
    899         self.AddRowWithIconAndContainer(texturePath='', container=cont, minRowSize=rowSize)
    900 
    901     def AddCountDownTimes(self, itemID):
    902         self._AddCountDownTimes(itemID, const.attributeModuleReactivationDelay, self.stateManager.GetCooldownTimes)
    903         self._AddCountDownTimes(itemID, const.attributeReloadTime, self.stateManager.GetReloadTimes)
    904 
    905     def AddDurationCountdownTimes(self, itemID):
    906         self._AddCountDownTimes(itemID, const.attributeDuration, self._GetDurationTimeLeft)
    907 
    908     def _AddCountDownTimes(self, itemID, attributeID, getTimeFunc):
    909         if GetActiveShip() != self.dogmaLocation.GetCurrentShipID():
    910             return
    911         itemCountdown = self.dogmaLocation.GetAccurateAttributeValue(itemID, attributeID)
    912         if not itemCountdown:
    913             return
    914         coundDownTimes = getTimeFunc(itemID)
    915         valueText = self._GetCountdownTime(coundDownTimes)
    916         if not valueText:
    917             return
    918         text = dogma_data.get_attribute_display_name(attributeID)
    919         self.AddRowWithIconAndTextAndValue(text=text, valueText=valueText, texturePath='')
    920 
    921     def _GetCountdownTime(self, countdownTimes):
    922         if countdownTimes is None:
    923             return
    924         startTime, delayTime = countdownTimes
    925         now = gametime.GetSimTime()
    926         finishTime = startTime + delayTime * const.MSEC
    927         diff = finishTime - now
    928         if diff < 0:
    929             return
    930         return FormatTimeIntervalShort(long(diff), showFrom='minute')
    931 
    932     def _GetDurationTimeLeft(self, itemID):
    933         return self.stateManager.GetDurationTimeLeft(itemID, self.moduleInfoItem.typeID)
    934 
    935     def GetTimeLeftRepairingCountDown(self, timeLeft):
    936         valueText = FormatTimeIntervalShort(long(timeLeft), showFrom='minute')
    937         text = localization.GetByLabel('UI/Inflight/ModuleRacks/Tooltips/RepairTimeLeft')
    938         self.AddRowWithIconAndTextAndValue(text=text, valueText=valueText, texturePath='')
    939 
    940     def GetTotalDamageWhileRepairing(self, itemID, showSingleModuleReadout = True):
    941         dogmaLocation = self.dogmaLocation
    942         currentShipID = dogmaLocation.GetCurrentShipID()
    943         masterID = dogmaLocation.IsInWeaponBank(currentShipID, itemID)
    944         if masterID and not showSingleModuleReadout:
    945             allModulesInBank = dogmaLocation.GetModulesInBank(currentShipID, masterID)
    946         else:
    947             allModulesInBank = [itemID]
    948         maxDamage = 0
    949         maxTimeLeft = 0
    950         newNow = blue.os.GetSimTime()
    951         rateOfRepairInMin = self.stateManager.GetAttribute(session.charid, 'moduleRepairRate')
    952         rateOfRepair = const.MIN / rateOfRepairInMin
    953         for eachModuleID in allModulesInBank:
    954             repairStartTimeStamp = self.stateManager.GetRepairTimeStamp(eachModuleID)
    955             if not repairStartTimeStamp:
    956                 continue
    957             moduleDamage = dogmaLocation.GetAccurateAttributeValue(eachModuleID, const.attributeDamage)
    958             repairTime = int(moduleDamage * rateOfRepair)
    959             timeLeft = max(0, repairStartTimeStamp + repairTime - newNow)
    960             damageLeftToRepair = timeLeft / float(repairTime)
    961             currentDamgeOnModule = moduleDamage * damageLeftToRepair
    962             maxDamage = max(currentDamgeOnModule, maxDamage)
    963             maxTimeLeft = max(timeLeft, maxTimeLeft)
    964 
    965         return (maxDamage, maxTimeLeft)