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

gameui.py (45481B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\parklife\gameui.py
      2 from __future__ import absolute_import
      3 import datetime
      4 import sys
      5 import blue
      6 import bluepy
      7 import carbon.client.script.sys.appUtils as appUtils
      8 import carbon.common.script.util.logUtil as log
      9 import datetimeutils
     10 import eve.common.script.net.eveMoniker as moniker
     11 import evegraphics.settings as gfxsettings
     12 import everesourceprefetch
     13 import gametime
     14 import langutils
     15 import localization
     16 import logmodule
     17 import paperdoll
     18 import player_messaging
     19 import player_messaging.client.ui_message
     20 import telemetry
     21 import trinity
     22 import uthread
     23 from achievement_system.client.api import repository as achievement_system_repository
     24 from achievement_system.client.ui.fanfare.controller import AchievementFanfareUIController
     25 from cosmetics.client.character.title.api import repository as character_titles_repository
     26 from auraguidance.client.api import initialize_aura_guidance_repository
     27 from campaigns.client.api import initialize_campaigns_repository_v1, initialize_campaigns_repository_v2
     28 from carbon.common.lib import const, serverInfo
     29 from carbon.common.script.sys.service import Service, SERVICE_RUNNING, SERVICE_START_PENDING, ROLEMASK_ELEVATEDPLAYER
     30 from carbonui import uiconst
     31 from carbonui.control.contextMenu.menuUtil import ClearMenuLayer
     32 from carbonui.control.message import QuickMessage
     33 from carbonui.text.settings import get_default_font_size_setting, get_font_size_setting, set_font_size_setting, update_font_size_factor
     34 from carbonui.uicore import uicore, GetWindowName
     35 from communitytracks.client.api import repository as community_tracks_repository_api
     36 from communitytracks.client.qa.settings import ShouldMockSetting as ShouldMockCommunityTracksSetting
     37 from content.client.api import initialize_content_registry
     38 from eve.client.script.parklife.messagebus.uiMessenger import UIMessenger
     39 from eve.client.script.ui.clonegrade.clone_grade_fanfare_ui_controller import CloneGradeFanfareUiController
     40 from eve.client.script.ui.clonegrade.upsell_neocom_controller import UpsellNeocomController
     41 from eve.client.script.ui.control.resourceLoadingIndicator import ResourceLoadingIndicator
     42 from eve.client.script.ui.inflight.shipHud.safeLogoffTimer import SafeLogoffTimer, SafeLogoffTimer2
     43 from eve.client.script.ui.mouseInputHandler import MouseInputHandler
     44 from eve.client.script.ui.podGuide.megaMenuManager import MegaMenuManager
     45 from eve.client.script.ui.services import sessionChangeWindowOpener
     46 from eve.client.script.ui.shared import messagebox
     47 from eve.client.script.ui.shared.messagebox import MessageBox
     48 from eve.client.script.ui.shared.radioButtonMessageBox import RadioButtonMessageBox
     49 from eve.client.script.ui.shared.systemMenu.systemmenu import SystemMenu
     50 from eve.client.script.ui.structure.structureBrowser.controllers.reinforceTimersBundle import ReinforcementBundle
     51 from eve.client.script.ui.structure.structureHackingResult import StructureHackingResultWnd
     52 from eve.client.script.ui.util.disconnectNotice import DisconnectNotice
     53 from eve.client.script.ui.view.viewStateConfig import SetupViewStates
     54 from eve.client.script.ui.view.viewStateConst import ViewState
     55 from eve.common.lib import appConst
     56 from eve.common.script.sys import eveCfg
     57 from eveexceptions import ExceptionEater
     58 from evegraphics.environments.environmentManager import EnvironmentManager
     59 from eveprefs import prefs
     60 from evespacescene.playerdeath import PlayerDeathHandler
     61 from evespacescene.routevisualizer import RouteVisualizer
     62 from evetypes import GetVolume
     63 from freelanceprojects.client.api import initialize_freelance_project_repository
     64 from freelanceprojects.client.contributionmethods.qa.settings import ShouldMockContributionMethodsSetting
     65 from freelanceprojects.client.qa.settings import ShouldMockFreelanceProjectSetting
     66 from globalConfig import AllowCharacterLogoff
     67 from inventoryutil.client.cargo_utils import get_cargo_volume_available
     68 from kiring.client.apigateway import get_kiring_api_gateway
     69 from kiring.common.const import ANTI_ADDICTION_CHECK_FAIL_JUVENILES_BLOCK_AFTER_22_AND_BEFORE_8, ANTI_ADDICTION_CHECK_FAIL_JUVENILES_PLAY_TIME_OVERFLOW, ANTI_ADDICTION_CHECK_FAIL_JUVENILES_PLAY_TIME_OVERFLOW_HOLIDAY
     70 from mining.client.location_arrival_indication_service import LocationArrivalIndicationService
     71 from newFeatures import newFeatureNotify
     72 from omega.client.clone_grade_svc import get_clone_grade_svc
     73 from operations.client.operationscontroller import ReleaseOperationsController
     74 from operations.client.tutorialOperationsController import ReleaseTutorialOperationsController
     75 from player_messaging.client import dialog_handler
     76 from player_messaging.client.ui_message import message_player, prompt_player_fsd_dialog
     77 from plex.client.api import repository as plex_repository
     78 from regionalui import manager
     79 from safespace.client.travel_listener import SafeSpaceTravelListener
     80 from stationintrocinematic.client.arrival_listener import StationIntroCinematicListener
     81 from .sceneManagerConsts import SCENE_TYPE_SPACE
     82 from sovereignty.mercenaryden.client.qa.settings.should_mock_data import ShouldMockSetting
     83 from sovereignty.mercenaryden.client.repository import setup_singleton
     84 from spacecomponents.client.components.mercenaryDen import extract_infomorphs_to_ship
     85 AFK_THRESHOLD = datetime.timedelta(minutes=5)
     86 DEFAULT_RESOURCE_LOADING_WHEEL_ALIGN = uiconst.BOTTOMLEFT
     87 DEFAULT_RESOURCE_LOADING_WHEEL_SIZE = 32
     88 DEFAULT_RESOURCE_LOADING_WHEEL_LEFT = 12
     89 DEFAULT_RESOURCE_LOADING_WHEEL_TOP = 12
     90 
     91 class GameUI(Service):
     92     __guid__ = 'svc.gameui'
     93     __startupdependencies__ = ['device',
     94      'settings',
     95      'viewState',
     96      'publicGatewaySvc',
     97      'ui_blinker',
     98      'station',
     99      'seasonService']
    100     __dependencies__ = ['michelle',
    101      'machoNet',
    102      'inv',
    103      'neocom',
    104      'infoPanel',
    105      'shipTreeUI',
    106      'agencyNew',
    107      'redeem',
    108      'sov',
    109      'epicArc']
    110     __notifyevents__ = ['OnConnectionRefused',
    111      'OnDisconnect',
    112      'OnServerMessage',
    113      'OnRemoteMessage',
    114      'DoSessionChanging',
    115      'ProcessSessionChange',
    116      'OnSessionChanged',
    117      'DoBallsAdded',
    118      'OnNewState',
    119      'OnClusterShutdownInitiated',
    120      'OnClusterShutdownCancelled',
    121      'OnJumpQueueMessage',
    122      'OnViewStateChanged',
    123      'OnSetDevice',
    124      'OnCreateNewMailWindow',
    125      'OnStructureHacked',
    126      'OnPodKilled',
    127      'DoBallClear',
    128      'OnAntiAddictionOnlineTimeWarning',
    129      'OnAntiAddictionOnlineTimeBlockWarning',
    130      'OnSafeLogoffTimerStarted',
    131      'OnSafeLogoffActivated',
    132      'OnSafeLogoffAborted',
    133      'OnSafeLogoffFailed']
    134 
    135     def __init__(self):
    136         self.state = SERVICE_START_PENDING
    137         super(GameUI, self).__init__()
    138         self.wannaBeEgo = -1
    139         self.languages = None
    140         self.shutdownTime = None
    141         self.podKillPodID = None
    142         self.sceneManager = None
    143         self.playerDeathHandler = None
    144         self.routeVisualizer = None
    145         self.uiLayerList = None
    146         self.shipAccess = None
    147         self.logoffTimer = None
    148         self._is_afk = False
    149         self._last_ui_interaction_timestamp = None
    150         self.ui_messenger = None
    151         self._upsell_neocom_controller = None
    152         self._clone_grade_fanfare_ui_controller = None
    153         self._achievement_fanfare_ui_controller = None
    154         self.initialize_services()
    155 
    156     def initialize_services(self):
    157         self.regionalUiManager = manager.RegionalUserInterfaceManager(self)
    158         LocationArrivalIndicationService.initialize()
    159         SafeSpaceTravelListener.initialize()
    160         StationIntroCinematicListener.initialize()
    161 
    162     def Run(self, memStream = None):
    163         Service.Run(self, memStream)
    164         self.ui_messenger = UIMessenger(self.publicGatewaySvc)
    165         self.sceneManager = sm.GetService('sceneManager')
    166         self.playerDeathHandler = PlayerDeathHandler(self.sceneManager)
    167         self.routeVisualizer = RouteVisualizer()
    168         self._upsell_neocom_controller = UpsellNeocomController()
    169         self._clone_grade_fanfare_ui_controller = CloneGradeFanfareUiController()
    170         self._achievement_fanfare_ui_controller = AchievementFanfareUIController()
    171         update_font_size_factor()
    172         defaults = {'debug': 0,
    173          'port': sm.services['machoNet'].defaultProxyPortOffset,
    174          'newbie': 1,
    175          'inputhost': 'localhost',
    176          'digit': ',',
    177          'decimal': '.',
    178          'eulaagreed': 0,
    179          'host': 0}
    180         for k, v in defaults.items():
    181             if not prefs.HasKey(k):
    182                 prefs.SetValue(k, v)
    183 
    184         self.uiLayerList = [('l_hint', None, None),
    185          ('l_dragging', None, None),
    186          ('l_menu', None, None),
    187          ('l_mloading', None, None),
    188          ('l_modal', None, [('l_loadingFill', None, None), ('l_systemmenu', SystemMenu, None)]),
    189          ('l_utilmenu', None, None),
    190          ('l_alwaysvisible', None, None),
    191          ('l_abovemain', None, None),
    192          ('l_loading', None, None),
    193          ('l_videoOverlay', None, None),
    194          ('l_infoBubble', None, None),
    195          ('l_main', None, None),
    196          ('l_viewstate', None, None)]
    197         self.shipAccess = None
    198         self.state = SERVICE_RUNNING
    199         uthread.worker('gameui::ShutdownTimer', self.__ShutdownTimer)
    200         uthread.worker('gameui::SessionTimeoutTimer', self.__SessionTimeoutTimer)
    201         uthread.new(self._UpdateCrashKeyValues).context = 'gameui::_UpdateCrashKeyValues'
    202 
    203     def _UpdateCrashKeyValues(self):
    204         self.LogInfo('UpdateCrashKeyValues loop starting')
    205         stats = []
    206         try:
    207             info = serverInfo.GetServerInfo()
    208             blue.SetCrashKeyValues('serverName', info.name)
    209             blue.SetCrashKeyValues('serverIP', info.IP)
    210         except:
    211             log.LogException()
    212 
    213         statNames = ['Blue/Memory/Malloc',
    214          'Blue/Memory/Python',
    215          'Blue/Memory/PageFileUsage',
    216          'Blue/Memory/WorkingSet']
    217         for each in statNames:
    218             s = blue.statistics.Find(each)
    219             if s:
    220                 stats.append(s)
    221 
    222         while self.state == SERVICE_RUNNING:
    223             for each in stats:
    224                 blue.SetCrashKeyValues(each.name, str(each.value))
    225 
    226             blue.SetCrashKeyValues('Trinity_shader_model', trinity.GetShaderModel())
    227             blue.synchro.SleepWallclock(5000)
    228 
    229     def GetShipAccess(self):
    230         if self.shipAccess is not None:
    231             shipAccess, locationID, shipID, charID = self.shipAccess
    232             if locationID != session.solarsystemid or session.stationid:
    233                 self.shipAccess = None
    234             elif shipID != session.shipid:
    235                 self.shipAccess = None
    236             elif charID != session.charid:
    237                 self.shipAccess = None
    238         if self.shipAccess is None:
    239             self.shipAccess = [moniker.GetShipAccess(),
    240              session.solarsystemid or session.stationid,
    241              session.shipid,
    242              session.charid]
    243         return self.shipAccess[0]
    244 
    245     def OnDisconnect(self, reason = 0, msg = ''):
    246         if MessageBox.is_disconnect_dialog_open():
    247             return
    248         disconnectNotice = DisconnectNotice(logProvider=self)
    249         disconnectNotice.OnDisconnect(reason, msg)
    250 
    251     @staticmethod
    252     def OnConnectionRefused():
    253         sm.GetService('loading').CleanUp()
    254 
    255     def OnServerMessage(self, msg):
    256         uthread.new(self.OnServerMessage_thread, msg).context = 'gameui.ServerMessage'
    257 
    258     @staticmethod
    259     def OnServerMessage_thread(msg):
    260         if isinstance(msg, tuple) and len(msg) == 2:
    261             label, kwargs = msg
    262             msg = localization.GetByLabel(label, **kwargs)
    263         prompt_player_fsd_dialog('ServerMessage', {'msg': msg})
    264 
    265     def OnClusterShutdownInitiated(self, explanationLabel, when, duration):
    266         self.shutdownTime = when
    267 
    268     def OnJumpQueueMessage(self, msg, ready):
    269         message_player(msg)
    270 
    271     def OnClusterShutdownCancelled(self, explanationLabel):
    272         self.shutdownTime = None
    273 
    274     def __ShutdownTimer(self):
    275         while self.state == SERVICE_RUNNING:
    276             try:
    277                 if self.shutdownTime and self.shutdownTime - blue.os.GetWallclockTime() < 5 * const.MIN:
    278                     timeLeft = max(0, self.shutdownTime - blue.os.GetWallclockTime())
    279                     message_player(localization.GetByLabel('UI/Shared/ClusterShutdownInSeconds', timeLeft=timeLeft))
    280             except Exception:
    281                 log.LogException()
    282                 sys.exc_clear()
    283 
    284             blue.pyos.synchro.SleepWallclock(5000)
    285 
    286     def __SessionTimeoutTimer(self):
    287         while self.state == SERVICE_RUNNING:
    288             try:
    289                 if session.maxSessionTime and session.maxSessionTime - blue.os.GetWallclockTime() < 15 * const.MIN:
    290                     message_player(localization.GetByLabel('UI/Shared/MaxSessionTimeExpiring', timeLeft=session.maxSessionTime - blue.os.GetWallclockTime()))
    291             except Exception:
    292                 log.LogException()
    293                 sys.exc_clear()
    294 
    295             blue.pyos.synchro.SleepWallclock(5000)
    296 
    297     def _start_afk_monitor(self):
    298         uicore.uilib.RegisterForTriuiEvents([uiconst.UI_MOUSEDOWN,
    299          uiconst.UI_MOUSEUP,
    300          uiconst.UI_MOUSEWHEEL,
    301          uiconst.UI_KEYDOWN], self._handle_global_ui_event)
    302         uthread.worker('gameui._afk_timer_loop', self._afk_timer_loop)
    303 
    304     def _handle_global_ui_event(self, *args, **kwargs):
    305         self._last_ui_interaction_timestamp = gametime.now()
    306         return self.state == SERVICE_RUNNING
    307 
    308     def _afk_timer_loop(self):
    309         last_observed_active_timestamp = self._last_ui_interaction_timestamp
    310         while self.state == SERVICE_RUNNING:
    311             blue.pyos.synchro.SleepWallclock(1000)
    312             try:
    313                 if session.charid is None:
    314                     continue
    315                 if self._last_ui_interaction_timestamp is None:
    316                     continue
    317                 dt_since_active = gametime.now() - self._last_ui_interaction_timestamp
    318                 if not self._is_afk and dt_since_active > AFK_THRESHOLD:
    319                     self._is_afk = True
    320                     self.LogNotice('I am now AFK after being idle for {dt} seconds.'.format(dt=dt_since_active.total_seconds()))
    321                     self.ui_messenger.idle_threshold_reached(AFK_THRESHOLD)
    322                     sm.RemoteSvc('charMgr').SetActivityStatus(status=appConst.PLAYER_STATUS_AFK, extraInfo=datetimeutils.datetime_to_filetime(self._last_ui_interaction_timestamp))
    323                 elif self._is_afk and dt_since_active < AFK_THRESHOLD:
    324                     self._is_afk = False
    325                     self.LogNotice('I am no longer AFK after being idle for {dt} seconds.'.format(dt=(gametime.now() - last_observed_active_timestamp).total_seconds()))
    326                     self.ui_messenger.activity_resumed()
    327                     sm.RemoteSvc('charMgr').SetActivityStatus(status=appConst.PLAYER_STATUS_ACTIVE, extraInfo=datetimeutils.datetime_to_filetime(last_observed_active_timestamp))
    328                 last_observed_active_timestamp = self._last_ui_interaction_timestamp
    329             except Exception:
    330                 log.LogException()
    331                 sys.exc_clear()
    332 
    333     @staticmethod
    334     def OnRemoteMessage(msgID, dict = None, kw = None):
    335         if kw is None:
    336             kw = {}
    337         remoteNotifyMessages = ('SelfDestructInitiatedOther', 'SelfDestructImmediateOther', 'SelfDestructAbortedOther2')
    338         if msgID in remoteNotifyMessages and not settings.user.ui.Get('notifyMessagesEnabled', 1):
    339             return
    340         uthread.new(prompt_player_fsd_dialog, msgID, dict, **kw).context = 'gameui.ServerMessage'
    341 
    342     @staticmethod
    343     def TransitionCleanup(change):
    344         ClearMenuLayer()
    345         uicore.layer.utilmenu.Flush()
    346         for each in uicore.registry.GetWindows()[:]:
    347             if each.name.startswith('infowindow') and 'shipid' in change and each.sr.itemID in change['shipid']:
    348                 each.Close()
    349 
    350     def ClearCacheFiles(self):
    351         if prompt_player_fsd_dialog('AskClearCacheReboot', {}, uiconst.YESNO) == uiconst.ID_YES:
    352             prefs.clearcache = 1
    353             appUtils.Reboot('clear cache')
    354 
    355     def ClearSettings(self):
    356         if prompt_player_fsd_dialog('AskClearSettingsReboot', {}, uiconst.YESNO) == uiconst.ID_YES:
    357             prefs.resetsettings = 1
    358             appUtils.Reboot('clear settings')
    359 
    360     @telemetry.ZONE_METHOD
    361     def ProcessSessionChange(self, isRemote, session, change, cheat = 0):
    362         self.settings.SaveSettings()
    363         self.LogInfo('ProcessSessionChange: ', change, ',', session)
    364 
    365     def DoSessionChanging(self, isRemote, session, change):
    366         wasPodded, movedToStructure, movedToStation = self._ParseStateChange(change)
    367         if wasPodded:
    368             view = ViewState.Hangar
    369             viewState = sm.GetService('viewState')
    370             viewState.SetTransitionReason(viewState.GetCurrentViewInfo().name, view, 'clone')
    371         self.TransitionCleanup(change)
    372         if 'charid' in change and change['charid'][0] or 'userid' in change and change['userid'][0]:
    373             self.shipAccess = None
    374 
    375     def _ParseStateChange(self, change):
    376         movedFromDestroyedPod = 'shipid' in change and self.podKillPodID and self.podKillPodID == change['shipid'][0]
    377         movedToStation = 'stationid' in change and change['stationid'][1] is not None
    378         movedToStructure = 'structureid' in change and change['structureid'][1] is not None
    379         wasPodded = movedFromDestroyedPod and (movedToStation or movedToStructure)
    380         return (wasPodded, movedToStructure, movedToStation)
    381 
    382     @staticmethod
    383     def OnSetDevice(*args):
    384         uicore.layer.utilmenu.Flush()
    385 
    386     @staticmethod
    387     def OnViewStateChanged(oldView, newView):
    388         uicore.layer.utilmenu.Flush()
    389         if oldView == ViewState.CharacterSelector:
    390             newFeatureNotify.CheckOpenNewFeatureNotifyWindow()
    391         if oldView == ViewState.CharacterSelector or oldView == ViewState.CharacterCreation and newView == ViewState.Hangar:
    392             sm.GetService('activities').CheckIfLoginOpenWindowEnabled()
    393         if newView == ViewState.CharacterSelector:
    394             sm.GetService('activities').AfterLogout()
    395 
    396     @staticmethod
    397     def AnythingImportantInChange(change):
    398         ignoreKeys = ('fleetid', 'wingid', 'squadid', 'fleetrole', 'languageID')
    399         ck = change.keys()
    400         for k in ignoreKeys:
    401             try:
    402                 ck.remove(k)
    403             except ValueError:
    404                 pass
    405 
    406         return bool(ck)
    407 
    408     @telemetry.ZONE_METHOD
    409     def OnSessionChanged(self, isRemote, session, change):
    410         if not self.AnythingImportantInChange(change):
    411             return
    412         relevantSessionAttributes = ('userid', 'charid', 'solarsystemid', 'stationid', 'structureid', 'shipid')
    413         changeIsRelevant = False
    414         for attribute in relevantSessionAttributes:
    415             if attribute in change:
    416                 changeIsRelevant = True
    417                 break
    418 
    419         if not changeIsRelevant:
    420             return
    421         if 'userid' in change:
    422             sm.GetService('tactical')
    423             get_clone_grade_svc().prime_clone_state_with_grace_period()
    424             uicore.mouseInputHandler = MouseInputHandler()
    425         if 'charid' in change:
    426             self.OnCharacterSelected()
    427             self._upsell_neocom_controller.update_neocom_button()
    428         if 'userid' in change or 'charid' in change:
    429             self.settings.LoadSettings()
    430             gfx = gfxsettings.GraphicsSettings.GetGlobal()
    431             gfx.InitializeSettingsGroup(gfxsettings.SETTINGS_GROUP_UI, settings.user.ui)
    432             self.device.ApplyTrinityUserSettings()
    433         if 'shipid' in change and session.sessionChangeReason == 'selfdestruct':
    434             session.sessionChangeReason = 'board'
    435         if session.charid is None:
    436             self._HandleUserLogonSessionChange(isRemote, session, change, self.viewState)
    437         elif session.stationid is not None:
    438             self._HandleSessionChangeInStation(isRemote, session, change, self.viewState)
    439         elif session.structureid is not None:
    440             self._HandleSessionChangeInStructure(isRemote, session, change, self.viewState)
    441         elif session.solarsystemid is not None:
    442             self._HandleSessionChangeInSpace(isRemote, session, change, self.viewState)
    443         else:
    444             self.LogWarn('GameUI::OnSessionChanged, Lame Teardown of the client, it should get propper OnDisconnect event', isRemote, session, change)
    445 
    446     def DoCharacterLogoff(self):
    447         sm.GetService('cc').on_character_log_off()
    448         sm.GetService('jumpQueue').PrepareQueueForCharID(None)
    449         self.LogInfo('ChainEvent ProcessSessionReset with current session', session)
    450         sm.ChainEvent('ProcessSessionReset')
    451         self.LogInfo('Closing game windows')
    452         sessionChangeWindowOpener.CloseOutOfScopeWindows(uiconst.SCOPE_NONE)
    453         viewStateSvc = sm.GetService('viewState')
    454         viewStateSvc.CloseSecondaryView()
    455         self.LogInfo('ScatterEvent OnSessionReset with current session', session)
    456         sm.ScatterEvent('OnSessionReset')
    457         ReleaseOperationsController()
    458         ReleaseTutorialOperationsController()
    459         self.LogInfo('Switching to character selection', session)
    460         viewStateSvc.ActivateView('charsel')
    461         trinity.app.title = GetWindowName()
    462         self.LogNotice('Character logoff complete')
    463 
    464     def LogOffCharacter(self):
    465         self.LogNotice('Logging out character %s' % cfg.eveowners.Get(session.charid).name)
    466         with ExceptionEater('Failed to persist tasks'):
    467             tasksUpdated = sm.GetService('monolithTaskTrackerSvc').CheckAchievementStatus()
    468             if not tasksUpdated:
    469                 sm.GetService('monolithTaskTrackerSvc').UpdateClientAchievementsAndCountersOnServer()
    470         if sm.GetService('sessionMgr').PerformSessionChange('logoff', sm.RemoteSvc('userSvc').UserLogOffCharacter):
    471             return self.DoCharacterLogoff()
    472         self.LogNotice('Character logoff denied')
    473 
    474     def OnCharacterSelected(self):
    475         sm.ScatterEvent('OnCharacterSelected')
    476         self.DoWindowIdentification()
    477 
    478     @staticmethod
    479     def _CheckSessionLanguage(userSession):
    480         prefsLang = langutils.any_to_comfy_language(prefs.GetValue('languageID', None))
    481         sessionLang = langutils.any_to_comfy_language(userSession.languageID)
    482         if prefsLang != sessionLang:
    483             userSession.__dict__['languageID'] = prefsLang.mls_language_id()
    484 
    485     def AlertAntiAddictionDialogBoxAndQuit(self, message):
    486         if prompt_player_fsd_dialog(message, {}, uiconst.OK) in (uiconst.ID_OK, uiconst.ID_CLOSE, uiconst.ID_CANCEL):
    487             uicore.cmd.DoQuitGame()
    488 
    489     def CheckAntiAddiction(self):
    490         if not localization.util.AmOnChineseServer():
    491             return
    492         kiringApiGateway = get_kiring_api_gateway()
    493         code = kiringApiGateway.check_anti_addiction()
    494         log.LogInfo('anti-addiction login check code: %s' % code)
    495         if code == ANTI_ADDICTION_CHECK_FAIL_JUVENILES_BLOCK_AFTER_22_AND_BEFORE_8:
    496             self.AlertAntiAddictionDialogBoxAndQuit('AntiAddictionLoginTimeBlock')
    497         elif code == ANTI_ADDICTION_CHECK_FAIL_JUVENILES_PLAY_TIME_OVERFLOW:
    498             self.AlertAntiAddictionDialogBoxAndQuit('AntiAddictionLoginBlock')
    499         elif code == ANTI_ADDICTION_CHECK_FAIL_JUVENILES_PLAY_TIME_OVERFLOW_HOLIDAY:
    500             self.AlertAntiAddictionDialogBoxAndQuit('AntiAddictionLoginBlockHoliday')
    501 
    502     def _HandleUserLogonSessionChange(self, isRemote, session, change, viewSvc):
    503         self._CheckSessionLanguage(session)
    504         self.regionalUiManager.on_login(session)
    505         self.CheckAntiAddiction()
    506         self.LogNotice('GameUI::OnSessionChanged, Heading for character selection', isRemote, session, change)
    507         self.regionalUiManager.on_splash_screen(session)
    508         characters = sm.GetService('cc').GetCharactersToSelect(False)
    509         if characters:
    510             uthread.pool('GameUI::ActivateView::charsel', viewSvc.ActivateView, 'charsel')
    511         else:
    512             uthread.pool('GameUI::GoCharacterCreation', self.GoCharacterCreation)
    513 
    514     def _GetStationView(self):
    515         view = settings.user.ui.Get('defaultDockingView', ViewState.Hangar)
    516         if view == 'station':
    517             view = ViewState.Hangar
    518         return view
    519 
    520     def _GetStructureView(self):
    521         return settings.user.ui.Get('defaultStructureView', ViewState.Hangar)
    522 
    523     def _GetActivateViewFunction(self, viewSvc):
    524         if viewSvc.IsViewActive('charactercreation'):
    525             activateViewFunc = viewSvc.ActivateView
    526         else:
    527             activateViewFunc = viewSvc.ChangePrimaryView
    528         return activateViewFunc
    529 
    530     def _HandleSessionChangeInStation(self, isRemote, session, change, viewSvc):
    531         if 'stationid' in change:
    532             self.LogNotice('GameUI::OnSessionChanged, Heading for station', isRemote, session, change)
    533             activateViewFunc = self._GetActivateViewFunction(viewSvc)
    534             view = self._GetStationView()
    535             uthread.pool('GameUI::ActivateView::station', activateViewFunc, view, change=change)
    536 
    537     def _HandleSessionChangeInStructure(self, isRemote, session, change, viewSvc):
    538         if not ('shipid' in change or 'structureid' in change):
    539             return
    540         structureView = None
    541         wasPodded, movedToStructure, _ = self._ParseStateChange(change)
    542         if movedToStructure:
    543             if 'shipid' in change and session.shipid == session.structureid:
    544                 uicore.layer.shipui.CloseView(recreate=False)
    545                 structureView = ViewState.Space
    546             elif wasPodded:
    547                 structureView = ViewState.Hangar
    548                 settings.user.ui.Set('defaultStructureView', structureView)
    549             else:
    550                 structureView = self._GetStructureView()
    551         elif 'shipid' in change and session.structureid:
    552             if session.shipid == session.structureid:
    553                 structureView = ViewState.Space
    554             elif not self.viewState.IsViewActive('structure', 'hangar'):
    555                 structureView = self._GetStructureView()
    556         ballpark = sm.GetService('michelle').GetBallpark()
    557         if ballpark and session.structureid in ballpark.balls:
    558             ballpark.ego = session.structureid
    559             self.wannaBeEgo = -1
    560             self.OnNewState(ballpark)
    561         else:
    562             self.LogNotice('_HandleSessionChangeInStructure:  Postponing ego assignment to', session.structureid, 'until ballpark is ready')
    563             self.wannaBeEgo = session.structureid
    564         if structureView:
    565             uthread.pool('GameUI::ActivateView::structure', self._GetActivateViewFunction(viewSvc), structureView, change=change)
    566 
    567     def _HandleSessionChangeInSpace(self, isRemote, session, change, viewSvc):
    568         bp = sm.GetService('michelle').GetBallpark()
    569         if 'solarsystemid' in change:
    570             oldSolarSystemID, newSolarSystemID = change['solarsystemid']
    571             if oldSolarSystemID:
    572                 sm.GetService('FxSequencer').ClearAll()
    573                 self.LogInfo('Cleared effects')
    574             if bp and session.shipid in bp.balls:
    575                 self.LogNotice('GameUI::OnSessionChanged, Heading for inflight with a valid ego', isRemote, session, change)
    576                 bp.ego = session.shipid
    577                 self.wannaBeEgo = -1
    578             else:
    579                 self.LogNotice('GameUI::OnSessionChanged:%s - Activating space view without a ego ball properly inserted into a proper ballpark.' % (change,))
    580                 self.wannaBeEgo = session.shipid
    581             uthread.pool('GameUI::ChangePrimaryView::inflight', self._GetActivateViewFunction(viewSvc), 'inflight', change=change)
    582         else:
    583             if 'shipid' in change and change['shipid'][1] is not None and bp is not None:
    584                 uthread.new(sm.GetService('target').ClearTargets)
    585                 if session.shipid in bp.balls:
    586                     self.LogNotice('Changing ego:', bp.ego, '->', session.shipid)
    587                     bp.ego = session.shipid
    588                     self.wannaBeEgo = -1
    589                     self.OnNewState(bp)
    590                 else:
    591                     self.LogNotice('Postponing ego:', bp.ego, '->', session.shipid)
    592                     self.wannaBeEgo = session.shipid
    593             if 'structureid' in change:
    594                 self.LogNotice('GameUI::OnSessionChanged, Heading for inflight', isRemote, session, change)
    595                 uthread.pool('GameUI::ChangePrimaryView::inflight', self._GetActivateViewFunction(viewSvc), 'inflight', change=change)
    596                 if bp and session.shipid in bp.balls:
    597                     bp.ego = session.shipid
    598                     self.wannaBeEgo = -1
    599                     self.OnNewState(bp)
    600                 else:
    601                     self.wannaBeEgo = session.shipid
    602             if 'shipid' in change and 'structureid' in change:
    603                 uicore.layer.shipui.CloseView(recreate=False)
    604 
    605     def StartupUI(self, *args):
    606         uthread.new(_ScheduleBackgroundDownloads)
    607         self.usingSingleSignOn = False
    608         uicore.SetAudioHandler(sm.StartService('audio'))
    609         uicore.Startup(self.uiLayerList)
    610         desktopIndex = uicore.desktop.children.index(uicore.layer.hint)
    611         self.resourceLoadingIndicator = ResourceLoadingIndicator(parent=uicore.desktop, align=DEFAULT_RESOURCE_LOADING_WHEEL_ALIGN, width=DEFAULT_RESOURCE_LOADING_WHEEL_SIZE, height=DEFAULT_RESOURCE_LOADING_WHEEL_SIZE, left=DEFAULT_RESOURCE_LOADING_WHEEL_LEFT, top=DEFAULT_RESOURCE_LOADING_WHEEL_TOP, isGlobal=True, idx=desktopIndex)
    612         uicore.SetCommandHandler(sm.StartService('cmd'))
    613         import eve.client.script.ui.control.tooltips as tooltipHandler
    614         uicore.SetTooltipHandler(tooltipHandler)
    615         logmodule.SetUiMessageFunc(prompt_player_fsd_dialog)
    616         SetupViewStates(self.viewState, uicore.layer.viewstate)
    617         sm.GetService('device').SetupUIScaling()
    618         sceneManager = sm.StartService('sceneManager')
    619         sceneManager.SetSceneType(SCENE_TYPE_SPACE)
    620         sceneManager.Initialize(trinity.EveSpaceScene())
    621         self.RegisterBlueResources()
    622         self._initialize_services()
    623         self._register_handlers()
    624         uicore.megaMenuManager = MegaMenuManager()
    625         token = None
    626         for arg in blue.pyos.GetArg()[1:]:
    627             if arg.startswith('/ssoToken'):
    628                 try:
    629                     argName, token = arg.split('=')
    630                 except:
    631                     raise RuntimeError('Invalid format of SSO token, should be /ssoToken=<token>')
    632 
    633         if token is not None:
    634             self.usingSingleSignOn = True
    635             self.DoLogin(token)
    636         else:
    637             self.viewState.ActivateView('login')
    638         self._start_afk_monitor()
    639 
    640     def _register_handlers(self):
    641         player_messaging.client.register_message_player_handler(QuickMessage)
    642         player_messaging.client.register_prompt_player_handler(messagebox.MessageBox)
    643         player_messaging.client.register_prompt_player_fsd_dialogs_handler(dialog_handler)
    644 
    645     def _initialize_services(self):
    646         sm.StartService('connection')
    647         sm.StartService('logger')
    648         sm.StartService('ownerprimer')
    649         sm.StartService('petition')
    650         sm.StartService('ui')
    651         sm.StartService('window')
    652         sm.StartService('log')
    653         sm.StartService('consider')
    654         sm.StartService('incursion')
    655         sm.StartService('moonScan')
    656         sm.StartService('preview')
    657         sm.StartService('crimewatchSvc')
    658         sm.StartService('flightControls')
    659         sm.StartService('flightPredictionSvc')
    660         sm.StartService('sensorSuite')
    661         sm.StartService('hackingUI')
    662         sm.StartService('radialmenu')
    663         sm.StartService('tourneyUISvc')
    664         sm.StartService('monolithTaskTrackerSvc')
    665         sm.StartService('contextualOfferSvc')
    666         sm.StartService('tacticalNavigation')
    667         sm.StartService('subway')
    668         sm.StartService('paperDollClient')
    669         achievement_system_repository.initialize_repository(self.publicGatewaySvc, sm)
    670         character_titles_repository.initialize_repository(self.publicGatewaySvc, sm)
    671         from content.client.definitions import CONTENT_DEFINITIONS
    672         initialize_content_registry(CONTENT_DEFINITIONS, public_gateway=self.publicGatewaySvc)
    673         initialize_freelance_project_repository(self.publicGatewaySvc, ShouldMockFreelanceProjectSetting(), ShouldMockContributionMethodsSetting(), sm, session)
    674         community_tracks_repository_api.initialize_repository(self.publicGatewaySvc, ShouldMockCommunityTracksSetting())
    675         plex_repository.initialize_repository(self.publicGatewaySvc)
    676         setup_singleton(self.publicGatewaySvc, GetVolume, get_cargo_volume_available, extract_infomorphs_to_ship, ShouldMockSetting())
    677         initialize_aura_guidance_repository(self.publicGatewaySvc)
    678         initialize_campaigns_repository_v1(self.publicGatewaySvc, sm, message_player)
    679         initialize_campaigns_repository_v2(self.publicGatewaySvc, sm, message_player)
    680 
    681     def RegisterBlueResources(self):
    682         from carbon.client.script.graphics.resourceConstructors.gradients import Gradient, GradientRadial, Gradient2D
    683         blue.resMan.RegisterResourceConstructor('gradient', Gradient)
    684         blue.resMan.RegisterResourceConstructor('gradient_radial', GradientRadial)
    685         blue.resMan.RegisterResourceConstructor('gradient2d', Gradient2D)
    686 
    687     def _IsResourceLoadingIndicatorAvailable(self):
    688         return self.resourceLoadingIndicator and not self.resourceLoadingIndicator.destroyed
    689 
    690     def EnableResourceLoadingIndicator(self):
    691         if self._IsResourceLoadingIndicatorAvailable():
    692             self.resourceLoadingIndicator.Enable()
    693 
    694     def DisableResourceLoadingIndicator(self):
    695         if self._IsResourceLoadingIndicatorAvailable():
    696             self.resourceLoadingIndicator.Disable()
    697 
    698     def MoveResourceLoadingIndicator(self, newAlignment, newLeft, newTop):
    699         if self._IsResourceLoadingIndicatorAvailable():
    700             self.resourceLoadingIndicator.align = newAlignment
    701             self.resourceLoadingIndicator.left = newLeft
    702             self.resourceLoadingIndicator.top = newTop
    703 
    704     def ResetResourceLoadingIndicatorPosition(self):
    705         self.MoveResourceLoadingIndicator(newAlignment=DEFAULT_RESOURCE_LOADING_WHEEL_ALIGN, newLeft=DEFAULT_RESOURCE_LOADING_WHEEL_LEFT, newTop=DEFAULT_RESOURCE_LOADING_WHEEL_TOP)
    706 
    707     def DoLogin(self, token):
    708         sm.ScatterEvent('OnClientReady', 'login')
    709         try:
    710             sm.GetService('connection').LoginSso(token)
    711         except:
    712             sm.GetService('loading').CleanUp()
    713             self.viewState.ActivateView('login')
    714             raise
    715 
    716     def SetLanguage(self, key, doReload = False):
    717         is_cool = bool(session.role & ROLEMASK_ELEVATEDPLAYER)
    718         if is_cool:
    719             comfylang = langutils.any_to_comfy_language(key)
    720         else:
    721             comfylang = langutils.client_valid_or_default(key)
    722         if comfylang != prefs.GetValue('languageID', None):
    723             if session.userid is not None:
    724                 sm.RemoteSvc('authentication').SetLanguageID(comfylang.mls_language_id())
    725             session.__dict__['languageID'] = comfylang.mls_language_id()
    726             prefs.languageID = comfylang.mls_language_id()
    727             blue.os.languageID = comfylang.mls_language_id()
    728             if doReload:
    729                 localization.LoadLanguageData()
    730                 setting = get_default_font_size_setting(comfylang.mls_language_id())
    731                 if setting > get_font_size_setting():
    732                     set_font_size_setting(setting)
    733             sm.ChainEvent('ProcessUIRefresh')
    734             sm.ScatterEvent('OnUIRefresh')
    735             sm.ScatterEvent('OnLanguageChanged', comfylang.code)
    736 
    737     def GoCharacterCreationCurrentCharacter(self, *args):
    738         if None in (session.charid, session.genderID, session.bloodlineID):
    739             return
    740         if self.viewState.IsViewActive('charactercreation'):
    741             return
    742         if sm.GetService('undocking').IsExiting():
    743             return
    744         stationSvc = sm.GetService('station')
    745         dollState = stationSvc.GetPaperdollStateCache()
    746         msg = None
    747         if dollState == paperdoll.State.resculpting:
    748             msg = 'AskRecustomizeCharacter'
    749         elif dollState == paperdoll.State.full_recustomizing:
    750             msg = 'AskRecustomizeCharacterChangeBloodline'
    751         message = uiconst.ID_NO
    752         if msg is not None:
    753             message = prompt_player_fsd_dialog(msg, {'charName': cfg.eveowners.Get(session.charid).ownerName}, uiconst.YESNO, default=uiconst.ID_NO, suppress=uiconst.ID_NO)
    754         if message == uiconst.ID_YES:
    755             self.GoCharacterCreation(session.charid, session.genderID, session.raceID, session.bloodlineID, dollState=dollState)
    756             stationSvc.ClearPaperdollStateCache()
    757         elif message == uiconst.ID_NO:
    758             self.GoCharacterCreation(session.charid, session.genderID, session.raceID, session.bloodlineID, dollState=paperdoll.State.no_recustomization)
    759 
    760     def GoCharacterCreation(self, charID = None, gender = None, raceID = None, bloodlineID = None, dollState = None, *args):
    761         if sm.GetService('undocking').IsExiting():
    762             return
    763         everesourceprefetch.PullToFront('ui_cc')
    764         self.viewState.ActivateView('charactercreation', charID=charID, gender=gender, raceID=raceID, bloodlineID=bloodlineID, dollState=dollState)
    765 
    766     def RadioButtonMessageBox(self, text, title = 'EVE', buttons = None, icon = None, suppText = None, customicon = None, radioOptions = None, height = None, width = None, blockconfirmonreturn = 0, default = None, modal = True):
    767         if radioOptions is None:
    768             radioOptions = []
    769         if not getattr(uicore, 'desktop', None):
    770             return
    771         if MessageBox.is_disconnect_dialog_open():
    772             return (default, False)
    773         if buttons is None:
    774             buttons = uiconst.ID_OK
    775         msgbox = RadioButtonMessageBox(useDefaultPos=True)
    776         msgbox.isModal = modal
    777         msgbox.blockconfirmonreturn = blockconfirmonreturn
    778         msgbox._show_message(text, title, buttons, icon, suppText, customicon, height, radioOptions=radioOptions, width=width, default=default, modal=modal)
    779         if modal:
    780             buttonResult = msgbox.ShowModal()
    781             radioSelection = msgbox.GetRadioSelection()
    782             ret = (buttonResult, radioSelection)
    783         else:
    784             buttonResult = msgbox.ShowDialog()
    785             radioSelection = msgbox.GetRadioSelection()
    786             ret = (buttonResult, radioSelection)
    787         return (ret, msgbox.suppress)
    788 
    789     def DoBallsAdded(self, *args, **kw):
    790         import stackless
    791         import blue
    792         t = stackless.getcurrent()
    793         timer = t.PushTimer(blue.pyos.taskletTimer.GetCurrent() + '::gameui')
    794         try:
    795             return self.DoBallsAdded_(*args, **kw)
    796         finally:
    797             t.PopTimer(timer)
    798 
    799     def DoBallsAdded_(self, lst):
    800         for each in lst:
    801             self.DoBallAdd(*each)
    802 
    803     def DoBallAdd(self, ball, slimItem):
    804         if ball.id == self.wannaBeEgo:
    805             self.LogNotice('Ego-wannabe', self.wannaBeEgo, 'added to park')
    806             bp = sm.GetService('michelle').GetBallpark()
    807             if bp.ego != self.wannaBeEgo:
    808                 self.LogNotice('Post-ego change: ', bp.ego, '->', self.wannaBeEgo)
    809                 bp.ego = self.wannaBeEgo
    810                 self.OnNewState(bp)
    811             self.wannaBeEgo = -1
    812             sm.ScatterEvent('OnDoBallAddEgoChange')
    813 
    814     def DoBallClear(self, solItem):
    815         if sm.GetService('subway').Enabled() and sm.GetService('subway').InJump():
    816             return
    817         uthread.new(self._DoBallClear, solItem)
    818 
    819     def _DoBallClear(self, solItem):
    820         spaceToSpaceTransition = self.viewState.GetTransitionByName('inflight', 'inflight')
    821         sm.GetService('tactical').TearDownOverlay()
    822         sc = self.sceneManager.GetScene()
    823         if spaceToSpaceTransition.active:
    824             spaceToSpaceTransition.Finalize()
    825         else:
    826             self.sceneManager.UnregisterScene('default', ignoreCamera=True)
    827             self.sceneManager.LoadScene(sc, registerKey='default')
    828             self.sceneManager.ApplySpaceScene()
    829             EnvironmentManager.GetInstance().RefreshEnvironments()
    830         if session.solarsystemid:
    831             sm.GetService('tactical').CheckInit()
    832 
    833     def OnNewState(self, bp):
    834         uthread.pool('GameUI::New shipui state', self._NewState, bp)
    835 
    836     @telemetry.ZONE_METHOD
    837     def _NewState(self, bp):
    838         if bp and bp.balls.get(bp.ego, None):
    839             if eveCfg.InSpace() and uicore.layer.shipui.isopen:
    840                 uicore.layer.shipui.SetupShip(False)
    841             camera = sm.GetService('sceneManager').GetActiveSpaceCamera()
    842             camera.LookAtMaintainDistance(bp.ego)
    843 
    844     def DoWindowIdentification(self):
    845         trinity.app.title = GetWindowName(session.charid)
    846 
    847     def UsingSingleSignOn(self):
    848         return getattr(self, 'usingSingleSignOn', False)
    849 
    850     def OnCreateNewMailWindow(self, recipients, corpOrAllianceRecipient, toListID, subject, body):
    851         sm.GetService('mailSvc').SendMsgDlg(toCharacterIDs=recipients, toCorpOrAllianceID=corpOrAllianceRecipient, toListID=toListID, subject=subject, body=body)
    852 
    853     def OnAntiAddictionOnlineTimeWarning(self, countdownSecond, playedSecond):
    854         accruedTime = localization.formatters.FormatTimeIntervalWritten(int(playedSecond) * const.SEC, showFrom='hour', showTo='second')
    855         leftTime = localization.formatters.FormatTimeIntervalWritten(int(countdownSecond) * const.SEC, showFrom='hour', showTo='second')
    856         prompt_player_fsd_dialog('AntiAddictionOnlineTimeWarning', {'accruedTime': accruedTime,
    857          'leftTime': leftTime})
    858 
    859     def OnAntiAddictionOnlineTimeBlockWarning(self, countdownSecond):
    860         leftTime = localization.formatters.FormatTimeIntervalWritten(int(countdownSecond) * const.SEC, showFrom='hour', showTo='second')
    861         prompt_player_fsd_dialog('AntiAddictionOnlineTimeBlockWarning', {'leftTime': leftTime})
    862 
    863     def OnStructureHacked(self, structureID, reinforceTiming):
    864         structureInfo = sm.GetService('structureDirectory').GetStructureInfo(structureID)
    865         reinforceWeekday, reinforceHour, nextReinforceWeekday, nextReinforceHour, applyAtTime = reinforceTiming
    866         reinforceTimers = ReinforcementBundle(reinforceHour=reinforceHour, nextReinforceHour=nextReinforceHour, nextReinforceApply=applyAtTime)
    867         wnd = StructureHackingResultWnd.GetIfOpen()
    868         if wnd:
    869             wnd.LoadWndWithResult(structureID, reinforceTimers, structureTypeID=structureInfo.typeID)
    870             wnd.Maximize()
    871         else:
    872             StructureHackingResultWnd.Open(structureID=structureID, reinforceTimers=reinforceTimers, structureTypeID=structureInfo.typeID)
    873 
    874     def OnPodKilled(self, destroyedPodID):
    875         self.podKillPodID = destroyedPodID
    876 
    877     def OnSafeLogoffTimerStarted(self, safeLogoffTime):
    878         if self.logoffTimer is not None:
    879             self.logoffTimer.Close()
    880         if AllowCharacterLogoff(sm.GetService('machoNet')):
    881             log.LogNotice('Starting safe logoff countdown for character %s' % cfg.eveowners.Get(session.charid).name)
    882             logoffTimerClass = SafeLogoffTimer2
    883         else:
    884             logoffTimerClass = SafeLogoffTimer
    885         self.logoffTimer = logoffTimerClass(parent=uicore.layer.abovemain, logoffTime=safeLogoffTime)
    886         self.logoffTimer.left = sm.GetService('window').GetCameraLeftOffset(self.logoffTimer.width, self.logoffTimer.align, self.logoffTimer.left)
    887 
    888     def OnSafeLogoffActivated(self):
    889         if self.logoffTimer is not None:
    890             self.logoffTimer.SetTimerCompleted()
    891         if self.logoffTimer.quitting:
    892             prompt_player_fsd_dialog('CustomNotify', {'notify': localization.GetByLabel('UI/Inflight/SafeLogoffDisconnect', {'charid': session.charid})})
    893             sm.GetService('clientStatsSvc').OnProcessExit()
    894             if AllowCharacterLogoff(sm.GetService('machoNet')):
    895                 log.LogNotice('Safe logoff countdown for character %s complete. Exiting client.' % cfg.eveowners.Get(session.charid).name)
    896                 bluepy.TerminateSilently(0, 'User exit after logoff')
    897         else:
    898             self.logoffTimer.Close()
    899             log.LogNotice('Safe logoff countdown for character %s complete. Returning to character selection screen.' % cfg.eveowners.Get(session.charid).name)
    900             sm.GetService('sessionMgr').PerformSessionChange('character logoff', sm.GetService('gameui').DoCharacterLogoff)
    901             self.logoffTimer = None
    902 
    903     def OnSafeLogoffAborted(self, reasonCode):
    904         self.AbortSafeLogoffTimer()
    905         prompt_player_fsd_dialog('CustomNotify', {'notify': localization.GetByLabel(reasonCode)})
    906 
    907     def OnSafeLogoffFailed(self, failedConditions):
    908         self.AbortSafeLogoffTimer()
    909         prompt_player_fsd_dialog('CustomNotify', {'notify': '<br>'.join([localization.GetByLabel('UI/Inflight/SafeLogoff/ConditionsFailedHeader')] + [ localization.GetByLabel(error) for error in failedConditions ])})
    910 
    911     def AbortSafeLogoffTimer(self):
    912         if self.logoffTimer is not None:
    913             self.logoffTimer.AbortLogoff()
    914             self.logoffTimer = None
    915 
    916 
    917 def _ScheduleBackgroundDownloads():
    918     everesourceprefetch.PrepareFilesets()
    919     keys = ['staticdata',
    920      'ui_basics',
    921      'ui_classes',
    922      'black_files',
    923      'low_detail_ships',
    924      'low_detail_nebulas',
    925      'ui_cc']
    926     for each in keys:
    927         everesourceprefetch.Schedule(each)