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)