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

drone.py (59078B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\inflight\drone.py
      2 import math
      3 import blue
      4 import dogma.data
      5 import eve.client.script.ui.shared.pointerTool.pointerToolConst as pConst
      6 import eve.common.script.mgt.entityConst as entities
      7 import evetypes
      8 import itertoolsext
      9 import localization
     10 import log
     11 import mathext
     12 import uthread
     13 from carbon.client.script.util.misc import Uthreaded
     14 from carbon.common.script.util.timerstuff import AutoTimer
     15 from carbonui import uiconst
     16 from carbonui.control.button import Button
     17 from carbonui.primitives.container import Container
     18 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     19 from carbonui.primitives.fill import Fill
     20 from carbonui.primitives.frame import Frame
     21 from carbonui.primitives.line import Line
     22 from carbonui.primitives.sprite import Sprite
     23 from clientsettings.setting import CharSettingBool
     24 from carbonui.uianimations import animations
     25 from carbonui.uicore import uicore
     26 from carbonui.util.bunch import Bunch
     27 from carbonui.util.various_unsorted import SortListOfTuples, Transplant
     28 from eve.client.script.parklife import states as state
     29 from eve.client.script.ui import eveColor
     30 from eve.client.script.ui.control.entries.generic import Generic
     31 from eve.client.script.ui.control.entries.util import GetFromClass
     32 from eve.client.script.ui.control.eveLabel import EveLabelMedium
     33 from eve.client.script.ui.control.eveScroll import Scroll
     34 from eve.client.script.ui.control.listgroup import ListGroup as Group
     35 from eve.client.script.ui.control.utilMenu import UtilMenu
     36 from eve.client.script.ui.inflight import actionPanelDefaults
     37 from eve.client.script.ui.inflight.actions import ActionPanel
     38 from eve.client.script.ui.inflight.baseTacticalEntry import BaseTacticalEntry
     39 from eve.client.script.ui.services.menuSvcExtras.droneFunctions import ReturnToDroneBay
     40 from eve.client.script.ui.util import uix
     41 from eve.client.script.ui.util.utilWindows import NamePopup
     42 from eveDrones.droneConst import DAMAGESTATE_NOT_READY
     43 from eveInflight.damageStateValue import CalculateCurrentDamageStateValues
     44 from eveservices.menu import GetMenuService
     45 from menu import MenuLabel, MenuList
     46 from player_messaging.client.ui_message import prompt_player_fsd_dialog
     47 from utillib import KeyVal
     48 GROUPID_INBAY = 'inbay'
     49 GROUPID_INLOCALSPACE = 'inlocalspace'
     50 GROUPID_INDISTANCESPACE = 'indistantspace'
     51 launchTexturePath = 'res:/UI/Texture/classes/Drones/launchDrones.png'
     52 DRONE_STATES = {entities.STATE_IDLE: 'UI/Inflight/Drone/Idle',
     53  entities.STATE_COMBAT: 'UI/Inflight/Drone/Fighting',
     54  entities.STATE_MINING: 'UI/Inflight/Drone/Mining',
     55  entities.STATE_APPROACHING: 'UI/Inflight/Drone/Approaching',
     56  entities.STATE_DEPARTING: 'UI/Inflight/Drone/ReturningToShip',
     57  entities.STATE_DEPARTING_2: 'UI/Inflight/Drone/ReturningToShip',
     58  entities.STATE_OPERATING: 'UI/Inflight/Drone/Operating',
     59  entities.STATE_PURSUIT: 'UI/Inflight/Drone/Following',
     60  entities.STATE_FLEEING: 'UI/Inflight/Drone/Fleeing',
     61  entities.STATE_ENGAGE: 'UI/Inflight/Drone/Repairing',
     62  entities.STATE_SALVAGING: 'UI/Inflight/Drone/Salvaging',
     63  None: 'UI/Inflight/Drone/NoState'}
     64 COLOR_BY_STATE = {entities.STATE_IDLE: '0xFF00FF00',
     65  entities.STATE_COMBAT: '0xFFFF0000',
     66  entities.STATE_MINING: '0xFFFF0000',
     67  entities.STATE_APPROACHING: '0xFFFFFF00',
     68  entities.STATE_DEPARTING: '0xFFFFFF00',
     69  entities.STATE_DEPARTING_2: '0xFFFFFF00',
     70  entities.STATE_OPERATING: '0xFFFF0000',
     71  entities.STATE_PURSUIT: '0xFFFFFF00',
     72  entities.STATE_FLEEING: '0xFFFFFF00',
     73  entities.STATE_ENGAGE: '0xFF00FF00',
     74  entities.STATE_SALVAGING: '0xFFFF0000'}
     75 damageIconInfo = {0: 'res:/UI/Texture/classes/Fitting/StatsIcons/structureHP.png',
     76  1: 'res:/UI/Texture/classes/Fitting/StatsIcons/armorHP.png',
     77  2: 'res:/UI/Texture/classes/Fitting/StatsIcons/shieldHP.png'}
     78 
     79 def GetFavoriteDroneGroupConfigName():
     80     shipTypeID = None
     81     if session.shipid:
     82         shipItem = sm.GetService('godma').GetItem(session.shipid)
     83         if shipItem:
     84             shipTypeID = shipItem.typeID
     85     return 'drones_favoriteGroupID_%s' % shipTypeID
     86 
     87 
     88 class DroneEntry(BaseTacticalEntry):
     89     __guid__ = 'listentry.DroneEntry'
     90     isDragObject = True
     91     __notifyevents__ = ['OnStateChange', 'OnDroneStateChange2', 'OnDroneActivityChange']
     92 
     93     def UpdateDamage(self):
     94         if self.destroyed:
     95             self.sr.dmgTimer = None
     96             return
     97         if self.sr.node.droneState == 'inbay':
     98             return self.UpdateDamageInBay()
     99         return BaseTacticalEntry.UpdateDamage(self)
    100 
    101     def UpdateDamageInBay(self):
    102         droneID = self.GetShipID()
    103         dmg = self.GetDamageInBay(droneID)
    104         ret = False
    105         if dmg is not None:
    106             if self.sr.dmgTimer is None:
    107                 self.sr.dmgTimer = AutoTimer(1000, self.UpdateDamage)
    108             if dmg == DAMAGESTATE_NOT_READY:
    109                 return
    110             ret = self.SetDamageState(dmg)
    111             self.ShowDamageDisplay()
    112         else:
    113             self.HideDamageDisplay()
    114         return ret
    115 
    116     def GetDamageInBay(self, itemID):
    117         if self.sr.node.damageState == DAMAGESTATE_NOT_READY:
    118             if not getattr(self, 'fetchingDamageValue', False):
    119                 self.fetchingDamageValue = True
    120                 uthread.new(self.SetDamageValue_thread, itemID)
    121             return DAMAGESTATE_NOT_READY
    122         if self.sr.node.damageState:
    123             damageInMichelleFormat = self.sr.node.damageState.GetInfoInMichelleFormat()
    124             time = self.sr.node.damageState.timestamp
    125             ret = CalculateCurrentDamageStateValues(damageInMichelleFormat, time)
    126             return ret
    127 
    128     def SetDamageValue_thread(self, droneID):
    129         self.sr.node.damageState = sm.GetService('tactical').GetInBayDroneDamageTracker().GetDamageStateForDrone(droneID)
    130         self.fetchingDamageValue = False
    131 
    132     def OnMouseDown(self, *args):
    133         uthread.new(self.OnMouseDown_thread)
    134 
    135     def OnMouseDown_thread(self):
    136         selelectedDrones = self.sr.node.scroll.GetSelectedNodes(self.sr.node)
    137         droneState = self.sr.node.droneState
    138         displayLabel = self.sr.node.label
    139         if len(selelectedDrones) > 1:
    140             displayLabel += '<fontsize=14> + %s' % (len(selelectedDrones) - 1)
    141         if droneState == 'inbay':
    142             nodesData = [ (drone.invItem, 0, None) for drone in selelectedDrones if drone.invItem is not None ]
    143             manyItemsData = Bunch(menuFunction=GetMenuService().InvItemMenu, itemData=nodesData, displayName='<b>%s</b>' % displayLabel)
    144         elif droneState in ('inlocalspace', 'inDistantSpace'):
    145             if len(selelectedDrones) > 1:
    146                 nodesData = [ (drone.itemID,
    147                  drone.typeID,
    148                  drone.ownerID,
    149                  drone.locationID) for drone in selelectedDrones if drone.typeID ]
    150                 manyItemsData = Bunch(menuFunction=GetMenuService().DroneMenu, itemData=nodesData, displayName='<b>%s</b>' % displayLabel)
    151             else:
    152                 manyItemsData = None
    153         else:
    154             return
    155         GetMenuService().TryExpandActionMenu(itemID=self.sr.node.itemID, typeID=self.sr.node.typeID, clickedObject=self, manyItemsData=manyItemsData)
    156 
    157     def Startup(self, *args):
    158         BaseTacticalEntry.Startup(self, *args)
    159         self.activityID = None
    160         self.activity = None
    161         text_gaugeContainer = Container(name='text_gaugeContainer', parent=self, idx=0, pos=(0, 0, 0, 0))
    162         self.sr.gaugesContainer = Container(name='gaugesContainer', parent=text_gaugeContainer, width=85, align=uiconst.TORIGHT, state=uiconst.UI_HIDDEN)
    163         tClip = Container(name='textClipper', parent=text_gaugeContainer, state=uiconst.UI_PICKCHILDREN, clipChildren=1)
    164         Transplant(self.sr.label, tClip)
    165 
    166     def Load(self, node):
    167         preselected = node.selected
    168         super(DroneEntry, self).Load(node)
    169         if preselected and not node.selected:
    170             node.selected = 1
    171             node.scroll.UpdateSelection(node)
    172         if self.sr.node.droneState in ('inlocalspace', 'indistantspace'):
    173             self.UpdateState()
    174         self.sr.gaugesContainer.state = uiconst.UI_PICKCHILDREN
    175 
    176     def UpdateState(self, droneState = None):
    177         michelle = sm.GetService('michelle')
    178         droneRow = michelle.GetDroneState(self.sr.node.itemID)
    179         droneActivity = michelle.GetDroneActivity(self.sr.node.itemID)
    180         if droneActivity:
    181             self.activity, self.activityID = droneActivity
    182         if droneState is None and droneRow is not None:
    183             droneState = droneRow.activityState
    184         stateText = localization.GetByLabel(DRONE_STATES.get(droneState, 'UI/Inflight/Drone/Incapacitated'))
    185         if droneState in COLOR_BY_STATE:
    186             stateText = '<color=%s>%s</color>' % (COLOR_BY_STATE[droneState], stateText)
    187         label = localization.GetByLabel('UI/Inflight/Drone/Label', droneType=self.sr.node.label, state=stateText)
    188         target = ''
    189         if droneState in [const.entityCombat, const.entityEngage, const.entityMining]:
    190             targetID = droneRow.targetID
    191             targetTypeName = None
    192             pilotName = None
    193             if targetID:
    194                 targetSlim = michelle.GetItem(targetID)
    195                 if targetSlim:
    196                     if targetSlim.groupID == const.categoryShip:
    197                         pilotID = michelle.GetCharIDFromShipID(targetSlim.itemID)
    198                         if pilotID:
    199                             pilotName = cfg.eveowners.Get(pilotID).name
    200                     targetTypeName = uix.GetSlimItemName(targetSlim)
    201             if pilotName:
    202                 target = pilotName
    203             elif targetTypeName:
    204                 target = targetTypeName
    205             else:
    206                 target = localization.GetByLabel('UI/Generic/Unknown')
    207         tooltipExtra = ''
    208         if self.sr.node.ownerID != session.charid:
    209             tooltipExtra = localization.GetByLabel('UI/Inflight/Drone/OwnershipText', owner=self.sr.node.ownerID)
    210         elif self.sr.node.controllerID != session.shipid:
    211             tooltipExtra = localization.GetByLabel('UI/Inflight/Drone/ControllerText', controller=self.sr.node.controllerOwnerID)
    212         elif self.activityID and self.activity:
    213             activity = ''
    214             if self.activity == 'guard':
    215                 activity = localization.GetByLabel('UI/Inflight/Drone/Guarding')
    216             elif self.activity == 'assist':
    217                 activity = localization.GetByLabel('UI/Inflight/Drone/Assisting')
    218             tooltipExtra = localization.GetByLabel('UI/Inflight/Drone/Activity', activity=activity, idInfo=cfg.eveowners.Get(self.activityID).name)
    219         tooltip = localization.GetByLabel('UI/Inflight/Drone/Tooltip', droneType=self.sr.node.label, state=stateText, target=target, tooltipExtra=tooltipExtra)
    220         self.sr.label.text = label
    221         self.hint = tooltip
    222 
    223     def LoadTooltipPanel(self, tooltipPanel, *args):
    224         tooltipPanel.margin = (10, 3, 10, 3)
    225         self._LoadTooltipPanel_thread(tooltipPanel)
    226         self.tooltipThread = AutoTimer(500, self._LoadTooltipPanel_thread, tooltipPanel)
    227 
    228     def _LoadTooltipPanel_thread(self, tooltipPanel):
    229         currentMouseOver = uicore.uilib.mouseOver
    230         if self.destroyed or currentMouseOver != self and not currentMouseOver.IsUnder(self):
    231             self.tooltipThread = None
    232             return
    233         tooltipPanel.Flush()
    234         text = self.hint or evetypes.GetName(self.sr.node.typeID)
    235         textLabel = EveLabelMedium(align=uiconst.CENTER, width=200, autoFitToText=True, parent=tooltipPanel, text=text)
    236         droneID = self.GetShipID()
    237         if self.sr.node.droneState == 'inbay':
    238             dmg = self.GetDamageInBay(droneID)
    239         else:
    240             dmg = self.GetDamage(droneID)
    241         if dmg in (DAMAGESTATE_NOT_READY, None):
    242             return
    243         dmgList = []
    244         for eachLayerDmgPerc in dmg[:3]:
    245             layerDmg = eachLayerDmgPerc * 100
    246             layerDmg = int(round(layerDmg))
    247             layerDmg = mathext.clamp(layerDmg, 0.0, 100)
    248             layerDmgText = localization.GetByLabel('UI/Common/Formatting/Percentage', percentage=layerDmg)
    249             dmgList.append(layerDmgText)
    250 
    251         dmgList.reverse()
    252         iconCont = Container(name='iconCont', parent=tooltipPanel, align=uiconst.CENTER, height=16, width=120, padTop=4)
    253         textCont = Container(name='textCont', parent=tooltipPanel, align=uiconst.CENTER, height=16, width=120, padTop=2)
    254         for i in xrange(len(dmgList)):
    255             Sprite(name='healthBar', parent=Container(parent=iconCont, align=uiconst.TOLEFT, width=40, height=16), align=uiconst.CENTER, width=16, height=16, texturePath=damageIconInfo.get(i, None))
    256             EveLabelMedium(parent=Container(parent=textCont, align=uiconst.TOLEFT, height=16, width=40), align=uiconst.CENTER, text=dmgList[i])
    257 
    258     def GetHeight(self, *args):
    259         node, width = args
    260         node.height = uix.GetTextHeight('Xg', maxLines=1) + 4
    261         return node.height
    262 
    263     def OnDroneStateChange2(self, droneID, oldActivityState, newActivityState):
    264         if not self or getattr(self, 'sr', None) is None:
    265             return
    266         if self.sr.node and self.sr.node.droneState in ('inlocalspace', 'indistantspace') and droneID == self.sr.node.itemID:
    267             droneRow = sm.GetService('michelle').GetDroneState(self.sr.node.itemID)
    268             if droneRow:
    269                 self.sr.node.controllerID = droneRow.controllerID
    270                 self.sr.node.controllerOwnerID = droneRow.controllerOwnerID
    271                 self.UpdateState(newActivityState)
    272 
    273     def OnDroneActivityChange(self, droneID, activityID, activity):
    274         if not self or getattr(self, 'sr', None) is None:
    275             return
    276         if self.sr.node and self.sr.node.droneState in ('inlocalspace', 'indistantspace') and droneID == self.sr.node.itemID:
    277             self.activity = activity
    278             self.activityID = activityID
    279             self.UpdateState()
    280 
    281     def OnClick(self, *args):
    282         if self.sr.node:
    283             self.sr.node.scroll.SelectNode(self.sr.node)
    284             prompt_player_fsd_dialog('ListEntryClick')
    285             if not uicore.uilib.Key(uiconst.VK_CONTROL):
    286                 if not uicore.uilib.Key(uiconst.VK_SHIFT):
    287                     sm.GetService('stateSvc').SetState(self.sr.node.itemID, state.selected, 1)
    288             else:
    289                 sm.GetService('target').TryLockTarget(self.sr.node.itemID)
    290 
    291     def GetSelected(self):
    292         ids = []
    293         sel = self.sr.node.scroll.GetSelectedNodes(self.sr.node)
    294         for node in sel:
    295             if node.Get('typeID', None) is None:
    296                 continue
    297             if evetypes.GetGroupID(node.typeID) == evetypes.GetGroupID(self.sr.node.typeID):
    298                 ids.append(node.itemID)
    299 
    300         return ids
    301 
    302     def GetSelectedItems(self):
    303         items = []
    304         sel = self.sr.node.scroll.GetSelectedNodes(self.sr.node)
    305         for node in sel:
    306             items.append(node.invItem)
    307 
    308         return items
    309 
    310     def GetMenu(self):
    311         m = MenuList()
    312         if self.sr.node.customMenu:
    313             m += self.sr.node.customMenu(self.sr.node)
    314         if self.sr.node.droneState != 'inbay':
    315             args = []
    316             selected = self.sr.node.scroll.GetSelectedNodes(self.sr.node)
    317             for node in selected:
    318                 if node.Get('typeID', None) is None:
    319                     continue
    320                 args.append((node.itemID,
    321                  None,
    322                  None,
    323                  node.typeID,
    324                  None))
    325 
    326             m += GetMenuService().CelestialMenu(args)
    327         else:
    328             selected = self.GetSelectedItems()
    329             args = []
    330             for rec in selected:
    331                 if rec is None:
    332                     continue
    333                 args.append((rec, 0, 0))
    334 
    335             m += GetMenuService().InvItemMenu(args).filter([MenuLabel('UI/Inventory/ItemActions/BuyThisType'),
    336              MenuLabel('UI/Inventory/ItemActions/AddTypeToMarketQuickbar'),
    337              MenuLabel('UI/Inventory/ItemActions/ViewTypesMarketDetails'),
    338              MenuLabel('UI/Inventory/ItemActions/FindInContracts')])
    339         return m
    340 
    341     def SelectAll(self):
    342         self.sr.node.scroll.SelectAll()
    343         sel = self.GetSelected()
    344         if len(sel) > 1:
    345             sm.ScatterEvent('OnDronesSelected', sel)
    346 
    347     def InitGauges(self):
    348         if getattr(self, 'gaugesInited', False):
    349             self.sr.gaugeParent.state = uiconst.UI_DISABLED
    350             return
    351         parent = self.sr.gaugesContainer
    352         Line(parent=parent, align=uiconst.TOLEFT)
    353         barw, barh = (22, 6)
    354         borderw = 2
    355         barsw = (barw + borderw) * 3 + borderw
    356         par = Container(name='gauges', parent=parent, align=uiconst.TORIGHT, width=barsw + 2, height=0, left=0, top=0, idx=10)
    357         self.sr.gauges = []
    358         l = 2
    359         for each in ('STRUCT', 'ARMOR', 'SHIELD'):
    360             g = Container(parent=par, name='gauge_%s' % each.lower(), align=uiconst.CENTERLEFT, width=barw, height=barh, left=l)
    361             g.sr.bar = Fill(parent=g, name='droneGaugeBar', align=uiconst.TOLEFT, color=(1.0, 1.0, 1.0, 0.4))
    362             Fill(parent=g, name='droneGaugeBarDmg', color=(158 / 256.0,
    363              11 / 256.0,
    364              14 / 256.0,
    365              1.0))
    366             self.sr.gauges.append(g)
    367             setattr(self.sr, 'gauge_%s' % each.lower(), g)
    368             l += barw + borderw
    369 
    370         self.sr.gaugeParent = par
    371         self.gaugesInited = True
    372 
    373     def GetDragData(self, *args):
    374         return [ node for node in self.sr.node.scroll.GetSelectedNodes(self.sr.node) if node.__guid__ == 'listentry.DroneEntry' ]
    375 
    376     def Close(self):
    377         self.tooltipThread = None
    378         BaseTacticalEntry.Close(self)
    379 
    380 
    381 class DroneView(ActionPanel):
    382     __guid__ = 'form.DroneView'
    383     __notifyevents__ = ['OnAttribute',
    384      'OnAttributes',
    385      'OnDroneControlLost',
    386      'OnItemChange',
    387      'OnItemLaunch',
    388      'OnClientEvent_WarpStarted',
    389      'OnClientEvent_WarpFinished',
    390      'OnWarpStarted2',
    391      'ProcessSessionChange']
    392     default_windowID = 'droneview'
    393     uniqueUiName = pConst.UNIQUE_NAME_DRONE_WND
    394     default_minSize = (240, 80)
    395 
    396     def get_wnd_menu_unique_name(self):
    397         return pConst.UNIQUE_NAME_DRONE_SETTINGS
    398 
    399     @staticmethod
    400     def default_left(*args):
    401         return actionPanelDefaults.GetDroneViewLeft()
    402 
    403     @staticmethod
    404     def default_top(*args):
    405         return actionPanelDefaults.GetDroneViewTop()
    406 
    407     @staticmethod
    408     def default_width():
    409         return actionPanelDefaults.GetDroneViewWidth()
    410 
    411     @staticmethod
    412     def default_height():
    413         return actionPanelDefaults.GetDroneViewHeight()
    414 
    415     def ApplyAttributes(self, attributes):
    416         self.droneAggressionDefVal = dogma.data.get_attribute_default_value(const.attributeDroneIsAggressive)
    417         self.droneFFDefVal = dogma.data.get_attribute_default_value(const.attributeDroneFocusFire)
    418         ActionPanel.ApplyAttributes(self, attributes)
    419 
    420     def OnItemLaunch(self, ids):
    421         reload = False
    422         for oldID, newIDs in ids.iteritems():
    423             group = self.GetDroneGroup(oldID)
    424             if group is not None:
    425                 for newID in newIDs:
    426                     if newID != oldID:
    427                         group['droneIDs'].add(newID)
    428                         reload = True
    429 
    430         if reload:
    431             self.UpdateGroupSettings()
    432             self.CheckReloadDronesScroll(True)
    433 
    434     def ProcessSessionChange(self, *etc):
    435         self.CheckReloadDronesScroll(True)
    436 
    437     ProcessSessionChange = Uthreaded(ProcessSessionChange)
    438 
    439     def OnDroneControlLost(self, droneID):
    440         self.CheckReloadDronesScroll(True)
    441 
    442     def OnAttributes(self, l):
    443         for attributeName, item, newValue in l:
    444             self.OnAttribute(attributeName, item, newValue)
    445 
    446     def OnAttribute(self, attributeName, item, newValue):
    447         if not self or self.destroyed:
    448             return
    449         if item.itemID == session.charid and attributeName == 'maxActiveDrones':
    450             t = self.sr.lastUpdate
    451             if t is None:
    452                 self.CheckReloadDronesScroll()
    453             else:
    454                 self.UpdateHeader(t[0], t[1] + t[2])
    455 
    456     def OnItemChange(self, item, change, location):
    457         if item.locationID == session.shipid:
    458             if item.flagID == const.flagDroneBay or change.get(const.ixFlag, None) == const.flagDroneBay:
    459                 ignoreClose = session.solarsystemid == change.get(const.ixLocationID, None)
    460                 self.CheckReloadDronesScroll(ignoreClose=ignoreClose)
    461         elif change.get(const.ixLocationID, None) == session.shipid and change.get(const.ixFlag, None) == const.flagDroneBay:
    462             self.CheckReloadDronesScroll()
    463 
    464     def PostStartup(self):
    465         if not self or self.destroyed:
    466             return
    467         self.SetMinSize((240, 80))
    468         self.warpWarning = WarpWarning(parent=self.sr.main, align=uiconst.TOTOP, state=uiconst.UI_HIDDEN, padBottom=2)
    469         self.sr.scroll = Scroll(name='dronescroll', align=uiconst.TOALL, parent=self.sr.main)
    470         self.sr.scroll.multiSelect = 1
    471         self.sr.scroll.OnChar = self.OnDronesScrollChar
    472         self.sr.inSpace = None
    473         self.sr.lastUpdate = None
    474         self.settingsName = 'droneBlah2'
    475         self.reloading = 0
    476         self.pending = None
    477         openState = uicore.registry.GetListGroupOpenState(('dronegroups', 'inbay'), default=False)
    478         uicore.registry.GetLockedGroup('dronegroups', 'inbay', localization.GetByLabel('UI/Inflight/Drone/DronesInBay'), openState=openState)
    479         openState = uicore.registry.GetListGroupOpenState(('dronegroups', 'inlocalspace'), default=False)
    480         uicore.registry.GetLockedGroup('dronegroups', 'inlocalspace', localization.GetByLabel('UI/Inflight/Drone/DronesInLocalSpace'), openState=openState)
    481         uicore.registry.GetLockedGroup('dronegroups', 'indistantspace', localization.GetByLabel('UI/Inflight/Drone/DronesInDistantSpace'))
    482         self.groups = self.SettifyGroups(settings.user.ui.Get(self.settingsName, {}))
    483         droneSettingChanges = {const.attributeDroneIsAggressive: settings.char.ui.Get('droneAggression', self.droneAggressionDefVal),
    484          const.attributeDroneFocusFire: settings.char.ui.Get('droneFocusFire', self.droneFFDefVal)}
    485         sm.GetService('godma').GetStateManager().ChangeDroneSettings(droneSettingChanges)
    486         if self and not self.destroyed:
    487             uthread.new(self.CheckReloadDronesScroll)
    488 
    489     def OnDronesScrollChar(self, *args):
    490         return False
    491 
    492     def GroupXfier(fn):
    493 
    494         def XfyGroup(group):
    495             ret = group.copy()
    496             ret['droneIDs'] = fn(group['droneIDs'])
    497             return ret
    498 
    499         return lambda self, groups: dict([ (name, XfyGroup(group)) for name, group in groups.iteritems() ])
    500 
    501     ListifyGroups = GroupXfier(list)
    502     SettifyGroups = GroupXfier(set)
    503     del GroupXfier
    504 
    505     def GetSelected(self, fromNode):
    506         nodes = []
    507         sel = self.sr.scroll.GetSelectedNodes(fromNode)
    508         for node in sel:
    509             if node.Get('typeID', None) is None:
    510                 continue
    511             if evetypes.GetGroupID(node.typeID) == evetypes.GetGroupID(fromNode.typeID):
    512                 if node.droneState == fromNode.droneState:
    513                     nodes.append(node)
    514 
    515         return nodes
    516 
    517     def UpdateHeader(self, inBay, inSpace):
    518         self.SetCaption(localization.GetByLabel('UI/Inflight/Drone/PanelHeader', panelName=self.panelname, inSpace=len(inSpace), maxTotal=int(sm.GetService('godma').GetItem(session.charid).maxActiveDrones) or 0))
    519 
    520     def UpdateAll(self):
    521         if self.sr.main.state != uiconst.UI_PICKCHILDREN:
    522             self.sr.actionsTimer = None
    523             return
    524         self.CheckReloadDronesScroll()
    525 
    526     def GetSubGroups(self, what):
    527         return []
    528 
    529     def CheckReloadDronesScroll(self, force = False, ignoreClose = False, *args):
    530         if session.stationid:
    531             return
    532         if not self.pending:
    533             self.pending = ('updating',)
    534         else:
    535             if 'updating' in self.pending:
    536                 self.pending = ('pending', force, ignoreClose)
    537                 return
    538             if 'pending' in self.pending:
    539                 p, oldForce, oldIgnoreClose = self.pending
    540                 self.pending = ('pending', force or oldForce, ignoreClose or oldIgnoreClose)
    541                 return
    542         if self.destroyed:
    543             return
    544         inBay = self.GetDronesInBay()
    545         inBayIDs = [ drone.itemID for drone in inBay ]
    546         inBayIDs.sort()
    547         uthread.new(sm.GetService('tactical').GetInBayDroneDamageTracker().FetchInBayDroneDamageToServer, inBayIDs)
    548         inLocalSpace = [ drone for drone in GetDronesInLocalSpace() if drone.droneID not in inBayIDs ]
    549         inLocalSpaceIDs = [ drone.droneID for drone in inLocalSpace ]
    550         inLocalSpaceIDs.sort()
    551         inDistantSpace = [ drone for drone in GetDronesInDistantSpace() if drone.droneID not in inBayIDs ]
    552         inDistantSpaceIDs = [ drone.droneID for drone in inDistantSpace ]
    553         inDistantSpaceIDs.sort()
    554         allDroneIDs = inBayIDs + inLocalSpaceIDs + inDistantSpaceIDs
    555         t = (inBayIDs, inLocalSpaceIDs, inDistantSpaceIDs)
    556         if self.sr.lastUpdate != t or force or inDistantSpace:
    557             self.sr.lastUpdate = t
    558             groupInfo = uicore.registry.GetListGroup(('dronegroups', GROUPID_INBAY))
    559             scrolllist = self.GetGroupListEntry(groupInfo, '%s' % GROUPID_INBAY, inBayIDs, allDroneIDs)
    560             groupInfo = uicore.registry.GetListGroup(('dronegroups', GROUPID_INLOCALSPACE))
    561             scrolllist += self.GetGroupListEntry(groupInfo, GROUPID_INLOCALSPACE, inLocalSpaceIDs)
    562             if inDistantSpaceIDs:
    563                 groupInfo = uicore.registry.GetListGroup(('dronegroups', GROUPID_INDISTANCESPACE))
    564                 scrolllist += self.GetGroupListEntry(groupInfo, GROUPID_INDISTANCESPACE, inDistantSpaceIDs)
    565             self.sr.scroll.Load(contentList=scrolllist)
    566         self.UpdateHeader(inBayIDs, inLocalSpaceIDs + inDistantSpaceIDs)
    567         self.CheckHint()
    568         self.UpdateWarpWarning()
    569         blue.pyos.synchro.SleepWallclock(500)
    570         if not self or self.destroyed:
    571             return
    572         if 'pending' in self.pending:
    573             p, force, ignoreClose = self.pending
    574             self.pending = None
    575             self.CheckReloadDronesScroll(force, ignoreClose)
    576             return
    577         self.pending = None
    578         if not ignoreClose and not self.destroyed:
    579             self.CheckClose()
    580 
    581     def CheckClose(self):
    582         if not (self.GetDronesInBay() or sm.GetService('michelle').GetDrones()) and hasattr(self, 'Close'):
    583             self.Close()
    584 
    585     def GetMainFolderMenu(self, node):
    586         m = MenuList([None])
    587         delMenu = [ (groupName, self.DeleteGroup, (groupName,)) for groupName, groupInfo in self.groups.iteritems() ]
    588         if delMenu:
    589             m += [(MenuLabel('UI/Commands/DeleteGroup'), delMenu), None]
    590         data = self.GetDroneDataForMainGroup(node)
    591         if not data:
    592             return m
    593         if node.droneState in ('inlocalspace', 'indistantspace'):
    594             m += GetMenuService().DroneMenu(data)
    595         else:
    596             m += GetMenuService().InvItemMenu(data).filter([MenuLabel('UI/Inventory/ItemActions/BuyThisType'),
    597              MenuLabel('UI/Inventory/ItemActions/AddTypeToMarketQuickbar'),
    598              MenuLabel('UI/Inventory/ItemActions/ViewTypesMarketDetails'),
    599              MenuLabel('UI/Inventory/ItemActions/FindInContracts')])
    600         return m
    601 
    602     def GetNodesToMoveAround(self, nodes):
    603         if len(nodes) > 1:
    604             return nodes
    605         firstNode = nodes[0]
    606         if firstNode['__guid__'] == 'listentry.DroneSubGroup':
    607             nodes = self.GetSubGroupContent(firstNode)
    608         elif firstNode['__guid__'] == 'listentry.DroneMainGroup':
    609             nodes = self.GetAllMainGroupContent(firstNode)
    610         return nodes
    611 
    612     def GroupDropData(self, dragObj, nodes):
    613         groupState = dragObj[1]
    614         self.LaunchOrPullDrones(groupState, nodes)
    615 
    616     def GetAllMainGroupContent(self, nodedata):
    617         nodes = self.GetGroupContent(nodedata)
    618         myNodes = []
    619         for eachNode in nodes:
    620             if eachNode.decoClass == DroneSubGroup:
    621                 myNodes += self.GetSubGroupContent(eachNode)
    622             else:
    623                 myNodes.append(eachNode)
    624 
    625         return myNodes
    626 
    627     def SubGroupDropData(self, dragObj, nodes):
    628         groupState = dragObj[0]
    629         groupNameAndID = dragObj[1]
    630         groupName = groupNameAndID[0]
    631         nodes = self.GetNodesToMoveAround(nodes)
    632         dronesWithChangedState = self.LaunchOrPullDrones(groupState, nodes)
    633         self.MoveDronesToSubGroup(groupName=groupName, nodes=nodes, excludedDrones=dronesWithChangedState)
    634 
    635     def MoveDronesToSubGroup(self, groupName, nodes, excludedDrones = []):
    636         subGroupInfo = self.GetSubGroup(groupName)
    637         movingDrones = []
    638         for droneNode in nodes:
    639             if droneNode in excludedDrones:
    640                 continue
    641             if droneNode.itemID not in subGroupInfo['droneIDs']:
    642                 movingDrones.append(droneNode)
    643 
    644         if movingDrones:
    645             firstDrone = movingDrones[0]
    646             self.MoveToGroup(groupName, firstDrone.itemID, evetypes.GetGroupID(firstDrone.typeID), movingDrones)
    647 
    648     def LaunchOrPullDrones(self, groupState, nodes, *args):
    649         nodes = self.GetNodesToMoveAround(nodes)
    650         changingState = []
    651         for droneNode in nodes:
    652             if droneNode.droneState in ('inlocalspace', 'inbay') and droneNode.droneState != groupState:
    653                 changingState.append(droneNode)
    654 
    655         if changingState:
    656             if groupState == 'inlocalspace':
    657                 GetMenuService().LaunchDrones([ droneNode.invItem for droneNode in changingState ])
    658             elif groupState == 'inbay':
    659                 ReturnToDroneBay([ droneNode.itemID for droneNode in changingState ])
    660         return changingState
    661 
    662     def DeleteGroup(self, groupName):
    663         self.EmptyGroup(groupName)
    664         if groupName in self.groups:
    665             del self.groups[groupName]
    666         self.UpdateGroupSettings()
    667         self.UpdateAll()
    668 
    669     def GetSubFolderMenu(self, node):
    670         m = MenuList([None])
    671         data = self.GetDroneDataForSubGroup(node)
    672         if not data:
    673             return m
    674         if node.droneState in ('inlocalspace', 'indistantspace'):
    675             m += GetMenuService().DroneMenu(data)
    676         elif node.droneState == 'inbay':
    677             m += GetMenuService().InvItemMenu(data).filter([MenuLabel('UI/Inventory/ItemActions/BuyThisType'),
    678              MenuLabel('UI/Inventory/ItemActions/AddTypeToMarketQuickbar'),
    679              MenuLabel('UI/Inventory/ItemActions/ViewTypesMarketDetails'),
    680              MenuLabel('UI/Inventory/ItemActions/FindInContracts')])
    681         favoriteGroup = self.GetFavoriteGroupIdentifier()
    682         groupIdentifier = node.id[-1]
    683         if favoriteGroup == groupIdentifier:
    684             m += [(MenuLabel('UI/Drones/UnmarkAsFavorite'), self.RemoveAsFavorite, (groupIdentifier,))]
    685         else:
    686             m += [(MenuLabel('UI/Drones/MarkAsFavorite'), self.AddAsFavorite, (groupIdentifier,))]
    687         return m
    688 
    689     def AddAsFavorite(self, groupIdentifier):
    690         self.SetFavoriteGroupSetting(groupIdentifier)
    691         self.CheckReloadDronesScroll(force=True)
    692 
    693     def RemoveAsFavorite(self, groupIdentifier):
    694         favoriteGroupID = self.GetFavoriteGroupIdentifier()
    695         if groupIdentifier == favoriteGroupID:
    696             self.SetFavoriteGroupSetting(None)
    697             self.CheckReloadDronesScroll(force=True)
    698 
    699     def SetFavoriteGroupSetting(self, value):
    700         settingName = GetFavoriteDroneGroupConfigName()
    701         settings.user.ui.Set(settingName, value)
    702 
    703     def GroupMenu(self, droneNode):
    704         selected = self.GetSelected(droneNode)
    705         m = MenuList()
    706         move = [(MenuLabel('UI/Commands/NewGroup'), self.CreateSubGroup, (droneNode.itemID, evetypes.GetGroupID(droneNode.typeID), selected))]
    707         inGroup = []
    708         for node in selected:
    709             group = self.GetDroneGroup(node.itemID)
    710             if group:
    711                 inGroup.append(node)
    712 
    713         if inGroup:
    714             move += [(MenuLabel('UI/Commands/OutOfThisGroup'), self.NoGroup, (inGroup,))]
    715         groupNames = self.groups.keys()[:]
    716         groupNames.sort(key=lambda x: x.lower())
    717         move += [ (groupName, self.MoveToGroup, (groupName,
    718           droneNode.itemID,
    719           evetypes.GetGroupID(droneNode.typeID),
    720           selected)) for groupName in groupNames ]
    721         m += [(MenuLabel('UI/Commands/MoveDrone'), move)]
    722         return m
    723 
    724     def GetEmptyGroups(self):
    725         empty = []
    726         for groupName, groupInfo in self.groups.iteritems():
    727             if not groupInfo['droneIDs']:
    728                 empty.append(groupName)
    729 
    730         return empty
    731 
    732     def DeleteEmptyGroups(self, *args):
    733         for groupName in self.GetEmptyGroups():
    734             del self.groups[groupName]
    735 
    736     def GetDroneGroup(self, droneID, getall = 0):
    737         retall = []
    738         for groupName, group in self.groups.iteritems():
    739             if droneID in group['droneIDs']:
    740                 if getall:
    741                     retall.append(group)
    742                 else:
    743                     return group
    744 
    745         if getall:
    746             return retall
    747 
    748     def NoGroup(self, nodes):
    749         for node in nodes:
    750             for group in self.GetDroneGroup(node.itemID, getall=1):
    751                 group['droneIDs'].remove(node.itemID)
    752 
    753         self.CheckReloadDronesScroll(1)
    754         self.UpdateGroupSettings()
    755 
    756     def EmptyGroup(self, groupName):
    757         droneGroup = self.GetSubGroup(groupName)
    758         for droneID in droneGroup.get('droneIDs', set()).copy():
    759             for group in self.GetDroneGroup(droneID, getall=1):
    760                 group['droneIDs'].remove(droneID)
    761 
    762         self.CheckReloadDronesScroll(1)
    763 
    764     def MoveToGroup(self, groupName, droneID, droneGroupID, nodes):
    765         group = self.GetSubGroup(groupName)
    766         if group['droneIDs'] and group['droneGroupID'] != droneGroupID:
    767             prompt_player_fsd_dialog('CannotMixDrones')
    768             return
    769         for node in nodes:
    770             for group in self.GetDroneGroup(node.itemID, getall=1):
    771                 group['droneIDs'].remove(node.itemID)
    772 
    773         group = self.GetSubGroup(groupName)
    774         if not group['droneIDs']:
    775             group['droneGroupID'] = droneGroupID
    776         if group:
    777             for node in nodes:
    778                 group['droneIDs'].add(node.itemID)
    779 
    780         self.CheckReloadDronesScroll(1)
    781         self.UpdateGroupSettings()
    782 
    783     def GetSubGroup(self, groupName):
    784         if groupName in self.groups:
    785             return self.groups[groupName]
    786 
    787     def CreateSubGroup(self, droneID, droneGroupID, nodes = None):
    788         ret = NamePopup(localization.GetByLabel('UI/Generic/TypeGroupName'), localization.GetByLabel('UI/Generic/TypeNameForGroup'))
    789         if not ret:
    790             return
    791         droneIDs = set()
    792         for node in nodes:
    793             for group in self.GetDroneGroup(node.itemID, getall=1):
    794                 group['droneIDs'].remove(node.itemID)
    795 
    796             droneIDs.add(node.itemID)
    797 
    798         origname = groupname = ret
    799         i = 2
    800         while groupname in self.groups:
    801             groupname = '%s_%i' % (origname, i)
    802             i += 1
    803 
    804         group = {}
    805         group['label'] = groupname
    806         group['droneIDs'] = droneIDs
    807         group['id'] = (groupname, str(blue.os.GetWallclockTime()))
    808         group['droneGroupID'] = droneGroupID
    809         self.groups[groupname] = group
    810         self.CheckReloadDronesScroll(1)
    811         self.UpdateGroupSettings()
    812 
    813     def OnMainGroupClick(self, group, *args):
    814         itemIDs = self.GetDroneIDsInMainGroup(group.sr.node)
    815         if itemIDs:
    816             sm.ScatterEvent('OnDronesSelected', itemIDs)
    817 
    818     def OnSubGroupClick(self, group, *args):
    819         itemIDs = self.GetDroneIDsInSubGroup(group.sr.node)
    820         if itemIDs:
    821             sm.ScatterEvent('OnDronesSelected', itemIDs)
    822 
    823     def GetDroneIDsInMainGroup(self, groupNode):
    824         droneDict = self.GetDroneDictFromDroneIDs(None, groupNode.droneState, includeFunction=self.IncludeAllDrones)
    825         return droneDict.keys()
    826 
    827     def GetDroneDataForMainGroup(self, groupNode):
    828         droneDict = self.GetDroneDictFromDroneIDs(None, groupNode.droneState, includeFunction=self.IncludeAllDrones)
    829         return droneDict.values()
    830 
    831     def GetDroneIDsInSubGroup(self, groupNode):
    832         droneDict = self.GetDroneDictForSubGroup(groupNode)
    833         return droneDict.keys()
    834 
    835     def GetDroneDataForSubGroup(self, groupNode):
    836         droneDict = self.GetDroneDictForSubGroup(groupNode)
    837         return droneDict.values()
    838 
    839     def GetDroneDictForSubGroup(self, groupNode):
    840         droneState = groupNode.droneState
    841         allDronesBelongingToGroup = self.GetSubGroup(groupNode.groupName)['droneIDs']
    842         droneDict = self.GetDroneDictFromDroneIDs(allDronesBelongingToGroup, droneState)
    843         return droneDict
    844 
    845     def GetDroneDictFromDroneIDs(self, itemIDs, droneState, includeFunction = None):
    846         if includeFunction is None:
    847             includeFunction = self.IsDroneIDinItemIDs
    848         droneDict = {}
    849         if droneState == 'inbay':
    850             droneDict = {drone.itemID:(drone, 0, None) for drone in self.GetDronesInBay() if includeFunction(drone.itemID, itemIDs)}
    851             return droneDict
    852         if droneState == 'inlocalspace':
    853             listOfDrones = GetDronesInLocalSpace()
    854         elif droneState == 'indistantspace':
    855             listOfDrones = GetDronesInDistantSpace()
    856         else:
    857             return droneDict
    858         droneDict = {drone.droneID:(drone.droneID,
    859          drone.typeID,
    860          drone.ownerID,
    861          drone.locationID) for drone in listOfDrones if includeFunction(drone.droneID, itemIDs)}
    862         return droneDict
    863 
    864     def IsDroneIDinItemIDs(self, droneID, itemIDs):
    865         return droneID in itemIDs
    866 
    867     def IncludeAllDrones(self, droneID, itemIDs):
    868         return True
    869 
    870     def OnMouseDownOnDroneMainGroup(self, group, *args):
    871         self.OnMouseDownOnDroneMainGroup_thread(group)
    872 
    873     def OnMouseDownOnDroneMainGroup_thread(self, group):
    874         groupNode = group.sr.node
    875         droneData = self.GetDroneDataForMainGroup(groupNode)
    876         return self.GetRadialMenuOnGroup(group, droneData)
    877 
    878     def OnMouseDownOnDroneSubGroup(self, group, *args):
    879         uthread.new(self.OnMouseDownOnDroneSubGroup_thread, group)
    880 
    881     def OnMouseDownOnDroneSubGroup_thread(self, group):
    882         groupNode = group.sr.node
    883         droneData = self.GetDroneDataForSubGroup(groupNode)
    884         return self.GetRadialMenuOnGroup(group, droneData)
    885 
    886     def GetRadialMenuOnGroup(self, group, droneData):
    887         if not droneData:
    888             return
    889         groupNode = group.sr.node
    890         droneState = groupNode.droneState
    891         if droneState == 'inbay':
    892             manyItemsData = Bunch(menuFunction=GetMenuService().InvItemMenu, itemData=droneData, displayName='<b>%s</b>' % groupNode.cleanLabel)
    893         elif droneState in ('inlocalspace', 'indistantspace'):
    894             manyItemsData = Bunch(menuFunction=GetMenuService().DroneMenu, itemData=droneData, displayName='<b>%s</b>' % groupNode.cleanLabel)
    895         else:
    896             return
    897         typeID = GetTypeIDForManyDrones(droneState, droneData)
    898         GetMenuService().TryExpandActionMenu(itemID=None, typeID=typeID, clickedObject=group, manyItemsData=manyItemsData)
    899 
    900     def GetDronesInBay(self, *args):
    901         if session.shipid:
    902             return sm.GetService('invCache').GetInventoryFromId(session.shipid).ListDroneBay()
    903         return []
    904 
    905     def GetSpaceDrone(self, droneID):
    906         return sm.GetService('michelle').GetDroneState(droneID)
    907 
    908     def GetGroupListEntry(self, group, state, items, allDroneIDs = None):
    909         if not group or 'id' not in group:
    910             return []
    911         numDrones = self.GetNumberOfDronesInGroup(state, items)
    912         states = {'INBAY': localization.GetByLabel('UI/Inflight/Drone/DronesInBay'),
    913          'INLOCALSPACE': localization.GetByLabel('UI/Inflight/Drone/DronesInLocalSpace'),
    914          'INDISTANTSPACE': localization.GetByLabel('UI/Inflight/Drone/DronesInDistantSpace')}
    915         icon = None
    916         if state == 'inbay' and not self.IsFavoriteGroupHere(allDroneIDs):
    917             icon = launchTexturePath
    918         data = {'GetSubContent': self.GetGroupContent,
    919          'DropData': self.GroupDropData,
    920          'MenuFunction': self.GetMainFolderMenu,
    921          'label': localization.GetByLabel('UI/Inflight/Drone/DroneGroupWithCount', groupLabel=states[state.upper()], count=numDrones),
    922          'id': group['id'],
    923          'groupItems': items,
    924          'iconMargin': 18,
    925          'state': 'locked',
    926          'sublevel': 0,
    927          'droneState': state,
    928          'BlockOpenWindow': 1,
    929          'OnClick': self.OnMainGroupClick,
    930          'showlen': 0,
    931          'groupName': group['label'],
    932          'name': 'droneOverview%s' % group['label'].replace(' ', '').capitalize(),
    933          'OnMouseDown': self.OnMouseDownOnDroneMainGroup,
    934          'showicon': icon,
    935          'GetUtilMenu': self.DroneSettings}
    936         groupCls = self._GetEntryGroupClass(state)
    937         return [GetFromClass(groupCls, data)]
    938 
    939     def _GetEntryGroupClass(self, groupID):
    940         if groupID == GROUPID_INLOCALSPACE:
    941             groupCls = DroneMainGroupInSpace
    942         else:
    943             groupCls = DroneMainGroup
    944         return groupCls
    945 
    946     def IsFavoriteGroupHere(self, allDroneIDs):
    947         favoriteGroupID = self.GetFavoriteGroupIdentifier()
    948         if favoriteGroupID is None:
    949             return False
    950         dronesByGroupIdentifiers = {x.get('id'):x.get('droneIDs') for x in self.groups.itervalues()}
    951         dronesInGroup = dronesByGroupIdentifiers.get(favoriteGroupID, [])
    952         if not dronesInGroup:
    953             return False
    954         if set(dronesInGroup).intersection(allDroneIDs):
    955             return True
    956         return False
    957 
    958     def GetFavoriteGroupIdentifier(self):
    959         settingName = GetFavoriteDroneGroupConfigName()
    960         return settings.user.ui.Get(settingName, None)
    961 
    962     def GetSubGroupListEntry(self, group, state, items):
    963         numDrones = self.GetNumberOfDronesInGroup(state, items)
    964         icon = None
    965         groupIdentifier = group['id']
    966         favoriteGroup = self.GetFavoriteGroupIdentifier()
    967         if favoriteGroup and favoriteGroup == groupIdentifier:
    968             icon = launchTexturePath
    969         return GetFromClass(DroneSubGroup, {'GetSubContent': self.GetSubGroupContent,
    970          'DropData': self.SubGroupDropData,
    971          'MenuFunction': self.GetSubFolderMenu,
    972          'label': localization.GetByLabel('UI/Inflight/Drone/DroneGroupWithCount', groupLabel=group['label'], count=numDrones),
    973          'id': (state, groupIdentifier),
    974          'droneGroupID': group['droneGroupID'],
    975          'groupItems': None,
    976          'iconMargin': 18,
    977          'state': 'locked',
    978          'sublevel': 1,
    979          'droneState': state,
    980          'BlockOpenWindow': 1,
    981          'OnClick': self.OnSubGroupClick,
    982          'showlen': 0,
    983          'groupName': group['label'],
    984          'OnMouseDown': self.OnMouseDownOnDroneSubGroup,
    985          'showicon': icon})
    986 
    987     def GetNumberOfDronesInGroup(self, droneState, items):
    988         t = 0
    989         if droneState == 'inbay':
    990             dronebay = {}
    991             for drone in self.GetDronesInBay():
    992                 dronebay[drone.itemID] = drone
    993 
    994             for droneID in items:
    995                 if droneID not in dronebay:
    996                     log.LogWarn("Drone we thought was in the dronebay wasn't actually there, droneID = ", droneID)
    997                     continue
    998                 t += dronebay[droneID].stacksize
    999 
   1000         else:
   1001             t = len(items)
   1002         return t
   1003 
   1004     def GetGroupContent(self, nodedata, newitems = 0):
   1005         scrollList = []
   1006         if nodedata.droneState == 'inbay':
   1007             dronebay = {}
   1008             for drone in self.GetDronesInBay():
   1009                 dronebay[drone.itemID] = drone
   1010 
   1011         subGroups = {}
   1012         for droneID in nodedata.groupItems:
   1013             group = self.GetDroneGroup(droneID)
   1014             if group:
   1015                 subGroups.setdefault(group['label'], []).append(droneID)
   1016                 continue
   1017             if nodedata.droneState == 'inbay':
   1018                 if droneID in dronebay:
   1019                     entry = self.GetBayDroneEntry(dronebay[droneID], nodedata.sublevel, nodedata.droneState)
   1020                     scrollList.append(((0, entry.label.lower()), entry))
   1021             else:
   1022                 entry = self.GetSpaceDroneEntry(self.GetSpaceDrone(droneID), nodedata.sublevel, nodedata.droneState)
   1023                 scrollList.append(((0, entry.label.lower()), entry))
   1024 
   1025         for groupName, droneIDs in subGroups.iteritems():
   1026             group = self.GetSubGroup(groupName)
   1027             if group:
   1028                 entry = self.GetSubGroupListEntry(group, nodedata.droneState, droneIDs)
   1029                 scrollList.append(((1, entry.label.lower()), entry))
   1030 
   1031         if not scrollList:
   1032             noItemEntry = self.GetNoItemEntry(sublevel=1, droneState=nodedata.droneState)
   1033             scrollList.append((0, noItemEntry))
   1034         return SortListOfTuples(scrollList)
   1035 
   1036     def GetSubGroupContent(self, nodedata, newitems = 0):
   1037         scrollList = []
   1038         subGroupInfo = self.GetSubGroup(nodedata.groupName)
   1039         if nodedata.droneState == 'inbay':
   1040             drones = self.GetDronesInBay()
   1041             for drone in drones:
   1042                 if drone.itemID in subGroupInfo['droneIDs']:
   1043                     entry = self.GetBayDroneEntry(drone, 1, nodedata.droneState)
   1044                     scrollList.append(((entry.subLevel, entry.label), entry))
   1045 
   1046         elif nodedata.droneState == 'inlocalspace':
   1047             drones = GetDronesInLocalSpace()
   1048             for drone in drones:
   1049                 if drone.droneID in subGroupInfo['droneIDs']:
   1050                     entry = self.GetSpaceDroneEntry(drone, 1, nodedata.droneState)
   1051                     scrollList.append(((entry.subLevel, entry.label), entry))
   1052 
   1053         else:
   1054             drones = GetDronesInDistantSpace()
   1055             for drone in drones:
   1056                 if drone.droneID in subGroupInfo['droneIDs']:
   1057                     entry = self.GetSpaceDroneEntry(drone, 1, nodedata.droneState)
   1058                     scrollList.append(((entry.subLevel, entry.label), entry))
   1059 
   1060         if not scrollList:
   1061             noItemEntry = self.GetNoItemEntry(sublevel=2, droneState=nodedata.droneState)
   1062             scrollList.append((0, noItemEntry))
   1063         return SortListOfTuples(scrollList)
   1064 
   1065     def GetNoItemEntry(self, sublevel, droneState, *args):
   1066         data = KeyVal()
   1067         data.droneState = droneState
   1068         data.label = localization.GetByLabel('/Carbon/UI/Controls/Common/NoItem')
   1069         data.sublevel = sublevel
   1070         data.itemID = None
   1071         data.OnDropData = lambda dragObject, nodes: self.DropDronesOnDroneEntry(data, dragObject, nodes)
   1072         return GetFromClass(Generic, data)
   1073 
   1074     def GetDroneDamageState(self, droneID):
   1075         damageTracker = sm.GetService('tactical').GetInBayDroneDamageTracker()
   1076         if damageTracker.IsDroneDamageReady(droneID):
   1077             damageState = damageTracker.GetDamageStateForDrone(droneID)
   1078         else:
   1079             damageState = -1
   1080         return damageState
   1081 
   1082     def GetBayDroneEntry(self, drone, level, droneState):
   1083         data = KeyVal()
   1084         data.itemID = drone.itemID
   1085         data.typeID = drone.typeID
   1086         data.invItem = drone
   1087         data.damageState = self.GetDroneDamageState(drone.itemID)
   1088         data.displayName = evetypes.GetName(drone.typeID)
   1089         if drone.stacksize > 1:
   1090             data.label = localization.GetByLabel('UI/Inflight/Drone/DroneBayEntryWithStacksize', drone=drone.typeID, stacksize=drone.stacksize)
   1091         else:
   1092             data.label = evetypes.GetName(drone.typeID)
   1093         data.sublevel = level
   1094         data.customMenu = self.GroupMenu
   1095         data.droneState = droneState
   1096         data.OnDropData = lambda dragObject, nodes: self.DropDronesOnDroneEntry(data, dragObject, nodes)
   1097         return GetFromClass(DroneEntry, data)
   1098 
   1099     def GetSpaceDroneEntry(self, drone, level, droneState):
   1100         data = KeyVal()
   1101         data.itemID = drone.droneID
   1102         data.typeID = drone.typeID
   1103         data.ownerID = drone.ownerID
   1104         data.controllerID = drone.controllerID
   1105         data.controllerOwnerID = drone.controllerOwnerID
   1106         data.displayName = data.label = evetypes.GetName(drone.typeID)
   1107         data.sublevel = level
   1108         data.customMenu = self.GroupMenu
   1109         data.droneState = droneState
   1110         data.OnDropData = lambda dragObject, nodes: self.DropDronesOnDroneEntry(data, dragObject, nodes)
   1111         return GetFromClass(DroneEntry, data)
   1112 
   1113     def DropDronesOnDroneEntry(self, entryData, dragObject, nodes):
   1114         if dragObject.sr.node['__guid__'] not in ('listentry.DroneEntry', 'listentry.DroneMainGroup', 'listentry.DroneSubGroup'):
   1115             return
   1116         newGroupState = entryData.droneState
   1117         dronesWithChangedState = self.LaunchOrPullDrones(newGroupState, nodes)
   1118         droneEntries = [ node for node in nodes if node.__guid__ == 'listentry.DroneEntry' ]
   1119         if not droneEntries:
   1120             return
   1121         group = self.GetDroneGroup(entryData.itemID)
   1122         if group:
   1123             self.MoveDronesToSubGroup(groupName=group['label'], nodes=droneEntries, excludedDrones=dronesWithChangedState)
   1124         else:
   1125             self.NoGroup(droneEntries)
   1126 
   1127     def CheckHint(self):
   1128         if not self.sr.scroll.GetNodes():
   1129             self.sr.scroll.ShowHint(localization.GetByLabel('UI/Inflight/Drone/NoDrones'))
   1130         else:
   1131             self.sr.scroll.ShowHint()
   1132 
   1133     def UpdateGroupSettings(self):
   1134         settings.user.ui.Set(self.settingsName, self.ListifyGroups(self.groups))
   1135         sm.GetService('settings').SaveSettings()
   1136 
   1137     def DroneSettings(self, menuParent):
   1138         self.droneBehaviour = sm.GetService('godma').GetStateManager().GetDroneSettingAttributes()
   1139         self.droneAggressionDefVal = dogma.data.get_attribute_default_value(const.attributeDroneIsAggressive)
   1140         self.droneFFDefVal = dogma.data.get_attribute_default_value(const.attributeDroneFocusFire)
   1141         if const.attributeDroneIsAggressive not in self.droneBehaviour:
   1142             self.droneBehaviour[const.attributeDroneIsAggressive] = settings.char.ui.Get('droneSettingAttributeID ' + str(const.attributeDroneIsAggressive), 0)
   1143         aggressive = settings.char.ui.Get('droneAggression', self.droneAggressionDefVal)
   1144         menuParent.AddRadioButton(text=localization.GetByLabel('UI/Drones/AggressionStatePassive'), checked=not aggressive, callback=(self.AggressiveChange, False))
   1145         menuParent.AddRadioButton(text=localization.GetByLabel('UI/Drones/AggressionStateAggressive'), checked=aggressive, callback=(self.AggressiveChange, True))
   1146         focusFire = settings.char.ui.Get('droneFocusFire', self.droneFFDefVal)
   1147         menuParent.AddCheckBox(text=localization.GetByLabel('UI/Drones/AttackModeFocusFire'), checked=focusFire, callback=(self.FocusFireChange, not focusFire))
   1148 
   1149     def GetMenuMoreOptions(self):
   1150         menuData = super(DroneView, self).GetMenuMoreOptions()
   1151         menuData.AddCheckbox(text=localization.GetByLabel('UI/Drones/WarpWarningOption'), setting=warp_warning_enabled_setting)
   1152         return menuData
   1153 
   1154     def AggressiveChange(self, aggressive, *args):
   1155         self.OnDroneSettingChanged(const.attributeDroneIsAggressive, 'droneAggression', aggressive)
   1156 
   1157     def FocusFireChange(self, focusFire, *args):
   1158         self.OnDroneSettingChanged(const.attributeDroneFocusFire, 'droneFocusFire', focusFire)
   1159 
   1160     def OnDroneSettingChanged(self, attributeID, configName, value):
   1161         settings.char.ui.Set(configName, value)
   1162         sm.GetService('godma').GetStateManager().ChangeDroneSettings({attributeID: value})
   1163 
   1164     def OnClientEvent_WarpStarted(self, *args):
   1165         self.UpdateWarpWarning()
   1166 
   1167     def OnWarpStarted2(self, *args):
   1168         self.UpdateWarpWarning()
   1169 
   1170     def OnClientEvent_WarpFinished(self, *args):
   1171         self.UpdateWarpWarning()
   1172 
   1173     def UpdateWarpWarning(self):
   1174         if not warp_warning_enabled_setting.is_enabled():
   1175             self.warpWarning.Hide()
   1176             return
   1177         michelle = sm.GetService('michelle')
   1178         if michelle.IsPreparingWarp() and michelle.AreDronesOut():
   1179             self.warpWarning.FadeIn()
   1180         else:
   1181             self.warpWarning.FadeOut()
   1182 
   1183 
   1184 def GetDronesInLocalSpace():
   1185     ballpark = sm.GetService('michelle').GetBallpark()
   1186     if ballpark is None:
   1187         return []
   1188     drones = sm.GetService('michelle').GetDrones()
   1189     return [ drones[droneID] for droneID in drones if droneID in ballpark.slimItems and (drones[droneID].ownerID == session.charid or drones[droneID].controllerID == session.shipid) ]
   1190 
   1191 
   1192 def GetDronesInDistantSpace():
   1193     ballpark = sm.GetService('michelle').GetBallpark()
   1194     if ballpark is None:
   1195         return []
   1196     drones = sm.GetService('michelle').GetDrones()
   1197     return [ drones[droneID] for droneID in drones if droneID not in ballpark.slimItems and (drones[droneID].ownerID == session.charid or drones[droneID].controllerID == session.shipid) ]
   1198 
   1199 
   1200 warp_warning_enabled_setting = CharSettingBool('drone_warp_warning_enabled', True)
   1201 
   1202 class WarpWarning(ContainerAutoSize):
   1203     default_alignMode = uiconst.TOTOP
   1204     default_opacity = 0.0
   1205 
   1206     def __init__(self, **kwargs):
   1207         super(WarpWarning, self).__init__(**kwargs)
   1208         self.constructed = False
   1209         self.is_active = False
   1210         self.whiteout = None
   1211 
   1212     def layout(self):
   1213         self.whiteout = Container(parent=self, align=uiconst.TOALL, bgColor=(1.0, 1.0, 1.0), opacity=0.5)
   1214         Frame(bgParent=self, texturePath='res:/UI/Texture/Shared/DarkStyle/panel1Corner_Solid.png', rotation=math.pi, cornerSize=9, color=eveColor.WARNING_ORANGE, opacity=0.15)
   1215         Sprite(name='cornerSprite', parent=self, align=uiconst.TOPLEFT, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/Shared/DarkStyle/cornerTriSmall.png', width=5, height=5, color=eveColor.WARNING_ORANGE, opacity=0.5)
   1216         warning_icon = Sprite(name='warningIcon', parent=self, align=uiconst.TOPLEFT, state=uiconst.UI_DISABLED, width=32, height=32, left=10, top=8, texturePath='res:/UI/Texture/classes/agency/iconExclamation.png', color=eveColor.WARNING_ORANGE)
   1217         animations.FadeTo(warning_icon, startVal=0.5, endVal=1.5, duration=0.7, loops=-1, curveType=uiconst.ANIM_WAVE)
   1218         EveLabelMedium(name='warningLabel', parent=self, align=uiconst.TOTOP, padding=(52, 8, 8, 8), text=localization.GetByLabel('UI/Drones/WarpWarningMessage'), color=eveColor.WARNING_ORANGE)
   1219         button_row_cont = ContainerAutoSize(parent=self, align=uiconst.TOTOP, padding=(52, 0, 8, 8))
   1220         Button(parent=button_row_cont, align=uiconst.TOPLEFT, label=localization.GetByLabel('UI/Drones/StopAndRecall'), func=stop_and_recall_drones, args=())
   1221         self.constructed = True
   1222 
   1223     def FadeIn(self):
   1224         if not self.constructed:
   1225             self.layout()
   1226         if self.is_active:
   1227             return
   1228         self.is_active = True
   1229         sm.GetService('audio').SendUIEvent('ui_warning_abandoning_drones')
   1230         self.Show()
   1231         animations.StopAllAnimations(self)
   1232         animations.FadeIn(self, duration=0.3)
   1233         animations.FadeTo(self.whiteout, startVal=0.0, endVal=1.0, duration=0.3, timeOffset=0.1, curveType=uiconst.ANIM_WAVE)
   1234 
   1235     def FadeOut(self):
   1236         if not self.constructed:
   1237             return
   1238         self.is_active = False
   1239         animations.StopAllAnimations(self.whiteout)
   1240         animations.FadeOut(self.whiteout, duration=0.1)
   1241         animations.FadeOut(self, duration=0.3, callback=self.Hide)
   1242 
   1243 
   1244 def stop_and_recall_drones():
   1245     uicore.cmd.GetCommandAndExecute('CmdStopShip')
   1246     ReturnToDroneBay([ drone.droneID for drone in GetDronesInLocalSpace() ])
   1247 
   1248 
   1249 class DroneMainGroup(Group):
   1250     __guid__ = 'listentry.DroneMainGroup'
   1251     isDragObject = True
   1252 
   1253     def Load(self, node):
   1254         Group.Load(self, node)
   1255         if node.showicon:
   1256             self.sr.icon.state = uiconst.UI_NORMAL
   1257             self.sr.icon.LoadTooltipPanel = LoadFavoriteTooltipPanel
   1258             self.sr.icon.DelegateEvents(self)
   1259 
   1260     def GetDragData(self, *args):
   1261         return [self.sr.node]
   1262 
   1263 
   1264 class DroneMainGroupInSpace(DroneMainGroup):
   1265 
   1266     def Load(self, node):
   1267         super(DroneMainGroupInSpace, self).Load(node)
   1268         utilMenu = UtilMenu(menuAlign=uiconst.TOPRIGHT, parent=self, align=uiconst.CENTERRIGHT, GetUtilMenu=node.GetUtilMenu)
   1269         utilMenu.uniqueUiName = pConst.UNIQUE_NAME_DRONE_SETTINGS
   1270 
   1271 
   1272 class DroneSubGroup(Group):
   1273     __guid__ = 'listentry.DroneSubGroup'
   1274     isDragObject = True
   1275 
   1276     def Startup(self, *args):
   1277         Group.Startup(self, args)
   1278         if self.sr.fill:
   1279             self.sr.fill.opacity = 0.9 * self.sr.fill.opacity
   1280 
   1281     def Load(self, node):
   1282         Group.Load(self, node)
   1283         if node.showicon:
   1284             self.sr.icon.state = uiconst.UI_NORMAL
   1285             self.sr.icon.LoadTooltipPanel = LoadFavoriteTooltipPanel
   1286             self.sr.icon.DelegateEvents(self)
   1287 
   1288     def GetDragData(self, *args):
   1289         return [self.sr.node]
   1290 
   1291 
   1292 def LoadFavoriteTooltipPanel(tooltipPanel, *args):
   1293     tooltipPanel.LoadGeneric2ColumnTemplate()
   1294     command = uicore.cmd.commandMap.GetCommandByName('CmdLaunchFavoriteDrones')
   1295     detailedDescription = command.GetDetailedDescription()
   1296     if detailedDescription:
   1297         tooltipPanel.AddLabelMedium(text=detailedDescription, align=uiconst.TOPLEFT, wrapWidth=200, colSpan=tooltipPanel.columns)
   1298     label = command.GetName()
   1299     shortcutStr = command.GetShortcutAsString()
   1300     labelObj, shortcutObj = tooltipPanel.AddLabelShortcut(label, shortcutStr)
   1301     labelObj.top = shortcutObj.top = 6
   1302 
   1303 
   1304 def GetTypeIDForManyDrones(droneState, droneData):
   1305     if not droneData:
   1306         return None
   1307     if droneState == 'inbay':
   1308         firstDroneData = droneData[0]
   1309         invItem, viewOnly, voucher = firstDroneData
   1310         return invItem.typeID
   1311     if droneState in ('inlocalspace', 'indistantspace'):
   1312         lowPriorityDrones = [const.groupMiningDrone, const.groupSalvageDrone, const.groupUnanchoringDrone]
   1313 
   1314         def IsHigherPrioritySpaceDrone(droneData):
   1315             droneID, typeID, ownerID, locationID = droneData
   1316             if evetypes.GetGroupID(typeID) in lowPriorityDrones:
   1317                 return False
   1318             return True
   1319 
   1320         priorityDrone = itertoolsext.first_or_default(droneData, predicate=IsHigherPrioritySpaceDrone, default=droneData[0])
   1321         droneSlimItem = sm.GetService('michelle').GetItem(priorityDrone[0])
   1322         if droneSlimItem:
   1323             return droneSlimItem.typeID
   1324 
   1325 
   1326 def DropDronesInSpace(dragObj, nodes):
   1327     if dragObj.sr.node.droneState != 'inbay':
   1328         return
   1329     droneWnd = DroneView.GetIfOpen()
   1330     if droneWnd is None:
   1331         return
   1332     if dragObj.__guid__ in ('listentry.DroneSubGroup', 'listentry.DroneMainGroup'):
   1333         if dragObj.__guid__ == 'listentry.DroneSubGroup':
   1334             nodes = droneWnd.GetSubGroupContent(dragObj.sr.node)
   1335         else:
   1336             nodes = droneWnd.GetAllMainGroupContent(dragObj.sr.node)
   1337     drones = [ node.invItem for node in nodes ]
   1338     GetMenuService().LaunchDrones(drones)