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

shipmodulebutton.py (61249B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\inflight\shipModuleButton\shipmodulebutton.py
      2 import math
      3 import numbers
      4 import re
      5 import dogma.data
      6 import eveicon
      7 import evetypes
      8 from carbon.client.script.environment.AudioUtil import PlaySound
      9 from carbon.client.script.util.misc import HasAttrs
     10 from carbon.common.script.sys.serviceConst import ROLE_GML, ROLE_WORLDMOD
     11 from carbon.common.script.util import timerstuff
     12 from carbon.common.script.util.format import FmtAmt
     13 from carbonui.control.contextMenu.menuEntryData import MenuEntryData
     14 from carbonui.primitives.container import Container
     15 from carbonui.primitives.sprite import Sprite
     16 from carbonui.loggers.buttonLogger import log_button_clicked
     17 from eve.client.script.environment.godma import MODULE_NOT_OVERLOADED, MODULE_OVERLOADED, MODULE_PENDING_OVERLOADING, MODULE_PENDING_STOPOVERLOADING
     18 from eve.client.script.ui.control import eveIcon, eveLabel
     19 from eveexceptions import UserError
     20 from inventorycommon.util import GetItemVolume
     21 import uthread
     22 from eve.client.script.ui.util import uix
     23 import blue
     24 from eve.client.script.parklife import states as state
     25 import carbonui.const as uiconst
     26 import inventorycommon.const as invconst
     27 import log
     28 import localization
     29 import trinity
     30 from eve.client.script.ui.crimewatch import crimewatchConst
     31 from eve.client.script.ui.inflight.shipModuleButton.moduleButtonTooltip import TooltipModuleWrapper
     32 from eve.client.script.ui.inflight.shipModuleButton.ramps import DamageStateCont, ShipModuleButtonRamps, ShipModuleReactivationTimer
     33 from eve.client.script.ui.tooltips.tooltipHandler import TOOLTIP_SETTINGS_MODULE, TOOLTIP_DELAY_MODULE
     34 from eve.common.script.sys.eveCfg import IsDocked
     35 from eve.common.script.sys.rowset import IndexedRows
     36 from carbonui.uicore import uicore
     37 from menu import MenuLabel
     38 from player_messaging.client.ui_message import prompt_player_fsd_dialog
     39 cgre = re.compile('chargeGroup\\d{1,2}')
     40 GLOWCOLOR = (0.24, 0.67, 0.16, 0.75)
     41 BUSYCOLOR = (1.0, 0.13, 0.0, 0.73)
     42 OVERLOADBTN_INDEX = 1
     43 MODULEHINTDELAY = 800
     44 GROUPS_THAT_ALWAYS_AUTO_REPEAT = (invconst.groupMiningLaser, invconst.groupStripMiner, invconst.groupEntosisLink)
     45 SOUND_MODULE_ONLINE = 'ui_module_online_play'
     46 SOUND_MODULE_OFFLINE = 'ui_module_offline_play'
     47 
     48 class ModuleButton(Container):
     49     __guid__ = 'xtriui.ModuleButton'
     50     __notifyevents__ = ['OnStateChange',
     51      'OnModuleRepaired',
     52      'OnAmmoInBankChanged',
     53      'OnFailLockTarget',
     54      'OnChargeBeingLoadedToModule',
     55      'OnModulesDeactivating',
     56      'OnMultiModuleRepairStarted']
     57     __update_on_reload__ = 1
     58     __cgattrs__ = []
     59     __chargesizecache__ = {}
     60     default_name = 'ModuleButton'
     61     default_pickRadius = 20
     62     isDragObject = True
     63     def_effect = None
     64     charge = None
     65     target = None
     66     waitingForActiveTarget = 0
     67     online = False
     68     stateManager = None
     69     dogmaLocation = None
     70     autorepeat = 0
     71     autoreload = 0
     72     quantity = None
     73     invReady = 1
     74     invCookie = None
     75     isInvItem = 1
     76     isBeingRepaired = 0
     77     blinking = 0
     78     blinkingDamage = 0
     79     effect_activating = 0
     80     typeName = ''
     81     ramp_active = False
     82     isMaster = 0
     83     animation = None
     84     isPendingUnlockForDeactivate = False
     85     moduleHintTimer = None
     86     shouldUpdate = False
     87     moduleButtonHint = None
     88     moduleinfo = None
     89     tooltipPanelClassInfo = TooltipModuleWrapper()
     90 
     91     @property
     92     def reloadingAmmo(self):
     93         try:
     94             return self.stateManager.IsModuleReloading(self.moduleinfo.itemID)
     95         except KeyError:
     96             return False
     97 
     98     def ApplyAttributes(self, attributes):
     99         Container.ApplyAttributes(self, attributes)
    100         self.icon = eveIcon.Icon(parent=self, align=uiconst.TOALL, state=uiconst.UI_DISABLED)
    101         self.reloadAnimationThread = self.blinkButtonThread = None
    102         sm.RegisterNotify(self)
    103 
    104     def Close(self, *args, **kwds):
    105         if getattr(self, 'invCookie', None) is not None:
    106             sm.GetService('inv').Unregister(self.invCookie)
    107         Container.Close(self, *args, **kwds)
    108 
    109     def GetRepeatCount(self):
    110         repeat = settings.char.autorepeat.Get(self.moduleinfo.itemID, -1)
    111         alwaysAutoRepeats = evetypes.GetGroupID(self.moduleinfo.typeID) in GROUPS_THAT_ALWAYS_AUTO_REPEAT
    112         containsRepeatableEffect = any(map(self.IsEffectRepeatable, self.moduleinfo.effects.itervalues()))
    113         if alwaysAutoRepeats:
    114             return 1000
    115         if repeat != -1:
    116             return repeat
    117         if containsRepeatableEffect:
    118             return 1000
    119         return 0
    120 
    121     def Setup(self, moduleinfo, grey = None):
    122         self.crimewatchSvc = sm.GetService('crimewatchSvc')
    123         if not len(self.__cgattrs__):
    124             self.__cgattrs__.extend([dogma.data.get_charge_group_attribute_ids()])
    125         self.id = moduleinfo.itemID
    126         self.moduleinfo = moduleinfo
    127         self.locationFlag = moduleinfo.flagID
    128         self.stateManager = sm.StartService('godma').GetStateManager()
    129         self.dogmaLocation = sm.GetService('clientDogmaIM').GetDogmaLocation()
    130         self.grey = grey
    131         self.isInActiveState = True
    132         self.isDeactivating = False
    133         icon = self.parent.GetChild('overloadBtn')
    134         icon.hint = localization.GetByLabel('UI/Inflight/Overload/TurnOnOverload')
    135         icon.OnClick = self.ToggleOverload
    136         icon.OnMouseDown = (self.OLButtonDown, icon)
    137         icon.OnMouseUp = (self.OLButtonUp, icon)
    138         icon.OnMouseEnter = (self.OLMouseEnter, icon)
    139         icon.OnMouseExit = (self.OLMouseExit, icon)
    140         icon.SetOrder(OVERLOADBTN_INDEX)
    141         self.sr.overloadButton = icon
    142         if cfg.IsChargeCompatible(moduleinfo):
    143             self.invCookie = sm.GetService('inv').Register(self)
    144         self.autoreload = settings.char.autoreload.Get(self.moduleinfo.itemID, 1)
    145         if evetypes.GetCategoryID(moduleinfo.typeID) == const.categoryCharge:
    146             self.SetCharge(moduleinfo)
    147         else:
    148             self.SetCharge(None)
    149         self.autoreload = settings.char.autoreload.Get(self.moduleinfo.itemID, 1)
    150         for key in moduleinfo.effects.iterkeys():
    151             effect = moduleinfo.effects[key]
    152             if self.IsEffectActivatible(effect):
    153                 self.def_effect = effect
    154                 if effect.isActive:
    155                     if effect.isDeactivating:
    156                         self.SetDeactivating()
    157                     else:
    158                         self.SetActive()
    159             if effect.effectName == 'online':
    160                 if effect.isActive:
    161                     self.ShowOnline()
    162                 else:
    163                     self.ShowOffline()
    164 
    165         self.autoreload = settings.char.autoreload.Get(self.moduleinfo.itemID, 1)
    166         repairTimeStamps = self.stateManager.GetRepairTimeStamp(self.id)
    167         if repairTimeStamps:
    168             self.isBeingRepaired = True
    169             self.SetRepairing(repairTimeStamps)
    170         self.TryStartCooldownTimers()
    171         reloadTimes = self.stateManager.GetReloadTimes(self.id)
    172         if reloadTimes:
    173             startTime, duration = reloadTimes
    174             self.DoReloadAnimation(duration, startTime=startTime)
    175         repeatCount = self.GetRepeatCount()
    176         self.SetRepeat(repeatCount)
    177         self.autoreload = settings.char.autoreload.Get(self.moduleinfo.itemID, 1)
    178         if not self.isDeactivating:
    179             self.isInActiveState = True
    180         else:
    181             self.isInActiveState = False
    182         self.slaves = self.dogmaLocation.GetSlaveModules(self.moduleinfo.itemID, session.shipid)
    183         self.UpdateModuleDamage()
    184         self.EnableDrag()
    185         self.autoreload = settings.char.autoreload.Get(self.moduleinfo.itemID, 1)
    186         uthread.new(self.BlinkIcon)
    187 
    188     def UpdateModuleDamage(self):
    189         moduleDamage = self.GetModuleDamage()
    190         if moduleDamage:
    191             self.SetDamage(moduleDamage / self.moduleinfo.hp)
    192         else:
    193             self.SetDamage(0.0)
    194 
    195     def OLButtonDown(self, btn, *args):
    196         btn.top = 6
    197 
    198     def OLButtonUp(self, btn, *args):
    199         btn.top = 5
    200 
    201     def OLMouseExit(self, btn, *args):
    202         btn.top = 5
    203 
    204     def OLMouseEnter(self, btn, *args):
    205         PlaySound(uiconst.SOUND_BUTTON_HOVER)
    206 
    207     def ToggleOverload(self, *args):
    208         icon = getattr(getattr(self, 'sr', None), 'overloadButton', None)
    209         if icon is not None:
    210             icon.analyticID = 'ship_hud.module_overload'
    211             log_button_clicked(icon)
    212         if settings.user.ui.Get('lockOverload', 0):
    213             existingModal = uicore.registry.GetModalWindow()
    214             if existingModal and existingModal.msgKey == 'LockedOverloadState':
    215                 return
    216             prompt_player_fsd_dialog('error')
    217             prompt_player_fsd_dialog('LockedOverloadState')
    218             return
    219         for effect in self.moduleinfo.effects.itervalues():
    220             if effect.effectCategory == const.dgmEffOverload:
    221                 effectID = effect.effectID
    222                 break
    223         else:
    224             return
    225 
    226         overloadState = self.stateManager.GetOverloadState(self.moduleinfo.itemID)
    227         prompt_player_fsd_dialog('click')
    228         itemID = self.moduleinfo.itemID
    229         if overloadState == MODULE_NOT_OVERLOADED:
    230             self.stateManager.Overload(itemID, effectID)
    231             self.sr.overloadButton.hint = localization.GetByLabel('UI/Inflight/Overload/TurnOffOverload')
    232         elif overloadState == MODULE_OVERLOADED:
    233             self.stateManager.StopOverload(itemID, effectID)
    234             self.sr.overloadButton.hint = localization.GetByLabel('UI/Inflight/Overload/TurnOnOverload')
    235         elif overloadState == MODULE_PENDING_OVERLOADING:
    236             self.stateManager.StopOverload(itemID, effectID)
    237         elif overloadState == MODULE_PENDING_STOPOVERLOADING:
    238             self.stateManager.StopOverload(itemID, effectID)
    239 
    240     def UpdateOverloadState(self):
    241         overloadState = self.stateManager.GetOverloadState(self.moduleinfo.itemID)
    242         if overloadState == MODULE_PENDING_OVERLOADING:
    243             self.animation = uicore.animations.BlinkIn(self.sr.overloadButton, startVal=1.8, endVal=1.0, duration=0.5, loops=uiconst.ANIM_REPEAT)
    244         elif overloadState == MODULE_PENDING_STOPOVERLOADING:
    245             self.animation = uicore.animations.BlinkIn(self.sr.overloadButton, startVal=0.2, endVal=1.0, duration=0.5, loops=uiconst.ANIM_REPEAT)
    246         else:
    247             if self.animation:
    248                 self.animation.Stop()
    249             self.sr.overloadButton.SetAlpha(1.0)
    250         if overloadState == MODULE_OVERLOADED:
    251             self.sr.overloadButton.hint = localization.GetByLabel('UI/Inflight/Overload/TurnOffOverload')
    252         elif overloadState == MODULE_NOT_OVERLOADED:
    253             self.sr.overloadButton.hint = localization.GetByLabel('UI/Inflight/Overload/TurnOnOverload')
    254 
    255     def InitQuantityLabel(self):
    256         if self.sr.qtylabel is None:
    257             quantityParent = Container(parent=self, name='quantityParent', pos=(18, 27, 24, 10), align=uiconst.TOPLEFT, state=uiconst.UI_DISABLED, idx=0)
    258             self.sr.qtylabel = eveLabel.Label(text='', parent=quantityParent, fontsize=9, letterspace=1, left=3, top=0, width=30, state=uiconst.UI_DISABLED)
    259             underlay = Sprite(parent=quantityParent, name='underlay', pos=(0, 0, 0, 0), align=uiconst.TOALL, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/classes/ShipUI/slotQuantityUnderlay.png', color=(0.0, 0.0, 0.0, 1.0))
    260 
    261     def SetCharge(self, charge):
    262         if charge and charge.stacksize != 0:
    263             if self.charge is None or charge.typeID != self.charge.typeID:
    264                 self.icon.LoadIconByTypeID(charge.typeID)
    265             self.charge = charge
    266             self.stateManager.ChangeAmmoTypeForModule(self.moduleinfo.itemID, charge.typeID)
    267             self.id = charge.itemID
    268             self.UpdateChargeQuantity(charge)
    269         else:
    270             self.icon.LoadIconByTypeID(self.moduleinfo.typeID)
    271             if self.sr.qtylabel:
    272                 self.sr.qtylabel.parent.state = uiconst.UI_HIDDEN
    273             self.quantity = 0
    274             self.id = self.moduleinfo.itemID
    275             self.charge = None
    276         self.CheckOverload()
    277         self.CheckOnline()
    278         self.CheckMasterSlave()
    279 
    280     def UpdateChargeQuantity(self, charge):
    281         if charge is self.charge:
    282             if evetypes.GetGroupID(charge.typeID) in cfg.GetCrystalGroups():
    283                 if self.sr.qtylabel:
    284                     self.sr.qtylabel.parent.state = uiconst.UI_HIDDEN
    285                 return
    286             self.InitQuantityLabel()
    287             self.quantity = charge.stacksize
    288             self.sr.qtylabel.text = '%s' % FmtAmt(charge.stacksize)
    289             self.sr.qtylabel.parent.state = uiconst.UI_DISABLED
    290 
    291     def ShowGroupHighlight(self):
    292         self.dragging = True
    293         if self.sr.groupHighlight is None:
    294             groupHighlight = Container(parent=self.parent, name='groupHighlight', pos=(0, 0, 64, 64), align=uiconst.TOPLEFT, state=uiconst.UI_DISABLED)
    295             leftCircle = Sprite(parent=groupHighlight, name='leftCircle', pos=(0, 0, 32, 64), texturePath='res:/UI/Texture/classes/ShipUI/slotRampLeft.png')
    296             rightCircle = Sprite(parent=groupHighlight, name='leftCircle', pos=(32, 0, 32, 64), texturePath='res:/UI/Texture/classes/ShipUI/slotRampRight.png')
    297             self.sr.groupHighlight = groupHighlight
    298         else:
    299             self.sr.groupHighlight.state = uiconst.UI_DISABLED
    300         uthread.new(self.PulseGroupHighlight)
    301 
    302     def StopShowingGroupHighlight(self):
    303         self.dragging = False
    304         if self.sr.groupHighlight:
    305             self.sr.groupHighlight.state = uiconst.UI_HIDDEN
    306 
    307     def PulseGroupHighlight(self):
    308         pulseSize = 0.4
    309         opacity = 1.0
    310         startTime = blue.os.GetSimTime()
    311         while self.dragging:
    312             self.sr.groupHighlight.opacity = opacity
    313             blue.pyos.synchro.SleepWallclock(200)
    314             if not self or self.destroyed:
    315                 break
    316             sinWave = math.cos(float(blue.os.GetSimTime() - startTime) / (0.5 * const.SEC))
    317             opacity = min(sinWave * pulseSize + (1 - pulseSize / 2), 1)
    318 
    319     def SetDamage(self, damage):
    320         if not damage or damage < 0.0001:
    321             if self.sr.damageState:
    322                 self.sr.damageState.state = uiconst.UI_HIDDEN
    323             return
    324         imageIndex = max(1, int(damage * 8))
    325         if self.sr.damageState is None:
    326             if self.sr.ramps:
    327                 idx = OVERLOADBTN_INDEX + 2
    328             else:
    329                 idx = OVERLOADBTN_INDEX + 1
    330             self.sr.damageState = DamageStateCont(parent=self.parent, name='damageState', pos=(0, 0, 64, 64), align=uiconst.TOPLEFT, state=uiconst.UI_NORMAL, texturePath='res:/UI/Texture/classes/ShipUI/slotDamage_%s.png' % imageIndex, idx=idx)
    331         self.sr.damageState.state = uiconst.UI_NORMAL
    332         self.sr.damageState.SetDamage(damage)
    333         amount = self.moduleinfo.damage / self.moduleinfo.hp * 100
    334         self.sr.damageState.hint = localization.GetByLabel('UI/Inflight/Overload/hintDamagedModule', preText='', amount=amount)
    335         self.sr.damageState.Blink(damage)
    336 
    337     def GetVolume(self):
    338         if self.charge:
    339             return GetItemVolume(self.charge, 1)
    340 
    341     def IsItemHere(self, rec):
    342         ret = rec.locationID == session.shipid and rec.flagID == self.locationFlag and evetypes.GetCategoryID(rec.typeID) == const.categoryCharge
    343         return ret
    344 
    345     def AddItem(self, rec):
    346         if evetypes.GetCategoryID(rec.typeID) == const.categoryCharge:
    347             self.SetCharge(rec)
    348 
    349     def UpdateItem(self, rec, change):
    350         if evetypes.GetCategoryID(rec.typeID) == const.categoryCharge:
    351             self.SetCharge(rec)
    352 
    353     def RemoveItem(self, rec):
    354         if evetypes.GetCategoryID(rec.typeID) == const.categoryCharge:
    355             if self.charge and rec.itemID == self.id:
    356                 self.SetCharge(None)
    357 
    358     def GetShell(self):
    359         return sm.GetService('invCache').GetInventoryFromId(session.shipid)
    360 
    361     def IsCorrectChargeSize(self, item, wantChargeSize):
    362         if item.typeID not in self.__chargesizecache__:
    363             cRS = dogma.data.get_type_attributes(item.typeID)
    364             cAttribs = IndexedRows(cRS, ('attributeID',))
    365             if const.attributeChargeSize in cAttribs:
    366                 gotChargeSize = cAttribs[const.attributeChargeSize].value
    367             else:
    368                 gotChargeSize = 0
    369             self.__chargesizecache__[item.typeID] = gotChargeSize
    370         else:
    371             gotChargeSize = self.__chargesizecache__[item.typeID]
    372         if wantChargeSize != gotChargeSize:
    373             return 0
    374         return 1
    375 
    376     def BlinkIcon(self, time = None):
    377         if self.destroyed or self.blinking:
    378             return
    379         startTime = blue.os.GetSimTime()
    380         if time is not None:
    381             timeToBlink = time * 10000
    382         while self.reloadingAmmo or self.waitingForActiveTarget or time:
    383             if time is not None:
    384                 if blue.os.GetSimTime() - startTime > timeToBlink:
    385                     break
    386             blue.pyos.synchro.SleepWallclock(250)
    387             if self.destroyed:
    388                 return
    389             self.icon.SetAlpha(0.25)
    390             blue.pyos.synchro.SleepWallclock(250)
    391             if self.destroyed:
    392                 return
    393             self.icon.SetAlpha(1.0)
    394 
    395         if self.destroyed:
    396             return
    397         self.blinking = 0
    398         self.CheckOverload()
    399         self.CheckOnline()
    400 
    401     def DoNothing(self, *args):
    402         pass
    403 
    404     def CopyItemIDToClipboard(self, itemID):
    405         blue.pyos.SetClipboardData(str(itemID))
    406 
    407     def GetMenu(self):
    408         ship = sm.GetService('godma').GetItem(session.shipid)
    409         if ship is None:
    410             return []
    411         m = []
    412         if session.role & (ROLE_GML | ROLE_WORLDMOD):
    413             m.append(('GM / WM Extras', self._GetMenuElevated()))
    414         groupID = evetypes.GetGroupID(self.moduleinfo.typeID)
    415         if cfg.IsChargeCompatible(self.moduleinfo):
    416             chargeTypeID, chargeQuantity, roomForReload = self.GetChargeReloadInfo()
    417             m.extend(self.dogmaLocation.GetAmmoMenu(session.shipid, self.moduleinfo.itemID, self.charge, roomForReload))
    418             if self.autoreload == 0:
    419                 m.append((MenuLabel('UI/Inflight/ModuleRacks/AutoReloadOn'), self.SetAutoReload, (1,)))
    420             else:
    421                 m.append((MenuLabel('UI/Inflight/ModuleRacks/AutoReloadOff'), self.SetAutoReload, (0,)))
    422         overloadLock = settings.user.ui.Get('lockOverload', 0)
    423         itemID = self.moduleinfo.itemID
    424         slaves = self.dogmaLocation.GetSlaveModules(itemID, session.shipid)
    425         thermodynamicsLevel = sm.GetService('skills').GetMyLevel(invconst.typeThermodynamics)
    426         for key in self.moduleinfo.effects.iterkeys():
    427             effect = self.moduleinfo.effects[key]
    428             if self.IsEffectRepeatable(effect) and groupID not in GROUPS_THAT_ALWAYS_AUTO_REPEAT:
    429                 if self.autorepeat == 0:
    430                     m.append((MenuLabel('UI/Inflight/ModuleRacks/AutoRepeatOn'), self.SetRepeat, (1000,)))
    431                 else:
    432                     m.append((MenuLabel('UI/Inflight/ModuleRacks/AutoRepeatOff'), self.SetRepeat, (0,)))
    433             if effect.effectName == 'online':
    434                 m.append(None)
    435                 if not slaves:
    436                     if effect.isActive:
    437                         m.append((MenuLabel('UI/Inflight/ModuleRacks/PutModuleOffline'), self.ChangeOnline, (0,)))
    438                     else:
    439                         m.append((MenuLabel('UI/Inflight/ModuleRacks/PutModuleOnline'), self.ChangeOnline, (1,)))
    440             if not overloadLock and effect.effectCategory == const.dgmEffOverload:
    441                 requiredThermoLevel = self.dogmaLocation.GetAttributeValue(itemID, const.attributeRequiredThermoDynamicsSkill)
    442                 if thermodynamicsLevel >= requiredThermoLevel:
    443                     active = effect.isActive
    444                     if active:
    445                         m.append(MenuEntryData(MenuLabel('UI/Inflight/Overload/TurnOffOverload'), lambda _effect = effect: self.Overload(False, _effect), texturePath=eveicon.pause))
    446                     else:
    447                         m.append(MenuEntryData(MenuLabel('UI/Inflight/Overload/TurnOnOverload'), lambda _effect = effect: self.Overload(True, _effect), texturePath=eveicon.circle_plus))
    448                     m.append((MenuLabel('UI/Inflight/OverloadRack'), self.OverloadRack, ()))
    449                     m.append((MenuLabel('UI/Inflight/StopOverloadingRack'), self.StopOverloadRack, ()))
    450 
    451         moduleDamage = self.GetModuleDamage()
    452         if moduleDamage:
    453             if self.stateManager.IsBeingRepaired(self.moduleinfo.itemID):
    454                 m.append((MenuLabel('UI/Inflight/menuCancelRepair'), self.CancelRepair, ()))
    455             else:
    456                 unitsToRepair = int(self.GetNumUnitsToRepair())
    457                 if unitsToRepair:
    458                     m.append((MenuLabel('UI/Inflight/RepairWithQty', {'qty': unitsToRepair}), self.RepairModule, ()))
    459                 else:
    460                     m.append((MenuLabel('UI/Commands/Repair'), self.RepairModule, ()))
    461         damagedModulesAndRepairCost = self.GetDamagedModulesAndRepairCost()
    462         if len(damagedModulesAndRepairCost) > 1:
    463             unitsToRepair = sum((x[1] for x in damagedModulesAndRepairCost))
    464             moduleIDs = [ x[0] for x in damagedModulesAndRepairCost ]
    465             if self.moduleinfo.itemID in moduleIDs and moduleIDs.index(self.moduleinfo.itemID) != 0:
    466                 moduleIDs.remove(self.moduleinfo.itemID)
    467                 moduleIDs.insert(0, self.moduleinfo.itemID)
    468             if unitsToRepair:
    469                 m.append((MenuLabel('UI/Inflight/RepairAllWithQty', {'qty': int(unitsToRepair)}), self.RepairManyModules, (moduleIDs,)))
    470             else:
    471                 m.append((MenuLabel('UI/Commands/RepairAll'), self.RepairManyModules, (moduleIDs,)))
    472         allRepairingModuleIDs = self.GetAllRepairingModules()
    473         if len(allRepairingModuleIDs) > 1:
    474             m.append((MenuLabel('UI/Inflight/menuCancelAllRepairs'), self.CancelRepairManyModuels, (allRepairingModuleIDs,)))
    475         if slaves:
    476             m.append((MenuLabel('UI/Fitting/ClearGroup'), self.UnlinkModule, ()))
    477         m += [(MenuLabel('UI/Commands/ShowInfo'), sm.GetService('info').ShowInfo, (self.moduleinfo.typeID,
    478            self.moduleinfo.itemID,
    479            0,
    480            self.moduleinfo))]
    481         return m
    482 
    483     def _GetMenuElevated(self):
    484         m = []
    485         effectMenu = []
    486         if cfg.IsChargeCompatible(self.moduleinfo):
    487             m += [('Launcher: ' + str(self.moduleinfo.itemID), self.CopyItemIDToClipboard, (self.moduleinfo.itemID,))]
    488             if self.id != self.moduleinfo.itemID:
    489                 m += [('Charge: ' + str(self.id), self.CopyItemIDToClipboard, (self.id,)), None]
    490                 chargeTypeID, _, _ = self.GetChargeReloadInfo()
    491                 chargeDefaultEffectID, chargeEffectName = self._GetDefaultEffectIDAndName(chargeTypeID)
    492                 if chargeDefaultEffectID:
    493                     effectMenu += [('Charge defaultEffect: %s (%s)' % (chargeEffectName, chargeDefaultEffectID), self.CopyItemIDToClipboard, (chargeDefaultEffectID,))]
    494         else:
    495             m += [(str(self.id), self.CopyItemIDToClipboard, (self.id,)), None]
    496         defaultEffectID, effectName = self._GetDefaultEffectIDAndName(self.moduleinfo.typeID)
    497         if defaultEffectID:
    498             effectMenu.insert(0, ('Module defaultEffect: %s (%s)' % (effectName, defaultEffectID), self.CopyItemIDToClipboard, (defaultEffectID,)))
    499         if effectMenu:
    500             m += effectMenu
    501             m += [None]
    502         m += sm.GetService('menu').GetGMTypeMenu(self.moduleinfo.typeID, itemID=self.id, divs=True, unload=True)
    503         return m
    504 
    505     def _GetDefaultEffectIDAndName(self, typeID):
    506         try:
    507             defaultEffectID = self.dogmaLocation.dogmaStaticMgr.GetDefaultEffect(typeID)
    508             if not defaultEffectID:
    509                 return (None, None)
    510             effect = self.dogmaLocation.dogmaStaticMgr.GetEffect(defaultEffectID)
    511             return (defaultEffectID, effect.effectName)
    512         except Exception:
    513             return (None, None)
    514 
    515     def RepairModule(self):
    516         error = None
    517         success = False
    518         try:
    519             success = self.stateManager.RepairModule(self.moduleinfo.itemID)
    520         except UserError as e:
    521             if e.msg == 'NotEnoughRepairMaterialToFinishAllRepairs':
    522                 error = e
    523             else:
    524                 raise
    525 
    526         if self.slaves:
    527             for slave in self.slaves:
    528                 try:
    529                     success = self.stateManager.RepairModule(slave) or success
    530                 except UserError as e:
    531                     if e.msg == 'NotEnoughRepairMaterialToFinishAllRepairs':
    532                         error = e
    533                     else:
    534                         raise
    535 
    536         if success:
    537             self.isBeingRepaired = True
    538             self.SetRepairing()
    539         if error:
    540             raise error
    541 
    542     def RepairManyModules(self, moduleIDs):
    543         self.stateManager.RepairManyModules(moduleIDs)
    544 
    545     def CancelRepair(self):
    546         success = self.stateManager.StopRepairModule(self.moduleinfo.itemID)
    547         if self.slaves:
    548             for slave in self.slaves:
    549                 success = self.stateManager.StopRepairModule(slave) and success
    550 
    551         if success == True:
    552             self.isBeingRepaired = False
    553             self.RemoveRepairing()
    554 
    555     def CancelRepairManyModuels(self, moduleIDs):
    556         self.stateManager.StopRepairModuleManyModules(moduleIDs)
    557 
    558     def OnModulesDeactivating(self, moduleIDs):
    559         if self.moduleinfo.itemID in moduleIDs:
    560             self.SetDeactivating()
    561 
    562     def OnFailLockTarget(self, tid, *args):
    563         self.waitingForActiveTarget = 0
    564 
    565     def OnMultiModuleRepairStarted(self, moduleIDs):
    566         if self.moduleinfo.itemID not in moduleIDs:
    567             return
    568         self.isBeingRepaired = True
    569         self.SetRepairing()
    570 
    571     def OnModuleRepaired(self, itemID):
    572         if itemID == self.moduleinfo.itemID:
    573             self.RemoveRepairing()
    574             self.isBeingRepaired = False
    575             self.UpdateModuleDamage()
    576 
    577     def OnAmmoInBankChanged(self, masterID):
    578         slaves = self.dogmaLocation.GetSlaveModules(masterID, session.shipid)
    579         if self.moduleinfo.itemID in slaves:
    580             self.SetCharge(self.moduleinfo)
    581 
    582     def OnChargeBeingLoadedToModule(self, itemIDs, chargeTypeID, time):
    583         if self.moduleinfo.itemID not in itemIDs:
    584             return
    585         if chargeTypeID is None:
    586             if self.reloadAnimationThread and self.reloadAnimationThread.alive:
    587                 self.reloadAnimationThread.kill()
    588             if self.blinkButtonThread and self.blinkButtonThread.alive:
    589                 self.blinkButtonThread.kill()
    590             if self.sr.ramps:
    591                 self.sr.ramps.display = False
    592         else:
    593             chargeGroupID = evetypes.GetGroupID(chargeTypeID)
    594             params = {'ammoGroupName': (const.UE_GROUPID, chargeGroupID),
    595              'launcherGroupName': (const.UE_GROUPID, self.moduleinfo.groupID),
    596              'time': time / 1000}
    597             self.DoReloadAnimation(time)
    598             self.DisplayLoadMessage(params)
    599 
    600     def DoReloadAnimation(self, duration, startTime = None):
    601         if startTime:
    602             blinkTime = max(0, duration - (blue.os.GetSimTime() - startTime) / 10000)
    603         else:
    604             blinkTime = duration
    605         self.reloadAnimationThread = uthread.new(self.ShowReloadLeft, duration, startTime)
    606         self.blinkButtonThread = uthread.new(self.BlinkIcon, blinkTime)
    607 
    608     def DisplayLoadMessage(self, params):
    609         blue.pyos.synchro.SleepSim(0.1)
    610         if self.reloadAnimationThread and self.reloadAnimationThread.alive:
    611             prompt_player_fsd_dialog('LauncherLoadDelay', params)
    612 
    613     def TryStartCooldownTimers(self):
    614         cooldownTimes = self.stateManager.GetCooldownTimes(self.moduleinfo.itemID)
    615         if cooldownTimes:
    616             startTime, duration = cooldownTimes
    617             self.DoReactivationAnimation(duration, startTime=startTime)
    618 
    619     def DoReactivationAnimation(self, duration, startTime = None):
    620         uthread.new(self.ShowReactivationLeft, duration, startTime)
    621 
    622     def UnlinkModule(self):
    623         self.dogmaLocation.DestroyWeaponBank(session.shipid, self.moduleinfo.itemID)
    624 
    625     def Overload(self, onoff, eff):
    626         if onoff:
    627             eff.Activate()
    628         else:
    629             eff.Deactivate()
    630 
    631     def OverloadRack(self):
    632         sm.GetService('godma').OverloadRack(self.moduleinfo.itemID)
    633 
    634     def StopOverloadRack(self):
    635         sm.GetService('godma').StopOverloadRack(self.moduleinfo.itemID)
    636 
    637     def GetChargeReloadInfo(self, ignoreCharge = 0):
    638         evetypes.RaiseIFNotExists(self.moduleinfo.typeID)
    639         lastChargeTypeID = self.stateManager.GetAmmoTypeForModule(self.moduleinfo.itemID)
    640         if self.charge and not ignoreCharge:
    641             chargeTypeID = self.charge.typeID
    642             chargeQuantity = self.charge.stacksize
    643         elif lastChargeTypeID is not None:
    644             chargeTypeID = lastChargeTypeID
    645             chargeQuantity = 0
    646         else:
    647             chargeTypeID = None
    648             chargeQuantity = 0
    649         if chargeTypeID is not None:
    650             roomForReload = int(evetypes.GetCapacity(self.moduleinfo.typeID) / evetypes.GetVolume(chargeTypeID) - chargeQuantity + 1e-07)
    651         else:
    652             roomForReload = 0
    653         return (chargeTypeID, chargeQuantity, roomForReload)
    654 
    655     def SetAutoReload(self, on):
    656         settings.char.autoreload.Set(self.moduleinfo.itemID, on)
    657         self.autoreload = on
    658         self.AutoReload()
    659 
    660     def AutoReload(self, force = 0, useItemID = None, useQuant = None):
    661         if self.reloadingAmmo is not False:
    662             return
    663         if not cfg.IsChargeCompatible(self.moduleinfo) or not (self.autoreload or force):
    664             return
    665         chargeTypeID, chargeQuantity, roomForReload = self.GetChargeReloadInfo()
    666         if chargeQuantity > 0 and not force or roomForReload <= 0:
    667             return
    668         if not uicore.layer.shipui:
    669             return
    670         self.dogmaLocation.LoadAmmoTypeToModule(self.moduleinfo.itemID, chargeTypeID)
    671 
    672     def CheckOverload(self):
    673         if not self or self.destroyed:
    674             return
    675         isActive = False
    676         hasOverloadEffect = False
    677         if not HasAttrs(self, 'moduleinfo', 'effects'):
    678             return
    679         for key in self.moduleinfo.effects.iterkeys():
    680             effect = self.moduleinfo.effects[key]
    681             if effect.effectCategory == const.dgmEffOverload:
    682                 if effect.isActive:
    683                     isActive = True
    684                 hasOverloadEffect = True
    685 
    686         if hasOverloadEffect:
    687             self.sr.overloadButton.top = 5
    688             if self.online:
    689                 if isActive:
    690                     self.sr.overloadButton.LoadTexture('res:/UI/Texture/classes/ShipUI/slotOverloadOn.png')
    691                     self.sr.overloadButton.hint = localization.GetByLabel('UI/Inflight/Overload/TurnOffOverload')
    692                 else:
    693                     self.sr.overloadButton.LoadTexture('res:/UI/Texture/classes/ShipUI/slotOverloadOff.png')
    694                     self.sr.overloadButton.hint = localization.GetByLabel('UI/Inflight/Overload/TurnOnOverload')
    695                 self.sr.overloadButton.state = uiconst.UI_NORMAL
    696             else:
    697                 self.sr.overloadButton.LoadTexture('res:/UI/Texture/classes/ShipUI/slotOverloadDisabled.png')
    698                 self.sr.overloadButton.state = uiconst.UI_DISABLED
    699         else:
    700             self.sr.overloadButton.top = 6
    701             self.sr.overloadButton.LoadTexture('res:/UI/Texture/classes/ShipUI/slotOverloadDisabled.png')
    702             self.sr.overloadButton.state = uiconst.UI_DISABLED
    703 
    704     def CheckMasterSlave(self):
    705         if not self or self.destroyed:
    706             return
    707         itemID = self.moduleinfo.itemID
    708         slaves = self.dogmaLocation.GetSlaveModules(itemID, session.shipid)
    709         if slaves:
    710             if self.sr.stackParent is None:
    711                 stackParent = Container(parent=self, name='stackParent', pos=(6, 27, 12, 10), align=uiconst.TOPLEFT, state=uiconst.UI_DISABLED, idx=0)
    712                 self.sr.stacklabel = eveLabel.Label(text=len(slaves) + 1, parent=stackParent, fontsize=9, letterspace=1, left=5, top=0, width=30, state=uiconst.UI_DISABLED, shadowOffset=(0, 0), color=(1.0, 1.0, 1.0, 1))
    713                 underlay = Sprite(parent=stackParent, name='underlay', pos=(0, 0, 0, 0), align=uiconst.TOALL, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/classes/ShipUI/slotStackUnderlay.png', color=(0.51, 0.0, 0.0, 1.0))
    714                 self.sr.stackParent = stackParent
    715             else:
    716                 self.sr.stackParent.state = uiconst.UI_DISABLED
    717                 self.sr.stacklabel.text = len(slaves) + 1
    718         elif self.sr.stackParent:
    719             self.sr.stackParent.state = uiconst.UI_HIDDEN
    720 
    721     def CheckOnline(self, sound = 0):
    722         if not self or self.destroyed:
    723             return
    724         if not HasAttrs(self, 'moduleinfo', 'effects'):
    725             return
    726         for key in self.moduleinfo.effects.keys():
    727             effect = self.moduleinfo.effects[key]
    728             if effect.effectName == 'online':
    729                 if effect.isActive:
    730                     self.ShowOnline()
    731                     if sound:
    732                         PlaySound(SOUND_MODULE_ONLINE)
    733                 else:
    734                     self.ShowOffline()
    735                 return
    736 
    737     def ChangeOnline(self, on = 1):
    738         uthread.new(self._ChangeOnline, on)
    739 
    740     def _ChangeOnline(self, on):
    741         masterID = self.dogmaLocation.IsInWeaponBank(session.shipid, self.moduleinfo.itemID)
    742         if masterID:
    743             if not on:
    744                 ret = prompt_player_fsd_dialog('CustomQuestion', {'header': 'OFFLINE',
    745                  'question': "When offlining this module you will destroy the weapons bank it's in. Are you sure you want to offline it? "}, uiconst.YESNO)
    746                 if ret != uiconst.ID_YES:
    747                     return
    748         elif not on and prompt_player_fsd_dialog('PutOffline', {}, uiconst.YESNO, suppress=uiconst.ID_YES) != uiconst.ID_YES:
    749             return
    750         for key in self.moduleinfo.effects.keys():
    751             effect = self.moduleinfo.effects[key]
    752             if effect.effectName == 'online':
    753                 if on:
    754                     effect.Activate()
    755                 else:
    756                     self.ShowOffline(1)
    757                     try:
    758                         effect.Deactivate()
    759                     except UserError:
    760                         self.ShowOnline()
    761                         raise
    762 
    763                 return
    764 
    765     def ShowOverload(self, on):
    766         self.CheckOverload()
    767 
    768     def ShowOnline(self):
    769         self.isMaster = 0
    770         if self.AreModulesOffline():
    771             self.ShowOffline()
    772             return
    773         self.online = True
    774         if self.grey:
    775             self.icon.SetAlpha(0.1)
    776         else:
    777             self.icon.SetAlpha(1.0)
    778         self.CheckOverload()
    779 
    780     def ShowOffline(self, ping = 0):
    781         self.online = False
    782         if self.grey:
    783             self.icon.SetAlpha(0.1)
    784         else:
    785             self.icon.SetAlpha(0.25)
    786         if ping:
    787             PlaySound(SOUND_MODULE_OFFLINE)
    788         self.CheckOverload()
    789         self.isInActiveState = True
    790 
    791     def AreModulesOffline(self):
    792         slaves = self.dogmaLocation.GetSlaveModules(self.moduleinfo.itemID, session.shipid)
    793         if not slaves:
    794             return False
    795         self.isMaster = 1
    796         onlineEffect = self.stateManager.GetEffect(self.moduleinfo.itemID, 'online')
    797         if onlineEffect is None or not onlineEffect.isActive:
    798             return True
    799         for slave in slaves:
    800             onlineEffect = self.stateManager.GetEffect(slave, 'online')
    801             if onlineEffect is None or not onlineEffect.isActive:
    802                 return True
    803 
    804         return False
    805 
    806     def IsEffectRepeatable(self, effect, activatibleKnown = 0):
    807         if activatibleKnown or self.IsEffectActivatible(effect):
    808             if not effect.item.disallowRepeatingActivation:
    809                 return effect.durationAttributeID is not None
    810         return 0
    811 
    812     def IsEffectActivatible(self, effect):
    813         return effect.isDefault and effect.effectName != 'online' and effect.effectCategory in (const.dgmEffActivation, const.dgmEffTarget)
    814 
    815     def SetRepeat(self, num):
    816         settings.char.autorepeat.Set(self.moduleinfo.itemID, num)
    817         self.autorepeat = num
    818 
    819     def GetDefaultEffect(self):
    820         if not self or self.destroyed:
    821             return
    822         if self.sr is None or self.moduleinfo is None or not self.stateManager.IsItemLoaded(self.moduleinfo.itemID):
    823             return
    824         for key in self.moduleinfo.effects.iterkeys():
    825             effect = self.moduleinfo.effects[key]
    826             if self.IsEffectActivatible(effect):
    827                 return effect
    828 
    829     def OnClick(self, *args):
    830         if not self or self.IsBeingDragged() or not self.isInActiveState:
    831             return
    832         PlaySound(uiconst.SOUND_BUTTON_CLICK)
    833         if uicore.uilib.Key(uiconst.VK_SHIFT):
    834             self.ToggleOverload()
    835             return
    836         ctrlRepeat = 0
    837         if uicore.uilib.Key(uiconst.VK_CONTROL):
    838             ctrlRepeat = 1000
    839         self.Click(ctrlRepeat)
    840 
    841     def Click(self, ctrlRepeat = 0):
    842         if self.stateManager.IsModuleReloading(self.moduleinfo.itemID):
    843             return
    844         if self.waitingForActiveTarget:
    845             sm.GetService('target').CancelTargetOrder(self)
    846             self.waitingForActiveTarget = 0
    847         else:
    848             if self.def_effect is None:
    849                 log.LogWarn('No default Effect available for this moduletypeID:', self.moduleinfo.typeID)
    850                 prompt_player_fsd_dialog('CustomNotify', {'notify': localization.GetByLabel('UI/Inflight/ModuleRacks/TryingToActivatePassiveModule')})
    851                 return
    852             if not self.online:
    853                 if getattr(self, 'isMaster', None):
    854                     prompt_player_fsd_dialog('ClickOffllineGroup')
    855                 else:
    856                     prompt_player_fsd_dialog('ClickOffllineModule')
    857                 return
    858             if self.IsModuleActivatingInGodma():
    859                 return
    860             if self.def_effect.isActive:
    861                 self.DeactivateEffect(self.def_effect)
    862             elif not self.effect_activating:
    863                 self.activationTimer = timerstuff.AutoTimer(500, self.ActivateEffectTimer)
    864                 self.effect_activating = 1
    865                 self.ActivateEffect(self.def_effect, ctrlRepeat=ctrlRepeat)
    866 
    867     def ActivateModule(self):
    868         if self.def_effect and not self.def_effect.isActive and not self.effect_activating:
    869             self.Click()
    870 
    871     def DeactivateModule(self):
    872         if self.def_effect and self.def_effect.isActive:
    873             self.Click()
    874 
    875     def ActivateEffectTimer(self, *args):
    876         self.effect_activating = 0
    877         self.activationTimer = None
    878 
    879     def OnEndDrag(self, *args):
    880         uthread.new(uicore.layer.shipui.ResetSwapMode)
    881 
    882     def GetDragData(self, *args):
    883         if settings.user.ui.Get('lockModules', 0):
    884             return []
    885         if self.charge:
    886             fakeNode = uix.GetItemData(self.charge, 'icons')
    887             fakeNode.isCharge = 1
    888         else:
    889             fakeNode = uix.GetItemData(self.moduleinfo, 'icons')
    890             fakeNode.isCharge = 0
    891         fakeNode.__guid__ = 'xtriui.ShipUIModule'
    892         fakeNode.slotFlag = self.moduleinfo.flagID
    893         uicore.layer.shipui.StartDragMode(self.moduleinfo.itemID, self.moduleinfo.typeID)
    894         return [fakeNode]
    895 
    896     def OnDropData(self, dragObj, nodes):
    897         log.LogInfo('Module.OnDropData', self.id)
    898         if not IsDocked() and dragObj.__guid__ == 'listentry.InvAssetItem':
    899             return
    900         flag1 = self.moduleinfo.flagID
    901         flag2 = None
    902         for node in nodes:
    903             if node.Get('__guid__', None) == 'xtriui.ShipUIModule':
    904                 flag2 = node.slotFlag
    905                 break
    906 
    907         if flag1 == flag2:
    908             return
    909         if flag2 is not None:
    910             uicore.layer.shipui.ChangeSlots(flag1, flag2)
    911             return
    912         chargeTypeID = None
    913         chargeItems = []
    914         for node in nodes:
    915             if not hasattr(node, 'rec'):
    916                 return
    917             chargeItem = node.rec
    918             if not isinstance(chargeItem.itemID, numbers.Integral):
    919                 return
    920             if not hasattr(chargeItem, 'categoryID'):
    921                 return
    922             if chargeItem.categoryID != const.categoryCharge:
    923                 continue
    924             if chargeTypeID is None:
    925                 chargeTypeID = chargeItem.typeID
    926             if chargeItem.typeID == chargeTypeID:
    927                 chargeItems.append(chargeItem)
    928 
    929         if len(chargeItems) > 0:
    930             self.dogmaLocation.DropLoadChargeToModule(self.moduleinfo.itemID, chargeTypeID, chargeItems=chargeItems)
    931 
    932     def OnMouseHover(self, *args):
    933         if uicore.uilib.Key(uiconst.VK_SHIFT):
    934             self.OverloadHiliteOn()
    935         else:
    936             self.OverloadHiliteOff()
    937 
    938     def OnMouseDown(self, *args):
    939         Container.OnMouseDown(self, *args)
    940         log.LogInfo('Module.OnMouseDown', self.id)
    941         if getattr(self, 'downTop', None) is not None or not self.isInActiveState or self.def_effect is None:
    942             return
    943         if not uicore.uilib.Key(uiconst.VK_SHIFT) and self.IsModuleActivatingInGodma():
    944             return
    945         self.downTop = self.parent.top
    946         self.parent.top += 2
    947 
    948     def IsModuleActivatingInGodma(self):
    949         effectName = self.def_effect.effectName
    950         isActivating = self.stateManager.IsModuleActivating(self.moduleinfo.itemID, effectName)
    951         if isActivating:
    952             return True
    953         return False
    954 
    955     def OnMouseUp(self, *args):
    956         Container.OnMouseUp(self, *args)
    957         if self.destroyed:
    958             return
    959         log.LogInfo('Module.OnMouseUp', self.id)
    960         if getattr(self, 'downTop', None) is not None:
    961             self.parent.top = self.downTop
    962             self.downTop = None
    963         if len(args) > 0 and args[0] == uiconst.MOUSERIGHT and getattr(uicore.layer.hint, 'moduleButtonHint', None):
    964             uicore.layer.hint.moduleButtonHint.FadeOpacity(0.0)
    965 
    966     def OnMouseEnter(self, *args):
    967         uthread.pool('ShipMobuleButton::MouseEnter', self.MouseEnter)
    968 
    969     def MouseEnter(self, *args):
    970         if self.destroyed or sm.GetService('godma').GetItem(self.moduleinfo.itemID) is None:
    971             return
    972         PlaySound(uiconst.SOUND_BUTTON_HOVER)
    973         if uicore.uilib.Key(uiconst.VK_SHIFT):
    974             self.OverloadHiliteOn()
    975         self.SetHilite()
    976         tacticalSvc = sm.GetService('tactical')
    977         bracketMgr = sm.GetService('bracket')
    978         maxRange, falloffDist, bombRadius, _ = tacticalSvc.FindMaxRange(self.moduleinfo, self.charge)
    979         if maxRange > 0:
    980             bracketMgr.ShowModuleRange(self.moduleinfo.itemID, maxRange + falloffDist)
    981             bracketMgr.ShowHairlinesForModule(self.moduleinfo.itemID)
    982         log.LogInfo('Module.OnMouseEnter', self.id)
    983         if settings.user.ui.Get('showModuleTooltips', 1):
    984             if self.tooltipPanelClassInfo is None:
    985                 self.tooltipPanelClassInfo = TooltipModuleWrapper()
    986         else:
    987             self.tooltipPanelClassInfo = None
    988         uthread.new(tacticalSvc.ShowModuleRange, self.moduleinfo, self.charge)
    989 
    990     def GetTooltipDelay(self):
    991         return settings.user.ui.Get(TOOLTIP_SETTINGS_MODULE, TOOLTIP_DELAY_MODULE)
    992 
    993     def OnMouseExit(self, *args):
    994         self.RemoveHilite()
    995         sm.GetService('bracket').StopShowingModuleRange(self.moduleinfo.itemID)
    996         self.OverloadHiliteOff()
    997         log.LogInfo('Module.OnMouseExit', self.id)
    998         uthread.new(sm.GetService('tactical').ClearModuleRange)
    999         self.OnMouseUp(None)
   1000 
   1001     def UpdateInfo_TimedCall(self):
   1002         self.UpdateInfo()
   1003 
   1004     def UpdateInfo(self):
   1005         if self.destroyed or not self.moduleButtonHint or self.moduleButtonHint.destroyed:
   1006             self.moduleButtonHint = None
   1007             self.updateTimer = None
   1008             return False
   1009         if not self.stateManager.IsItemLoaded(self.id):
   1010             return False
   1011         chargeItemID = None
   1012         if self.charge:
   1013             chargeItemID = self.charge.itemID
   1014         self.moduleButtonHint.UpdateAllInfo(self.moduleinfo.itemID, chargeItemID)
   1015         requiredSafetyLevel = self.GetRequiredSafetyLevel()
   1016         if self.crimewatchSvc.CheckUnsafe(requiredSafetyLevel):
   1017             self.moduleButtonHint.SetSafetyWarning(requiredSafetyLevel)
   1018         else:
   1019             self.moduleButtonHint.RemoveSafetyWarning()
   1020         return True
   1021 
   1022     def GetSafetyWarning(self):
   1023         requiredSafetyLevel = self.GetRequiredSafetyLevel()
   1024         if self.crimewatchSvc.CheckUnsafe(requiredSafetyLevel):
   1025             return requiredSafetyLevel
   1026         else:
   1027             return None
   1028 
   1029     def GetModuleDamage(self):
   1030         return uicore.layer.shipui.controller.GetModuleGroupDamage(self.moduleinfo.itemID)
   1031 
   1032     def GetNumUnitsToRepair(self):
   1033         return uicore.layer.shipui.controller.GetNumUnitsToRepairModuleBank(self.moduleinfo.typeID, self.moduleinfo.itemID)
   1034 
   1035     def GetDamagedModulesAndRepairCost(self):
   1036         return uicore.layer.shipui.controller.GetDamagedModulesAndRepairCost()
   1037 
   1038     def GetAllRepairingModules(self):
   1039         return uicore.layer.shipui.controller.GetAllRepairingModules()
   1040 
   1041     def GetAccuracy(self, targetID = None):
   1042         if self is None or self.destroyed:
   1043             return
   1044 
   1045     def SetActive(self):
   1046         self.InitGlow()
   1047         self.sr.glow.state = uiconst.UI_DISABLED
   1048         sm.GetService('ui').BlinkSpriteA(self.sr.glow, 0.75, 1000, None, passColor=0)
   1049         self.effect_activating = 0
   1050         self.activationTimer = None
   1051         self.isInActiveState = True
   1052         self.ActivateRamps()
   1053 
   1054     def SetDeactivating(self):
   1055         self.isDeactivating = True
   1056         if self.sr.glow:
   1057             self.sr.glow.state = uiconst.UI_HIDDEN
   1058         self.InitBusyState()
   1059         self.sr.busy.state = uiconst.UI_DISABLED
   1060         sm.GetService('ui').BlinkSpriteA(self.sr.busy, 0.75, 1000, None, passColor=0)
   1061         self.isInActiveState = False
   1062         self.DeActivateRamps()
   1063 
   1064     def SetIdle(self):
   1065         self.isDeactivating = False
   1066         if self.sr.glow:
   1067             self.sr.glow.state = uiconst.UI_HIDDEN
   1068             sm.GetService('ui').StopBlink(self.sr.glow)
   1069         if self.sr.busy:
   1070             self.sr.busy.state = uiconst.UI_HIDDEN
   1071             sm.GetService('ui').StopBlink(self.sr.busy)
   1072         self.isInActiveState = True
   1073         self.IdleRamps()
   1074         self.TryStartCooldownTimers()
   1075 
   1076     def SetRepairing(self, startTime = None):
   1077         self.InitGlow()
   1078         self.sr.glow.state = uiconst.UI_DISABLED
   1079         self.sr.glow.SetRGBA(1, 1, 1, 1)
   1080         sm.GetService('ui').BlinkSpriteA(self.sr.glow, 0.9, 2500, None, passColor=0)
   1081         self.isInActiveState = True
   1082         uthread.new(self.ShowRepairLeft, startTime)
   1083 
   1084     def RemoveRepairing(self):
   1085         if self.sr.glow:
   1086             sm.GetService('ui').StopBlink(self.sr.glow)
   1087             self.sr.glow.SetRGBA(*GLOWCOLOR)
   1088             self.sr.glow.state = uiconst.UI_HIDDEN
   1089         if self.sr.damageState:
   1090             self.sr.damageState.StopRepair()
   1091 
   1092     def SetHilite(self):
   1093         self.InitHilite()
   1094         self.sr.hilite.display = True
   1095         requiredSafetyLevel = self.GetRequiredSafetyLevel()
   1096         if self.crimewatchSvc.CheckUnsafe(requiredSafetyLevel):
   1097             self.InitSafetyGlow()
   1098             if requiredSafetyLevel == const.shipSafetyLevelNone:
   1099                 color = crimewatchConst.Colors.Criminal
   1100             else:
   1101                 color = crimewatchConst.Colors.Suspect
   1102             self.sr.safetyGlow.SetRGBA(*color.GetRGBA())
   1103             self.sr.safetyGlow.display = True
   1104 
   1105     def GetRequiredSafetyLevel(self):
   1106         requiredSafetyLevel = self.crimewatchSvc.GetRequiredSafetyLevelForEffect(self.GetRelevantEffect(), targetID=None)
   1107         return requiredSafetyLevel
   1108 
   1109     def RemoveHilite(self):
   1110         if self.sr.hilite:
   1111             self.sr.hilite.display = False
   1112         if self.sr.safetyGlow:
   1113             self.sr.safetyGlow.display = False
   1114 
   1115     def InitSafetyGlow(self):
   1116         if self.sr.safetyGlow is None:
   1117             self.sr.safetyGlow = Sprite(parent=self.parent, name='safetyGlow', padding=2, align=uiconst.TOALL, state=uiconst.UI_HIDDEN, texturePath='res:/UI/Texture/classes/ShipUI/slotGlow.png', color=crimewatchConst.Colors.Yellow.GetRGBA())
   1118 
   1119     def InitGlow(self):
   1120         if self.sr.glow is None:
   1121             self.sr.glow = Sprite(parent=self.parent, name='glow', padding=2, align=uiconst.TOALL, state=uiconst.UI_HIDDEN, texturePath='res:/UI/Texture/classes/ShipUI/slotGlow.png', color=GLOWCOLOR)
   1122 
   1123     def InitBusyState(self):
   1124         if self.sr.busy is None:
   1125             self.sr.busy = Sprite(parent=self.parent, name='busy', padding=2, align=uiconst.TOALL, state=uiconst.UI_HIDDEN, texturePath='res:/UI/Texture/classes/ShipUI/slotGlow.png', color=BUSYCOLOR)
   1126 
   1127     def InitHilite(self):
   1128         if self.sr.hilite is None:
   1129             if getattr(self.parent, 'mainShape', None) is not None:
   1130                 idx = max(-1, self.parent.mainShape.GetOrder() - 1)
   1131             else:
   1132                 idx = -1
   1133             self.sr.hilite = Sprite(parent=self.parent, name='hilite', padding=(10, 10, 10, 10), align=uiconst.TOALL, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/classes/ShipUI/slotHilite.png', blendMode=trinity.TR2_SBM_ADDX2, idx=idx)
   1134             self.sr.hilite.display = False
   1135 
   1136     def OverloadHiliteOn(self):
   1137         self.sr.overloadButton.SetAlpha(1.5)
   1138 
   1139     def OverloadHiliteOff(self):
   1140         self.sr.overloadButton.SetAlpha(1.0)
   1141 
   1142     def GetEffectByName(self, effectName):
   1143         for key in self.moduleinfo.effects.iterkeys():
   1144             effect = self.moduleinfo.effects[key]
   1145             if effect.effectName == effectName:
   1146                 return effect
   1147 
   1148     def Update(self, effectState):
   1149         if not self or self.destroyed:
   1150             return
   1151         if not self.stateManager.IsItemLoaded(self.id):
   1152             return
   1153         if self.def_effect and effectState.effectName == self.def_effect.effectName:
   1154             if effectState.start:
   1155                 self.SetActive()
   1156             else:
   1157                 self.SetIdle()
   1158         effect = self.GetEffectByName(effectState.effectName)
   1159         if effect and effect.effectCategory == const.dgmEffOverload:
   1160             self.ShowOverload(effect.isActive)
   1161         if effectState.effectName == 'online':
   1162             if effectState.active:
   1163                 self.ShowOnline()
   1164             else:
   1165                 self.ShowOffline()
   1166         if effect.effectCategory in [const.dgmEffTarget, const.dgmEffActivation, const.dgmEffArea] and effect.effectID != const.effectOnline:
   1167             if not effectState.active and self.quantity == 0:
   1168                 self.AutoReload()
   1169         self.UpdateInfo()
   1170 
   1171     def GetRelevantEffect(self):
   1172         if self.def_effect and (self.def_effect.effectName == 'useMissiles' or self.def_effect.effectName == 'warpDisruptSphere' and self.charge is not None):
   1173             if self.charge is None:
   1174                 return
   1175             effect = sm.GetService('godma').GetStateManager().GetDefaultEffect(self.charge.typeID)
   1176         else:
   1177             effect = self.def_effect
   1178         return effect
   1179 
   1180     def ActivateEffect(self, effect, targetID = None, ctrlRepeat = 0):
   1181         if self.charge and self.charge.typeID in const.orbitalStrikeAmmo:
   1182             return False
   1183         relevantEffect = self.GetRelevantEffect()
   1184         if relevantEffect is None:
   1185             typeID, _ = self.GetModuleType()
   1186             raise UserError('NoCharges', {'launcher': (const.UE_TYPEID, typeID)})
   1187         if relevantEffect and not targetID and relevantEffect.effectCategory == 2:
   1188             targetID = sm.GetService('target').GetActiveTargetID()
   1189             if not targetID:
   1190                 sm.GetService('target').OrderTarget(self)
   1191                 uthread.new(self.BlinkIcon)
   1192                 self.waitingForActiveTarget = 1
   1193                 return
   1194         if self.sr.Get('moduleinfo'):
   1195             for key in self.moduleinfo.effects.iterkeys():
   1196                 checkeffect = self.moduleinfo.effects[key]
   1197                 if checkeffect.effectName == 'online':
   1198                     if not checkeffect.isActive:
   1199                         self._ChangeOnline(1)
   1200                     break
   1201 
   1202         if self.def_effect:
   1203             if relevantEffect.isOffensive:
   1204                 if not sm.GetService('consider').DoAttackConfirmations(targetID, relevantEffect):
   1205                     return
   1206             repeats = ctrlRepeat or self.autorepeat
   1207             if not self.IsEffectRepeatable(self.def_effect, 1):
   1208                 repeats = 0
   1209             if not self.charge:
   1210                 self.stateManager.ChangeAmmoTypeForModule(self.moduleinfo.itemID, None)
   1211             targetPointCount = self.moduleinfo.isPointTargeted
   1212             if targetPointCount:
   1213                 eveCommands = sm.GetService('cmd')
   1214                 eveCommands.CmdAimWeapon(self.moduleinfo.itemID, self.def_effect, targetPointCount)
   1215                 return
   1216             try:
   1217                 self.def_effect.Activate(targetID, repeats)
   1218             except UserError as e:
   1219                 if e.msg == 'ModuleReactivationDelayed2':
   1220                     self.SetReactivationTime(e.dict)
   1221                 raise
   1222 
   1223             sm.ScatterEvent('OnClientEvent_ActivateModule', self.def_effect.effectID)
   1224 
   1225     def DeactivateEffect(self, effect):
   1226         self.SetDeactivating()
   1227         try:
   1228             effect.Deactivate()
   1229             sm.ScatterEvent('OnClientEvent_DeactivateModule', self.def_effect.effectID)
   1230         except UserError as e:
   1231             if e.msg == 'EffectStillActive':
   1232                 if not self.isPendingUnlockForDeactivate:
   1233                     self.isPendingUnlockForDeactivate = True
   1234                     uthread.new(self.DelayButtonUnlockForDeactivate, max(0, e.dict['timeLeft']))
   1235             raise
   1236 
   1237     def SetReactivationTime(self, errorDict):
   1238         try:
   1239             timeSinceLastStop = errorDict['timeSinceLastStop']
   1240             itemID = errorDict['itemID']
   1241             cooldownTimes = self.stateManager.GetCooldownTimes(itemID)
   1242             if cooldownTimes:
   1243                 return
   1244             startTime = blue.os.GetSimTime() - timeSinceLastStop * const.MSEC
   1245             if startTime < 0:
   1246                 return
   1247             self.stateManager.RecordLastStopTime(itemID, startTime)
   1248             self.TryStartCooldownTimers()
   1249         except Exception as e:
   1250             pass
   1251 
   1252     def DelayButtonUnlockForDeactivate(self, sleepTimeBlue):
   1253         blue.pyos.synchro.SleepSim(sleepTimeBlue / const.MSEC)
   1254         self.isInActiveState = True
   1255         self.isPendingUnlockForDeactivate = False
   1256 
   1257     def OnStateChange(self, itemID, flag, isTrue, *args):
   1258         if self and isTrue and flag == state.activeTarget and self.waitingForActiveTarget:
   1259             self.waitingForActiveTarget = 0
   1260             self.ActivateEffect(self.def_effect, itemID)
   1261             sm.GetService('target').CancelTargetOrder(self)
   1262 
   1263     def GetModuleType(self):
   1264         return (self.moduleinfo.typeID, self.moduleinfo.itemID)
   1265 
   1266     def ActivateRamps(self):
   1267         if not self or self.destroyed:
   1268             return
   1269         if self.ramp_active:
   1270             self.UpdateRamps()
   1271             return
   1272         self.DoActivateRamps()
   1273 
   1274     def DeActivateRamps(self):
   1275         self.UpdateRamps()
   1276 
   1277     def IdleRamps(self):
   1278         self.ramp_active = False
   1279         if not uicore.layer.shipui:
   1280             return
   1281         moduleID = self.moduleinfo.itemID
   1282         rampTimers = uicore.layer.shipui.sr.rampTimers
   1283         if moduleID in rampTimers:
   1284             del rampTimers[moduleID]
   1285         if self.sr.ramps:
   1286             self.sr.ramps.display = False
   1287 
   1288     def UpdateRamps(self):
   1289         self.DoActivateRamps()
   1290 
   1291     def DoActivateRamps(self):
   1292         if self.ramp_active:
   1293             return
   1294         uthread.new(self.DoActivateRampsThread)
   1295 
   1296     def InitRamps(self):
   1297         if self.sr.ramps and not self.sr.ramps.destroyed:
   1298             return
   1299         self.sr.ramps = ShipModuleButtonRamps(parent=self.parent, idx=OVERLOADBTN_INDEX + 1)
   1300 
   1301     def InitReactivationRamps(self):
   1302         if self.sr.reactivationRamps and not self.sr.reactivationRamps.destroyed:
   1303             return
   1304         self.sr.reactivationRamps = ShipModuleReactivationTimer(parent=self.parent, idx=-1)
   1305 
   1306     def DoActivateRampsThread(self):
   1307         if not self or self.destroyed:
   1308             return
   1309         (firstActivation, startTime), durationInMilliseconds = self.GetEffectTiming()
   1310         if durationInMilliseconds <= 0:
   1311             return
   1312         now = blue.os.GetSimTime()
   1313         if firstActivation:
   1314             startTimeAdjustment = now - startTime
   1315             if startTimeAdjustment > const.SEC:
   1316                 startTimeAdjustment = 0
   1317             correctionTimeMS = durationInMilliseconds / 2
   1318             adjustmentDecayPerSec = float(-startTimeAdjustment) / (correctionTimeMS / 1000)
   1319         else:
   1320             startTimeAdjustment = 0
   1321             correctionTimeMS = 0
   1322         self.ramp_active = True
   1323         self.InitRamps()
   1324         self.sr.ramps.display = True
   1325         while self and not self.destroyed and self.ramp_active and durationInMilliseconds:
   1326             newNow = blue.os.GetSimTime()
   1327             deltaTime = newNow - now
   1328             now = newNow
   1329             if correctionTimeMS != 0:
   1330                 deltaMS = min(deltaTime / const.MSEC, correctionTimeMS)
   1331                 startTimeAdjustment += long(adjustmentDecayPerSec * (float(deltaMS) / 1000))
   1332                 correctionTimeMS -= deltaMS
   1333             else:
   1334                 startTimeAdjustment = 0
   1335             portionDone = blue.os.TimeDiffInMs(startTime + startTimeAdjustment, now) / durationInMilliseconds
   1336             if portionDone > 1:
   1337                 iterations = int(portionDone)
   1338                 startTime += long(durationInMilliseconds * iterations * const.MSEC)
   1339                 _, durationInMilliseconds = self.GetEffectTiming()
   1340                 try:
   1341                     uicore.layer.shipui.sr.rampTimers[self.moduleinfo.itemID] = (False, startTime)
   1342                 except AttributeError:
   1343                     pass
   1344 
   1345                 portionDone -= iterations
   1346                 if self.InLimboState():
   1347                     self.IdleRamps()
   1348                     sm.ScatterEvent('OnClientEvent_DeactivateModule', self.def_effect.effectID)
   1349                     break
   1350             self.sr.ramps.SetRampValues(portionDone)
   1351             blue.pyos.synchro.Yield()
   1352 
   1353     def InLimboState(self):
   1354         for each in ['waitingForActiveTarget', 'reloadingAmmo', 'isDeactivating']:
   1355             if getattr(self, each, False):
   1356                 return True
   1357 
   1358         return False
   1359 
   1360     def GetRampStartTime(self):
   1361         if not uicore.layer.shipui:
   1362             return
   1363         moduleID = self.moduleinfo.itemID
   1364         rampTimers = uicore.layer.shipui.sr.rampTimers
   1365         if moduleID not in rampTimers:
   1366             now = blue.os.GetSimTime()
   1367             default = getattr(self.def_effect, 'startTime', now) or now
   1368             rampTimers[moduleID] = (True, default)
   1369         return rampTimers[moduleID]
   1370 
   1371     def ShowRepairLeft(self, startTime = None):
   1372         if self.sr.damageState is None:
   1373             return
   1374         dmg = self.GetModuleDamage()
   1375         rateOfRepair = self.stateManager.GetAttribute(session.charid, 'moduleRepairRate')
   1376         repairTime = dmg / rateOfRepair
   1377         repairTime = int(repairTime * const.MIN)
   1378         if startTime is None:
   1379             startTime = blue.os.GetSimTime()
   1380         hp = self.dogmaLocation.GetAttributeValue(self.moduleinfo.itemID, const.attributeHp)
   1381         self.sr.damageState.AnimateRepair(dmg, hp, repairTime, startTime)
   1382 
   1383     def ShowReloadLeft(self, reloadTime, startTime = None):
   1384         if startTime is None:
   1385             startTime = blue.os.GetSimTime()
   1386         reloadTime = int(reloadTime * const.MSEC)
   1387         if self.sr.reactivationRamps and self.sr.reactivationRamps.endTime > startTime + reloadTime:
   1388             return
   1389         self.InitRamps()
   1390         self.sr.ramps.display = True
   1391         self.sr.ramps.AnimateReload(startTime, reloadTime)
   1392 
   1393     def ShowReactivationLeft(self, reactivationTime, startTime = None):
   1394         self.InitReactivationRamps()
   1395         reactivationTime = int(reactivationTime * const.MSEC)
   1396         if startTime is None:
   1397             startTime = blue.os.GetSimTime()
   1398         self.sr.reactivationRamps.AnimateTimer(startTime, reactivationTime)
   1399 
   1400     def GetEffectTiming(self):
   1401         rampStartTime = self.GetRampStartTime()
   1402         durationInMilliseconds = 0.0
   1403         durationAttributeID = getattr(self.def_effect, 'durationAttributeID', None)
   1404         attribute = dogma.data.get_attribute(durationAttributeID) if durationAttributeID is not None else None
   1405         item = self.stateManager.GetItem(self.def_effect.itemID)
   1406         if item is None:
   1407             return (0, 0.0)
   1408         if attribute:
   1409             durationInMilliseconds = self.stateManager.GetAttribute(self.def_effect.itemID, attribute.name)
   1410         if not durationInMilliseconds:
   1411             durationInMilliseconds = getattr(self.def_effect, 'duration', 0.0)
   1412         return (rampStartTime, durationInMilliseconds)