autopilot.py (33223B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\parklife\autopilot.py 2 import itertools 3 import sys 4 from collections import defaultdict 5 import utillib 6 from carbon.common.script.sys.service import Service 7 from carbon.common.script.util.timerstuff import AutoTimer 8 from eve.common.lib import appConst as const 9 from eve.client.script.ui.services.menuSvcExtras import movementFunctions 10 import destiny 11 import uthread 12 import blue 13 import log 14 import localization 15 import geo2 16 from carbonui import uiconst 17 from eve.client.script.ui.services.menuSvcExtras.movementFunctions import WarpToItem 18 from carbonui.uicore import uicore 19 from eve.common.script.sys import eveCfg, idCheckers 20 from eve.common.script.sys.idCheckers import IsTriglavianSystem 21 from eveexceptions import ExceptionEater, UserError 22 from eveservices.menu import GetMenuService 23 from player_messaging.client.ui_message import prompt_player_fsd_dialog 24 from structures import SERVICE_JUMP_BRIDGE 25 from stargate.client.jump_checks import check_cancel_stargate_jump 26 AUTO_NAVIGATION_LOOP_INTERVAL_MS = 2000 27 28 class AutoPilot(Service): 29 __guid__ = 'svc.autoPilot' 30 __exportedcalls__ = {'SetOn': [], 31 'SetOff': [], 32 'GetState': []} 33 __notifyevents__ = ['OnBallparkCall', 34 'OnSessionChanged', 35 'OnRemoteMessage', 36 'OnPlayerPodRespawn', 37 'OnCharacterSessionChanged', 38 'OnSessionReset', 39 'OnMapShowsJumpGatesChanged'] 40 __dependencies__ = ['michelle', 41 'starmap', 42 'clientPathfinderService', 43 'structureDirectory'] 44 45 def __init__(self): 46 Service.__init__(self) 47 self.updateTimer = None 48 self.updateJumpGatesTimer = None 49 self.autopilot = 0 50 self.ignoreTimerCycles = 0 51 self.isOptimizing = False 52 self.__navigateSystemDestinationItemID = None 53 self.__navigateSystemThread = None 54 self.enabledTimestamp = 0 55 uthread.new(self.UpdateWaypointsThread).context = 'autoPilot::UpdateWaypointsThread' 56 57 def UpdateWaypointsThread(self): 58 blue.pyos.synchro.SleepWallclock(2000) 59 starmapSvc = sm.GetService('starmap') 60 waypoints = starmapSvc.GetWaypoints() 61 if len(waypoints): 62 loopCounter = 0 63 while loopCounter < 5 and (not sm.IsServiceRunning('clientPathfinderService') or not sm.GetService('clientPathfinderService').initialized): 64 loopCounter += 1 65 blue.pyos.synchro.SleepWallclock(1000) 66 67 starmapSvc.SetWaypoints(waypoints) 68 69 def Run(self, memStream = None): 70 Service.Run(self, memStream) 71 self.StartTimer() 72 73 def DoLogAutopilotEvent(self, columnNames, *args): 74 if sm.GetService('machoNet').GetGlobalConfig().get('disableAutopilotLogging', 0): 75 return 76 try: 77 sm.ProxySvc('eventLog').LogClientEvent('autopilot', columnNames, *args) 78 except UserError: 79 pass 80 81 def SetOn(self): 82 if self.autopilot == 1: 83 return 84 if not eveCfg.InShip(): 85 return 86 self.autopilot = 1 87 self.CancelSystemNavigation() 88 sm.ScatterEvent('OnAutoPilotOn') 89 prompt_player_fsd_dialog('AutoPilotEnabled') 90 self.KillTimer() 91 self.StartTimer() 92 self.LogNotice('Autopilot Enabled') 93 self.enabledTimestamp = blue.os.GetWallclockTimeNow() 94 with ExceptionEater('eventLog'): 95 starmapSvc = sm.GetService('starmap') 96 waypoints = starmapSvc.GetWaypoints() 97 destinationID = waypoints[-1] if waypoints else None 98 routeData = starmapSvc.GetAutopilotRoute() 99 routeData = routeData or [] 100 solarSystemIDs = ','.join([ str(s) for s in routeData ]) 101 routeType = starmapSvc.GetRouteType() 102 avoidPodKill = settings.char.ui.Get('pfAvoidPodKill', 0) 103 avoidTriglavianTales = settings.char.ui.Get('pfAvoidTriglavianTales', 0) 104 stopAtEachWaypoint = settings.user.ui.Get('autopilot_stop_at_each_waypoint', False) 105 avoidSystems = settings.char.ui.Get('pfAvoidSystems', True) 106 avoidanceItems = self.clientPathfinderService.GetAvoidanceItems() 107 mandatoryAvoidanceItems = self.clientPathfinderService.GetMandatoryAvoidanceItems() 108 securityPenalty = settings.char.ui.Get('pfPenalty', 50.0) 109 uthread.new(self.DoLogAutopilotEvent, ['numWaypoints', 110 'numJumps', 111 'routeType', 112 'avoidSystems', 113 'avoidanceItems', 114 'mandatoryAvoidanceItems', 115 'securityPenalty', 116 'avoidPodKill', 117 'avoidTriglavianTales', 118 'stopAtEachWaypoint', 119 'destinationID', 120 'celestialID...'], 'Enabled', len(waypoints), len(routeData), routeType, avoidSystems, ','.join([ str(i) for i in avoidanceItems ]), ','.join([ str(i) for i in mandatoryAvoidanceItems ]), securityPenalty, avoidPodKill, avoidTriglavianTales, stopAtEachWaypoint, destinationID, solarSystemIDs) 121 122 def OnSessionChanged(self, isremote, session, change): 123 self.KillTimer() 124 self.ignoreTimerCycles = 3 125 self.StartTimer() 126 sm.GetService('starmap').UpdateRoute(fakeUpdate=True) 127 128 def SetOff(self, reason = ''): 129 if self.autopilot == 0: 130 self.KillTimer() 131 return 132 sm.ScatterEvent('OnAutoPilotOff') 133 self.autopilot = 0 134 if reason == ' - waypoint reached': 135 prompt_player_fsd_dialog('AutoPilotWaypointReached') 136 elif reason == ' - no destination path set': 137 prompt_player_fsd_dialog('AutoPilotDisabledNoPathSet') 138 else: 139 prompt_player_fsd_dialog('AutoPilotDisabled') 140 self.LogNotice('Autopilot Disabled', reason) 141 with ExceptionEater('eventLog'): 142 starmapSvc = sm.GetService('starmap') 143 numSeconds = (blue.os.GetWallclockTimeNow() - self.enabledTimestamp) / const.SEC 144 uthread.new(self.DoLogAutopilotEvent, ['secondsEnabled', 'reason'], 'Disabled', numSeconds, reason) 145 self.enabledTimestamp = 0 146 147 def OnRemoteMessage(self, msgID, *args, **kwargs): 148 if msgID == 'FleetWarp': 149 self.LogInfo('Canceling auto navigation due to fleet warp detected') 150 self.CancelSystemNavigation() 151 152 def OnPlayerPodRespawn(self): 153 self.CancelSystemNavigation() 154 155 def OnBallparkCall(self, functionName, args): 156 functions = ['GotoDirection', 'GotoPoint'] 157 if args[0] != session.shipid: 158 return 159 cancelAutoNavigation = False 160 if self.__navigateSystemDestinationItemID is None: 161 pass 162 elif functionName in {'GotoDirection', 'GotoPoint', 'Orbit'}: 163 cancelAutoNavigation = True 164 elif functionName == 'FollowBall' and self.__navigateSystemDestinationItemID != args[1]: 165 cancelAutoNavigation = True 166 elif functionName == 'WarpTo': 167 bp = sm.GetService('michelle').GetBallpark() 168 autoNavigationBall = bp.GetBall(self.__navigateSystemDestinationItemID) 169 if autoNavigationBall: 170 navDstVector = (autoNavigationBall.x, autoNavigationBall.y, autoNavigationBall.z) 171 dstVector = args[1:-2] 172 if navDstVector and dstVector and geo2.Vec3DistanceD(navDstVector, dstVector) - autoNavigationBall.radius > const.minWarpDistance: 173 cancelAutoNavigation = True 174 if cancelAutoNavigation: 175 self.LogInfo('Canceling auto navigation to', self.__navigateSystemDestinationItemID, 'as a respons to OnBallparkCall:', functionName, args) 176 self.CancelSystemNavigation() 177 if functionName in functions: 178 if functionName == 'GotoDirection' and self.gotoCount > 0: 179 self.gotoCount = 0 180 self.LogInfo('Autopilot gotocount set to 0') 181 return 182 if self.gotoCount == 0: 183 waypoints = sm.GetService('starmap').GetWaypoints() 184 if waypoints and self.IsStationOrStructure(waypoints[-1]): 185 return 186 self.SetOff(functionName + str(args)) 187 self.LogInfo('Autopilot stopped gotocount is ', self.gotoCount) 188 189 def GetState(self): 190 return self.autopilot 191 192 def Stop(self, stream): 193 self.KillTimer() 194 Service.Stop(self) 195 196 def KillTimer(self): 197 self.updateTimer = None 198 199 def StartTimer(self): 200 self.gotoCount = 0 201 self.updateTimer = AutoTimer(2000, self.Update) 202 self.updateJumpGatesTimer = AutoTimer(const.HOUR / const.MSEC, self.UpdateJumpGatesTimerThread) 203 204 def IsStationOrStructure(self, itemID): 205 if idCheckers.IsSolarSystem(itemID): 206 return False 207 return idCheckers.IsStation(itemID) or self.structureDirectory.GetStructureInfo(itemID) is not None 208 209 def GetGateOrStationID(self, bp, destinationID): 210 if idCheckers.IsSolarSystem(destinationID): 211 gateID = self._GetGateIDToSolarsystem(destinationID) 212 if gateID: 213 return gateID 214 if idCheckers.IsStation(destinationID): 215 return destinationID 216 slimItem = bp.GetInvItem(destinationID) 217 if slimItem and slimItem.categoryID == const.categoryStructure: 218 return destinationID 219 return self.GetGateOrStation(bp, destinationID)[0] 220 221 def _GetGateIDToSolarsystem(self, destinationID): 222 fromLocationID = session.solarsystemid2 223 return self.GetGateIDToSolarsystemFromLocationID(destinationID, fromLocationID) 224 225 def GetGateIDToSolarsystemFromLocationID(self, destinationID, fromLocationID, addWarning = True): 226 try: 227 destGateIds = cfg.mapSolarSystemContentCache[destinationID].stargates.keys() 228 for sgid, sg in cfg.mapSolarSystemContentCache[fromLocationID].stargates.iteritems(): 229 if sg.destination in destGateIds: 230 return sgid 231 232 if addWarning: 233 self.LogWarn('Autopilot: _GetGateIDToSolarsystem, did not find gate for destination', destinationID, fromLocationID) 234 except Exception: 235 self.LogException() 236 237 def GetGateOrStation(self, bp, destinationID, addWarning = True): 238 for solarsystemItem in sm.GetService('map').GetSolarsystemItems(session.solarsystemid2, False): 239 ballID = solarsystemItem.itemID 240 slimItem = bp.GetInvItem(ballID) 241 if slimItem is None: 242 continue 243 if slimItem.groupID == const.groupStargate and destinationID in map(lambda x: x.locationID, slimItem.jumps): 244 return (ballID, slimItem) 245 if slimItem.groupID == const.groupUpwellJumpGate: 246 if destinationID == getattr(slimItem, 'targetSolarsystemID', None): 247 return (ballID, slimItem) 248 elif destinationID == slimItem.itemID: 249 return (ballID, slimItem) 250 251 if addWarning: 252 self.LogWarn('Autopilot: GetGateOrStation, did not find destination', destinationID, session.solarsystemid2) 253 return (None, None) 254 255 def GetDestinationItemID(self): 256 destinationPath = sm.GetService('starmap').GetDestinationPath() 257 if len(destinationPath) > 0: 258 nextItemInRoute = destinationPath[0] 259 ballpark = sm.GetService('michelle').GetBallpark() 260 if ballpark and session.solarsystemid and ballpark.solarsystemID == session.solarsystemid: 261 destinationID, _ = self.GetGateOrStation(ballpark, nextItemInRoute, addWarning=False) 262 return destinationID 263 264 def Update(self): 265 if self.autopilot == 0: 266 self.KillTimer() 267 return 268 if self.ignoreTimerCycles > 0: 269 self.ignoreTimerCycles -= 1 270 return 271 if not session.IsItSafe(): 272 self.LogInfo('returning as it is not safe') 273 return 274 if not session.rwlock.IsCool(): 275 self.LogInfo("returning as the session rwlock isn't cool") 276 return 277 if not eveCfg.InShip(): 278 return 279 starmapSvc = sm.GetService('starmap') 280 destinationPath = starmapSvc.GetDestinationPath() 281 if len(destinationPath) == 0: 282 self.SetOff(' - no destination path set') 283 return 284 if destinationPath[0] is None: 285 if not starmapSvc.GetWaypoints(): 286 self.SetOff(' - no destination path set') 287 else: 288 self.SetOff(' - destination not in expected system.') 289 return 290 bp = sm.GetService('michelle').GetBallpark() 291 if not bp: 292 return 293 if sm.GetService('jumpQueue').IsJumpQueued(): 294 return 295 ship = bp.GetBall(session.shipid) 296 if ship is None: 297 return 298 if ship.mode == destiny.DSTBALL_WARP: 299 return 300 if bp.solarsystemID != session.solarsystemid: 301 self.LogError('autopilot - waiting a bit as we have not updated the ballpark', session.solarsystemid, bp.solarsystemID) 302 return 303 destID, destItem = self.GetGateOrStation(bp, destinationPath[0]) 304 if destID is None: 305 if len(destinationPath) == 1: 306 self.SetOff(' - destination not in expected system.') 307 return 308 jumpingToCelestial = not idCheckers.IsSolarSystem(destinationPath[0]) 309 theJump = None 310 if not jumpingToCelestial: 311 for jump in destItem.jumps: 312 if destinationPath[0] == jump.locationID: 313 theJump = jump 314 break 315 316 if destinationPath[0] and destinationPath[0] == getattr(destItem, 'targetSolarsystemID', None): 317 theJump = utillib.KeyVal(locationID=destItem.targetSolarsystemID, toCelestialID='-1') 318 if theJump is None and not jumpingToCelestial: 319 return 320 approachObject = bp.GetBall(destID) 321 if approachObject is None: 322 return 323 if jumpingToCelestial: 324 jumpToLocationName = cfg.evelocations.Get(destinationPath[0]).name 325 else: 326 jumpToLocationName = cfg.evelocations.Get(theJump.locationID).name 327 shipDestDistance = bp.GetSurfaceDist(ship.id, destID) 328 if shipDestDistance < const.maxStargateJumpingDistance and not jumpingToCelestial: 329 if not self._IsSessionAndBpSafeToJump(bp, ship): 330 return 331 try: 332 self.LogNotice('Autopilot jumping from', destID, 'to', theJump.toCelestialID, '(', jumpToLocationName, ')') 333 if not jumpingToCelestial: 334 if check_cancel_stargate_jump(destID, theJump.locationID, exclude_checks=['illicit_goods']): 335 self.SetOff() 336 return 337 if destItem.groupID == const.groupUpwellJumpGate: 338 didJumpThrough = sm.GetService('sessionMgr').PerformSessionChange('autopilot', sm.RemoteSvc('structureJumpBridgeMgr').CmdJumpThroughStructureStargate, destID) 339 if not didJumpThrough: 340 self.SetOff() 341 return 342 else: 343 gateRestricted = sm.GetService('gatejump').CheckForGateRestriction(session.solarsystemid, destID) 344 if gateRestricted: 345 self.SetOff(' - Gate Restricted') 346 return 347 sm.GetService('sessionMgr').PerformSessionChange('autopilot', sm.GetService('michelle').GetRemotePark().CmdStargateJump, destID, theJump.toCelestialID, session.shipid) 348 prompt_player_fsd_dialog('AutoPilotJumping', {'what': jumpToLocationName}) 349 sm.ScatterEvent('OnAutoPilotJump') 350 if IsTriglavianSystem(theJump.locationID): 351 self.ignoreTimerCycles = 13 352 else: 353 self.ignoreTimerCycles = 5 354 except UserError as e: 355 self._HandleJumpUserErrors(e, destID, jumpToLocationName) 356 except: 357 sys.exc_clear() 358 self.LogError('Autopilot: jumping to ' + jumpToLocationName + ' failed. Will try again') 359 self.ignoreTimerCycles = 5 360 361 return 362 if shipDestDistance < const.maxStargateJumpingDistance and destItem.groupID == const.groupUpwellJumpGate: 363 if not self._IsSessionAndBpSafeToJump(bp, ship): 364 return 365 try: 366 didJumpThrough = sm.GetService('menu').ReallyJumpThroughStructureJumpBridge(destID) 367 self.ignoreTimerCycles = 5 368 if didJumpThrough: 369 prompt_player_fsd_dialog('AutoPilotJumping', {'what': jumpToLocationName}) 370 sm.ScatterEvent('OnAutoPilotJump') 371 else: 372 self.SetOff() 373 except UserError as e: 374 self._HandleJumpUserErrors(e, destID, jumpToLocationName) 375 except: 376 sys.exc_clear() 377 self.LogError('Autopilot: jumping to ' + jumpToLocationName + ' failed. Will try again') 378 self.ignoreTimerCycles = 5 379 380 return 381 if jumpingToCelestial and shipDestDistance < const.maxDockingDistance and self.IsStationOrStructure(destID): 382 if self.__navigateSystemDestinationItemID != destID: 383 if shipDestDistance > 2500: 384 sm.GetService('audio').AudioMessage('msg_AutoPilotApproachingStation_play') 385 GetMenuService().Dock(destID) 386 self.ignoreTimerCycles = 5 387 elif shipDestDistance < const.minWarpDistance: 388 if ship.mode == destiny.DSTBALL_FOLLOW and ship.followId == destID: 389 return 390 self.AP_ApproachDestID(destID, jumpingToCelestial) 391 else: 392 self.AP_WarpToDestID(destID, jumpToLocationName, jumpingToCelestial) 393 394 def _IsSessionAndBpSafeToJump(self, bp, ship): 395 if ship.isCloaked: 396 return False 397 if session.mutating: 398 self.LogInfo('session is mutating') 399 return False 400 if session.changing: 401 self.LogInfo('session is changing') 402 return False 403 if bp.solarsystemID != session.solarsystemid: 404 self.LogInfo('bp.solarsystemid is not solarsystemid') 405 return False 406 if sm.GetService('michelle').GetRemotePark()._Moniker__bindParams != session.solarsystemid: 407 self.LogInfo('remote park moniker bindparams is not solarsystemid') 408 return False 409 return True 410 411 def _HandleJumpUserErrors(self, e, destID, jumpToLocationName): 412 if e.msg in ('SystemCheck_JumpFailed_Stuck', 'CantJumpEmanationLockedToAnotherGate'): 413 self.SetOff() 414 raise 415 elif e.msg.startswith('SystemCheck_JumpFailed_'): 416 prompt_player_fsd_dialog(e.msg, e.dict) 417 if e.msg == 'SystemCheck_JumpFailed_StandingsTooLow': 418 self.SetOff() 419 raise 420 elif e.msg == 'NotCloseEnoughToJump': 421 park = sm.GetService('michelle').GetRemotePark() 422 park.CmdSetSpeedFraction(1.0) 423 if uicore.layer.shipui.isopen: 424 uicore.layer.shipui.SetSpeed(1.0) 425 park.CmdFollowBall(destID, 0.0) 426 self.LogWarn("Autopilot: I thought I was close enough to jump, but I wasn't.") 427 elif e.msg in ('JumpNotEnoughCharge3', 'StructureServiceUnavailable', 'CannotUseJumpGateShipTooHeavy', 'NoLinkToDestination'): 428 self.SetOff() 429 raise 430 sys.exc_clear() 431 self.LogError('Autopilot: jumping to ' + jumpToLocationName + ' failed. Will try again') 432 self.ignoreTimerCycles = 5 433 434 def AP_ApproachDestID(self, destinationID, isFinal): 435 self.CancelSystemNavigation() 436 park = sm.GetService('michelle').GetRemotePark() 437 park.CmdSetSpeedFraction(1.0) 438 if uicore.layer.shipui.isopen: 439 uicore.layer.shipui.SetSpeed(1.0) 440 park.CmdFollowBall(destinationID, 0.0) 441 prompt_player_fsd_dialog('AutoPilotApproaching') 442 if not (isFinal and self.IsStationOrStructure(destinationID)): 443 sm.GetService('audio').AudioMessage('msg_AutoPilotApproaching_play') 444 self.LogInfo('Autopilot: approaching') 445 self.ignoreTimerCycles = 2 446 447 def AP_WarpToDestID(self, destinationID, name, isFinal): 448 try: 449 sm.GetService('space').WarpDestination(celestialID=destinationID) 450 sm.GetService('michelle').GetRemotePark().CmdWarpToStuffAutopilot(destinationID) 451 prompt_player_fsd_dialog('AutoPilotWarpingTo', {'what': name}) 452 if isFinal: 453 if self.IsStationOrStructure(destinationID): 454 sm.GetService('audio').AudioMessage('msg_AutoPilotWarpingToStation_play') 455 self.LogInfo('Autopilot: warping to celestial object', destinationID) 456 else: 457 sm.GetService('audio').AudioMessage('msg_AutoPilotWarpingTo_play') 458 self.LogInfo('Autopilot: warping to gate') 459 sm.ScatterEvent('OnAutoPilotWarp') 460 self.ignoreTimerCycles = 2 461 except UserError as e: 462 sys.exc_clear() 463 item = sm.GetService('godma').GetItem(session.shipid) 464 if item.warpScrambleStatus > 0: 465 self.SetOff('Autopilot cannot warp while warp scrambled.') 466 if 'WarpDisrupted' in e.msg: 467 self.SetOff('Autopilot cannot warp while warp scrambled by bubble.') 468 except Exception: 469 self.SetOff('Unknown error') 470 471 def NavigateSystemTo(self, itemID, interactionRange, commandFunc, *args, **kwargs): 472 self.LogInfo('Navigate to item', itemID, 'range', interactionRange, 'and execute', commandFunc) 473 self.__navigateSystemDestinationItemID = itemID 474 self.__navigateSystemThread = AutoTimer(50, self.__NavigateSystemTo, itemID, interactionRange, commandFunc, *args, **kwargs) 475 476 def CancelSystemNavigation(self): 477 self.LogInfo('Cancel system navigation') 478 self.__navigateSystemDestinationItemID = None 479 self.__navigateSystemThread = None 480 481 def IsSystemNavigationComplete(self, itemID, interactionRange): 482 return not self.InWarp() and self.InInteractionRange(itemID, interactionRange) and not self.IsCloaked() 483 484 def __NavigateSystemTo(self, itemID, interactionRange, commandFunc, *args, **kwargs): 485 try: 486 if self.InWarp(): 487 pass 488 elif self.InInteractionRange(itemID, interactionRange) and not self.IsCloaked(): 489 self.LogInfo('System navigation: at target location. Triggering action') 490 try: 491 commandFunc(*args, **kwargs) 492 except UserError: 493 raise 494 finally: 495 self.CancelSystemNavigation() 496 497 elif self.InWarpRange(itemID): 498 self.LogInfo('System navigation: warping to target', itemID, interactionRange) 499 WarpToItem(itemID, warpRange=const.minWarpEndDistance, cancelAutoNavigation=False) 500 elif self.IsApproachable(itemID): 501 movementFunctions.ShipApproach(itemID, cancelAutoNavigation=False) 502 else: 503 self.LogInfo('Unable to resolve the proper navigation action. Aborting.', itemID, interactionRange, commandFunc) 504 self.CancelSystemNavigation() 505 if self.__navigateSystemThread: 506 self.__navigateSystemThread.interval = AUTO_NAVIGATION_LOOP_INTERVAL_MS 507 except UserError as e: 508 self.LogInfo('User error detected', e.msg, itemID, interactionRange, commandFunc) 509 raise 510 except Exception as e: 511 if isinstance(e, RuntimeError) and e.args[0] in ('MonikerSessionCheckFailure', 'Ship reattaching while in process of departing', 'Missing shipid', 'User not in any solar system'): 512 self.LogInfo('Exception ignored due to session changing while still trying to navigate.', itemID, interactionRange, commandFunc) 513 else: 514 self.LogError('Problem while navigating system', itemID, interactionRange, commandFunc) 515 log.LogException(channel=self.__guid__) 516 self.CancelSystemNavigation() 517 518 def IsApproachable(self, itemID): 519 destBall = self.michelle.GetBall(itemID) 520 if destBall is not None and destBall.surfaceDist < const.minWarpDistance: 521 return True 522 return False 523 524 def InInteractionRange(self, itemID, interactionRange): 525 destBall = self.michelle.GetBall(itemID) 526 if destBall is not None and destBall.surfaceDist < interactionRange: 527 return True 528 return False 529 530 def InWarp(self): 531 shipBall = self.michelle.GetBall(session.shipid) 532 if shipBall is not None and shipBall.mode == destiny.DSTBALL_WARP: 533 return True 534 return False 535 536 def InWarpRange(self, itemID): 537 destBall = self.michelle.GetBall(itemID) 538 if destBall is not None and destBall.surfaceDist > const.minWarpDistance: 539 return True 540 return False 541 542 def IsCloaked(self): 543 shipBall = self.michelle.GetBall(session.shipid) 544 if shipBall is not None: 545 return bool(shipBall.isCloaked) 546 return False 547 548 def OnCharacterSessionChanged(self, _oldCharacterID, newCharacterID): 549 if newCharacterID is not None: 550 self.updateJumpGatesTimer = AutoTimer(const.HOUR / const.MSEC, self.UpdateJumpGatesTimerThread) 551 552 def OnSessionReset(self): 553 if self.updateJumpGatesTimer: 554 self.updateJumpGatesTimer.KillTimer() 555 self.updateJumpGatesTimer = None 556 557 def UpdateJumpGatesTimerThread(self): 558 if not settings.char.ui.Get('pathFinder_includeJumpGates', False): 559 return 560 with ExceptionEater('UpdateJumpGatesTimerThread'): 561 self.VerifyJumpGatesInRoute() 562 563 def FindJumpGatesInRoute(self): 564 starmapSvc = sm.GetService('starmap') 565 path = starmapSvc.GetRouteFromWaypoints(starmapSvc.GetWaypoints()) 566 if len(path) < 2: 567 return [] 568 lastKnownJumpGates = self.clientPathfinderService.GetLastKnownJumpGates() 569 potentialJumpGatePairs = [] 570 for fromLocationID, toLocationID in itertools.izip(path, path[1:]): 571 if (fromLocationID, toLocationID) in lastKnownJumpGates: 572 potentialJumpGatePairs.append((fromLocationID, toLocationID)) 573 574 jumpGatesInRoute = [] 575 for fromLocationID, toLocationID in potentialJumpGatePairs: 576 if not self.GetGateIDToSolarsystemFromLocationID(toLocationID, fromLocationID): 577 jGate = lastKnownJumpGates.get((fromLocationID, toLocationID), None) 578 if jGate: 579 jumpGatesInRoute.append(jGate) 580 581 return jumpGatesInRoute 582 583 def VerifyJumpGatesInRoute(self): 584 with ExceptionEater('VerifyJumpGatesInRoute'): 585 if not settings.char.ui.Get('pathFinder_includeJumpGates', False): 586 return 587 jumpGates = self.FindJumpGatesInRoute() 588 structureServicesSvc = sm.GetService('structureServices') 589 needsRefresh = False 590 for jGate in jumpGates: 591 availableServices = structureServicesSvc.CharacterGetServices(jGate) 592 if SERVICE_JUMP_BRIDGE not in availableServices: 593 needsRefresh = True 594 break 595 596 if needsRefresh: 597 self.UpdateJumpGatesAndRoute() 598 599 def UpdateJumpGatesAndRoute(self): 600 self.clientPathfinderService.UpdateJumpGates() 601 sm.GetService('autoPilot').OptimizeRoute() 602 603 def OnMapShowsJumpGatesChanged(self): 604 self.UpdateJumpGatesAndRoute() 605 606 def OptimizeRoute(self, *args): 607 if self.isOptimizing: 608 return 609 try: 610 self.isOptimizing = True 611 starmapSvc = sm.GetService('starmap') 612 waypoints = list(starmapSvc.GetWaypoints()) 613 originalWaypointsLen = len(waypoints) 614 isReturnTrip = False 615 for idx in reversed(xrange(len(waypoints))): 616 if waypoints[idx] == session.solarsystemid2: 617 del waypoints[idx] 618 isReturnTrip = True 619 break 620 621 solarSystemToStations = defaultdict(list) 622 for i, waypoint in enumerate(waypoints): 623 if self.IsStationOrStructure(waypoint): 624 if idCheckers.IsStation(waypoint): 625 solarSystemID = cfg.stations.Get(waypoint).solarSystemID 626 else: 627 solarSystemID = self.structureDirectory.GetStructureInfo(waypoint).solarSystemID 628 solarSystemToStations[solarSystemID].append(waypoint) 629 waypoints[i] = solarSystemID 630 631 waypoints = list(set(waypoints)) 632 if session.solarsystemid2 in waypoints: 633 waypoints.remove(session.solarsystemid2) 634 numWaypoints = len(waypoints) 635 if numWaypoints == 0: 636 return 637 msg = None 638 if numWaypoints > 12: 639 msg = 'UI/Map/MapPallet/msgOptimizeQuestion1' 640 elif numWaypoints > 10: 641 msg = 'UI/Map/MapPallet/msgOptimizeQuestion2' 642 if msg: 643 yesNo = prompt_player_fsd_dialog('AskAreYouSure', {'cons': localization.GetByLabel(msg, numWaypoints=originalWaypointsLen)}, uiconst.YESNO) 644 if yesNo != uiconst.ID_YES: 645 return 646 distance = {} 647 waypoints.append(session.solarsystemid2) 648 for fromID in waypoints: 649 distance[fromID] = {} 650 for toID in waypoints: 651 if fromID == toID: 652 continue 653 distance[fromID][toID] = self.clientPathfinderService.GetAutopilotJumpCount(toID, fromID) 654 655 waypoints.pop() 656 startTime = blue.os.GetWallclockTimeNow() 657 prefix = [None] 658 _push = prefix.append 659 _pop = prefix.pop 660 661 def FindShortestRoute(prefix, distanceSoFar, toID): 662 distanceTo = distance[toID] 663 prefix[-1] = toID 664 shortestDist = shortestRouteSoFar[0] 665 if len(prefix) < numWaypoints: 666 _push(None) 667 for i in indexes: 668 toID = waypoints[i] 669 if not toID: 670 continue 671 candidateDist = distanceSoFar + distanceTo[toID] 672 if candidateDist >= shortestDist: 673 continue 674 waypoints[i] = None 675 FindShortestRoute(prefix, candidateDist, toID) 676 waypoints[i] = toID 677 678 _pop() 679 else: 680 for i in indexes: 681 toID = waypoints[i] 682 if not toID: 683 continue 684 candidateDist = distanceSoFar + distanceTo[toID] 685 if candidateDist < shortestDist: 686 shortestRouteSoFar[:] = [candidateDist, prefix[:], toID] 687 shortestDist = candidateDist 688 689 shortestRouteSoFar = [999999999, None, None] 690 indexes = range(len(waypoints)) 691 FindShortestRoute(prefix, 0, session.solarsystemid2) 692 distance, waypoints, last = shortestRouteSoFar 693 if waypoints is not None and waypoints[0] == session.solarsystemid2: 694 waypoints = waypoints[1:] 695 blue.pyos.synchro.SleepWallclock(1) 696 if waypoints is None: 697 raise UserError('AutoPilotDisabledUnreachable') 698 waypoints.append(last) 699 waypointsWithStations = [] 700 for waypoint in waypoints: 701 if waypoint in solarSystemToStations: 702 waypointsWithStations.extend(solarSystemToStations[waypoint]) 703 else: 704 waypointsWithStations.append(waypoint) 705 706 if isReturnTrip == True: 707 sm.GetService('starmap').SetWaypoints(waypointsWithStations + [session.solarsystemid2]) 708 else: 709 sm.GetService('starmap').SetWaypoints(waypointsWithStations) 710 finally: 711 self.isOptimizing = False 712 713 def _GetNextItemIDInRoute(self): 714 destinationPath = sm.GetService('starmap').GetDestinationPath() 715 if destinationPath: 716 return destinationPath[0] 717 718 def GetNextItemIDInRoute(self): 719 itemID = self._GetNextItemIDInRoute() 720 if not itemID: 721 return 722 bp = sm.GetService('michelle').GetBallpark() 723 if not bp: 724 return 725 for slimItem in bp.slimItems.values(): 726 if slimItem.jumps and slimItem.jumps[0].locationID == itemID: 727 return slimItem.itemID 728 if slimItem.itemID == itemID: 729 return itemID