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

tactical.py (37500B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\parklife\tactical.py
      2 from __future__ import print_function
      3 import sys
      4 from collections import defaultdict
      5 import blue
      6 import log
      7 import telemetry
      8 import dogma.effects
      9 import evecamera
     10 import evetypes
     11 import localization
     12 import uthread
     13 from caching.memoize import Memoize
     14 from carbon.common.script.sys.service import Service
     15 from carbon.common.script.sys.serviceConst import SERVICE_RUNNING
     16 from carbonui import uiconst
     17 from carbonui.control.window import Window
     18 from carbonui.uicore import uicore
     19 from carbonui.util.bunch import Bunch
     20 from dogma.const import falloffEffectivnessModuleGroups
     21 from eve.client.script.parklife import states as state
     22 from eve.client.script.parklife import tacticalOverlay
     23 from eve.client.script.ui.inflight.activeitem import ActiveItem
     24 from eve.client.script.ui.inflight.drone import DroneView
     25 from eve.client.script.ui.inflight.drones.dronesWindow import DronesWindow
     26 from eve.client.script.ui.inflight.overview.overviewWindow import OverviewWindow
     27 from eve.client.script.ui.inflight.overview.overviewWindowUtil import OpenOverview
     28 from eve.client.script.ui.inflight.selectedItemWnd import SelectedItemWnd
     29 from eve.client.script.ui.shared.export import ExportOverviewWindow, ImportOverviewWindow
     30 from eve.client.script.ui.util import uix
     31 from eve.client.script.ui.view.viewStateConst import ViewState
     32 from eve.common.lib import appConst as const
     33 from eve.common.script.sys import eveCfg, idCheckers
     34 from eveDrones.droneDamageTracker import InBayDroneDamageTracker
     35 from inventorycommon.const import categoryEntity
     36 from overviewPresets import overviewSettingsConst
     37 BRACKETBORDER = 17
     38 OVERVIEW_CONFIGNAME = 0
     39 OVERVIEW_GROUPDATA = 1
     40 BRACKETS_CONFIGNAME = 2
     41 BRACKETS_GROUPDATA = 3
     42 INVISIBLE_TYPES = [const.typeUnlitModularEffectBeacon]
     43 WARP_SCRAMBLE_MESSAGE_ATTACKER = 'OnWarpScrambleAttacker'
     44 WARP_SCRAMBLE_MESSAGE_DEFENDER = 'OnWarpScrambleDefender'
     45 WARP_SCRAMBLE_MESSAGE_OTHER = 'OnWarpScrambleOther'
     46 WARP_DISRUPT_MESSAGE_ATTACKER = 'OnWarpDisruptAttacker'
     47 WARP_DISRUPT_MESSAGE_DEFENDER = 'OnWarpDisruptDefender'
     48 WARP_DISRUPT_MESSAGE_OTHER = 'OnWarpDisruptOther'
     49 TACTICAL_OVERLAY_DISALLOWED_CAMERAS = (evecamera.CAM_SHIPPOV, evecamera.CAM_SHIPORBIT_RESTRICTED, evecamera.CAM_JUMP)
     50 
     51 @Memoize
     52 def GetCacheByLabel(key):
     53     return localization.GetByLabel(key)
     54 
     55 
     56 def _SendWarpScrambleMessages(shipID, targetID):
     57     _SendWarpMessages(shipID, targetID, WARP_SCRAMBLE_MESSAGE_ATTACKER, WARP_SCRAMBLE_MESSAGE_DEFENDER, WARP_SCRAMBLE_MESSAGE_OTHER)
     58 
     59 
     60 def _SendWarpDisruptionMessages(shipID, targetID):
     61     _SendWarpMessages(shipID, targetID, WARP_DISRUPT_MESSAGE_ATTACKER, WARP_DISRUPT_MESSAGE_DEFENDER, WARP_DISRUPT_MESSAGE_OTHER)
     62 
     63 
     64 def _SendWarpMessages(shipID, targetID, attackerMessageID, defenderMessageID, otherMessageID):
     65     attackerName = sm.GetService('bracket').GetBracketName2(shipID)
     66     defenderName = sm.GetService('bracket').GetBracketName2(targetID)
     67     if attackerName and defenderName:
     68         if session.shipid == targetID:
     69             sm.GetService('logger').AddCombatMessage(defenderMessageID, {'attacker': attackerName})
     70         elif session.shipid == shipID:
     71             sm.GetService('logger').AddCombatMessage(attackerMessageID, {'defender': defenderName})
     72         else:
     73             sm.GetService('logger').AddCombatMessage(otherMessageID, {'attacker': attackerName,
     74              'defender': defenderName})
     75 
     76 
     77 class TacticalSvc(Service):
     78     __guid__ = 'svc.tactical'
     79     __update_on_reload__ = 0
     80     __notifyevents__ = ['DoBallsAdded',
     81      'DoBallRemove',
     82      'OnOverviewPresetLoaded',
     83      'OnStateChange',
     84      'OnStateSetupChange',
     85      'ProcessSessionChange',
     86      'OnSessionChanged',
     87      'OnSessionReset',
     88      'OnClientEvent_JumpStarted',
     89      'OnClientEvent_JumpExecuted',
     90      'OnSpecialFX',
     91      'ProcessOnUIAllianceRelationshipChanged',
     92      'OnSetCorpStanding',
     93      'OnSetAllianceStanding',
     94      'OnSuspectsAndCriminalsUpdate',
     95      'OnDisapprovalUpdate',
     96      'OnSlimItemChange',
     97      'OnDroneStateChange2',
     98      'OnDroneControlLost',
     99      'OnItemChange',
    100      'OnBallparkCall',
    101      'OnEwarStart',
    102      'OnEwarEnd',
    103      'OnEwarOnConnect',
    104      'OnContactChange',
    105      'OnCrimewatchEngagementUpdated',
    106      'DoBallsRemove',
    107      'OnHideUI',
    108      'OnShowUI',
    109      'OnCombatMessage',
    110      'OnOverviewTabsChanged']
    111     __startupdependencies__ = ['settings', 'tacticalNavigation', 'viewState']
    112     __dependencies__ = ['clientDogmaStaticSvc',
    113      'stateSvc',
    114      'bracket',
    115      'overviewPresetSvc',
    116      'godma']
    117 
    118     def __init__(self):
    119         Service.__init__(self)
    120         self._clearModuleTasklet = None
    121 
    122     def Run(self, *etc):
    123         Service.Run(self, *etc)
    124         self.tacticalOverlay = tacticalOverlay.TacticalOverlay(self, self.tacticalNavigation)
    125         self.logme = 0
    126         self.jammers = {}
    127         self.jammersByJammingType = {}
    128         self.filterFuncs = None
    129         self.CleanUp()
    130         self.Setup()
    131         self.hideUI = False
    132         self.flagsAreDirty = False
    133         self.dirtyFlags = set()
    134         self.flagCheckingThread = uthread.new(self.FlagsDirtyCheckingLoop)
    135         self.inBayDroneDamageTracker = None
    136 
    137     def Setup(self):
    138         self.CleanUp()
    139         self.AssureSetup()
    140         if eveCfg.InSpace():
    141             if self.IsTacticalOverlayActive():
    142                 self.tacticalOverlay.Initialize()
    143             self.Open()
    144 
    145     def Stop(self, *etc):
    146         Service.Stop(self, *etc)
    147         self.CleanUp()
    148 
    149     def OnCombatMessage(self, messageName, messageArgs):
    150         if 'specialObject' in messageArgs:
    151             objectName = sm.GetService('bracket').GetBracketName2(messageArgs['specialObject'])
    152             messageArgs['specialObject'] = objectName
    153         sm.GetService('logger').AddCombatMessage(messageName, messageArgs)
    154 
    155     @telemetry.ZONE_METHOD
    156     def OnBallparkCall(self, eventName, argTuple):
    157         if self.sr is None:
    158             return
    159         if eventName == 'SetBallInteractive' and argTuple[1] == 1:
    160             bp = sm.GetService('michelle').GetBallpark()
    161             if not bp:
    162                 return
    163             slimItem = bp.GetInvItem(argTuple[0])
    164             if not slimItem:
    165                 return
    166             self.MarkFlagsForSpecificItemsAsDirty({slimItem.itemID})
    167 
    168     @telemetry.ZONE_METHOD
    169     def OnItemChange(self, item, change, location):
    170         if (const.ixFlag in change or const.ixLocationID in change) and item.flagID == const.flagDroneBay:
    171             droneview = self.GetPanel('droneview')
    172             if droneview:
    173                 droneview.CheckReloadDronesScroll()
    174             else:
    175                 self.CheckInitDrones()
    176 
    177     @telemetry.ZONE_METHOD
    178     def ProcessSessionChange(self, isRemote, session, change):
    179         doResetJammers = False
    180         if self.logme:
    181             self.LogInfo('Tactical::ProcessSessionChange', isRemote, session, change)
    182         if 'stationid' in change:
    183             doResetJammers = True
    184         if 'solarsystemid' in change:
    185             self.TearDownOverlay()
    186             doResetJammers = True
    187         if 'shipid' in change:
    188             for itemID in self.attackers:
    189                 self.stateSvc.SetState(itemID, state.threatAttackingMe, 0)
    190 
    191             self.attackers = {}
    192             doResetJammers = True
    193             droneview = self.GetPanel('droneview')
    194             if droneview:
    195                 if getattr(self, '_initingDrones', False):
    196                     self.LogInfo('Tactical: ProcessSessionChange: busy initing drones, cannot close the window')
    197                 else:
    198                     droneview.Close()
    199         if doResetJammers:
    200             self.ResetJammers()
    201 
    202     def ResetJammers(self):
    203         self.jammers = {}
    204         self.jammersByJammingType = {}
    205         sm.ScatterEvent('OnRefreshBuffBar')
    206 
    207     def RemoveBallFromJammers(self, ball, *args):
    208         ballID = ball.id
    209         effectsFromBall = self.jammers.get(ballID)
    210         if getattr(self, 'fighterService', None) is None:
    211             setattr(self, 'fighterService', sm.GetService('fighters'))
    212         self.fighterService.shipFighterState.KillEffectsFromBall(ballID)
    213         if effectsFromBall is None:
    214             return
    215         doUpdate = False
    216         for effectName, effectSet in self.jammersByJammingType.iteritems():
    217             if effectName not in effectsFromBall:
    218                 continue
    219             tuplesToRemove = set()
    220             for effectTuple in effectSet:
    221                 effectBallID, moduleID = effectTuple
    222                 if effectBallID == ballID:
    223                     tuplesToRemove.add(effectTuple)
    224 
    225             if tuplesToRemove:
    226                 effectSet.difference_update(tuplesToRemove)
    227                 doUpdate = True
    228 
    229         self.jammers.pop(ballID, None)
    230         if doUpdate:
    231             sm.ScatterEvent('OnEwarEndFromTactical')
    232 
    233     def OnSessionChanged(self, isRemote, session, change):
    234         if eveCfg.InSpace():
    235             if sm.GetService('subway').InJump():
    236                 return
    237             self.AssureSetup()
    238             self.Open()
    239             if self.IsTacticalOverlayActive() and not self.hideUI:
    240                 self.tacticalOverlay.Initialize()
    241             self.CheckInitDrones()
    242             self.MarkFlagsAsDirty()
    243             if 'shipid' in change:
    244                 self.tacticalOverlay.OnShipChange()
    245         else:
    246             self.CleanUp()
    247 
    248     def OnSessionReset(self):
    249         self.inBayDroneDamageTracker = None
    250 
    251     def OnClientEvent_JumpStarted(self, *args):
    252         if self.IsTacticalOverlayActive():
    253             self.TearDownOverlay()
    254             self.ResetJammers()
    255 
    256     def OnClientEvent_JumpExecuted(self, itemID):
    257         self.AssureSetup()
    258         self.Open()
    259         if self.IsTacticalOverlayActive() and not self.hideUI:
    260             self.tacticalOverlay.Initialize()
    261         self.CheckInitDrones()
    262         self.MarkFlagsAsDirty()
    263 
    264     @telemetry.ZONE_METHOD
    265     def OnSlimItemChange(self, oldSlim, newSlim):
    266         if not eveCfg.InSpace():
    267             return
    268         update = 0
    269         if getattr(newSlim, 'allianceID', None) and newSlim.allianceID != getattr(oldSlim, 'allianceID', None):
    270             update = 1
    271         elif newSlim.corpID and newSlim.corpID != oldSlim.corpID:
    272             update = 2
    273         elif newSlim.charID != oldSlim.charID:
    274             update = 3
    275         elif newSlim.ownerID != oldSlim.ownerID:
    276             update = 4
    277         elif getattr(newSlim, 'lootRights', None) != getattr(oldSlim, 'lootRights', None):
    278             update = 5
    279         elif getattr(newSlim, 'isEmpty', None) != getattr(oldSlim, 'isEmpty', None):
    280             update = 6
    281         if update:
    282             if session.charid in (newSlim.charID, oldSlim.charID):
    283                 self.MarkFlagsAsDirty()
    284             else:
    285                 self.MarkFlagsForSpecificItemsAsDirty({oldSlim.itemID, newSlim.itemID})
    286 
    287     def ProcessOnUIAllianceRelationshipChanged(self, *args):
    288         if self.InSpace():
    289             self.MarkFlagsAsDirty()
    290 
    291     def OnContactChange(self, contactIDs, contactType = None):
    292         if eveCfg.InSpace():
    293             self.MarkFlagsAsDirty()
    294 
    295     def OnSetCorpStanding(self, *args):
    296         if eveCfg.InSpace():
    297             self.MarkFlagsAsDirty()
    298 
    299     def OnSetAllianceStanding(self, *args):
    300         if eveCfg.InSpace():
    301             self.MarkFlagsAsDirty()
    302 
    303     def OnCrimewatchEngagementUpdated(self, otherCharId, timeout):
    304         if eveCfg.InSpace():
    305             uthread.new(self.DelayedFlagStateUpdate)
    306 
    307     def OnSuspectsAndCriminalsUpdate(self, criminalizedCharIDs, decriminalizedCharIDs):
    308         if eveCfg.InSpace():
    309             uthread.new(self.DelayedFlagStateUpdate)
    310 
    311     def OnDisapprovalUpdate(self, newCharID, removedCharIDs):
    312         if eveCfg.InSpace():
    313             uthread.new(self.DelayedFlagStateUpdate)
    314 
    315     def DelayedFlagStateUpdate(self):
    316         if getattr(self, 'delayedFlagStateUpdate', False):
    317             return
    318         setattr(self, 'delayedFlagStateUpdate', True)
    319         blue.pyos.synchro.SleepWallclock(1000)
    320         self.MarkFlagsAsDirty()
    321         setattr(self, 'delayedFlagStateUpdate', False)
    322 
    323     def OnHideUI(self):
    324         self.hideUI = True
    325         if self.IsTacticalOverlayActive():
    326             self.tacticalOverlay.TearDown()
    327 
    328     def OnShowUI(self):
    329         self.hideUI = False
    330         if eveCfg.InSpace() and self.IsTacticalOverlayActive():
    331             self.tacticalOverlay.Initialize()
    332 
    333     @telemetry.ZONE_METHOD
    334     def OnSpecialFX(self, shipID, moduleID, moduleTypeID, targetID, otherTypeID, guid, isOffensive, start, active, duration = -1, repeat = None, startTime = None, timeFromStart = 0, graphicInfo = None):
    335         if targetID == session.shipid and isOffensive:
    336             attackerID = shipID
    337             attackTime = startTime
    338             attackRepeat = repeat
    339             shipItem = sm.StartService('michelle').GetItem(shipID)
    340             if shipItem and shipItem.categoryID == const.categoryStarbase:
    341                 attackerID = moduleID
    342                 attackTime = 0
    343                 attackRepeat = 0
    344             data = self.attackers.get(attackerID, [])
    345             key = (moduleID,
    346              guid,
    347              attackTime,
    348              duration,
    349              attackRepeat)
    350             if start and shipID != session.shipid:
    351                 if key not in data:
    352                     data.append(key)
    353                 self.stateSvc.SetState(attackerID, state.threatAttackingMe, 1)
    354             else:
    355                 toRemove = None
    356                 for signature in data:
    357                     if signature[0] == key[0] and signature[1] == key[1] and signature[2] == key[2] and signature[3] == key[3]:
    358                         toRemove = signature
    359                         break
    360 
    361                 if toRemove is not None:
    362                     data.remove(toRemove)
    363                 if not data:
    364                     self.stateSvc.SetState(attackerID, state.threatAttackingMe, 0)
    365             self.attackers[attackerID] = data
    366         if start:
    367             if settings.user.ui.Get('notifyMessagesEnabled', 1) or session.shipid in (shipID, targetID):
    368                 if guid == 'effects.WarpDisrupt':
    369                     _SendWarpDisruptionMessages(shipID, targetID)
    370                 elif guid == 'effects.WarpScramble':
    371                     _SendWarpScrambleMessages(shipID, targetID)
    372 
    373     def CheckInitDrones(self):
    374         mySlim = uix.GetBallparkRecord(session.shipid)
    375         if not mySlim:
    376             return
    377         if mySlim.groupID == const.groupCapsule:
    378             return
    379         dronesInBay = sm.GetService('invCache').GetInventoryFromId(session.shipid).ListDroneBay()
    380         if dronesInBay:
    381             self.InitDrones()
    382         else:
    383             myDrones = sm.GetService('michelle').GetDrones()
    384             if myDrones:
    385                 self.InitDrones()
    386 
    387     @telemetry.ZONE_METHOD
    388     def Open(self):
    389         self.InitSelectedItem()
    390         OpenOverview()
    391         self.CheckInitDrones()
    392 
    393     def GetMain(self):
    394         if self and getattr(self.sr, 'mainParent', None):
    395             return self.sr.mainParent
    396 
    397     def OnStateChange(self, itemID, flag, true, *args):
    398         uthread.new(self._OnStateChange, itemID, flag, true, *args)
    399 
    400     def _OnStateChange(self, itemID, flag, true, *args):
    401         if not eveCfg.InSpace():
    402             return
    403         if not self or getattr(self, 'sr', None) is None:
    404             return
    405         if self.logme:
    406             self.LogInfo('Tactical::OnStateChange', itemID, flag, true, *args)
    407         self.tacticalOverlay.CheckState(itemID, flag, true)
    408 
    409     def OnOverviewPresetLoaded(self, label, set):
    410         uthread.new(self.InitConnectors).context = 'tactical::OnOverviewPresetLoaded-->InitConnectors'
    411 
    412     def OnStateSetupChange(self, what):
    413         self.MarkFlagsAsDirty()
    414         self.InitConnectors()
    415 
    416     def Toggle(self):
    417         pass
    418 
    419     def BlinkHeader(self, key):
    420         if not self or self.sr is None:
    421             return
    422         panel = getattr(self.sr, key.lower(), None)
    423         if panel:
    424             panel.Blink()
    425 
    426     def IsExpanded(self, key):
    427         panel = getattr(self.sr, key.lower(), None)
    428         if panel:
    429             return panel.sr.main.state == uiconst.UI_PICKCHILDREN
    430 
    431     def AssureSetup(self):
    432         if self.logme:
    433             self.LogInfo('Tactical::AssureSetup')
    434         if getattr(self, 'setupAssured', None):
    435             return
    436         if getattr(self, 'sr', None) is None:
    437             self.sr = Bunch()
    438         self.setupAssured = 1
    439 
    440     def CleanUp(self):
    441         if self.logme:
    442             self.LogInfo('Tactical::CleanUp')
    443         self.sr = None
    444         self.targetingRanges = None
    445         self.toggling = 0
    446         self.setupAssured = 0
    447         self.lastFactor = None
    448         self.groupList = None
    449         self.groupIDs = []
    450         self.intersections = []
    451         self.threats = {}
    452         self.attackers = {}
    453         self.TearDownOverlay()
    454         uicore.layer.tactical.Flush()
    455         self.dronesInited = 0
    456         self.busy = 0
    457 
    458     def GetFilterFuncs(self):
    459         if self.filterFuncs is None:
    460             stateSvc = sm.GetService('stateSvc')
    461             self.filterFuncs = {'Criminal': stateSvc.CheckCriminal,
    462              'Suspect': stateSvc.CheckSuspect,
    463              'Outlaw': stateSvc.CheckOutlaw,
    464              'Dangerous': stateSvc.CheckDangerous,
    465              'StandingHigh': stateSvc.CheckStandingHigh,
    466              'StandingGood': stateSvc.CheckStandingGood,
    467              'StandingNeutral': stateSvc.CheckStandingNeutral,
    468              'StandingBad': stateSvc.CheckStandingBad,
    469              'StandingHorrible': stateSvc.CheckStandingHorrible,
    470              'NoStanding': stateSvc.CheckNoStanding,
    471              'SameFleet': stateSvc.CheckSameFleet,
    472              'SamePlayerCorp': stateSvc.CheckSamePlayerCorp,
    473              'SameNpcCorp': stateSvc.CheckSameNpcCorp,
    474              'SameAlliance': stateSvc.CheckSameAlliance,
    475              'SameMilitia': stateSvc.CheckSameMilitia,
    476              'AtWarCanFight': stateSvc.CheckAtWarCanFight,
    477              'AtWarMilitia': stateSvc.CheckAtWarMilitia,
    478              'IsWanted': stateSvc.CheckIsWanted,
    479              'HasKillRight': stateSvc.CheckHasKillRight,
    480              'WreckViewed': stateSvc.CheckWreckViewed,
    481              'WreckEmpty': stateSvc.CheckWreckEmpty,
    482              'LimitedEngagement': stateSvc.CheckLimitedEngagement,
    483              'AlliesAtWar': stateSvc.CheckAlliesAtWar,
    484              'AgentInteractable': stateSvc.CheckAgentInteractable}
    485         return self.filterFuncs
    486 
    487     def CheckFiltered(self, slimItem, filtered, alwaysShow):
    488         stateSvc = sm.GetService('stateSvc')
    489         if len(filtered) + len(alwaysShow) > 3:
    490             ownerID = slimItem.ownerID
    491             if ownerID is None or ownerID == const.ownerSystem or idCheckers.IsNPC(ownerID):
    492                 checkArgs = (slimItem, None)
    493             else:
    494                 checkArgs = (slimItem, stateSvc._GetRelationship(slimItem))
    495         else:
    496             checkArgs = (slimItem,)
    497         functionDict = self.GetFilterFuncs()
    498         for functionName in alwaysShow:
    499             f = functionDict.get(functionName, None)
    500             if f is None:
    501                 self.LogError('CheckFiltered got bad functionName: %r' % functionName)
    502                 continue
    503             if f(*checkArgs):
    504                 return False
    505 
    506         for functionName in filtered:
    507             f = functionDict.get(functionName, None)
    508             if f is None:
    509                 self.LogError('CheckFiltered got bad functionName: %r' % functionName)
    510                 continue
    511             if f(*checkArgs):
    512                 return True
    513 
    514         return False
    515 
    516     def UpdateStates(self, slimItem, uiwindow):
    517         print('Deprecated, TacticalSvc.UpdateStates, call UpdateFlagAndBackground on the uiwindow instead')
    518 
    519     def UpdateBackground(self, slimItem, uiwindow):
    520         print('Deprecated, TacticalSvc.UpdateBackground, call on the uiwindow instead')
    521 
    522     def UpdateIcon(self, slimItem, uiwindow):
    523         print('Deprecated, TacticalSvc.UpdateIcon, call UpdateIconColor on the uiwindow instead')
    524 
    525     def UpdateFlag(self, slimItem, uiwindow):
    526         print('Deprecated, TacticalSvc.UpdateFlag, call on the uiwindow instead')
    527 
    528     def GetFlagUI(self, parent):
    529         print('Deprecated, TacticalSvc.GetFlagUI, make the icon yourself')
    530 
    531     def UpdateFlagPositions(self, uiwindow, icon = None):
    532         print('Deprecated, TacticalSvc.UpdateFlagPositions, call on the uiwindow instead')
    533 
    534     def MarkFlagsAsDirty(self):
    535         self.dirtyFlags.clear()
    536         self.flagsAreDirty = True
    537 
    538     def MarkFlagsForSpecificItemsAsDirty(self, itemIDs):
    539         self.dirtyFlags.update(itemIDs)
    540 
    541     @telemetry.ZONE_METHOD
    542     def FlagsDirtyCheckingLoop(self):
    543         while self.state == SERVICE_RUNNING:
    544             try:
    545                 if self.flagsAreDirty:
    546                     self.flagsAreDirty = False
    547                     self.InvalidateFlags()
    548                 elif self.dirtyFlags:
    549                     itemIDs = self.dirtyFlags
    550                     self.dirtyFlags = set()
    551                     self.InvalidateFlagsForSpecificItems(itemIDs)
    552             except Exception:
    553                 log.LogException(extraText='Error invalidating tactical flags')
    554                 sys.exc_clear()
    555 
    556             blue.pyos.synchro.SleepWallclock(500)
    557 
    558     def InvalidateFlags(self):
    559         if not eveCfg.InSpace():
    560             return
    561         sm.ScatterEvent('OnFlagsInvalidated', None)
    562         sm.GetService('bracket').RenewFlags()
    563 
    564     def InvalidateFlagsForSpecificItems(self, itemIDs):
    565         if not eveCfg.InSpace():
    566             return
    567         sm.ScatterEvent('OnFlagsInvalidated', itemIDs)
    568         sm.GetService('bracket').RenewFlagForItemIDs(itemIDs)
    569 
    570     def InvalidateFlagsExtraLimited(self, charID):
    571         if eveCfg.InSpace():
    572             sm.GetService('bracket').RenewSingleFlagForCharID(charID)
    573 
    574     def GetFilteredStatesFunctionNames(self, isBracket = False, presetName = None):
    575         return [ self.stateSvc.GetStateProps(flag).label for flag in self.overviewPresetSvc.GetFilteredStates(isBracket=isBracket, presetName=presetName) ]
    576 
    577     def GetAlwaysShownStatesFunctionNames(self, isBracket = False, presetName = None):
    578         return [ self.stateSvc.GetStateProps(flag).label for flag in self.overviewPresetSvc.GetAlwaysShownStates(isBracket=isBracket, presetName=presetName) ]
    579 
    580     def Get(self, what, default):
    581         if self.logme:
    582             self.LogInfo('Tactical::Get', what, default)
    583         return getattr(self, what, default)
    584 
    585     def OpenSettings(self, *args):
    586         uicore.cmd.OpenOverviewSettings()
    587 
    588     def ToggleOnOff(self):
    589         current = self.IsTacticalOverlayActive()
    590         if not current:
    591             self.ShowTacticalOverlay()
    592         elif self.tacticalOverlay.IsInitialized():
    593             self.HideTacticalOverlay()
    594 
    595     def HideTacticalOverlay(self):
    596         self._SetTacticalOverlayActive(False)
    597         self.TearDownOverlay()
    598         sm.ScatterEvent('OnTacticalOverlayChange')
    599 
    600     def _SetTacticalOverlayActive(self, isActive):
    601         cam = sm.GetService('sceneManager').GetActiveSpaceCamera()
    602         if cam.cameraID == evecamera.CAM_TACTICAL:
    603             settings.user.overview.Set('viewTactical_camTactical', isActive)
    604         elif cam.cameraID in (evecamera.CAM_SHIPORBIT, evecamera.CAM_SHIPORBIT_ABYSSAL_SPACE, evecamera.CAM_SHIPORBIT_HAZARD):
    605             settings.user.overview.Set('viewTactical', isActive)
    606 
    607     def ShowTacticalOverlay(self):
    608         if not self.IsTacticalOverlayAllowed():
    609             return
    610         self._SetTacticalOverlayActive(True)
    611         if not self.hideUI:
    612             self.tacticalOverlay.Initialize()
    613         sm.ScatterEvent('OnTacticalOverlayChange')
    614 
    615     def IsTacticalOverlayAllowed(self):
    616         cam = sm.GetService('sceneManager').GetActiveSpaceCamera()
    617         return cam.cameraID not in TACTICAL_OVERLAY_DISALLOWED_CAMERAS
    618 
    619     def IsTacticalOverlayActive(self):
    620         cameraID = self.viewState.GetView(ViewState.Space).GetRegisteredCameraID()
    621         if cameraID == evecamera.CAM_TACTICAL:
    622             return settings.user.overview.Get('viewTactical_camTactical', True)
    623         elif cameraID in TACTICAL_OVERLAY_DISALLOWED_CAMERAS:
    624             return False
    625         else:
    626             return settings.user.overview.Get('viewTactical', False)
    627 
    628     def CheckInit(self):
    629         if eveCfg.InSpace() and self.IsTacticalOverlayActive() and not self.hideUI:
    630             self.tacticalOverlay.Initialize()
    631 
    632     def TearDownOverlay(self):
    633         if self.targetingRanges:
    634             self.targetingRanges.KillTimer()
    635         self.tacticalOverlay.TearDown()
    636 
    637     def GetOverlay(self):
    638         return self.tacticalOverlay
    639 
    640     def ShowModuleRange(self, module, charge = None):
    641         if self._clearModuleTasklet is not None:
    642             self._clearModuleTasklet.kill()
    643         self._clearModuleTasklet = None
    644         self.tacticalOverlay.UpdateModuleRange(module, charge)
    645 
    646     def ClearModuleRange(self):
    647 
    648         def _task():
    649             blue.synchro.SleepSim(500)
    650             if self._clearModuleTasklet is not None:
    651                 self._clearModuleTasklet = None
    652                 self.tacticalOverlay.UpdateModuleRange()
    653 
    654         self._clearModuleTasklet = uthread.new(_task)
    655 
    656     def FindMaxRange(self, module, charge, dogmaLocation = None, *args):
    657         maxRange = 0
    658         falloffDist = 0
    659         bombRadius = 0
    660         cynoRadius = 0
    661         if not dogmaLocation:
    662             dogmaLocation = sm.GetService('clientDogmaIM').GetDogmaLocation()
    663         try:
    664             effectID = self.clientDogmaStaticSvc.GetDefaultEffect(module.typeID)
    665         except KeyError:
    666             pass
    667         else:
    668             effect = self.clientDogmaStaticSvc.GetEffect(effectID)
    669             if effect.rangeAttributeID is not None:
    670                 maxRange = dogmaLocation.GetAccurateAttributeValue(module.itemID, effect.rangeAttributeID)
    671                 if module.groupID in falloffEffectivnessModuleGroups:
    672                     falloffDist = dogmaLocation.GetAccurateAttributeValue(module.itemID, const.attributeFalloffEffectiveness)
    673                 else:
    674                     falloffDist = dogmaLocation.GetAccurateAttributeValue(module.itemID, const.attributeFalloff)
    675 
    676         if module.groupID == const.groupCynosuralField:
    677             isCovert = dogmaLocation.GetAttributeValue(module.itemID, const.attributeIsCovert)
    678             beaconRadius = 0.0
    679             if isCovert:
    680                 beaconRadius = evetypes.GetRadius(const.typeCovertCynosuralFieldI)
    681             else:
    682                 beaconRadius = evetypes.GetRadius(const.typeCynosuralFieldI)
    683             cynoRadius = dogmaLocation.GetAccurateAttributeValue(module.itemID, const.attributeCynosuralFieldSpawnRadius)
    684             cynoRadius += 2.0 * beaconRadius
    685         excludedChargeGroups = [const.groupScannerProbe, const.groupSurveyProbe]
    686         if not maxRange and charge and charge.groupID not in excludedChargeGroups:
    687             flightTime = dogmaLocation.GetAccurateAttributeValue(charge.itemID, const.attributeExplosionDelay)
    688             velocity = dogmaLocation.GetAccurateAttributeValue(charge.itemID, const.attributeMaxVelocity)
    689             bombRadius = dogmaLocation.GetAccurateAttributeValue(charge.itemID, const.attributeEmpFieldRange)
    690             maxRange = flightTime * velocity / 1000.0
    691             if dogmaLocation.dogmaStaticMgr.TypeHasAttribute(charge.typeID, const.attributeMaxFOFTargetRange):
    692                 maxFofRange = dogmaLocation.GetAccurateAttributeValue(charge.itemID, dogma.const.attributeMaxFOFTargetRange)
    693                 maxRange = min(maxRange, maxFofRange)
    694         return (maxRange,
    695          falloffDist,
    696          bombRadius,
    697          cynoRadius)
    698 
    699     def GetPanelForUpdate(self, what):
    700         panel = self.GetPanel(what)
    701         if panel and not panel.IsCollapsed() and not panel.IsMinimized():
    702             return panel
    703 
    704     def GetPanel(self, what):
    705         wnd = Window.GetIfOpen(what)
    706         if wnd and not wnd.destroyed:
    707             return wnd
    708 
    709     def InitDrones(self):
    710         if getattr(self, '_initingDrones', False):
    711             return
    712         self._initingDrones = True
    713         try:
    714             if not DronesWindow.GetIfOpen():
    715                 DronesWindow.OpenBehindFullscreenViews(showActions=False, panelName=localization.GetByLabel('UI/Drones/Drones'))
    716         finally:
    717             self._initingDrones = False
    718 
    719     def OnOverviewTabsChanged(self):
    720         windowInstanceIDs = self.overviewPresetSvc.GetWindowInstanceIDs()
    721         self._CloseOverviewWindowsWithNoTabs(windowInstanceIDs)
    722         OpenOverview()
    723 
    724     def _CloseOverviewWindowsWithNoTabs(self, windowInstanceIDs):
    725         openWindows = uicore.registry.GetWindowsByClass(OverviewWindow)
    726         for wnd in openWindows:
    727             if wnd.windowInstanceID not in windowInstanceIDs:
    728                 wnd.Close()
    729 
    730     def InitSelectedItem(self):
    731         if not SelectedItemWnd.GetIfOpen():
    732             SelectedItemWnd.OpenBehindFullscreenViews(panelname=localization.GetByLabel('UI/Inflight/ActiveItem/SelectedItem'))
    733 
    734     def InitConnectors(self):
    735         if self.logme:
    736             self.LogInfo('Tactical::InitConnectors')
    737         if not self.tacticalOverlay.IsInitialized():
    738             return
    739         self.tacticalOverlay.InitConnectors()
    740 
    741     def WantIt(self, slimItem, filtered = None, alwaysShown = None, isBracket = False):
    742         if not slimItem:
    743             return False
    744         if slimItem.typeID in INVISIBLE_TYPES:
    745             return False
    746         overviewOverride = self.overviewPresetSvc.GetOverrideState(slimItem)
    747         if overviewOverride is not None:
    748             return overviewOverride
    749         if isBracket and self.overviewPresetSvc.GetActiveBracketPresetName() in (None, overviewSettingsConst.BRACKET_FILTER_SHOWALL):
    750             return True
    751         if self.logme:
    752             self.LogInfo('Tactical::WantIt', slimItem)
    753         if slimItem.itemID == session.shipid:
    754             return isBracket
    755         filterGroups = self.overviewPresetSvc.GetValidGroups(isBracket=isBracket)
    756         if slimItem.groupID in filterGroups:
    757             if self.stateSvc.CheckIfFilterItem(slimItem) and self.CheckFiltered(slimItem, filtered, alwaysShown):
    758                 return False
    759             return True
    760         return False
    761 
    762     def DoBallsAdded(self, *args, **kw):
    763         import stackless
    764         import blue
    765         t = stackless.getcurrent()
    766         timer = t.PushTimer(blue.pyos.taskletTimer.GetCurrent() + '::tactical')
    767         try:
    768             return self.DoBallsAdded_(*args, **kw)
    769         finally:
    770             t.PopTimer(timer)
    771 
    772     def DoBallsAdded_(self, lst):
    773         if not self or getattr(self, 'sr', None) is None:
    774             return
    775         uthread.pool('Tactical::DoBallsAdded', self._DoBallsAdded, lst)
    776 
    777     def _DoBallsAdded(self, lst):
    778         if not self or self.sr is None:
    779             return
    780         if self.logme:
    781             self.LogInfo('Tactical::DoBallsAdded', lst)
    782         self.LogInfo('Tactical - adding balls, num balls:', len(lst))
    783         inCapsule = 0
    784         mySlim = uix.GetBallparkRecord(session.shipid)
    785         if mySlim and mySlim.groupID == const.groupCapsule:
    786             inCapsule = 1
    787         checkDrones = 0
    788         filtered = self.GetFilteredStatesFunctionNames()
    789         alwaysShown = self.GetAlwaysShownStatesFunctionNames()
    790         for each in lst:
    791             if each[1].itemID == session.shipid:
    792                 checkDrones = 1
    793             if not checkDrones and not inCapsule and each[1].categoryID == const.categoryDrone:
    794                 drone = sm.GetService('michelle').GetDroneState(each[1].itemID)
    795                 if drone and (drone.ownerID == session.charid or drone.controllerID == session.shipid):
    796                     checkDrones = 1
    797             if not self.WantIt(each[1], filtered, alwaysShown):
    798                 continue
    799             self.tacticalOverlay.AddConnector(each[0], each[1])
    800 
    801         if checkDrones:
    802             droneview = self.GetPanel('droneview')
    803             if droneview:
    804                 droneview.CheckReloadDronesScroll()
    805             else:
    806                 self.CheckInitDrones()
    807 
    808     def OnDroneStateChange2(self, droneID, oldState, newState):
    809         self.InitDrones()
    810         droneview = self.GetPanel('droneview')
    811         if droneview:
    812             droneview.CheckReloadDronesScroll()
    813 
    814     def OnDroneControlLost(self, droneID):
    815         droneview = self.GetPanel('droneview')
    816         if droneview:
    817             droneview.CheckReloadDronesScroll()
    818 
    819     @telemetry.ZONE_METHOD
    820     def DoBallsRemove(self, pythonBalls, isRelease):
    821         for ball, slimItem, terminal in pythonBalls:
    822             self.DoBallRemove(ball, slimItem, terminal)
    823 
    824     def DoBallRemove(self, ball, slimItem, terminal):
    825         if not self or getattr(self, 'sr', None) is None:
    826             return
    827         if ball is None:
    828             return
    829         if not eveCfg.InSpace():
    830             return
    831         self.RemoveBallFromJammers(ball)
    832         if not self.GetDroneViewForSlimItem(slimItem) and not self.tacticalOverlay.HasConnector(ball.id):
    833             return
    834         if self.logme:
    835             self.LogInfo('Tactical::DoBallRemove', ball.id)
    836         uthread.pool('tactical::DoBallRemoveThread', self.DoBallRemoveThread, ball, slimItem, terminal)
    837 
    838     def GetDroneViewForSlimItem(self, slimItem):
    839         if slimItem.categoryID == const.categoryDrone and slimItem.ownerID == session.charid:
    840             return self.GetPanel('droneview')
    841 
    842     def DoBallRemoveThread(self, ball, slimItem, terminal):
    843         self.tacticalOverlay.RemoveConnector(ball.id)
    844         droneview = self.GetDroneViewForSlimItem(slimItem)
    845         if droneview:
    846             droneview.CheckReloadDronesScroll()
    847 
    848     def OnEwarStart(self, sourceBallID, moduleID, targetBallID, jammingType):
    849         if not jammingType:
    850             self.LogError('Tactical::OnEwarStart', sourceBallID, jammingType)
    851             return
    852         if not hasattr(self, 'jammers'):
    853             self.jammers = {}
    854         if not hasattr(self, 'jammersByJammingType'):
    855             self.jammersByJammingType = {}
    856         if targetBallID == session.shipid:
    857             if sourceBallID not in self.jammers:
    858                 self.jammers[sourceBallID] = {}
    859             self.jammers[sourceBallID][jammingType] = self.stateSvc.GetEwarFlag(jammingType)
    860             if jammingType not in self.jammersByJammingType:
    861                 self.jammersByJammingType[jammingType] = set()
    862             self.jammersByJammingType[jammingType].add((sourceBallID, moduleID))
    863             sm.ScatterEvent('OnEwarStartFromTactical')
    864 
    865     def OnEwarEnd(self, sourceBallID, moduleID, targetBallID, jammingType):
    866         if not jammingType:
    867             self.LogError('Tactical::OnEwarEnd', sourceBallID, jammingType)
    868             return
    869         if not hasattr(self, 'jammers'):
    870             return
    871         if sourceBallID in self.jammers and jammingType in self.jammers[sourceBallID]:
    872             del self.jammers[sourceBallID][jammingType]
    873         if jammingType in self.jammersByJammingType and (sourceBallID, moduleID) in self.jammersByJammingType[jammingType]:
    874             self.jammersByJammingType[jammingType].remove((sourceBallID, moduleID))
    875         ewarId = (sourceBallID, moduleID, targetBallID)
    876         sm.ScatterEvent('OnEwarEndFromTactical', jammingType, ewarId)
    877 
    878     def OnEwarOnConnect(self, shipID, moduleID, moduleTypeID, targetID, guid, *args):
    879         if targetID != session.shipid:
    880             return
    881         if evetypes.GetCategoryID(moduleTypeID) == categoryEntity:
    882             ewarType = self.FindEwarTypeOnNpcFromGuid(moduleTypeID, guid)
    883         else:
    884             ewarType = self.FindEwarTypeFromModuleTypeID(moduleTypeID)
    885         if ewarType is not None:
    886             self.OnEwarStart(shipID, moduleID, targetID, ewarType)
    887 
    888     def FindEwarTypeFromModuleTypeID(self, moduleTypeID, *args):
    889         try:
    890             effectID = self.clientDogmaStaticSvc.GetDefaultEffect(moduleTypeID)
    891             return dogma.effects.GetEwarTypeByEffectID(effectID)
    892         except KeyError:
    893             pass
    894 
    895     def FindEwarTypeOnNpcFromGuid(self, entityTypeID, guid):
    896         effectID = None
    897         try:
    898             effects = self.godma.GetStateManager().GetEffectsByTypeID(entityTypeID)
    899             for effect in effects.itervalues():
    900                 if effect.guid == guid:
    901                     if effectID is not None:
    902                         self.LogWarn('Entity=%s has multiple effects with same guid=%s - this will lead to dbuff icon conflict' % (entityTypeID, guid))
    903                     effectID = effect.effectID
    904 
    905             return dogma.effects.GetEwarTypeByEffectID(effectID)
    906         except KeyError:
    907             return effectID
    908 
    909     def GetEffectSourcesAndCountByJammingType(self, jammingType):
    910         effectSourcesSet = self.jammersByJammingType.get(jammingType, set())
    911         effectSources = defaultdict(int)
    912         for effectInfo in effectSourcesSet:
    913             sourceID, moduleID = effectInfo
    914             effectSources[sourceID] += 1
    915 
    916         return dict(effectSources)
    917 
    918     def ImportOverviewSettings(self, *args):
    919         ImportOverviewWindow.Open()
    920 
    921     def ExportOverviewSettings(self, *args):
    922         ExportOverviewWindow.Open()
    923 
    924     def OnEveGetsFocus(self, *args):
    925         pass
    926 
    927     def GetInBayDroneDamageTracker(self):
    928         dogmaLM = self.godma.GetDogmaLM()
    929         clientDogmaLM = sm.GetService('clientDogmaIM').GetDogmaLocation()
    930         if self.inBayDroneDamageTracker is None:
    931             self.inBayDroneDamageTracker = InBayDroneDamageTracker(dogmaLM, clientDogmaLM)
    932         else:
    933             self.inBayDroneDamageTracker.SetDogmaLM(dogmaLM)
    934         return self.inBayDroneDamageTracker