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)