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

sensorSuiteService.py (31760B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\parklife\sensorSuiteService.py
      2 import math
      3 import audio2
      4 import bluepy
      5 import gametime
      6 import locks
      7 import signals
      8 import trinity
      9 import uthread
     10 import uthread2
     11 from carbon.common.lib.const import SEC
     12 from carbon.common.script.sys import service
     13 from carbon.common.script.util.logUtil import LogException
     14 from carbon.common.script.util.timerstuff import AutoTimer
     15 from carbonui import uiconst
     16 from carbonui.uianimations import animations
     17 from carbonui.uicore import uicore
     18 from carbonui.util.various_unsorted import IsUnder
     19 from eve.common.lib.appConst import AU
     20 from eve.common.script.sys import eveCfg
     21 from eveexceptions import UserError
     22 from eveuniverse.solar_systems import is_sensor_overlay_suppressed
     23 from sensorsuite.error import InvalidClientStateError
     24 import sensorsuite.overlay.const as overlayConst
     25 from sensorsuite.overlay import sitetype
     26 from sensorsuite.overlay.const import SWEEP_CYCLE_TIME, SWEEP_START_GRACE_TIME_SEC, SWEEP_START_GRACE_TIME, SUPPRESS_GFX_WARPING, SUPPRESS_GFX_NO_UI
     27 from sensorsuite.overlay.anomalies import AnomalyHandler
     28 from sensorsuite.overlay.bookmarks import BookmarkHandler, SharedBookmarkHandler
     29 from sensorsuite.overlay.controllers.probescanner import ProbeScannerController
     30 from sensorsuite.overlay.gfxhandler import GfxHandler
     31 from sensorsuite.overlay.missions import MissionHandler
     32 from sensorsuite.overlay.signatures import SignatureHandler
     33 from sensorsuite.overlay.spacesitecontroller import SpaceSiteController
     34 from sensorsuite.overlay.staticsites import StaticSiteHandler
     35 from sensorsuite.overlay.structures import StructureHandler
     36 SENSOR_SUITE_ENABLED = 'sensorSuiteEnabled'
     37 MAX_MOUSEOVER_RANGE = 40.0
     38 MAX_MOUSEOVER_RANGE_SQUARED = MAX_MOUSEOVER_RANGE ** 2
     39 MAX_OVERLAPPING_RANGE_SQUARED = 900.0
     40 MAX_RTPC_VALUE = 99
     41 BRACKET_OVERLAP_DISTANCE = 8
     42 SWEEP_CYCLE_TIME_SEC = float(SWEEP_CYCLE_TIME) / SEC
     43 UPDATE_STRUCTURES_DELAY = 1000
     44 UPDATE_SITES_DELAY = 200
     45 
     46 def GetSensorSuiteService():
     47     return sm.GetService('sensorSuite')
     48 
     49 
     50 class SensorSuiteService(service.Service):
     51     __guid__ = 'svc.sensorSuite'
     52     __notifyevents__ = ['DoBallRemove',
     53      'DoBallsRemove',
     54      'OnAgentMissionChanged',
     55      'OnBallAdded',
     56      'OnBallparkSetState',
     57      'OnEnterSpace',
     58      'OnHideUI',
     59      'OnClientEvent_JumpStarted',
     60      'OnClientEvent_JumpExecuted',
     61      'OnRefreshBookmarks',
     62      'OnReleaseBallpark',
     63      'OnShowUI',
     64      'OnSignalTrackerAnomalyUpdate',
     65      'OnSignalTrackerFullState',
     66      'OnSignalTrackerSignatureUpdate',
     67      'OnSignalTrackerStructureUpdate',
     68      'OnSpecialFX',
     69      'OnStructuresVisibilityUpdated',
     70      'OnSystemScanDone',
     71      'OnUpdateWindowPosition',
     72      'OnClientEvent_WarpStarted',
     73      'OnClientEvent_WarpFinished']
     74     __dependencies__ = ['audio',
     75      'bookmarkSvc',
     76      'michelle',
     77      'scanSvc',
     78      'sceneManager',
     79      'viewState']
     80     __startupdependencies__ = []
     81 
     82     def Run(self, *args):
     83         service.Service.Run(self)
     84         self.isOverlayActive = True
     85         self.toggleLock = locks.RLock()
     86         self.resultEffectsThread = None
     87         self.messenger = signals.Messenger()
     88         self.gfxHandler = GfxHandler(self, self.sceneManager, self.michelle)
     89         self.siteController = SpaceSiteController(self, self.michelle)
     90         self.siteController.AddSiteHandler(sitetype.ANOMALY, AnomalyHandler())
     91         self.siteController.AddSiteHandler(sitetype.SIGNATURE, SignatureHandler())
     92         self.siteController.AddSiteHandler(sitetype.STATIC_SITE, StaticSiteHandler())
     93         self.siteController.AddSiteHandler(sitetype.BOOKMARK, BookmarkHandler(self, self.bookmarkSvc))
     94         self.siteController.AddSiteHandler(sitetype.SHARED_BOOKMARK, SharedBookmarkHandler(self, self.bookmarkSvc))
     95         self.siteController.AddSiteHandler(sitetype.MISSION, MissionHandler(self.bookmarkSvc))
     96         self.siteController.AddSiteHandler(sitetype.STRUCTURE, StructureHandler())
     97         self.Initialize()
     98         self.UpdateVisibleStructures()
     99 
    100     def IsSweepDone(self):
    101         return self.systemReadyTime and not self.sensorSweepActive
    102 
    103     def IsSolarSystemReady(self):
    104         if session.solarsystemid is None:
    105             return False
    106         bp = self.michelle.GetBallpark()
    107         if bp is None:
    108             return False
    109         if not bp.ego:
    110             return False
    111         return True
    112 
    113     def NotifySweepStartedIfRequired(self, handler, messageName):
    114         if messageName is overlayConst.MESSAGE_ON_SENSOR_OVERLAY_SWEEP_STARTED and self.sweepStartedData is not None:
    115             uthread.new(handler, *self.sweepStartedData)
    116 
    117     def Subscribe(self, messageName, handler):
    118         self.NotifySweepStartedIfRequired(handler, messageName)
    119         self.messenger.SubscribeToMessage(messageName, handler)
    120 
    121     def Unsubscribe(self, messageName, handler):
    122         self.messenger.UnsubscribeFromMessage(messageName, handler)
    123 
    124     def SendMessage(self, messageName, *args, **kwargs):
    125         self.messenger.SendMessage(messageName, *args, **kwargs)
    126 
    127     def Initialize(self):
    128         self.siteController.Clear()
    129         self.probeScannerController = ProbeScannerController(self.scanSvc, self.michelle, self.siteController)
    130         self.locatorFadeInTimeSec = 0.25
    131         self.doMouseTrackingUpdates = False
    132         self.systemReadyTime = None
    133         self.sitesUnderCursor = set()
    134         leftPush, rightPush = uicore.layer.sidePanels.GetSideOffset()
    135         self.OnUpdateWindowPosition(leftPush, rightPush)
    136         self.sensorSweepActive = False
    137         self.sweepStartedData = None
    138 
    139     def UpdateScanner(self, removedSites):
    140         targetIDs = []
    141         for siteData in removedSites:
    142             if siteData.GetSiteType() in (sitetype.ANOMALY, sitetype.SIGNATURE):
    143                 targetIDs.append(siteData.targetID)
    144 
    145         if targetIDs:
    146             self.scanSvc.ClearResults(*targetIDs)
    147 
    148     def InjectScannerResults(self, siteType):
    149         sitesById = self.siteController.siteMaps.GetSiteMapByKey(siteType)
    150         self.probeScannerController.InjectSiteScanResults(sitesById.itervalues())
    151 
    152     def OnSpecialFX(self, shipID, moduleID, moduleTypeID, targetID, otherTypeID, guid, *args, **kw):
    153         if shipID == session.shipid:
    154             if guid is not None and 'effects.JumpOut' in guid:
    155                 self.LogInfo('Jumping out, hiding the overlay')
    156                 self._Hide()
    157 
    158     def OnEnterSpace(self):
    159         self.Reset()
    160         if session.solarsystemid and session.structureid is None:
    161             self.Initialize()
    162             self._SetOverlayActive(settings.char.ui.Get(SENSOR_SUITE_ENABLED, True))
    163             self.LogInfo('Entered new system', session.solarsystemid)
    164             try:
    165                 sm.RemoteSvc('scanMgr').SignalTrackerRegister()
    166             except UserError as e:
    167                 if e.msg == 'UnMachoDestination':
    168                     self.LogInfo('Entered new system failed due to match as session seems to have changed')
    169                     return
    170                 raise
    171 
    172             for siteType in (sitetype.BOOKMARK, sitetype.MISSION, sitetype.SHARED_BOOKMARK):
    173                 self.siteController.GetSiteHandler(siteType).LoadSites(session.solarsystemid)
    174 
    175             if self.IsOverlaySuppressed():
    176                 self._Hide()
    177                 self.messenger.SendMessage(overlayConst.MESSAGE_ON_SENSOR_OVERLAY_DISABLED)
    178             elif self.isOverlayActive:
    179                 self._Show()
    180                 self.messenger.SendMessage(overlayConst.MESSAGE_ON_SENSOR_OVERLAY_ENABLED)
    181             ballpark = self.michelle.GetBallpark()
    182             if ballpark and ballpark.ego:
    183                 self._InitiateSensorSweep()
    184 
    185     def OnReleaseBallpark(self):
    186         self.Reset()
    187 
    188     def OnBallparkSetState(self):
    189         if session.solarsystemid and session.structureid is None and not sm.GetService('subway').InJump():
    190             self._InitiateSensorSweep()
    191 
    192     def _InitiateSensorSweep(self):
    193         self.LogInfo('Ballpark is ready so we start the sweep timer')
    194         self.systemReadyTime = gametime.GetSimTime()
    195         self.StartSensorSweep()
    196 
    197     def Reset(self):
    198         self.LogInfo('Clearing all overlay objects')
    199         if self.resultEffectsThread:
    200             self.resultEffectsThread.kill()
    201             self.resultEffectsThread = None
    202         self.siteController.ClearFromBallpark()
    203         self.siteController.Clear()
    204         uicore.layer.sensorSuite.Flush()
    205         self.gfxHandler.StopGfxSwipe()
    206         self.gfxHandler.StopSwipeThread()
    207 
    208     def IsOverlaySuppressed(self):
    209         return is_sensor_overlay_suppressed(session.solarsystemid)
    210 
    211     def IsOverlayActive(self):
    212         if self.IsOverlaySuppressed():
    213             return False
    214         return self.isOverlayActive
    215 
    216     def ToggleOverlay(self):
    217         if self.IsOverlaySuppressed():
    218             return
    219         with self.toggleLock:
    220             self.LogInfo('Toggle Overlay')
    221             if self.isOverlayActive:
    222                 self.DisableSensorOverlay()
    223             else:
    224                 self.EnableSensorOverlay()
    225 
    226     def DisableSensorOverlay(self):
    227         self.LogInfo('DisableSensorOverlay')
    228         if self.isOverlayActive:
    229             self._SetOverlayActive(False)
    230             if not self.sensorSweepActive:
    231                 self._Hide()
    232             self.messenger.SendMessage(overlayConst.MESSAGE_ON_SENSOR_OVERLAY_DISABLED)
    233 
    234     def EnableSensorOverlay(self):
    235         self.LogInfo('EnableSensorOverlay')
    236         if not self.isOverlayActive:
    237             self._SetOverlayActive(True)
    238             if not self.sensorSweepActive:
    239                 self._Show()
    240             self.messenger.SendMessage(overlayConst.MESSAGE_ON_SENSOR_OVERLAY_ENABLED)
    241 
    242     def _SetOverlayActive(self, isActive):
    243         self.isOverlayActive = isActive
    244         settings.char.ui.Set(SENSOR_SUITE_ENABLED, isActive)
    245 
    246     def _Show(self):
    247         self.LogInfo('Showing overlay')
    248         try:
    249             if not self.sensorSweepActive:
    250                 self.UpdateVisibleSites()
    251                 self.UpdateVisibleStructures()
    252                 self.audio.SendUIEvent('ui_scanner_stop')
    253                 self.EnableMouseTracking()
    254         except InvalidClientStateError:
    255             pass
    256 
    257     def _Hide(self):
    258         self.LogInfo('Hiding overlay')
    259         self.gfxHandler.StopGfxSwipe()
    260         self.UpdateVisibleSites()
    261         self.UpdateVisibleStructures()
    262         self.audio.SendUIEvent('ui_scanner_stop')
    263         self.doMouseTrackingUpdates = False
    264 
    265     def EnableMouseTracking(self):
    266         if not self.doMouseTrackingUpdates:
    267             self.doMouseTrackingUpdates = True
    268             uthread.new(self.UpdateMouseTracking).context = 'sensorSuite::UpdateMouseTracking'
    269 
    270     def StartSensorSweep(self):
    271         uthread.new(self._DoSystemEnterScan)
    272 
    273     def TryFadeOutBracketAndReturnCurveSet(self, curveSet, points, siteData, totalDuration):
    274         try:
    275             locatorData = self.siteController.spaceLocations.GetBySiteID(siteData.siteID)
    276             curveSet = animations.FadeTo(locatorData.bracket, startVal=0.0, endVal=1.0, duration=totalDuration, curveType=points, curveSet=curveSet)
    277         except KeyError:
    278             pass
    279 
    280         return curveSet
    281 
    282     def GetSiteListOrderedByDelay(self, ballpark, sweepCycleTimeSec, viewAngleInPlane):
    283         if ballpark is None:
    284             raise InvalidClientStateError("We don't have a ballpark")
    285         myBall = ballpark.GetBall(ballpark.ego)
    286         if myBall is None:
    287             raise InvalidClientStateError("We don't have an active ship in the park")
    288         mx, mz = myBall.x, myBall.z
    289         sitesOrdered = []
    290         pi2 = math.pi * 2
    291         for siteData in self.GetVisibleSites():
    292             if sitetype.IsSiteInstantlyAccessible(siteData):
    293                 sitesOrdered.append((0, siteData))
    294                 continue
    295             x, y, z = siteData.position
    296             dx, dz = x - mx, z - mz
    297             angle = math.atan2(-dz, dx) - viewAngleInPlane
    298             angle %= pi2
    299             ratioOfCircle = angle / pi2
    300             delay = SWEEP_START_GRACE_TIME_SEC + sweepCycleTimeSec * ratioOfCircle
    301             sitesOrdered.append((delay, siteData))
    302 
    303         sitesOrdered.sort()
    304         return sitesOrdered
    305 
    306     def GetVisibleSites(self):
    307         return self.siteController.GetVisibleSites()
    308 
    309     def SetupSiteSweepAnimation(self, sitesOrdered):
    310         curveSet = animations.CreateCurveSet(useRealTime=False)
    311         for delay, siteData in sitesOrdered:
    312             points, totalDuration = self.GetLocationFlashCurve(delay)
    313             curveSet = self.TryFadeOutBracketAndReturnCurveSet(curveSet, points, siteData, totalDuration)
    314 
    315     def _DoSystemEnterScan(self):
    316         self.LogInfo('_DoSystemEnterScan entered')
    317         if not eveCfg.InSpace():
    318             return
    319         self.sensorSweepActive = True
    320         try:
    321             self.CreateResults()
    322             viewAngleInPlane = self.gfxHandler.GetViewAngleInPlane()
    323             self.LogInfo('Sensor sweep stating from angle', viewAngleInPlane)
    324             ballpark = self.michelle.GetBallpark()
    325             sitesOrdered = self.GetSiteListOrderedByDelay(ballpark, SWEEP_CYCLE_TIME_SEC, viewAngleInPlane)
    326         except InvalidClientStateError:
    327             self.sensorSweepActive = False
    328             return
    329 
    330         if self.IsOverlayActive():
    331             self.SetupSiteSweepAnimation(sitesOrdered)
    332             self.gfxHandler.StartGfxSwipeThread(viewAngleInPlane=viewAngleInPlane)
    333             self.audio.SendUIEvent('ui_scanner_start')
    334             self.resultEffectsThread = uthread.new(self.PlayResultEffects, sitesOrdered)
    335         else:
    336             self.sensorSweepActive = False
    337             self._Hide()
    338         self.LogInfo('Sweep started observers notified')
    339         self.sweepStartedData = (self.systemReadyTime,
    340          SWEEP_CYCLE_TIME_SEC,
    341          viewAngleInPlane,
    342          sitesOrdered,
    343          SWEEP_START_GRACE_TIME_SEC)
    344         self.SendMessage(overlayConst.MESSAGE_ON_SENSOR_OVERLAY_SWEEP_STARTED, *self.sweepStartedData)
    345 
    346     def ShowSiteDuringSweep(self, locatorData, scene, siteData, sleepTimeSec, soundLocators, vectorCurve):
    347         ball = locatorData.ballRef()
    348         if ball is None:
    349             return
    350         audio = audio2.AudEmitter('sensor_overlay_site_%s' % str(siteData.siteID))
    351         obs = trinity.TriObserverLocal()
    352         obs.observer = audio
    353         vectorSequencer = trinity.TriVectorSequencer()
    354         vectorSequencer.operator = trinity.TRIOP_MULTIPLY
    355         vectorSequencer.functions.append(ball)
    356         vectorSequencer.functions.append(vectorCurve)
    357         tr = trinity.EveRootTransform()
    358         tr.name = 'sensorSuiteSoundLocator_%s' % str(siteData.siteID)
    359         tr.translationCurve = vectorSequencer
    360         tr.observers.append(obs)
    361         scene.objects.append(tr)
    362         soundLocators.append(tr)
    363         uthread2.SleepSim(sleepTimeSec)
    364         if siteData.GetSiteType() == sitetype.ANOMALY:
    365             audio.SendEvent('ui_scanner_result_anomaly')
    366         elif siteData.GetSiteType() == sitetype.SIGNATURE:
    367             audio.SendEvent('ui_scanner_result_signature')
    368         locatorData.bracket.DoEntryAnimation(enable=False)
    369         locatorData.bracket.state = uiconst.UI_DISABLED
    370 
    371     def PlayResultEffects(self, sitesOrdered):
    372         self.LogInfo('PlayResultEffects')
    373         scene = self.sceneManager.GetRegisteredScene('default')
    374         soundLocators = []
    375         invAU = 1.0 / AU
    376         vectorCurve = trinity.Tr2CurveVector3()
    377         vectorCurve.AddKey(0, (invAU, invAU, invAU))
    378         try:
    379             if self.systemReadyTime is None:
    380                 return
    381             startTimeSec = float(self.systemReadyTime) / SEC
    382             for delaySec, siteData in sitesOrdered:
    383                 if not self.siteController.spaceLocations.ContainsSite(siteData.siteID):
    384                     continue
    385                 if sitetype.IsSiteInstantlyAccessible(siteData):
    386                     continue
    387                 locatorData = self.siteController.spaceLocations.GetBySiteID(siteData.siteID)
    388                 playTimeSec = startTimeSec + delaySec
    389                 now = float(gametime.GetSimTime()) / SEC
    390                 sleepTimeSec = max(0, playTimeSec - now)
    391                 self.ShowSiteDuringSweep(locatorData, scene, siteData, sleepTimeSec, soundLocators, vectorCurve)
    392 
    393             currentTimeSec = float(gametime.GetSimTime()) / SEC
    394             endTimeSec = startTimeSec + SWEEP_START_GRACE_TIME_SEC + SWEEP_CYCLE_TIME_SEC
    395             timeLeftSec = endTimeSec - currentTimeSec
    396             if timeLeftSec > 0:
    397                 uthread2.SleepSim(timeLeftSec)
    398             self.audio.SendUIEvent('ui_scanner_stop')
    399             self.sensorSweepActive = False
    400             if not self.IsOverlayActive():
    401                 self._Hide()
    402             else:
    403                 for locatorData in self.siteController.spaceLocations.IterLocations():
    404                     if not sitetype.IsSiteInstantlyAccessible(locatorData.siteData):
    405                         locatorData.bracket.DoEnableAnimation()
    406                         locatorData.bracket.state = uiconst.UI_NORMAL
    407 
    408             uthread2.SleepSim(1.0)
    409             self.DoScanEnded(sitesOrdered)
    410             self.audio.SendUIEvent('ui_scanner_stop')
    411             self.SendMessage(overlayConst.MESSAGE_ON_SENSOR_OVERLAY_SWEEP_ENDED)
    412             self.UpdateVisibleSites()
    413         finally:
    414             self.sensorSweepActive = False
    415             if scene is not None:
    416                 for tr in soundLocators:
    417                     if tr in scene.objects:
    418                         scene.objects.remove(tr)
    419 
    420     def DoScanEnded(self, sitesOrdered):
    421         self.LogInfo('DoScanEnded')
    422         if len(sitesOrdered) > 0:
    423             self.audio.SendUIEvent('ui_scanner_result_positive')
    424         else:
    425             self.audio.SendUIEvent('ui_scanner_result_negative')
    426 
    427     def CreateResults(self):
    428         self.LogInfo('CreateResults')
    429         self.gfxHandler.WaitForSceneReady()
    430         for siteData in self.GetVisibleSites():
    431             self.siteController.AddSiteToSpace(siteData, animate=False)
    432 
    433     def GetLocationFlashCurve(self, delay):
    434         totalDuration = delay + self.locatorFadeInTimeSec
    435         points = []
    436         totalTime = 0
    437         for keyDuration, keyValue in ((delay, 0.0), (self.locatorFadeInTimeSec, 1.0)):
    438             totalTime += keyDuration
    439             points.append((totalTime / totalDuration, keyValue))
    440 
    441         return (points, totalDuration)
    442 
    443     def IsSiteBall(self, ballID):
    444         return self.GetBracketByBallID(ballID) is not None
    445 
    446     def GetBracketByBallID(self, ballID):
    447         return self.siteController.spaceLocations.GetBracketByBallID(ballID)
    448 
    449     def GetBracketBySiteID(self, siteID):
    450         return self.siteController.spaceLocations.GetBracketBySiteID(siteID)
    451 
    452     def OnRefreshBookmarks(self):
    453         self.LogInfo('OnRefreshBookmarks')
    454         if session.solarsystemid:
    455             for siteType in (sitetype.BOOKMARK, sitetype.SHARED_BOOKMARK):
    456                 self.siteController.GetSiteHandler(siteType).UpdateSites(session.solarsystemid)
    457 
    458             self.UpdateVisibleSites()
    459 
    460     def OnAgentMissionChanged(self, *args, **kwargs):
    461         self.siteController.GetSiteHandler(sitetype.MISSION).UpdateSites(session.solarsystemid)
    462 
    463     def OnSignalTrackerFullState(self, solarSystemID, fullState, shouldRemoveOldSites = False):
    464         self.LogInfo('OnSignalTrackerFullState', solarSystemID, fullState)
    465         anomalies, signatures, staticSites, structures = fullState
    466         for siteType, rawSites in ((sitetype.ANOMALY, anomalies),
    467          (sitetype.SIGNATURE, signatures),
    468          (sitetype.STATIC_SITE, staticSites),
    469          (sitetype.STRUCTURE, structures)):
    470             addedSites = rawSites or dict()
    471             removedSites = set()
    472             siteHandler = self.siteController.GetSiteHandler(siteType)
    473             if shouldRemoveOldSites:
    474                 sites = siteHandler.GetSites().keys()
    475                 for siteID in sites:
    476                     if siteID not in addedSites:
    477                         removedSites.add(siteID)
    478 
    479             if addedSites or removedSites:
    480                 siteHandler.ProcessSiteUpdate(addedSites, removedSites)
    481 
    482         self.probeScannerController.InjectSiteScanResults(self.siteController.siteMaps.IterSitesByKeys(sitetype.ANOMALY, sitetype.SIGNATURE))
    483         sm.GetService('scanSvc').PruneSites()
    484 
    485     def OnSignalTrackerAnomalyUpdate(self, solarSystemID, addedAnomalies, removedAnomalies):
    486         self.LogInfo('OnSignalTrackerAnomalyUpdate', solarSystemID, addedAnomalies, removedAnomalies)
    487         self.siteController.GetSiteHandler(sitetype.ANOMALY).ProcessSiteUpdate(addedAnomalies, removedAnomalies)
    488 
    489     def OnSignalTrackerSignatureUpdate(self, solarSystemID, addedSignatures, removedSignatures):
    490         self.LogInfo('OnSignalTrackerSignatureUpdate', solarSystemID, addedSignatures, removedSignatures)
    491         self.siteController.GetSiteHandler(sitetype.SIGNATURE).ProcessSiteUpdate(addedSignatures, removedSignatures)
    492 
    493     def OnSignalTrackerStructureUpdate(self, solarSystemID, addedStructures, removedStructures):
    494         self.LogInfo('OnSignalTrackerStructureUpdate', solarSystemID, addedStructures, removedStructures)
    495         self.siteController.GetSiteHandler(sitetype.STRUCTURE).ProcessSiteUpdate(addedStructures, removedStructures)
    496 
    497     def OnUpdateWindowPosition(self, leftPush, rightPush):
    498         uicore.layer.sensorsuite.padLeft = -leftPush
    499         uicore.layer.sensorsuite.padRight = -rightPush
    500 
    501     def IsMouseInSpaceView(self):
    502         if self.viewState.IsViewActive('inflight'):
    503             mouseOver = uicore.uilib.mouseOver
    504             for uiContainer in (uicore.layer.inflight, uicore.layer.sensorsuite, uicore.layer.bracket):
    505                 if mouseOver is uiContainer or IsUnder(mouseOver, uiContainer):
    506                     return True
    507 
    508         return False
    509 
    510     @bluepy.TimedFunction('sensorSuiteService::UpdateMouseHoverSound')
    511     def UpdateMouseHoverSound(self, activeBracket, bestProximity, closestBracket, lastSoundStrength):
    512         soundStrength = bestProximity or 0
    513         if closestBracket is not None:
    514             if soundStrength != 0 or lastSoundStrength != 0:
    515                 if lastSoundStrength == 0 or activeBracket != closestBracket:
    516                     signalStrength = MAX_RTPC_VALUE
    517                     self.audio.SendUIEvent(closestBracket.data.hoverSoundEvent)
    518                     self.audio.SetGlobalRTPC('scanner_signal_strength', min(signalStrength, MAX_RTPC_VALUE))
    519                     self.audio.SendUIEvent('ui_scanner_state_difficulty_easy')
    520                     self.audio.SendUIEvent('ui_scanner_mouseover')
    521                     activeBracket = closestBracket
    522                 self.audio.SetGlobalRTPC('scanner_mouseover', soundStrength)
    523         elif soundStrength == 0 and lastSoundStrength > 0:
    524             self.DisableMouseOverSound()
    525             activeBracket = None
    526         lastSoundStrength = soundStrength
    527         return (activeBracket, lastSoundStrength)
    528 
    529     @bluepy.TimedFunction('sensorSuiteService::UpdateMouseTracking')
    530     def UpdateMouseTracking(self):
    531         self.LogInfo('Mouse tracking update thread started')
    532         lastSoundStrength = 0.0
    533         activeBracket = None
    534         self.sitesUnderCursor = set()
    535         self.audio.SetGlobalRTPC('scanner_mouseover', 0)
    536         while self.doMouseTrackingUpdates:
    537             try:
    538                 if not self.IsMouseInSpaceView():
    539                     if activeBracket is not None:
    540                         self.DisableMouseOverSound()
    541                         activeBracket = None
    542                         lastSoundStrength = 0.0
    543                     continue
    544                 desktopWidth = uicore.desktop.width
    545                 desktopHeight = uicore.desktop.height
    546                 mouseX = uicore.uilib.x
    547                 mouseY = uicore.uilib.y
    548                 self.currentOverlapCoordinates = (mouseX, mouseY)
    549                 closestBracket = None
    550                 bestProximity = None
    551                 for data in self.siteController.spaceLocations.IterLocations():
    552                     self.sitesUnderCursor.discard(data.siteData)
    553                     bracket = data.bracket
    554                     if bracket is None or bracket.destroyed:
    555                         continue
    556                     if bracket.state == uiconst.UI_DISABLED:
    557                         continue
    558                     centerX = bracket.left + bracket.width / 2
    559                     centerY = bracket.top + bracket.height / 2
    560                     if centerX < 0:
    561                         continue
    562                     if centerX > desktopWidth:
    563                         continue
    564                     if centerY < 0:
    565                         continue
    566                     if centerY > desktopHeight:
    567                         continue
    568                     if mouseX < centerX - MAX_MOUSEOVER_RANGE:
    569                         continue
    570                     if mouseX > centerX + MAX_MOUSEOVER_RANGE:
    571                         continue
    572                     if mouseY < centerY - MAX_MOUSEOVER_RANGE:
    573                         continue
    574                     if mouseY > centerY + MAX_MOUSEOVER_RANGE:
    575                         continue
    576                     dx = centerX - mouseX
    577                     dy = centerY - mouseY
    578                     if -BRACKET_OVERLAP_DISTANCE <= dx <= BRACKET_OVERLAP_DISTANCE and -BRACKET_OVERLAP_DISTANCE <= dy <= BRACKET_OVERLAP_DISTANCE:
    579                         self.sitesUnderCursor.add(data.siteData)
    580                     if data.siteData.hoverSoundEvent is None:
    581                         continue
    582                     distanceSquared = dx * dx + dy * dy
    583                     if distanceSquared >= MAX_MOUSEOVER_RANGE_SQUARED:
    584                         continue
    585                     proximity = MAX_RTPC_VALUE - distanceSquared / MAX_MOUSEOVER_RANGE_SQUARED * MAX_RTPC_VALUE
    586                     if closestBracket is not None:
    587                         if proximity < bestProximity:
    588                             closestBracket = bracket
    589                             bestProximity = proximity
    590                     else:
    591                         closestBracket = bracket
    592                         bestProximity = proximity
    593 
    594                 activeBracket, lastSoundStrength = self.UpdateMouseHoverSound(activeBracket, bestProximity, closestBracket, lastSoundStrength)
    595             except (ValueError, OverflowError):
    596                 pass
    597             except Exception:
    598                 LogException('The sound update loop errored out')
    599             finally:
    600                 uthread2.Sleep(0.025)
    601 
    602         if activeBracket is not None:
    603             self.DisableMouseOverSound()
    604         self.LogInfo('Mouse tracking update thread ended')
    605 
    606     def DisableMouseOverSound(self):
    607         self.audio.SendUIEvent('ui_scanner_mouseover_stop')
    608         self.audio.SetGlobalRTPC('scanner_mouseover', 0)
    609 
    610     def OnClientEvent_WarpStarted(self, *args):
    611         self.LogInfo('OnClientEvent_WarpStarted hiding the sweep gfx')
    612         self.gfxHandler.DisableGfx(SUPPRESS_GFX_WARPING)
    613 
    614     def OnClientEvent_WarpFinished(self, *args):
    615         self.LogInfo('OnClientEvent_WarpFinished showing the sweep gfx')
    616         self.gfxHandler.EnableGfx(SUPPRESS_GFX_WARPING)
    617 
    618     def OnShowUI(self):
    619         self.LogInfo('OnShowUI showing the sweep gfx')
    620         uicore.layer.sensorsuite.display = True
    621         self.gfxHandler.EnableGfx(SUPPRESS_GFX_NO_UI)
    622 
    623     def OnHideUI(self):
    624         self.LogInfo('OnHideUI hiding the sweep gfx')
    625         uicore.layer.sensorsuite.display = False
    626         self.gfxHandler.DisableGfx(SUPPRESS_GFX_NO_UI)
    627 
    628     def OnSystemScanDone(self):
    629         self.probeScannerController.UpdateProbeResultBrackets()
    630 
    631     def GetOverlappingSites(self):
    632         overlappingBrackets = []
    633         for siteData in self.sitesUnderCursor:
    634             bracket = self.siteController.spaceLocations.GetBracketBySiteID(siteData.siteID)
    635             if bracket:
    636                 overlappingBrackets.append(bracket)
    637 
    638         return overlappingBrackets
    639 
    640     def GetAnomalies(self):
    641         return self.siteController.GetSiteHandler(sitetype.ANOMALY).GetSites().values()
    642 
    643     def GetAnomaly(self, instanceID):
    644         anomalies = self.GetAnomalies()
    645         for anomaly in anomalies:
    646             if anomaly.instanceID == instanceID:
    647                 return anomaly
    648 
    649     def GetStaticSites(self):
    650         return self.siteController.GetSiteHandler(sitetype.STATIC_SITE).GetSites().values()
    651 
    652     @uthread2.debounce(1.0)
    653     def FullyUpdateSignalTrackerDebounced(self):
    654         self.OnSignalTrackerFullState(session.solarsystemid2, self.scanSvc.GetScanMan().GetFullState(), shouldRemoveOldSites=True)
    655 
    656     def UpdateSignalTracker(self):
    657         self.OnSignalTrackerFullState(session.solarsystemid2, self.scanSvc.GetScanMan().GetFullState(), shouldRemoveOldSites=False)
    658 
    659     def GetSignatures(self):
    660         return self.siteController.GetSiteHandler(sitetype.SIGNATURE).GetSites().values()
    661 
    662     def GetLocatedSignatures(self):
    663         return [ signature for signature in self.GetSignatures() if signature.IsAccurate() ]
    664 
    665     def GetUnknownSignatures(self):
    666         return [ signature for signature in self.GetSignatures() if not signature.IsAccurate() ]
    667 
    668     def SetSiteFilter(self, siteType, enabled):
    669         handler = self.siteController.GetSiteHandler(siteType)
    670         handler.SetFilterEnabled(enabled)
    671         if siteType in sitetype.STRUCTURE_SITE_TYPES:
    672             self.UpdateVisibleStructures()
    673         else:
    674             self.UpdateVisibleSites()
    675 
    676     def OnStructuresVisibilityUpdated(self):
    677         self.UpdateVisibleStructures()
    678 
    679     def OnBallAdded(self, slimItem):
    680         if slimItem.categoryID == const.categoryStructure:
    681             self.UpdateVisibleStructures()
    682 
    683     def DoBallRemove(self, ball, slimItem, terminal):
    684         if slimItem.categoryID == const.categoryStructure:
    685             self.UpdateVisibleStructures()
    686 
    687     def DoBallsRemove(self, pythonBalls, isRelease):
    688         for _ball, slimItem, _terminal in pythonBalls:
    689             if slimItem.categoryID == const.categoryStructure:
    690                 self.UpdateVisibleStructures()
    691                 return
    692 
    693     @bluepy.TimedFunction('sensorSuiteService::UpdateVisibleStructures')
    694     def UpdateVisibleStructures(self):
    695         setattr(self, 'updateVisibleStructuresTimerThread', AutoTimer(UPDATE_STRUCTURES_DELAY, self._UpdateVisibleStructures))
    696 
    697     @bluepy.TimedFunction('sensorSuiteService::_UpdateVisibleStructures')
    698     def _UpdateVisibleStructures(self):
    699         self.LogInfo('UpdateVisibleStructures')
    700         try:
    701             if not self.IsSolarSystemReady():
    702                 return
    703             self.siteController.UpdateSiteVisibility(siteTypesToUpdate=sitetype.STRUCTURE_SITE_TYPES)
    704         finally:
    705             self.updateVisibleStructuresTimerThread = None
    706 
    707     @bluepy.TimedFunction('sensorSuiteService::UpdateVisibleSites')
    708     def UpdateVisibleSites(self):
    709         setattr(self, 'updateVisibleSitesTimerThread', AutoTimer(UPDATE_SITES_DELAY, self._UpdateVisibleSites))
    710 
    711     @bluepy.TimedFunction('sensorSuiteService::_UpdateVisibleSites')
    712     def _UpdateVisibleSites(self):
    713         self.LogInfo('UpdateVisibleSites')
    714         try:
    715             if not self.IsSolarSystemReady():
    716                 return
    717             self.siteController.UpdateSiteVisibility(siteTypesToUpdate=sitetype.NON_STRUCTURE_SITE_TYPES)
    718         finally:
    719             self.updateVisibleSitesTimerThread = None
    720 
    721     def GetPositionalSiteItemIDFromTargetID(self, targetID):
    722         for site in self.siteController.siteMaps.IterSitesByKeys(sitetype.ANOMALY, sitetype.STRUCTURE):
    723             if site.targetID == targetID:
    724                 return (site.siteID, site.groupID)
    725 
    726         return (None, None)
    727 
    728     def OnClientEvent_JumpStarted(self, *args):
    729         self._Hide()
    730 
    731     def OnClientEvent_JumpExecuted(self, itemID):
    732         self._Show()
    733         self._InitiateSensorSweep()