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

michelle.py (67881B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\remote\michelle.py
      2 from __future__ import print_function
      3 import math
      4 import sys
      5 from operator import attrgetter
      6 import stackless
      7 import blue
      8 import collections
      9 import decometaclass
     10 import destiny
     11 import dogma.data
     12 import evetypes
     13 import geo2
     14 import log
     15 import logging
     16 import logmodule
     17 import telemetry
     18 import uthread
     19 import uthread2
     20 from ballparkCommon.parkhelpers import HasWarpInPoint
     21 from ballparkCommon.parkhelpers.warpSubjects import WarpSubjects
     22 from carbon.common.script.sys.service import Service
     23 from carbon.common.script.sys.serviceConst import ROLE_ANY, ROLEMASK_VIEW, SERVICE_RUNNING, SERVICE_START_PENDING
     24 from carbon.common.script.util.commonutils import IsFullLogging
     25 from carbon.common.script.util.format import FmtDate, FmtSec
     26 from destructionEffect import destructionType as destructionEffectType
     27 from eve.client.script.remote import ballpark_signals
     28 from eve.client.script.ui.shared.fitting.fittingController import FittingController
     29 from eve.client.script.ui.shared.fleet.fleetFormations import FORMATION_SKILL, FORMATION_DISTANCE_SKILL
     30 from eve.client.script.ui.view.viewStateConst import ViewOverlay
     31 from eve.common.lib import appConst as const
     32 from eve.common.script.net import eveMoniker
     33 from eve.common.script.sys import eveCfg, idCheckers
     34 from eve.common.script.sys.idCheckers import IsNPC
     35 from eve.common.script.util.slimItem import SlimItem
     36 from eveInflight.damageStateValue import CalculateCurrentDamageStateValues
     37 from evefleet.client.util import GetFormationSelected, GetFormationSpacingSelected, GetFormationSizeSelected
     38 from eveprefs import prefs
     39 from gametime import Timer
     40 from inventorycommon.util import IsModularShip
     41 from spacecomponents.client.factory import COMPONENTS
     42 from spacecomponents.client.messages import MSG_ON_SLIM_ITEM_UPDATED, MSG_ON_ADDED_TO_SPACE, MSG_ON_REMOVED_FROM_SPACE
     43 from spacecomponents.common.componentregistry import ComponentRegistry
     44 from utillib import KeyVal
     45 VISIBILITY_RANGE = 50000000
     46 DESTINY_MODE_ORBIT = 4
     47 stdlog = logging.getLogger(__name__)
     48 
     49 def GetMichelle():
     50     return sm.GetService('michelle')
     51 
     52 
     53 class Michelle(Service):
     54     __guid__ = 'svc.michelle'
     55     __exportedcalls__ = {'AddBallpark': [ROLE_ANY],
     56      'RemoveBallpark': [ROLE_ANY],
     57      'GetBallpark': [ROLE_ANY],
     58      'GetRemotePark': [ROLE_ANY],
     59      'GetBallparkForScene': [ROLE_ANY],
     60      'GetBall': [ROLE_ANY],
     61      'GetItem': [ROLE_ANY],
     62      'GetDroneState': [ROLE_ANY],
     63      'GetDrones': [ROLE_ANY],
     64      'AreDronesOut': [ROLE_ANY],
     65      'Refresh': [ROLE_ANY],
     66      'GetCharIDFromShipID': [ROLE_ANY],
     67      'GetFleetState': [ROLE_ANY],
     68      'AdminGetBubbleID': [ROLEMASK_VIEW]}
     69     __notifyevents__ = ['DoDestinyUpdate',
     70      'DoDestinyUpdates',
     71      'OnFleetStateChange',
     72      'OnDroneStateChange',
     73      'OnDroneActivityChange',
     74      'OnAudioActivated',
     75      'DoSimClockRebase',
     76      'OnPrimaryViewChanged',
     77      'OnSpaceWhiteOutStart',
     78      'OnAsteroidRevealed',
     79      'OnAsteroidTerminated']
     80     __dependencies__ = ['machoNet',
     81      'dataconfig',
     82      'crimewatchSvc',
     83      'godma',
     84      'viewState',
     85      'abyss',
     86      'audio']
     87     __startupdependencies__ = ['dungeonTracking', 'dotWeaponSvc']
     88 
     89     def Run(self, ms):
     90         self.state = SERVICE_START_PENDING
     91         self.__bp = None
     92         self.bpReady = False
     93         self.quit = 0
     94         self.scenes = {}
     95         self.ballNotInParkErrors = {}
     96         self.handledBallErrors = set()
     97         self.ballQueue = uthread.Queue()
     98         uthread.pool('michelle:ballDispatcher', self.Dispatcher)
     99         self.componentRegistryDependencies = None
    100         self.state = SERVICE_RUNNING
    101 
    102     def OnSpaceWhiteOutStart(self, solarsystemID):
    103         self.LogWarn('michelle.OnSpaceWhiteOutStart', solarsystemID)
    104         if not session.shipid:
    105             return
    106         ballpark = self.GetBallpark()
    107         if not ballpark:
    108             return
    109         ballpark.OnSpecialFX(session.shipid, None, None, None, None, 'effects.WhiteOut', 0, 1, 0, duration=60000)
    110 
    111     def OnPrimaryViewChanged(self, oldView, newView):
    112         if oldView and oldView.name == 'inflight' and newView.name == 'structure':
    113             self.RemoveBallpark()
    114         self.UpdateBallpark()
    115 
    116     def UpdateBallpark(self):
    117         if session.solarsystemid and self.viewState.IsPrimaryViewActive('inflight', 'structure'):
    118             if self.__bp and self.__bp.solarsystemID != session.solarsystemid:
    119                 self.RemoveBallpark()
    120             if self.__bp is None:
    121                 sm.GetService('space')
    122                 self.AddBallpark(session.solarsystemid)
    123             sm.ScatterEvent('OnEnterSpace')
    124         else:
    125             self.RemoveBallpark()
    126         if self.__bp is None:
    127             sm.GetService('sceneManager').UnregisterScene('default')
    128             sm.GetService('sceneManager').CleanupSpaceResources()
    129             blue.recycler.Clear()
    130 
    131     @telemetry.ZONE_METHOD
    132     def DetectAndFixMissingHudModules(self):
    133         try:
    134             t = uthread2.StartTasklet(self._DetectAndFixMissingHudModules)
    135             t.context = 'michelle.py::DetectAndFixMissingHudModules (EVE-120280)'
    136         except Exception as e:
    137             log.LogException("Exception in Unnar's magical _DetectAndFixMissingHudModules. Thank god we're not taking that seriously. %s" % e)
    138 
    139     def _DetectAndFixMissingHudModules(self):
    140 
    141         def _IsPassiveModule(typeID):
    142             effects = dogma.data.get_type_effects(typeID)
    143             activeEffectsList = []
    144             for type_effect in effects:
    145                 effect = dogma.data.get_effect(type_effect.effectID)
    146                 if type_effect.isDefault and effect.effectName != 'online' and effect.effectCategory in (const.dgmEffActivation, const.dgmEffTarget):
    147                     activeEffectsList.append(type_effect)
    148 
    149             return len(activeEffectsList) == 0
    150 
    151         def _GetReducedModuleList(listName, modules, attrgetters):
    152             ret = []
    153             godma = sm.StartService('godma')
    154             reportString = ''
    155             for module in modules:
    156                 itemID = attrgetters['itemID'](module)
    157                 typeID = attrgetters['typeID'](module)
    158                 flagID = attrgetters['flagID'](module)
    159                 reportString += '\nModule %s (%s):\n' % (itemID, evetypes.GetName(typeID))
    160                 godmaItem = None
    161                 if not isinstance(itemID, tuple):
    162                     godmaItem = godma.GetItem(itemID)
    163                 if isinstance(itemID, tuple) or godmaItem and godmaItem.typeID != typeID:
    164                     newItemIDReport = 'Checking if I need to substitute parent module itemID.... %s\n'
    165                     itemWrapper = godma.GetItem(session.shipid)
    166                     sloccupants = itemWrapper.GetSlotOccupants(flagID)
    167                     for sloccupant in sloccupants:
    168                         if sloccupant.flagID == flagID and sloccupant.typeID == typeID and sloccupant.itemID != itemID:
    169                             newItemIDReport = newItemIDReport % 'itemID %s -> %s' % (itemID, sloccupant.itemID)
    170                             reportString += newItemIDReport
    171                             itemID = sloccupant.itemID
    172                             godmaItem = godma.GetItem(itemID)
    173                             break
    174                     else:
    175                         newItemIDReport = newItemIDReport % 'Nope!'
    176                         reportString += newItemIDReport
    177 
    178                 if not godmaItem:
    179                     reportString += 'ABORT: No godma item found!\n'
    180                     continue
    181                 if not const.flagLoSlot0 <= flagID <= const.flagHiSlot7:
    182                     reportString += 'ABORT: Is not a module!\n'
    183                     continue
    184                 if clientDogmaLM.IsModuleSlave(itemID, session.shipid):
    185                     reportString += 'ABORT: Is a slave module!\n'
    186                     continue
    187                 if evetypes.GetCategoryID(typeID) == const.categoryCharge:
    188                     reportString += 'ABORT: Is a charge category!\n'
    189                     continue
    190                 if _IsPassiveModule(typeID) and hidePassive:
    191                     reportString += 'ABORT: Is filtered by passive settings!\n'
    192                     continue
    193                 reportString += 'Added (%s, %s, %s)\n' % (itemID, typeID, flagID)
    194                 ret.append((itemID, typeID, flagID))
    195 
    196             ret.sort()
    197             return (ret, reportString)
    198 
    199         sleepUntil = blue.os.GetWallclockTime() + 10 * const.SEC
    200         log.LogInfo('_DetectAndFixMissingHudModules sleeping until %s' % FmtDate(sleepUntil))
    201         sessionWaitTimer = Timer(blue.os.GetWallclockTime, blue.synchro.SleepWallclock, const.MIN)
    202         sessionWaitTimer.SleepUntil(sleepUntil)
    203         if not self.viewState.IsPrimaryViewActive('inflight'):
    204             log.LogInfo('_DetectAndFixMissingHudModules slept until player was no longer in space. Aborting check!')
    205             return
    206         log.LogInfo('_DetectAndFixMissingHudModules done sleeping! Checking HUD module state against clientdogma!')
    207         hidePassive = not settings.user.ui.Get('showPassiveModules', True)
    208         shipUI = sm.GetService('viewState').overlaysByName.get(ViewOverlay.ShipUI, None)
    209         if not shipUI:
    210             log.LogInfo('Viewstate not found. Bailing.')
    211             return
    212         clientDogmaLM = sm.GetService('clientDogmaIM').GetDogmaLocation()
    213         hudController = getattr(shipUI, 'controller', None)
    214         slotsContainer = getattr(shipUI, 'slotsContainer', None)
    215         if not hudController or not slotsContainer:
    216             log.LogInfo("No controller found in shipUI. We're not in space yet!")
    217             return
    218         if not hudController.IsLoaded():
    219             return
    220         attrgetters = {'itemID': attrgetter('itemID'),
    221          'typeID': attrgetter('typeID'),
    222          'flagID': attrgetter('flagID')}
    223         dogmaModules, dogmaReport = _GetReducedModuleList('dogmaModules', FittingController(session.shipid).GetFittedModules(), attrgetters)
    224         godmaModules, godmaReport = _GetReducedModuleList('godmaModules', hudController.GetModules(), attrgetters)
    225         attrgetters = {'itemID': attrgetter('id'),
    226          'typeID': attrgetter('moduleinfo.typeID'),
    227          'flagID': attrgetter('moduleinfo.flagID')}
    228         shipModuleButtons, shipModuleReport = _GetReducedModuleList('shipUIButtons', [ x for x in shipUI.slotsContainer.modulesByID.itervalues() ], attrgetters)
    229         if dogmaModules != godmaModules:
    230             dogmaReport = '\n---------------- %s ----------------\n=>%s\n%s' % ('dogmaModules', dogmaModules, dogmaReport)
    231             godmaReport = '\n---------------- %s ----------------\n=>%s\n%s' % ('godmaModules', godmaModules, godmaReport)
    232             log.LogTraceback('_DetectAndFixMissingHudModules found missing modules in spaceUI data! I will attempt to refresh godma items and redraw!\n%s %s' % (dogmaReport, godmaReport))
    233             sm.GetService('godma').GetStateManager().ForcePrimeLocation([session.shipid])
    234             shipUI.SetupShip()
    235         elif dogmaModules != shipModuleButtons:
    236             dogmaReport = '\n---------------- %s ----------------\n=>%s\n%s' % ('dogmaModules', dogmaModules, dogmaReport)
    237             godmaReport = '\n---------------- %s ----------------\n=>%s\n%s' % ('godmaModules', godmaModules, godmaReport)
    238             shipModuleReport = '\n---------------- %s ----------------\n=>%s\n%s' % ('shipUIButtons', shipModuleButtons, shipModuleReport)
    239             log.LogTraceback('_DetectAndFixMissingHudModules found missing modules in spaceUI view. I will attempt to redraw!\n%s%s%s' % (dogmaReport, godmaReport, shipModuleReport))
    240             shipUI.SetupShip()
    241 
    242     def OnFleetStateChange(self, fleetState):
    243         if self.__bp is not None:
    244             self.__bp.OnFleetStateChange(fleetState)
    245 
    246     def OnDroneStateChange(self, droneID, ownerID, controllerID, activityState, droneTypeID, controllerOwnerID, targetID):
    247         if self.__bp is not None:
    248             self.__bp.OnDroneStateChange(droneID, ownerID, controllerID, activityState, droneTypeID, controllerOwnerID, targetID)
    249 
    250     def OnDroneActivityChange(self, droneID, activityID, activity):
    251         if self.__bp is not None:
    252             self.__bp.OnDroneActivityChange(droneID, activityID, activity)
    253 
    254     def OnAsteroidRevealed(self, asteroidID):
    255         self.SetControllerVariableOnBall(asteroidID, 'IsHighlighted', True)
    256         self.audio.SendUIEvent('res_wars_power_up_play')
    257         self.audio.SendUIEvent('voc_rw_aura_sourceuncovered_aura_play')
    258 
    259     def OnAsteroidTerminated(self, asteroidID):
    260         self.audio.SendUIEvent('res_wars_power_down_play')
    261         self.audio.SendUIEvent('voc_rw_aura_sourcedepleted_aura_play')
    262         self.SetControllerVariableOnBall(asteroidID, 'IsHighlighted', False)
    263 
    264     def GetFleetState(self):
    265         if self.__bp is None:
    266             return
    267         if session.fleetid is None:
    268             return
    269         if self.__bp.fleetState is None:
    270             self.__bp.fleetState = self.GetRemotePark().GetFleetState()
    271         return self.__bp.fleetState
    272 
    273     def Refresh(self):
    274         if self.__bp is not None:
    275             self.__bp.Refresh()
    276 
    277     def RequestReset(self):
    278         if self.__bp is not None:
    279             self.__bp.RequestReset()
    280 
    281     def Stop(self, ms):
    282         if self.__bp is not None:
    283             self.__bp.Release()
    284             self.__bp = None
    285         self.ballQueue.non_blocking_put(None)
    286         self.quit = 1
    287 
    288     def AddBallpark(self, solarsystemID):
    289         self.LogNotice('Adding ballpark', solarsystemID)
    290         self.bpReady = False
    291         if self.__bp is not None:
    292             self.RemoveBallpark()
    293         self.__bp = blue.classes.CreateInstance('destiny.Ballpark')
    294         self.LogInfo('GetBallpark1 object', self.__bp, 'now has:', sys.getrefcount(self.__bp), 'references')
    295         Park(self.__bp, {'broker': self,
    296          'solarsystemID': long(solarsystemID)})
    297         self.LogInfo('GetBallpark2 object', self.__bp, 'now has:', sys.getrefcount(self.__bp), 'references')
    298         formations = sm.RemoteSvc('beyonce').GetFormations()
    299         self.__bp.LoadFormations(formations)
    300         self.ballNotInParkErrors = {}
    301         self.handledBallErrors = set()
    302         self.__bp.SetBallNotInParkCallback(self.HandleBallNotInParkError)
    303         sm.ScatterEvent('OnAddBallpark')
    304         self.__bp.Start()
    305         self.bpReady = True
    306         self.LogNotice('Done adding ballpark', solarsystemID)
    307         return self.__bp
    308 
    309     def RemoveBallpark(self):
    310         if self.__bp is not None:
    311             self.ballQueue.clear()
    312             self.__bp.Release()
    313             self.__bp = None
    314             self.bpReady = False
    315 
    316     def GetBallpark(self, doWait = False):
    317         if self.bpReady:
    318             return self.__bp
    319         elif not doWait:
    320             return None
    321         tries = self.WaitForBallpark()
    322         if not self.bpReady:
    323             logstring = 'Failed to get a valid ballpark in time after trying %d times' % tries
    324             self.LogError(logstring)
    325             if session.charid:
    326                 uthread.new(sm.ProxySvc('clientStatLogger').LogString, logstring)
    327             return None
    328         else:
    329             return self.__bp
    330 
    331     def WaitForBallpark(self):
    332         WAIT_TIME = 1
    333         MAX_TRIES = 30
    334         tries = 0
    335         while not self.bpReady and tries < MAX_TRIES:
    336             self.LogInfo('Waiting for ballpark', tries)
    337             tries = tries + 1
    338             blue.pyos.synchro.SleepSim(WAIT_TIME * 1000)
    339 
    340         return tries
    341 
    342     def GetRemotePark(self):
    343         if self.__bp is None:
    344             return
    345         return self.__bp.remoteBallpark
    346 
    347     def GetBallparkForScene(self, scene):
    348         self.LogInfo('GetBallpark object', self.__bp, 'now has:', sys.getrefcount(self.__bp), 'references')
    349         if not self.__bp:
    350             return None
    351         if self.__bp not in self.scenes:
    352             self.scenes[self.__bp] = []
    353         self.scenes[self.__bp].append(scene)
    354         return self.__bp
    355 
    356     def GetBall(self, id):
    357         if self.__bp is not None:
    358             return self.__bp.GetBall(id)
    359 
    360     def GetBalls(self):
    361         if self.__bp is not None:
    362             return self.__bp.balls.values()
    363 
    364     def GetBallsAndSlimItems(self):
    365         if self.__bp is not None:
    366             return self.__bp.GetBallsAndItems()
    367 
    368     def GetSlimItem(self, item_id):
    369         if self.__bp is not None:
    370             return self.__bp.GetSlimItem(item_id)
    371 
    372     def GetBallAndWaitForModel(self, id):
    373         ball = self.GetBall(id)
    374         if ball:
    375             maxYieldCount = 100
    376             currentYieldCount = 0
    377             while getattr(ball, 'model', None) is None and currentYieldCount != maxYieldCount:
    378                 blue.synchro.Yield()
    379                 currentYieldCount += 1
    380 
    381         return ball
    382 
    383     def GetItem(self, id):
    384         if self.__bp is not None:
    385             return self.__bp.GetInvItem(id)
    386 
    387     def IsPreparingWarp(self):
    388         ball = self.GetBall(session.shipid)
    389         return ball and ball.mode == destiny.DSTBALL_WARP and ball.effectStamp < 0
    390 
    391     def InWarp(self):
    392         ball = self.GetBall(session.shipid)
    393         return ball and ball.mode == destiny.DSTBALL_WARP
    394 
    395     def IsBallVisible(self, id):
    396         if self.__bp is not None:
    397             return self.__bp.IsBallVisible(id)
    398         else:
    399             return False
    400 
    401     def GetMyPosition(self):
    402         ballpark = self.GetBallpark(doWait=True)
    403         if ballpark is None or not ballpark.ego:
    404             return
    405         myBallID = ballpark.ego
    406         myBall = ballpark.GetBall(myBallID)
    407         return (myBall.x, myBall.y, myBall.z)
    408 
    409     def GetDroneState(self, droneID):
    410         if self.__bp is not None:
    411             return self.__bp.stateByDroneID.get(droneID, None)
    412 
    413     def GetDroneActivity(self, droneID):
    414         if self.__bp is not None:
    415             return self.__bp.activityByDrone.get(droneID, None)
    416 
    417     def GetDrones(self):
    418         if self.__bp is not None:
    419             return self.__bp.stateByDroneID
    420 
    421     def AreDronesOut(self):
    422         ballpark = sm.GetService('michelle').GetBallpark()
    423         drones = self.GetDrones()
    424         if drones:
    425             inSpace = [ drones[droneID] for droneID in drones if droneID in ballpark.slimItems and (drones[droneID].ownerID == session.charid or drones[droneID].controllerID == session.shipid) ]
    426             return len(inSpace) > 0
    427         else:
    428             return False
    429 
    430     def DoDestinyUpdate(self, state, waitForBubble, dogmaMessages = []):
    431         self.LogInfo('DoDestinyUpdate call for tick', state[0][0], 'containing', len(state), 'updates.  waitForBubble=', waitForBubble)
    432         if self.__bp is None:
    433             return
    434         if dogmaMessages:
    435             self.LogInfo('OnMultiEvent has', len(dogmaMessages), 'messages')
    436             sm.ScatterEvent('OnMultiEvent', dogmaMessages)
    437         expandedStates = []
    438         for action in state:
    439             if action[1][0] == 'PackagedAction':
    440                 try:
    441                     unpackagedActions = blue.marshal.Load(action[1][1])
    442                     expandedStates.extend(unpackagedActions)
    443                 except Exception:
    444                     log.LogException('Exception whilst expanding a PackagedAction')
    445                     sys.exc_clear()
    446 
    447             else:
    448                 expandedStates.append(action)
    449 
    450         state = expandedStates
    451         timestamps = {action[0] for action in state}
    452         if len(timestamps) > 1:
    453             self.LogError('Found update batch with', len(state), 'items and', len(timestamps), 'timestamps')
    454             for action in state:
    455                 self.LogError('Action:', action)
    456 
    457             sm.GetService('clientStatsSvc').OnFatalDesync()
    458         self.__bp.FlushState(state, waitForBubble)
    459 
    460     def DoDestinyUpdates(self, updates):
    461         self.LogInfo('DoDestinyUpdates call, count=', len(updates))
    462         for idx, args in enumerate(updates):
    463             self.LogInfo('DoDestinyUpdate(s)', idx, '/', len(updates))
    464             if len(args) == 2:
    465                 state, waitForBubble = args
    466                 self.DoDestinyUpdate(state, waitForBubble)
    467             else:
    468                 state, waitForBubble, dogmaMessages = args
    469                 self.DoDestinyUpdate(state, waitForBubble, dogmaMessages)
    470 
    471     def GetCharIDFromShipID(self, shipID):
    472         if self.__bp is None:
    473             return
    474         if shipID not in self.__bp.slimItems:
    475             return
    476         slimItem = self.__bp.slimItems[shipID]
    477         return slimItem.charID
    478 
    479     def GetRelativity(self):
    480         lpark = self.GetBallpark()
    481         ball = self.GetBall(session.shipid)
    482         presTime = lpark.currentTime
    483         pretTime = blue.os.GetWallclockTime()
    484         prerTime = blue.os.GetWallclockTimeNow()
    485         preX = ball.x
    486         preY = ball.y
    487         preZ = ball.z
    488         diff, x, y, z, sTime, tTime, rTime = self.GetRemotePark().GetRelativity(preX, preY, preZ, presTime, pretTime, prerTime)
    489         ldiff = math.sqrt((preX - x) ** 2 + (preY - y) ** 2 + (preZ - z) ** 2)
    490         postsTime = lpark.currentTime
    491         posttTime = blue.os.GetWallclockTime()
    492         postrTime = blue.os.GetWallclockTimeNow()
    493         pdiff = math.sqrt((ball.x - x) ** 2 + (ball.y - y) ** 2 + (ball.z - z) ** 2)
    494         print('------------------------------------------------------------------------------------')
    495         print('Ticks: client(%s) to server(%s) = %s, server(%s) to client(%s) = %s' % (presTime,
    496          sTime,
    497          sTime - presTime,
    498          sTime,
    499          postsTime,
    500          postsTime - sTime))
    501         print('Time: client to server %s, server to client %s. Total time to handle call %s' % (FmtSec(rTime - prerTime), FmtSec(postrTime - rTime), FmtSec(postrTime - prerTime)))
    502         print('pre diff %s on server %s, post diff %s' % (ldiff, diff, pdiff))
    503         if sTime - postsTime > 0:
    504             print('We are behind the server, lets catch up')
    505             last = lpark.currentTime
    506             while sTime - lpark.currentTime >= 0:
    507                 if last != lpark.currentTime:
    508                     last = lpark.currentTime
    509                     print('pos diff at step %s is %s' % (lpark.currentTime, math.sqrt((ball.x - x) ** 2 + (ball.y - y) ** 2 + (ball.z - z) ** 2)))
    510                 blue.pyos.synchro.Yield()
    511 
    512         elif sTime - postsTime == 0:
    513             print('client and server match, as the client should be ahead there is some lag')
    514         else:
    515             print('We are ahead of the server (at least when the call had come back), to handle this case we need the pos history for the last 10 ticks or so')
    516 
    517     def Dispatcher(self):
    518         while self.state in (SERVICE_RUNNING, SERVICE_START_PENDING):
    519             try:
    520                 orders = None
    521                 orders = self.ballQueue.get()
    522                 if orders is None:
    523                     return
    524                 self.ProcessDispatchOrders(orders)
    525             except Exception:
    526                 self.LogError('In michelle.Dispatcher')
    527                 log.LogException()
    528                 sys.exc_clear()
    529 
    530     def ProcessDispatchOrders(self, orders):
    531         ownersToPrime, tickersToPrime, allyTickersToPrime, stuffToAdd, newState, locationsToPrime = orders
    532         if locationsToPrime:
    533             try:
    534                 cfg.evelocations.Prime(locationsToPrime)
    535             except Exception:
    536                 self.LogError('In michelle.Dispatcher')
    537                 log.LogException()
    538                 sys.exc_clear()
    539 
    540         if ownersToPrime:
    541             try:
    542                 cfg.eveowners.Prime(ownersToPrime)
    543             except Exception:
    544                 self.LogError('In michelle.Dispatcher')
    545                 log.LogException()
    546                 sys.exc_clear()
    547 
    548         if tickersToPrime:
    549             try:
    550                 cfg.corptickernames.Prime(tickersToPrime)
    551             except Exception:
    552                 self.LogError('In michelle.Dispatcher')
    553                 log.LogException()
    554                 sys.exc_clear()
    555 
    556         if allyTickersToPrime:
    557             try:
    558                 cfg.allianceshortnames.Prime(allyTickersToPrime)
    559             except Exception:
    560                 self.LogError('In michelle.Dispatcher')
    561                 log.LogException()
    562                 sys.exc_clear()
    563 
    564         realBalls = {}
    565         for ballID, slimItem in stuffToAdd:
    566             try:
    567                 if self.__bp and ballID in self.__bp.balls:
    568                     ball = self.__bp.balls[ballID]
    569                     if ballID not in self.__bp.slimItems:
    570                         realBalls[ballID] = (ball, slimItem)
    571                     self.__bp.slimItems[ballID] = slimItem
    572             except Exception:
    573                 self.LogError('In michelle.Dispatcher')
    574                 log.LogException()
    575                 sys.exc_clear()
    576 
    577         if len(realBalls):
    578             t = stackless.getcurrent()
    579             timer = t.PushTimer(blue.pyos.taskletTimer.GetCurrent() + '::DoBallsAdded')
    580             balls_added = realBalls.values()
    581             sm.SendEvent('DoBallsAdded', balls_added)
    582             t.PopTimer(timer)
    583             ballpark_signals.on_balls_added(balls_added)
    584         if newState is not None:
    585             t = stackless.getcurrent()
    586             timer = t.PushTimer(blue.pyos.taskletTimer.GetCurrent() + '::OnNewState')
    587             sm.ScatterEvent('OnNewState', newState)
    588             t.PopTimer(timer)
    589 
    590     def HandleBallNotInParkError(self, ball):
    591         reportBallNotInPark = prefs.GetValue('reportBallNotInPark', False)
    592         if not reportBallNotInPark:
    593             return
    594         now = blue.os.GetWallclockTime()
    595         ballID = ball.id
    596         if ballID in self.ballNotInParkErrors and ballID not in self.handledBallErrors:
    597             diff = now - self.ballNotInParkErrors[ballID]
    598             if 5 * const.SEC < diff:
    599                 if not self.logChannel.IsOpen(logmodule.LGERR):
    600                     print('Ball', ballID, 'not in park!')
    601                 else:
    602                     self.LogError('-----------------------------------------------------------------------------------')
    603                     self.LogError('BALL NOT IN PARK:', ballID)
    604                     self.LogError('-----------------------------------------------------------------------------------')
    605                     self.LogError('Ball has not been in park for', diff / const.SEC, 'seconds.')
    606                     self.LogError("Probable cause is trinity graphics that haven't been cleaned up.")
    607                     self.LogError('Ball Info:')
    608                     self.LogError('\tBall deco type:', getattr(ball, '__class__', '?'))
    609                     self.LogError('\tModel:', getattr(ball, 'model', None))
    610                     self.LogError('\tExploded:', getattr(ball, 'exploded', '?'))
    611                     if ball.ballpark is None:
    612                         self.LogError('\tNot in any ballpark')
    613                     else:
    614                         self.LogError('\tIn Ballpark:', getattr(ball.ballpark, 'solarsystemID', '?'))
    615                     slimItem = self.GetItem(ball.id)
    616                     if slimItem:
    617                         self.LogError('Slim Item Info:')
    618                         self.LogError('\tType:', slimItem.typeID)
    619                     self.LogError('Checking Scene')
    620                     scene = sm.GetService('sceneManager').GetActiveScene()
    621                     if scene is not None:
    622                         for obj in scene.objects:
    623                             if len([ foundBall for foundBall in obj.Find('destiny.ClientBall') if foundBall == ball ]):
    624                                 if hasattr(obj, 'display'):
    625                                     obj.display = 0
    626                                 if hasattr(obj, 'update'):
    627                                     obj.update = 0
    628                                 self.LogError('\tAttached to', obj.__bluetype__, 'named', getattr(obj, 'name', '?'), ' in Scene')
    629 
    630                 self.handledBallErrors.add(ballID)
    631                 del self.ballNotInParkErrors[ballID]
    632         else:
    633             self.ballNotInParkErrors[ballID] = now
    634 
    635     def OnAudioActivated(self):
    636         if self.__bp is not None:
    637             self.__bp.OnAudioActivated()
    638 
    639     def DoSimClockRebase(self, times):
    640         oldSimTime, newSimTime = times
    641         if self.__bp:
    642             self.__bp.AdjustTimes(newSimTime - oldSimTime)
    643 
    644     def IsPositionWithinWarpDistance(self, position):
    645         ballpark = self.GetBallpark(doWait=True)
    646         if ballpark is None:
    647             self.LogWarn('IsPositionWithinWarpDistance did not get a ballpark')
    648             return False
    649         egoBall = ballpark.GetBall(ballpark.ego)
    650         if not egoBall:
    651             return False
    652         distance = geo2.Vec3Distance((egoBall.x, egoBall.y, egoBall.z), position)
    653         return distance > const.minWarpDistance
    654 
    655     @telemetry.ZONE_METHOD
    656     def InitializeComponentRegistryDependencies(self):
    657         self.componentRegistryDependencies = KeyVal(componentClassTypes=COMPONENTS, typeIDToClassMapping={}, asyncFuncs=KeyVal(GetWallclockTime=blue.os.GetWallclockTime, SleepWallclock=blue.pyos.synchro.SleepWallclock, GetSimTime=blue.os.GetSimTime, SleepSim=blue.pyos.synchro.SleepSim, TimeDiffInMs=blue.os.TimeDiffInMs, UThreadNew=uthread.new))
    658 
    659     def CreateComponentRegistry(self):
    660         if self.componentRegistryDependencies is None:
    661             self.InitializeComponentRegistryDependencies()
    662         return ComponentRegistry(self.componentRegistryDependencies.asyncFuncs, None, self.componentRegistryDependencies.componentClassTypes, self.componentRegistryDependencies.typeIDToClassMapping)
    663 
    664     def SetControllerVariableOnBall(self, ballID, name, value):
    665         ball = self.GetBall(ballID)
    666         if ball:
    667             ball.SetControllerVariable(name, value)
    668 
    669     def CmdWarpToStuff(self, subject, subjectID, **kwargs):
    670         self.SetFleetFormationSettings(kwargs)
    671         bp = self.GetRemotePark()
    672         if bp:
    673             bp.CmdWarpToStuff(subject, subjectID, **kwargs)
    674 
    675     def CmdWarpToExternalDungeon(self, dungeonInstanceIdentifier, **kwargs):
    676         self.CmdWarpToStuff(WarpSubjects.EXTERNAL_DUNGEON, dungeonInstanceIdentifier, **kwargs)
    677 
    678     def CanWarpToOperationSite(self, siteID):
    679         bp = self.GetRemotePark()
    680         if bp:
    681             try:
    682                 position = bp.GetOperationSpawnpointPosition(siteID)
    683             except Exception:
    684                 return False
    685 
    686             return self.IsPositionWithinWarpDistance(position)
    687 
    688     def SetFleetFormationSettings(self, kwargs):
    689         if kwargs.get('fleet', False):
    690             skillSvc = sm.GetService('skills')
    691             kwargs['fleetFormationSettings'] = {'formationType': GetFormationSelected(skillSvc.MySkillLevel(FORMATION_SKILL)),
    692              'formationSpacing': GetFormationSpacingSelected(skillSvc.MySkillLevel(FORMATION_DISTANCE_SKILL)),
    693              'formationSize': GetFormationSizeSelected(skillSvc.MySkillLevel(FORMATION_DISTANCE_SKILL))}
    694 
    695     def AdminGetBubbleID(self, itemID):
    696         return sm.RemoteSvc('beyonce').AdminGetBubbleID(itemID)
    697 
    698 
    699 class BallQueueData():
    700 
    701     def __init__(self):
    702         self.stuffToAdd = []
    703         self.ownersToPrime = []
    704         self.tickersToPrime = []
    705         self.allyTickersToPrime = []
    706         self.locationsToPrime = []
    707 
    708 
    709 class Park(decometaclass.WrapBlueClass('destiny.Ballpark')):
    710     __guid__ = 'michelle.Park'
    711     __nonpersistvars__ = ['states',
    712      'broker',
    713      'lastStamp',
    714      'dirty',
    715      'remoteBallpark',
    716      'slimItems',
    717      'damageState',
    718      'solItem',
    719      'validState',
    720      'history']
    721     __persistdeco__ = 0
    722     __categoryRequireOwnerPrime__ = None
    723     __predefs__ = 0
    724 
    725     def __init__(self):
    726         if not self.__predefs__:
    727             self.__predefs__ = 1
    728             self.__categoryRequireOwnerPrime__ = (const.categoryShip, const.categoryDrone)
    729         self.tickInterval = const.simulationTimeStep
    730         self.lastStamp = -1
    731         self.dirty = 0
    732         self.states = []
    733         self.slimItems = {}
    734         self.damageState = {}
    735         self.stateByDroneID = {}
    736         self.componentRegistry = None
    737         self.fleetState = None
    738         self.solItem = None
    739         self.validState = 0
    740         self.history = []
    741         self.latestSetStateTime = 0
    742         self.shouldRebase = False
    743         self.activityByDrone = {}
    744         self.clientBallNextID = -8000000000000000000L
    745         self.initRemoteBallparkThread = uthread2.StartTasklet(self.InitializeRemoteBallpark)
    746         self.scatterEvents = {'Orbit',
    747          'GotoDirection',
    748          'WarpTo',
    749          'SetBallRadius',
    750          'GotoPoint',
    751          'SetBallInteractive',
    752          'SetBallFree',
    753          'SetBallHarmonic',
    754          'FollowBall',
    755          'Stop'}
    756         self.nonDestinyCriticalFunctions = {'OnDamageStateChange',
    757          'OnSpecialFX',
    758          'OnFleetDamageStateChange',
    759          'OnShipStateUpdate',
    760          'OnSlimItemChange',
    761          'OnDroneStateChange',
    762          'OnSovereigntyChanged',
    763          'OnDbuffUpdated',
    764          'OnClientControllerEvent',
    765          'OnDotVictimUpdated'}
    766         self.localActions = {'AddBalls',
    767          'AddBalls2',
    768          'RemoveBalls',
    769          'SetState',
    770          'RemoveBall',
    771          'TerminalPlayDestructionEffect'}
    772 
    773     def __del__(self):
    774         self.remoteBallpark = None
    775         self.broker = None
    776         self.states = []
    777 
    778     def _UpdateStateRequestThread(self):
    779         if self.remoteBallpark is not None:
    780             self.remoteBallpark.UpdateStateRequest()
    781 
    782     def RequestReset(self):
    783         self.validState = False
    784         self.FlushSimulationHistory(newBaseSnapshot=False)
    785         uthread.pool('michelle::UpdateStateRequest', self._UpdateStateRequestThread)
    786 
    787     def DoPostTick(self, stamp):
    788         if self.shouldRebase:
    789             self.FlushSimulationHistory()
    790             self.shouldRebase = False
    791         elif stamp > self.lastStamp + 10:
    792             self.StoreState()
    793 
    794     def DumpHistory(self):
    795         if not self.broker.logChannel.IsOpen(logmodule.LGINFO):
    796             return
    797         self.broker.LogInfo('------ History Dump', self.currentTime, '-------')
    798         rev = self.history[:]
    799         rev.reverse()
    800         for state, waitForBubble in rev:
    801             self.broker.LogInfo('state waiting:', ['no', 'yes'][waitForBubble])
    802             lastState = None
    803             lastStateCount = 0
    804             for entry in state:
    805                 eventStamp, event = entry
    806                 funcName, args = event
    807                 nextState = ['[',
    808                  eventStamp,
    809                  ']',
    810                  funcName]
    811                 if nextState != lastState:
    812                     if lastState is not None:
    813                         if lastStateCount != 1:
    814                             lastState.append('(x %d)' % lastStateCount)
    815                         self.broker.LogInfo(*lastState)
    816                     lastState = nextState
    817                     lastStateCount = 1
    818                 else:
    819                     lastStateCount += 1
    820 
    821             if lastState is not None:
    822                 if lastStateCount != 1:
    823                     lastState.append('(x %d)' % lastStateCount)
    824                 self.broker.LogInfo(*lastState)
    825             self.broker.LogInfo(' ')
    826 
    827     def DoPreTick(self, stamp):
    828         while len(self.history) > 0:
    829             state, waitForBubble = self.history[0]
    830             if waitForBubble:
    831                 return
    832             eventStamp = state[0][0]
    833             if eventStamp > self.currentTime and eventStamp - self.currentTime < 3:
    834                 break
    835             self.RealFlushState(state)
    836             del self.history[0]
    837             if self.validState and self.shouldRebase:
    838                 self.StoreState(midTick=True)
    839             if len(self.history) > 1:
    840                 if self.history[0][1]:
    841                     return
    842                 self._parent_Evolve()
    843 
    844     def StoreState(self, midTick = False):
    845         if self.dirty or not self.isRunning:
    846             return
    847         ms = blue.MemStream()
    848         self.WriteFullStateToStream(ms)
    849         self.states.append([ms, self.currentTime, midTick])
    850         self.broker.LogInfo('StoreState:', self.currentTime, 'midTick:', midTick)
    851         if len(self.states) > 10:
    852             self.states = self.states[:1] + self.states[3:3] + self.states[-5:]
    853         self.lastStamp = self.currentTime
    854 
    855     def _CleanUpRemoteBallpark(self):
    856         self.initRemoteBallparkThread.Kill()
    857         if self.remoteBallpark is not None:
    858             self.remoteBallpark.Unbind()
    859         self.remoteBallpark = None
    860 
    861     def Release(self):
    862         sm.ScatterEvent('OnReleaseBallpark')
    863         self._parent_Pause()
    864         subwaysvc = sm.GetService('subway')
    865         maxTimeToWait = 10000
    866         timeWaited = 0
    867         if subwaysvc.InJump():
    868             while not subwaysvc.OkToDumpBallPark() and timeWaited < maxTimeToWait:
    869                 blue.synchro.SleepWallclock(100)
    870                 timeWaited += 100
    871 
    872         self.RemoveBalls(self.balls.iterkeys(), isRelease=True)
    873         self._parent_ClearAll()
    874         self.FlushSimulationHistory(newBaseSnapshot=False)
    875         if self in self.broker.scenes:
    876             for scene in self.broker.scenes[self]:
    877                 if scene and hasattr(scene, 'ballpark'):
    878                     scene.ballpark = None
    879 
    880             del self.broker.scenes[self]
    881         self.validState = False
    882         self.solItem = None
    883         self._CleanUpRemoteBallpark()
    884         self.slimItems = {}
    885         self.damageState = {}
    886         self.activityByDrone = {}
    887         self.history = []
    888         self.latestSetStateTime = 0
    889 
    890     def SetState(self, bag):
    891         self.componentRegistry = self.broker.CreateComponentRegistry()
    892         self.stateByDroneID = {d.droneID:KeyVal(droneID=d.droneID, ownerID=d.ownerID, locationID=session.solarsystemid, controllerID=d.controllerID, activityState=d.activityState, typeID=d.typeID, controllerOwnerID=d.controllerOwnerID, targetID=d.targetID) for d in bag.droneState}
    893         sm.SendEvent('DoBallClear', bag.solItem)
    894         self.ClearAll()
    895         ms = blue.MemStream()
    896         ms.Write(bag.state)
    897         self._parent_ReadFullStateFromStream(ms)
    898         self.ego = long(bag.ego)
    899         self._parent_Start()
    900         self.slimItems = {}
    901         ballQueueData = BallQueueData()
    902         for slimItem in bag.slims:
    903             if type(slimItem) is int:
    904                 raise TypeError
    905             if slimItem.itemID not in self.balls:
    906                 raise RuntimeError('BallNotInPark', slimItem.itemID, slimItem)
    907             self.ProcessBallAdd(slimItem, ballQueueData)
    908 
    909         self.validState = True
    910         self.FlushSimulationHistory()
    911         self.QueueBallData(ballQueueData, newState=self)
    912         now = blue.os.GetSimTime()
    913         self.damageState = {}
    914         for k, v in bag.damageState.iteritems():
    915             self.damageState[k] = (v, now)
    916 
    917         for effectInfo in bag.effectStates:
    918             self.OnSpecialFX(*effectInfo)
    919             self.ScatterEwars(*effectInfo)
    920 
    921         self.solItem = bag.solItem
    922         self.industryLevel = bag.industryLevel
    923         self.researchLevel = bag.researchLevel
    924         self.OnDbuffUpdated(self.ego, bag.dbuffState)
    925         sm.ScatterEvent('OnBallparkSetState')
    926         ballpark_signals.on_ballpark_reset()
    927 
    928     def QueueBallData(self, ballQueueData, newState = None):
    929         self.broker.ballQueue.non_blocking_put((ballQueueData.ownersToPrime,
    930          ballQueueData.tickersToPrime,
    931          ballQueueData.allyTickersToPrime,
    932          ballQueueData.stuffToAdd,
    933          newState,
    934          ballQueueData.locationsToPrime))
    935 
    936     def GetDamageState(self, itemID):
    937         if itemID not in self.damageState:
    938             return
    939         mainctx = blue.pyos.taskletTimer.EnterTasklet('Michelle::GetDamageState')
    940         try:
    941             state, time = self.damageState[itemID]
    942             if not state:
    943                 return
    944             ret = CalculateCurrentDamageStateValues(state, time)
    945             ret = ret + list(state[-2:])
    946         finally:
    947             blue.pyos.taskletTimer.ReturnFromTasklet(mainctx)
    948 
    949         return ret
    950 
    951     def DistanceBetween(self, srcID, dstID):
    952         dist = self.GetSurfaceDist(srcID, dstID)
    953         if dist is None:
    954             raise RuntimeError('DistanceBetween:: invalid balls', srcID, dstID)
    955         if dist < 0.0:
    956             dist = 0.0
    957         return dist
    958 
    959     def IsShipInRangeOfStructureControlTower(self, shipID, structureID):
    960         structureSlim = self.slimItems.get(structureID)
    961         if structureSlim is None:
    962             return False
    963         controlTowerID = None
    964         if structureSlim.groupID == const.groupControlTower:
    965             controlTowerID = structureID
    966         elif structureSlim.controlTowerID is not None:
    967             controlTowerID = structureSlim.controlTowerID
    968         if controlTowerID is None:
    969             return False
    970         towerSlim = self.slimItems.get(controlTowerID)
    971         if towerSlim is None:
    972             return False
    973         towerShieldRadius = self.broker.godma.GetStateManager().GetType(towerSlim.typeID).shieldRadius
    974         return self.GetCenterDist(controlTowerID, shipID) < towerShieldRadius
    975 
    976     def FlushSimulationHistory(self, newBaseSnapshot = True):
    977         self.broker.LogInfo('State history rebased at', self.currentTime, 'newBaseSnapshot', newBaseSnapshot)
    978         lastMidState = None
    979         if newBaseSnapshot and len(self.states):
    980             lastMidState = self.states[-1]
    981             if not lastMidState[2] or lastMidState[1] != self.currentTime - 1:
    982                 lastMidState = None
    983         self.states = []
    984         if newBaseSnapshot:
    985             if lastMidState:
    986                 self.states.append(lastMidState)
    987             self.StoreState()
    988             map(self.broker.LogInfo, ['State entry'], self.states)
    989 
    990     def SynchroniseToSimulationTime(self, stamp):
    991         self.broker.LogInfo('SynchroniseToSimulationTime looking for:', stamp, '- current:', self.currentTime)
    992         if stamp < self.currentTime:
    993             lastStamp = 0
    994             lastState = None
    995             for item in self.states:
    996                 if item[1] <= stamp:
    997                     lastStamp = item[1]
    998                     lastState = item[0]
    999                 else:
   1000                     continue
   1001 
   1002             if not lastState:
   1003                 self.broker.LogWarn('SynchroniseToSimulationTime: Did not find any state')
   1004                 return 0
   1005             self._parent_ReadFullStateFromStream(lastState, 1)
   1006         else:
   1007             lastStamp = self.currentTime
   1008         for i in range(stamp - lastStamp):
   1009             self._parent_Evolve()
   1010 
   1011         self.broker.LogInfo('SynchroniseToSimulationTime found:', self.currentTime)
   1012         return 1
   1013 
   1014     def FlushState(self, state, waitForBubble):
   1015         self.broker.LogInfo('Server Update with', len(state), 'event(s) added to history')
   1016         if len(state) == 0:
   1017             self.broker.LogWarn('Empty state received from remote ballpark')
   1018             return
   1019         if state[0][1][0] == 'SetState':
   1020             self.latestSetStateTime = state[0][0]
   1021             self.broker.LogInfo('Michelle received a SetState at time', self.latestSetStateTime, '. Clearing out-of-date entries...')
   1022             self.history = [ historyEntry for historyEntry in self.history if historyEntry[0][0][0] >= self.latestSetStateTime ]
   1023             self.DumpHistory()
   1024         else:
   1025             entryTime = state[0][0]
   1026             if entryTime < self.latestSetStateTime:
   1027                 self.broker.LogWarn('Michelle discarded a state that was too old', entryTime, ' < ', self.latestSetStateTime)
   1028                 self.DumpHistory()
   1029                 return
   1030         entriesByTime = collections.defaultdict(list)
   1031         for entry in state:
   1032             entriesByTime[entry[0]].append(entry)
   1033 
   1034         timeList = sorted(entriesByTime.iterkeys())
   1035         timeListIdx = 0
   1036         for historyIdx, historyTimeEntries in enumerate(self.history):
   1037             historyTime = historyTimeEntries[0][0][0]
   1038             entryTime = timeList[timeListIdx]
   1039             if historyTime < entryTime:
   1040                 waitForBubble = False
   1041                 continue
   1042             if historyTime == entryTime:
   1043                 historyTimeEntries[0].extend(entriesByTime[entryTime])
   1044                 historyTimeEntries[1] = False
   1045             else:
   1046                 self.history.insert(historyIdx, [entriesByTime[entryTime], waitForBubble])
   1047                 waitForBubble = False
   1048             timeListIdx += 1
   1049             if timeListIdx >= len(timeList):
   1050                 break
   1051 
   1052         for tlIdx in xrange(timeListIdx, len(timeList)):
   1053             self.history.append([entriesByTime[timeList[tlIdx]], waitForBubble])
   1054 
   1055         self.DumpHistory()
   1056 
   1057     def RealFlushState(self, state):
   1058         self.broker.LogInfo('Handling Server Update with', len(state), 'event(s)')
   1059         if len(state) == 0:
   1060             self.broker.LogWarn('Empty state received from remote ballpark')
   1061             return
   1062         eventStamp, event = state[0]
   1063         funcName, args = event
   1064         if funcName == 'SetState':
   1065             if IsFullLogging():
   1066                 self.broker.LogInfo('Action: %12.12s' % funcName, eventStamp, '- current:', self.currentTime, args)
   1067             self.SetState(*args)
   1068         if self.validState:
   1069             self.shouldRebase = False
   1070             synchronised = False
   1071             exploders = {x[1][1][0]:x[1][1][-1] for x in state if x[1][0] == 'TerminalPlayDestructionEffect'}
   1072             for action in state:
   1073                 eventStamp, event = action
   1074                 funcName, args = event
   1075                 if funcName == 'SetState':
   1076                     continue
   1077                 if IsFullLogging():
   1078                     self.broker.LogInfo('Action: %12.12s' % funcName, eventStamp, '- current:', self.currentTime, args)
   1079                 try:
   1080                     if funcName in self.nonDestinyCriticalFunctions:
   1081                         getattr(self, funcName)(*args)
   1082                     else:
   1083                         if not synchronised:
   1084                             synchronised = self.SynchroniseToSimulationTime(eventStamp)
   1085                         if not synchronised:
   1086                             sm.GetService('clientStatsSvc').OnRecoverableDesync()
   1087                             self.broker.LogWarn('Failed to synchronize to', eventStamp, 'Requesting new state')
   1088                             self.RequestReset()
   1089                             return
   1090                         self.shouldRebase = True
   1091                         if funcName in self.localActions:
   1092                             if funcName == 'RemoveBalls':
   1093                                 args = args + (exploders,)
   1094                             getattr(self, funcName)(*args)
   1095                         else:
   1096                             getattr(self, '_parent_' + funcName)(*args)
   1097                             if funcName in self.scatterEvents:
   1098                                 sm.ScatterEvent('OnBallparkCall', funcName, args)
   1099                             if funcName == 'CloakBall':
   1100                                 eventBallID = args[0]
   1101                                 if self.ego and self.ego != eventBallID:
   1102                                     self.RemoveBall(eventBallID)
   1103                 except Exception:
   1104                     message = '{} failed.'.format(funcName)
   1105                     stdlog.error(message, exc_info=1)
   1106                     sys.exc_clear()
   1107                     continue
   1108 
   1109         else:
   1110             self.broker.LogInfo('Events ignored')
   1111 
   1112     def TerminalPlayDestructionEffect(self, shipID, destructionEffectId):
   1113         self.SetDestructionEffectOnRemove(shipID, destructionEffectId)
   1114 
   1115     def SetDestructionEffectOnRemove(self, ballID, destructionEffectId):
   1116         ball = self.balls.get(ballID, None)
   1117         if not ball or not hasattr(ball, '__class__'):
   1118             return
   1119         ball.destructionEffectId = destructionEffectId if destructionEffectId is not None else destructionEffectType.EXPLOSION
   1120 
   1121     def ProcessBallAdd(self, slimItem, ballQueueData):
   1122         ball = self.balls[slimItem.itemID]
   1123         if slimItem.itemID > destiny.DSTLOCALBALLS:
   1124             ballQueueData.stuffToAdd.append((slimItem.itemID, slimItem))
   1125             if slimItem.categoryID in self.__categoryRequireOwnerPrime__:
   1126                 ballQueueData.ownersToPrime.append(slimItem.ownerID)
   1127                 if slimItem.charID is not None:
   1128                     ballQueueData.ownersToPrime.append(slimItem.charID)
   1129                     ballQueueData.ownersToPrime.append(slimItem.corpID)
   1130             if slimItem.corpID and slimItem.corpID not in ballQueueData.tickersToPrime:
   1131                 ballQueueData.tickersToPrime.append(slimItem.corpID)
   1132             if slimItem.allianceID and slimItem.allianceID not in ballQueueData.allyTickersToPrime:
   1133                 ballQueueData.allyTickersToPrime.append(slimItem.allianceID)
   1134             if idCheckers.IsCelestial(slimItem.itemID) or idCheckers.IsStargate(slimItem.itemID):
   1135                 ballQueueData.locationsToPrime.append(slimItem.itemID)
   1136             elif idCheckers.IsFakeItem(slimItem.itemID) and slimItem.nameID is not None:
   1137                 location = [slimItem.itemID,
   1138                  '',
   1139                  self.solarsystemID,
   1140                  ball.x,
   1141                  ball.y,
   1142                  ball.z,
   1143                  slimItem.nameID]
   1144                 cfg.evelocations.Hint(slimItem.itemID, location)
   1145             elif not (slimItem.categoryID == const.categoryAsteroid or slimItem.groupID == const.groupHarvestableCloud):
   1146                 location = [slimItem.itemID,
   1147                  slimItem.name,
   1148                  self.solarsystemID,
   1149                  ball.x,
   1150                  ball.y,
   1151                  ball.z,
   1152                  slimItem.nameID]
   1153                 cfg.evelocations.Hint(slimItem.itemID, location)
   1154         if self.componentRegistry.GetComponentClassesForTypeID(slimItem.typeID):
   1155             self.componentRegistry.CreateComponentInstances(slimItem.itemID, slimItem.typeID)
   1156             self.componentRegistry.SendMessageToItem(slimItem.itemID, MSG_ON_ADDED_TO_SPACE, slimItem)
   1157         sm.ScatterEvent('OnBallAdded', slimItem)
   1158 
   1159     def AddBalls(self, chunk):
   1160         state, slims, damageDict = chunk
   1161         ms = blue.MemStream()
   1162         ms.Write(state)
   1163         self._parent_ReadFullStateFromStream(ms, 2)
   1164         ballQueueData = BallQueueData()
   1165         for slimItem in slims:
   1166             if type(slimItem) is int:
   1167                 raise TypeError
   1168             if slimItem.itemID in self.slimItems:
   1169                 continue
   1170             self.ProcessBallAdd(slimItem, ballQueueData)
   1171 
   1172         self.QueueBallData(ballQueueData)
   1173         t = blue.os.GetSimTime()
   1174         for ballID, damage in damageDict.iteritems():
   1175             self.damageState[ballID] = (damage, t)
   1176 
   1177     def AddBalls2(self, chunk):
   1178         state, extraBallData = chunk
   1179         ms = blue.MemStream()
   1180         ms.Write(state)
   1181         self._parent_ReadFullStateFromStream(ms, 2)
   1182         ballQueueData = BallQueueData()
   1183         damageTime = blue.os.GetSimTime()
   1184         for data in extraBallData:
   1185             if type(data) is tuple:
   1186                 slimItemDict, damageState = data
   1187             else:
   1188                 slimItemDict = data
   1189                 damageState = None
   1190             slimItem = SlimItem()
   1191             slimItem.__dict__ = slimItemDict
   1192             self.damageState[slimItem.itemID] = (damageState, damageTime)
   1193             if slimItem.itemID in self.slimItems:
   1194                 continue
   1195             self.ProcessBallAdd(slimItem, ballQueueData)
   1196 
   1197         self.QueueBallData(ballQueueData)
   1198 
   1199     def AddClientSideBall(self, position, isGlobal = False):
   1200         x, y, z = position
   1201         ball = self.AddBall(self.clientBallNextID, 1.0, 0.0, 0, False, isGlobal, False, False, False, x, y, z, 0, 0, 0, 0, 0)
   1202         self.clientBallNextID -= 1
   1203         return ball
   1204 
   1205     def RemoveClientSideBall(self, ballID):
   1206         ball = self.GetBall(ballID)
   1207         self._parent_RemoveBall(ballID, 0)
   1208         if ball:
   1209             sm.SendEvent('DoClientSideBallRemove', ball)
   1210 
   1211     def UpdateClientSideBallPosition(self, ballID, position):
   1212         ball = self.GetBall(ballID)
   1213         if ball is not None:
   1214             ball.x, ball.y, ball.z = position
   1215 
   1216     def IsBallDeparting(self, ballID):
   1217         return False
   1218 
   1219     @telemetry.ZONE_METHOD
   1220     def RemoveBall(self, ballID, terminal = False):
   1221         self.broker.LogInfo('Removing ball', ballID, '(terminal)' if terminal else '')
   1222         ball = self.balls.get(ballID, None)
   1223         delay = 0
   1224         if hasattr(ball, 'KillBooster'):
   1225             ball.KillBooster()
   1226         if terminal and hasattr(ball, '__class__'):
   1227             delay = ball.GetTotalDestructionEffectTime()
   1228             ball.destructionEffectId = destructionEffectType.EXPLOSION
   1229         self._parent_RemoveBall(ballID, delay)
   1230         self.DeleteComponents(ballID)
   1231         if ballID in self.damageState:
   1232             del self.damageState[ballID]
   1233         if ballID in self.activityByDrone:
   1234             del self.activityByDrone[ballID]
   1235         if ballID not in self.slimItems:
   1236             return
   1237         slimItem = self.slimItems[ballID]
   1238         if ballID > destiny.DSTLOCALBALLS:
   1239             if ball is None:
   1240                 self.broker.LogWarn('DoBallRemove sending a None ball', slimItem, terminal)
   1241             sm.SendEvent('DoBallRemove', ball, slimItem, terminal)
   1242             ballpark_signals.on_ball_removed(ball, slimItem)
   1243         if ballID in self.slimItems:
   1244             del self.slimItems[ballID]
   1245 
   1246     @telemetry.ZONE_METHOD
   1247     def RemoveBalls(self, ballIDs, exploders = None, isRelease = False):
   1248         if exploders:
   1249             self.broker.LogInfo('RemoveBalls: Has exploders')
   1250             for ballID, destructionEffectId in exploders.iteritems():
   1251                 self.SetDestructionEffectOnRemove(ballID, destructionEffectId)
   1252 
   1253         pythonBalls = []
   1254         destinyBalls = []
   1255         for ballID in ballIDs:
   1256             ball = self.balls.get(ballID)
   1257             if ballID < 0:
   1258                 continue
   1259             if ball is None:
   1260                 continue
   1261             if hasattr(ball, 'KillBooster'):
   1262                 ball.KillBooster()
   1263             destinyBalls.append(ball)
   1264             if ballID in self.slimItems and ballID > destiny.DSTLOCALBALLS:
   1265                 terminal = getattr(ball, 'destructionEffectId', destructionEffectType.NONE) != destructionEffectType.NONE
   1266                 pythonBalls.append((ball, self.slimItems[ballID], terminal))
   1267 
   1268         sm.SendEvent('DoBallsRemove', pythonBalls, isRelease)
   1269         ballpark_signals.on_balls_removed(pythonBalls)
   1270         for ball in destinyBalls:
   1271             delay = 0
   1272             if not isRelease and hasattr(ball, 'IsTriggeredForDestruction') and ball.IsTriggeredForDestruction():
   1273                 delay = ball.GetTotalDestructionEffectTime()
   1274             self._parent_RemoveBall(ball.id, delay)
   1275             self.DeleteComponents(ball.id)
   1276 
   1277         for ballID in ballIDs:
   1278             if ballID in self.damageState:
   1279                 del self.damageState[ballID]
   1280             if ballID in self.activityByDrone:
   1281                 del self.activityByDrone[ballID]
   1282             if ballID in self.slimItems:
   1283                 del self.slimItems[ballID]
   1284 
   1285     def DeleteComponents(self, itemID):
   1286         if self.componentRegistry is None:
   1287             return
   1288         self.componentRegistry.SendMessageToItem(itemID, MSG_ON_REMOVED_FROM_SPACE)
   1289         try:
   1290             self.componentRegistry.DeleteComponentInstances(itemID)
   1291         except KeyError:
   1292             pass
   1293 
   1294     def GetBallsAndItems(self):
   1295         ballList = []
   1296         for ball in self.balls.itervalues():
   1297             if ball.id in self.slimItems:
   1298                 ballList.append((ball, self.slimItems[ball.id]))
   1299 
   1300         return ballList
   1301 
   1302     def GetBall(self, ballID):
   1303         return self.balls.get(ballID, None)
   1304 
   1305     def GetBallById(self, ballID):
   1306         return self.GetBall(ballID)
   1307 
   1308     def GetOrbitTarget(self):
   1309         myShipID = session.shipid
   1310         if myShipID:
   1311             myShipBall = self.GetBall(myShipID)
   1312             if myShipBall and myShipBall.mode == DESTINY_MODE_ORBIT:
   1313                 return myShipBall.followId
   1314 
   1315     def GetWarpinPoint(self, ballID):
   1316         ball = self.GetBall(ballID)
   1317         item = self.GetInvItem(ballID)
   1318         if item.groupID == const.groupPlanet:
   1319             return eveCfg.GetPlanetWarpInPoint(ballID, (ball.x, ball.y, ball.z), ball.radius)
   1320         elif item.groupID == const.groupSun:
   1321             return eveCfg.GetSunWarpInPoint(ballID, (ball.x, ball.y, ball.z), ball.radius)
   1322         else:
   1323             return eveCfg.GetWarpInPoint(ballID, (ball.x, ball.y, ball.z), ball.radius)
   1324 
   1325     def GetWarpinPoints(self):
   1326         balls = []
   1327         for ball in self.globals.itervalues():
   1328             ballItem = self.GetInvItem(ball.id)
   1329             if ballItem is None:
   1330                 continue
   1331             if HasWarpInPoint(ballItem, ball.radius):
   1332                 balls.append((ball, ballItem, self.GetWarpinPoint(ball.id)))
   1333 
   1334         return balls
   1335 
   1336     def OnFleetStateChange(self, fleetState):
   1337         self.broker.LogInfo('OnFleetStateChange', fleetState)
   1338         self.fleetState = fleetState
   1339 
   1340     def GetLootRights(self, objectID):
   1341         if objectID in self.slimItems:
   1342             slim = self.slimItems[objectID]
   1343             return getattr(slim, 'lootRights', None)
   1344 
   1345     def IsAbandoned(self, objectID):
   1346         if objectID in self.slimItems:
   1347             slim = self.slimItems[objectID]
   1348             lootRights = getattr(slim, 'lootRights', None)
   1349             if lootRights is not None:
   1350                 ownerID, corpID, fleetID, abandoned = lootRights
   1351                 return abandoned
   1352         return False
   1353 
   1354     def HaveLootRight(self, objectID):
   1355         if objectID in self.slimItems:
   1356             slim = self.slimItems[objectID]
   1357             if session.charid == slim.ownerID:
   1358                 return True
   1359             if IsNPC(slim.ownerID):
   1360                 return True
   1361             lootRights = getattr(slim, 'lootRights', None)
   1362             if lootRights is not None:
   1363                 ownerID, corpID, fleetID, abandoned = lootRights
   1364                 if abandoned:
   1365                     return True
   1366                 if session.charid == ownerID:
   1367                     return True
   1368                 if not idCheckers.IsNPCCorporation(session.corpid) and session.corpid in (ownerID, corpID):
   1369                     return True
   1370                 if session.fleetid is not None and session.fleetid == fleetID:
   1371                     return True
   1372                 if self.broker.crimewatchSvc.CanAttackFreely(slim):
   1373                     return True
   1374             elif not idCheckers.IsNPCCorporation(session.corpid) and session.corpid == slim.ownerID:
   1375                 return True
   1376         return False
   1377 
   1378     def GetTypeIDsAndFlagIDsForModules(self, modules):
   1379         return {(typeID, flagID) for _, typeID, flagID in modules}
   1380 
   1381     def IsSubSystemChange(self, oldSlim, newSlim):
   1382         if not IsModularShip(newSlim.typeID):
   1383             return False
   1384         return self.GetTypeIDsAndFlagIDsForModules(oldSlim.modules) != self.GetTypeIDsAndFlagIDsForModules(newSlim.modules)
   1385 
   1386     def OnSlimItemChange(self, itemID, newSlim):
   1387         if itemID in self.slimItems:
   1388             oldSlim = self.slimItems[itemID]
   1389             self.slimItems[itemID] = newSlim
   1390             sm.ScatterEvent('OnSlimItemChange', oldSlim, newSlim)
   1391             ball = self.GetBall(itemID)
   1392             if ball is not None:
   1393                 if hasattr(ball, 'OnSlimItemUpdated'):
   1394                     ball.OnSlimItemUpdated(newSlim)
   1395                 if self.IsSubSystemChange(oldSlim, newSlim):
   1396                     ball.OnSubSystemChanged(newSlim)
   1397             self.componentRegistry.SendMessageToItem(itemID, MSG_ON_SLIM_ITEM_UPDATED, newSlim)
   1398 
   1399     def OnDroneStateChange(self, itemID, ownerID, controllerID, activityState, typeID, controllerOwnerID, targetID):
   1400         if session.charid != ownerID and session.shipid != controllerID:
   1401             if itemID in self.stateByDroneID:
   1402                 del self.stateByDroneID[itemID]
   1403             if itemID in self.activityByDrone:
   1404                 del self.activityByDrone[itemID]
   1405             sm.ScatterEvent('OnDroneControlLost', itemID)
   1406             return
   1407         state = self.stateByDroneID.get(itemID, None)
   1408         if state is None:
   1409             oldActivityState = None
   1410             self.stateByDroneID[itemID] = KeyVal(droneID=itemID, ownerID=ownerID, locationID=session.solarsystemid, controllerID=controllerID, activityState=activityState, typeID=typeID, controllerOwnerID=controllerOwnerID, targetID=targetID)
   1411         else:
   1412             state.ownerID = ownerID
   1413             state.controllerID = controllerID
   1414             state.locationID = session.solarsystemid
   1415             state.controllerOwnerID = controllerOwnerID
   1416             oldActivityState = state.activityState
   1417             state.activityState = activityState
   1418             state.targetID = targetID
   1419         sm.ScatterEvent('OnDroneStateChange2', itemID, oldActivityState, activityState)
   1420 
   1421     def OnDroneActivityChange(self, droneID, activityID, activity):
   1422         if not activity:
   1423             if droneID in self.activityByDrone:
   1424                 del self.activityByDrone[droneID]
   1425         else:
   1426             self.activityByDrone[droneID] = (activity, activityID)
   1427 
   1428     def OnShipStateUpdate(self, shipState):
   1429         self.broker.LogInfo('OnModuleAttributeChange', shipState)
   1430         for moduleID, moduleState in shipState.iteritems():
   1431             sm.ScatterEvent('OnModuleAttributeChange', *moduleState)
   1432 
   1433     def OnDamageStateChange(self, shipID, damageState):
   1434         if IsFullLogging():
   1435             self.broker.LogInfo('OnDamageStateChange', shipID, damageState)
   1436         else:
   1437             self.broker.LogInfo('OnDamageStateChange')
   1438         self.damageState[shipID] = (damageState, blue.os.GetSimTime())
   1439         sm.ScatterEvent('OnDamageStateChange', shipID, self.GetDamageState(shipID))
   1440 
   1441     def OnFleetDamageStateChange(self, shipID, damageState):
   1442         self.broker.LogInfo('OnFleetDamageStateChange', shipID, damageState)
   1443         self.damageState[shipID] = (damageState, blue.os.GetSimTime())
   1444         sm.ScatterEvent('OnFleetDamageStateChange', shipID, self.GetDamageState(shipID))
   1445 
   1446     def OnSpecialFX(self, shipID, moduleID, moduleTypeID, targetID, otherTypeID, guid, isOffensive, start, active, duration = -1, repeat = None, startTime = None, timeFromStart = 0, graphicInfo = None):
   1447         if isinstance(moduleID, collections.Iterable):
   1448             for m in moduleID:
   1449                 sm.ScatterEvent('OnSpecialFX', shipID, m, moduleTypeID, targetID, otherTypeID, guid, isOffensive, start, active, duration, repeat, startTime, timeFromStart, graphicInfo)
   1450 
   1451         else:
   1452             sm.ScatterEvent('OnSpecialFX', shipID, moduleID, moduleTypeID, targetID, otherTypeID, guid, isOffensive, start, active, duration, repeat, startTime, timeFromStart, graphicInfo)
   1453 
   1454     def ScatterEwars(self, shipID, moduleID, moduleTypeID, targetID, otherTypeID, guid, isOffensive, start, active, duration = -1, repeat = None, startTime = None, timeFromStart = 0, graphicInfo = None):
   1455         if isinstance(moduleID, collections.Iterable):
   1456             for m in moduleID:
   1457                 sm.ScatterEvent('OnEwarOnConnect', shipID, m, moduleTypeID, targetID, guid)
   1458 
   1459         else:
   1460             sm.ScatterEvent('OnEwarOnConnect', shipID, moduleID, moduleTypeID, targetID, guid)
   1461 
   1462     def OnSovereigntyChanged(self, *args):
   1463         sm.ScatterEvent('OnSovereigntyChanged', *args)
   1464 
   1465     def OnDbuffUpdated(self, shipID, dbuffState):
   1466         sm.GetService('dbuffClient').incomingDbuffTracker.OnDbuffUpdated(shipID, dbuffState)
   1467 
   1468     def OnDotVictimUpdated(self, targetID, dmgAppsInfo):
   1469         sm.GetService('dotWeaponSvc').incomingDotTracker.OnDotWeaponsUpdated(targetID, dmgAppsInfo)
   1470 
   1471     def OnClientControllerEvent(self, ballID, targetID, eventName, value = None, delay = None):
   1472         sm.GetService('space').OnClientControllerEvent(ballID, targetID, eventName, value, delay)
   1473 
   1474     def GetInvItem(self, id):
   1475         return self.slimItems.get(id, None)
   1476 
   1477     def GetSlimItem(self, item_id):
   1478         return self.GetInvItem(item_id)
   1479 
   1480     def GetSlimItemsByID(self):
   1481         return self.slimItems
   1482 
   1483     def OnAudioActivated(self):
   1484         for ball in self.balls.itervalues():
   1485             if ball and hasattr(ball, 'SetupAmbientAudio'):
   1486                 ball.SetupAmbientAudio()
   1487 
   1488     def OnActivatingWarp(self, srcID, stamp):
   1489         ball = self.GetBall(srcID)
   1490         if ball is not None:
   1491             if hasattr(ball, 'EnterWarp'):
   1492                 ball.EnterWarp()
   1493 
   1494     def OnDeactivatingWarp(self, srcID, stamp):
   1495         ball = self.GetBall(srcID)
   1496         if ball is not None:
   1497             if hasattr(ball, 'ExitWarp'):
   1498                 ball.ExitWarp()
   1499 
   1500     def IsBallVisible(self, ballID):
   1501         if not self.ego:
   1502             return False
   1503         if ballID == self.ego:
   1504             return True
   1505         try:
   1506             distance = self.GetSurfaceDist(self.ego, ballID)
   1507             if distance is not None and distance < VISIBILITY_RANGE:
   1508                 return True
   1509             return False
   1510         except RuntimeError:
   1511             return False
   1512 
   1513     def IsTypeInRange(self, typeID, radius, operator):
   1514         for ballID in self.balls.iterkeys():
   1515             if ballID == self.ego:
   1516                 continue
   1517             item = self.GetInvItem(ballID)
   1518             if item is None or item.typeID != typeID:
   1519                 continue
   1520             distance = self.GetSurfaceDist(self.ego, ballID)
   1521             if distance is not None and operator(distance, radius):
   1522                 return True
   1523 
   1524         return False
   1525 
   1526     def GetItemsAndDistanceByCategory(self, categoryID, maxRadius):
   1527         ballsInRange = []
   1528         for ballID in self.balls.iterkeys():
   1529             if ballID == self.ego:
   1530                 continue
   1531             item = self.GetInvItem(ballID)
   1532             if item is None or item.categoryID != categoryID:
   1533                 continue
   1534             distance = self.GetSurfaceDist(self.ego, ballID)
   1535             if distance is not None and distance <= maxRadius:
   1536                 ballsInRange.append((distance, item))
   1537 
   1538         return sorted(ballsInRange)
   1539 
   1540     def GetBallsInRange(self, fromID, radius):
   1541         balls = []
   1542         for ballID in self.balls.iterkeys():
   1543             if ballID == fromID:
   1544                 continue
   1545             distance = self.GetSurfaceDist(fromID, ballID)
   1546             if distance is not None and distance < radius:
   1547                 balls.append(ballID)
   1548 
   1549         return balls
   1550 
   1551     def IterBallIdsByDistance(self, fromID, radius, predicate = None, allowOwnShip = False):
   1552         for ballID in self.balls.iterkeys():
   1553             if not allowOwnShip and ballID == fromID:
   1554                 continue
   1555             if predicate and not predicate(ballID):
   1556                 continue
   1557             distance = self.GetSurfaceDist(fromID, ballID)
   1558             if distance is None:
   1559                 continue
   1560             if not radius or distance < radius:
   1561                 yield (distance, ballID)
   1562 
   1563     def FindClosestBallIdByTypeId(self, fromID, typeID, radius):
   1564         ballIdsByDistance = sorted(self.IterBallIdsByDistance(fromID, radius, predicate=self.GetBallPredicate('typeID', typeID)))
   1565         if len(ballIdsByDistance) == 0:
   1566             return None
   1567         _, closestBallId = ballIdsByDistance[0]
   1568         return closestBallId
   1569 
   1570     def GetBallPredicate(self, key, value):
   1571 
   1572         def predicate(ballID):
   1573             item = self.GetInvItem(ballID)
   1574             return getattr(item, key, None) == value
   1575 
   1576         return predicate
   1577 
   1578     def GetComponentRegistry(self):
   1579         return self.componentRegistry
   1580 
   1581     def InitializeRemoteBallpark(self):
   1582         if self.remoteBallpark is None:
   1583             tries = triesRemaining = 10
   1584             while triesRemaining:
   1585                 try:
   1586                     self.remoteBallpark = eveMoniker.GetBallPark(self.solarsystemID)
   1587                     self.remoteBallpark.Bind()
   1588                     return
   1589                 except:
   1590                     blue.synchro.SleepWallclock(1000)
   1591                     triesRemaining -= 1
   1592                     stdlog.exception('Failed to connect to remote ballpark. Retrying')
   1593 
   1594             logstring = 'Failed to connect to remote ballpark in time after trying %d times' % tries
   1595             stdlog.error(logstring)