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

characterSelection.py (40862B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\login\charSelection\characterSelection.py
      2 import logging
      3 import paperdoll
      4 import blue
      5 from carbon.client.script.environment.AudioUtil import PlaySound
      6 from carbon.common.script.net import moniker
      7 from carbon.common.script.sys.serviceConst import ROLEMASK_ELEVATEDPLAYER
      8 from carbonui.control.layer import LayerCore
      9 from carbonui.primitives.fill import Fill
     10 from carbonui.primitives.sprite import Sprite
     11 from eve.client.script.ui.control.eveLoadingWheel import LoadingWheel
     12 from player_messaging.client.ui_message import message_player, prompt_player_fsd_dialog
     13 from eve.client.script.ui.login.charSelection.surveyBanner import SurveyBanner
     14 from eve.client.script.ui.util import uix
     15 from eve.client.script.ui.util.uiComponents import ButtonEffect, Component
     16 import uthread
     17 import log
     18 import carbonui.const as uiconst
     19 import localization
     20 import evetypes
     21 from carbonui.primitives.container import Container
     22 from carbonui.control.buttonIcon import ButtonIcon
     23 from eve.client.script.ui.shared.redeem.redeemPanel import GetRedeemPanel
     24 from eve.client.script.ui.login.charSelection.lapsedGametimeWarning import LapsedGametimeWarning
     25 from eve.client.script.ui.shared.cloneGrade.multiLoginBlockedWindow import MultiLoginBlockedWindow
     26 from eve.client.script.ui.shared.cloneGrade import ORIGIN_CHARACTERSELECTION
     27 from eve.common.lib import appConst
     28 from eveexceptions import ServiceNotFound, UserError, ExceptionEater
     29 from eve.client.script.ui.login.charSelection.characterSlots import SmallCharacterSlot, SmallEmptySlot
     30 from eve.client.script.ui.login.charSelection.characterSlots import CharacterDetailsLocation as CharLocation
     31 import evegraphics.settings as gfxsettings
     32 import eve.client.script.ui.login.charSelection.characterSelectionUtils as csUtil
     33 import eve.client.script.ui.login.charSelection.characterSelectionColors as csColors
     34 from carbonui.uicore import uicore
     35 from eveprefs import boot
     36 import launchdarkly
     37 LOGO_WIDTH = 205
     38 if boot.region == 'optic':
     39     LOGO_WIDTH = 551
     40 LOGO_HEIGHT = 81
     41 LOGO_COLOR = (1,
     42  1,
     43  1,
     44  1)
     45 MINIMUM_LOGOHEIGHT = 50
     46 LOGO_PADDING = 40
     47 BG_WIDTH = 2117
     48 BG_HEIGHT = 1200
     49 FEATURE_BAR_HEIGHT = 100
     50 FEATURE_BAR_INNER_HEIGHT = 90
     51 BANNER_WIDTH = 550
     52 WARNING_BANNER_HEIGHT = 40
     53 logger = logging.getLogger(__name__)
     54 
     55 class CharacterSelection(LayerCore):
     56     __notifyevents__ = ['OnSetDevice',
     57      'OnJumpQueueMessage',
     58      'OnCharacterHandler',
     59      'OnUIRefresh',
     60      'OnUIScalingChange',
     61      'OnGraphicSettingsChanged',
     62      'OnUIoffsetChanged']
     63     isTopLevelWindow = True
     64     minSidePadding = 50
     65 
     66     def ApplyAttributes(self, attributes):
     67         self.bannerAdService = None
     68         self.isInitialLogin = True
     69         super(CharacterSelection, self).ApplyAttributes(attributes)
     70 
     71     def OnSetDevice(self):
     72         if not self.isopen:
     73             return
     74         self.OnRefreshScreen()
     75 
     76     def OnUIRefresh(self):
     77         self.OnRefreshScreen()
     78 
     79     def OnUIScalingChange(self, *args):
     80         self.OnRefreshScreen()
     81 
     82     def OnGraphicSettingsChanged(self, changes):
     83         if gfxsettings.UI_CAMERA_OFFSET in changes:
     84             self.ChangeUIoffset()
     85 
     86     def OnUIoffsetChanged(self):
     87         self.ChangeUIoffset()
     88 
     89     def ChangeUIoffset(self):
     90         cameraOffset = settings.user.ui.offsetUIwithCamera
     91         uiOffsetWithCamera = settings.user.ui.cameraOffset
     92         if self.uiOffset != (cameraOffset, uiOffsetWithCamera):
     93             self.OnRefreshScreen()
     94 
     95     def OnCloseView(self):
     96         self.isTabStop = False
     97         screen = self.selectionScreen
     98         self.selectionScreen = None
     99         self.ClearBackground()
    100         if screen is not None and not screen.destroyed:
    101             screen.Close()
    102         self.characterSlotList = []
    103         self.slotsByCharID.clear()
    104         self.slotsByIdx.clear()
    105         self.ResetVariablesForUiElements()
    106 
    107     def OnJumpQueueMessage(self, msgtext, ready):
    108         if ready:
    109             log.LogInfo('Jump Queue: ready, slamming through...')
    110             self.__Confirm(sm.GetService('jumpQueue').GetPreparedQueueCharID())
    111         else:
    112             log.LogInfo('Jump Queue: message=', msgtext)
    113             message_player(msgtext)
    114 
    115     def OnCharacterHandler(self, *_):
    116         self.SetData()
    117         self.OnRefreshScreen()
    118 
    119     def GetChars(self):
    120         return sm.GetService('cc').GetCharactersToSelect()
    121 
    122     def ReduceCharacterGraphics(self):
    123         gfxsettings.Set(gfxsettings.GFX_CHAR_FAST_CHARACTER_CREATION, True, pending=False)
    124         gfxsettings.Set(gfxsettings.GFX_CHAR_TEXTURE_QUALITY, 2, pending=False)
    125 
    126     def OnOpenView(self, **kwargs):
    127         self.isTabStop = True
    128         self.remoteCharacterSvc = sm.RemoteSvc('charUnboundMgr')
    129         self.mapSvc = sm.GetService('map')
    130         self.SetData()
    131         self.OnRefreshScreen()
    132         self.AnimateScreenIn()
    133         sm.GetService('audio').SendUIEvent('character_selection_start')
    134         with ExceptionEater('Failing to perform survey checks'):
    135             sm.GetService('survey').PerformSurveyChecks()
    136         uthread.new(evetypes.GetTypeIDByNameDict)
    137         charId = blue.os.GetStartupArgValue('character')
    138         if charId:
    139             self.ready = True
    140             self.ConfirmWithCharID(int(charId))
    141         isElevated = session.role & ROLEMASK_ELEVATEDPLAYER
    142         if isElevated and settings.public.ui.Get('Insider', True):
    143             try:
    144                 insider = sm.GetService('insider')
    145                 uthread.new(insider.Show, True, True)
    146             except ServiceNotFound:
    147                 pass
    148 
    149         is_auto_select = self._CheckAutoSelectCharacter()
    150         self._CheckOpenRewardsWindow(is_auto_select)
    151         self.isInitialLogin = False
    152 
    153     def _CheckOpenRewardsWindow(self, is_auto_select):
    154         if not self._ShouldOpenRewardsWindow():
    155             return
    156         if is_auto_select:
    157             sm.GetService('loginCampaignService').blink_reward_window_on_dock()
    158         else:
    159             with ExceptionEater('Failing to open reward window'):
    160                 uthread.new(self._OpenRewardsWindow)
    161 
    162     def _ShouldOpenRewardsWindow(self):
    163         if sm.GetService('loginCampaignService').should_open_reward_window_on_login():
    164             return True
    165         if sm.GetService('seasonalLoginCampaignService').should_open_reward_window_on_login():
    166             return True
    167         if sm.GetService('loginCampaignService').should_reopen_DLI_window():
    168             return True
    169         return False
    170 
    171     def _CheckAutoSelectCharacter(self):
    172         if not self.isInitialLogin:
    173             return False
    174         try:
    175             for arg in blue.pyos.GetArg()[1:]:
    176                 if arg.startswith('/autoSelectCharacter:'):
    177                     try:
    178                         slotNum = int(arg.split(':')[1])
    179                         slot = self.slotsByCharID.get(slotNum, None)
    180                         if not slot:
    181                             slot = self.slotsByIdx[slotNum]
    182                     except:
    183                         raise RuntimeError('Format of parameter incorrect, should be /autoSelectCharacter:slotID<int> (slotNum can characterID or index (0, 1 or 2)')
    184 
    185                     self.EnterGameWithCharacter(slot)
    186                     return True
    187 
    188         except Exception as e:
    189             logger.exception(e)
    190 
    191         return False
    192 
    193     def _OpenRewardsWindow(self):
    194         from eve.client.script.ui.shared.loginRewards.loginRewardsWnd import DailyLoginRewardsWnd
    195         wnd = DailyLoginRewardsWnd.Open()
    196         wnd.Lock()
    197         wnd.ShowDialog(modal=True, closeWhenClicked=True)
    198 
    199     def SetData(self, force = False):
    200         characterSelectionData = self.GetCharacterSelectionData(force=force)
    201         self.chars = characterSelectionData.GetChars()
    202         self.subscriptionEndTimes = characterSelectionData.GetSubscriptionEndTime()
    203         self.numSlotsOwnedByUser = characterSelectionData.GetNumCharacterSlots()
    204         self.slotsToDisplay = characterSelectionData.GetMaxServerCharacters()
    205         self.surveyID = None
    206         uthread.new(self.FetchSurveyID)
    207 
    208     def FetchSurveyID(self):
    209         self.surveyID = sm.GetService('survey').GetActiveSurveyID()
    210         if self.surveyID:
    211             self.DisplaySurveyBanner()
    212 
    213     def OnRefreshScreen(self):
    214         if self._IsSelectionScreenDisabled():
    215             return
    216         uicore.registry.SetFocus(self)
    217         self.ready = False
    218         self.countDownThread = None
    219         self.characterSlotList = []
    220         self.adSprite = None
    221         self.uiOffset = (0, 0)
    222         self.maxFullSlotSize = None
    223         self.Flush()
    224         self.ClearBackground()
    225         self.selectionScreen = Container(name='selectionScreen', parent=self, state=uiconst.UI_PICKCHILDREN)
    226         self.InitUI()
    227         self.AddBackground()
    228         self.AddFeatureContainer()
    229         self.LoadCharacterSlots()
    230         self.AdjustFeatureBarPosition()
    231         self.CollapseOrExpandSlots(animate=False, loadingSlots=False)
    232         self.AdjustLogo()
    233         self.AddLoginRewardWindowButton()
    234         self.loadingWheel = LoadingWheel(parent=self.selectionScreen, align=uiconst.CENTER, state=uiconst.UI_NORMAL, idx=0)
    235         self.loadingWheel.display = False
    236 
    237     def AddLoginRewardWindowButton(self):
    238         with ExceptionEater('Failing to add reward button'):
    239             loginCampaignService = sm.GetService('loginCampaignService')
    240             if loginCampaignService.should_reward_btn_be_visible():
    241                 ButtonIcon(parent=self.featureContainer, align=uiconst.BOTTOMRIGHT, texturePath='res:/UI/Texture/\\WindowIcons/gift.png', iconSize=64, width=64, height=64, padRight=30, padLeft=-30, state=uiconst.UI_NORMAL, func=self._OpenRewardsWindow, idx=0)
    242 
    243     def _IsSelectionScreenDisabled(self):
    244         return self._IsSelectionScreenAvailable() and self.selectionScreen.state == uiconst.UI_DISABLED
    245 
    246     def _IsSelectionScreenAvailable(self):
    247         return hasattr(self, 'selectionScreen') and self.selectionScreen and not self.selectionScreen.destroyed
    248 
    249     def AnimateScreenIn(self):
    250         uicore.animations.MorphScalar(self.bg, 'opacity', startVal=0.0, endVal=1.0, duration=1.0)
    251         uicore.animations.MorphScalar(self.logo, 'opacity', startVal=0.0, endVal=1.0, duration=0.5, timeOffset=0.5)
    252         uicore.animations.MorphScalar(self.featureContainer, 'opacity', startVal=0.0, endVal=1.0, duration=0.5, timeOffset=1.5)
    253         slotDelay = 0.5
    254         uthread.new(self.PlaySound_thread, event='character_selection_animstart', sleepTime=slotDelay)
    255         for idx, eachSlot in self.slotsByIdx.iteritems():
    256             baseAnimationOffset = slotDelay + idx * 0.1
    257             eachSlot.AnimateSlotIn(animationOffset=baseAnimationOffset, soundFunction=self.PlaySound_thread, charContHeight=self.charactersCont.height)
    258 
    259     def PlaySound_thread(self, event, sleepTime):
    260         blue.pyos.synchro.Sleep(1000 * sleepTime)
    261         sm.GetService('audio').SendUIEvent(event)
    262 
    263     def AnimateScreenOut(self, excludeSlotForCharID):
    264         self.selectionScreen.state = uiconst.UI_DISABLED
    265         slotDelay = 0.5
    266         slots = self.slotsByIdx.values()
    267         slots = sorted(slots, key=lambda x: x.slotIdx, reverse=True)
    268         counter = 0
    269         uthread.new(self.PlaySound_thread, event='character_selection_animstart', sleepTime=slotDelay)
    270         for eachSlot in slots:
    271             if eachSlot.charID == excludeSlotForCharID:
    272                 continue
    273             baseAnimationOffset = slotDelay + counter * 0.1
    274             eachSlot.AnimateSlotOut(animationOffset=baseAnimationOffset, soundFunction=self.PlaySound_thread, charContHeight=self.charactersCont.height)
    275             counter += 1
    276 
    277         uicore.animations.MorphScalar(self.logo, 'opacity', startVal=1.0, endVal=0.0, duration=0.5, timeOffset=2.0)
    278         uicore.animations.MorphScalar(self.featureContainer, 'opacity', startVal=1.0, endVal=0.0, duration=0.3, timeOffset=0)
    279         self.redeemPanel.FadeOut()
    280 
    281     def _HandleSubscriptionDowngradeStatus(self):
    282         try:
    283             if self.remoteCharacterSvc.GetCharOmegaDowngradeStatus():
    284                 self.warningBar.height = self._GetWarningHeight()
    285                 self.lapsedWarning = LapsedGametimeWarning(parent=self.warningBar, name='lapsed_sub_warning', height=0)
    286                 uicore.animations.MorphScalar(self.lapsedWarning, 'height', startVal=self.lapsedWarning.height, endVal=self.lapsedWarning.default_height, duration=0.1, timeOffset=0)
    287         except Exception:
    288             log.LogTraceback('Failed to construct lapsed game time warning in character selection')
    289 
    290     def EnableScreen(self):
    291         self.selectionScreen.state = uiconst.UI_PICKCHILDREN
    292         self.AnimateScreenIn()
    293 
    294     def GetCharacterSelectionData(self, force = False):
    295         return sm.GetService('cc').GetCharacterSelectionData(force=force)
    296 
    297     def ResetVariablesForUiElements(self):
    298         self.redeemPanel = None
    299         self.topBorder = None
    300         self.warningBar = None
    301         self.centerArea = None
    302         self.logoCont = None
    303         self.logo = None
    304         self.charactersCont = None
    305         self.adContainer = None
    306         self.featureContainer = None
    307         self.openStoreContainer = None
    308 
    309     def InitUI(self):
    310         self.selectionScreen.Flush()
    311         redeemPanelClass = GetRedeemPanel()
    312         self.redeemPanel = redeemPanelClass(name='redeemPanel', width=uicore.desktop.width, align=uiconst.TOBOTTOM, parent=self.selectionScreen, animationDuration=csUtil.COLLAPSE_TIME, dragEnabled=True, instructionText=localization.GetByLabel('UI/RedeemWindow/DragAndDropToGive'), redeemButtonBorderColor=csColors.REDEEM_BORDER, redeemButtonBackgroundColor=csColors.REDEEM_BORDER_BACKGROUND, redeemButtonFillColor=csColors.REDEEM_BORDER_FILL, textColor=csColors.REDEEM_PANEL_AVAILABLE_TEXT, redeemPanelBackgroundColor=csColors.REDEEM_PANEL_FILL, onVisibilityChanged=self.OnRedeemPanelVisibilityChanged, onCollapsibilityChanged=self.OnRedeemPanelCollapsibilityChanged)
    313         self.topBorder = Container(name='topBorder', parent=self.selectionScreen, align=uiconst.TOTOP_NOPUSH, height=40, state=uiconst.UI_PICKCHILDREN)
    314         warningWrapper = Container(name='warningWrapper', parent=self.selectionScreen, align=uiconst.TOALL)
    315         self.warningBar = Container(name='warningBar', parent=warningWrapper, align=uiconst.TOTOP, height=0, state=uiconst.UI_PICKCHILDREN)
    316         self.centerArea = Container(name='centerAra', parent=self.selectionScreen, align=uiconst.TOALL, state=uiconst.UI_PICKCHILDREN)
    317         self.logoCont = Container(parent=self, name='logoCont', align=uiconst.TOTOP_NOPUSH, height=100, state=uiconst.UI_NORMAL)
    318         self.logo = Sprite(parent=self.logoCont, texturePath='res:/UI/Texture/classes/CharacterSelection/logo.png', align=uiconst.CENTER, pos=(0,
    319          0,
    320          LOGO_WIDTH,
    321          LOGO_HEIGHT), color=LOGO_COLOR)
    322         self.charactersCont = Container(name='charactersCont', parent=self.centerArea, align=uiconst.CENTER, state=uiconst.UI_PICKCHILDREN, width=1050, height=600)
    323         self.SetupCharacterSlots()
    324         uthread.new(self._HandleSubscriptionDowngradeStatus)
    325 
    326     def AddBackground(self):
    327         clientHeight = uicore.desktop.height
    328         percentOfClientHeight = float(clientHeight) / BG_HEIGHT
    329         newHeight = clientHeight
    330         newWidth = int(percentOfClientHeight * BG_WIDTH)
    331         self.bg = Sprite(parent=uicore.desktop, name='charselBackground', texturePath='res:/UI/Texture/classes/CharacterSelection/background.png', align=uiconst.CENTER, pos=(0,
    332          0,
    333          newWidth,
    334          newHeight), state=uiconst.UI_DISABLED)
    335         if csUtil.IsMonochromeStyleEnabled():
    336             csUtil.SetMonochromeStyle(self.bg, True)
    337         self.bgOverlay = Fill(bgParent=uicore.desktop, color=(0, 0, 0, 1.0))
    338         push = sm.GetService('window').GetCameraLeftOffset(self.bg.width, align=uiconst.CENTER, left=0)
    339         self.bg.left = push
    340 
    341     def ClearBackground(self):
    342         if getattr(self, 'bg', None):
    343             self.bg.Close()
    344             self.bgOverlay.Close()
    345         if getattr(self, 'logoCont', None):
    346             self.logoCont.Close()
    347 
    348     def ResetOpenStoreVisibility(self):
    349         self.openStoreContainer.StopAnimations()
    350         self.openStoreContainer.opacity = 1.0
    351 
    352     def FadeInOpenStoreContainer(self, duration):
    353         self.openStoreContainer.StopAnimations()
    354         uicore.animations.FadeIn(self.openStoreContainer, duration=duration)
    355 
    356     def FadeOutOpenStoreContainer(self, duration):
    357         self.openStoreContainer.StopAnimations()
    358         uicore.animations.FadeOut(self.openStoreContainer, duration=duration)
    359 
    360     def OnRedeemPanelVisibilityChanged(self):
    361         self.ResetOpenStoreVisibility()
    362         self.AdjustFeatureBarPosition()
    363         self.CollapseOrExpandSlots(animate=False, loadingSlots=False)
    364         self.AdjustLogo()
    365 
    366     def OnRedeemPanelCollapsibilityChanged(self):
    367         if self.redeemPanel.IsCollapsed():
    368             self.FadeInOpenStoreContainer(duration=csUtil.COLLAPSE_TIME)
    369         else:
    370             self.FadeOutOpenStoreContainer(duration=csUtil.COLLAPSE_TIME)
    371 
    372     def CollapseOrExpandSlots(self, animate = True, loadingSlots = False):
    373         shouldShipBeVisible = self.ShouldShipBeVisible()
    374         self.ChangeSlotCollapsedState(animate=animate, loadingSlots=loadingSlots)
    375         for eachSlot in self.slotsByIdx.itervalues():
    376             if shouldShipBeVisible:
    377                 eachSlot.ExpandSlot(animate=animate)
    378             else:
    379                 eachSlot.CollapseSlot(animate=animate)
    380 
    381     def ShouldShipBeVisible(self):
    382         if self.maxFullSlotSize:
    383             maxFullSlotSize = self.maxFullSlotSize
    384         else:
    385             l, ct, cw, ch = self.charactersCont.GetAbsolute()
    386             maxFullSlotSize = ch
    387         redeemPanelHeight = self.redeemPanel.height
    388         emptySpace = uicore.desktop.height - maxFullSlotSize
    389         if emptySpace < 2 * (MINIMUM_LOGOHEIGHT + LOGO_PADDING) or emptySpace < FEATURE_BAR_HEIGHT + redeemPanelHeight:
    390             shipVisible = False
    391         else:
    392             shipVisible = True
    393         return shipVisible
    394 
    395     def ChangeSlotCollapsedState(self, animate, loadingSlots = False):
    396         shouldShipBeVisible = self.ShouldShipBeVisible()
    397         maxCurrentCharacterSlotHeight = self.GetMaxCharacterSlotHeight(shipVisible=shouldShipBeVisible)
    398         charactersContTop = 0
    399         diff = self.charactersCont.height - maxCurrentCharacterSlotHeight
    400         if animate or loadingSlots and not self.redeemPanel.IsCollapsed():
    401             charactersContTop = min(0, int(-diff / 2.0))
    402         freeSpace = uicore.desktop.height - maxCurrentCharacterSlotHeight
    403 
    404         def FindExtraShift(componentHeight):
    405             shift = 0
    406             if freeSpace / 2.0 < componentHeight:
    407                 shift = componentHeight - int(freeSpace / 2.0)
    408             return shift
    409 
    410         extraShift = 0
    411         if self.redeemPanel.IsVisible() and not self.redeemPanel.IsCollapsed():
    412             extraShift = FindExtraShift(self.redeemPanel.height)
    413         if extraShift:
    414             charactersContTop = min(charactersContTop, -extraShift)
    415         uicore.animations.MorphScalar(self.logo, 'opacity', startVal=self.logo.opacity, endVal=1.0, duration=csUtil.COLLAPSE_TIME)
    416         if extraShift:
    417             cl, ct, cw, ch = self.charactersCont.GetAbsolute()
    418             if ct - extraShift < self.logoCont.height - LOGO_PADDING / 2:
    419                 uicore.animations.MorphScalar(self.logo, 'opacity', startVal=self.logo.opacity, endVal=0.05, duration=csUtil.COLLAPSE_TIME)
    420         if animate:
    421             uicore.animations.MorphScalar(self.charactersCont, 'height', startVal=self.charactersCont.height, endVal=maxCurrentCharacterSlotHeight, duration=csUtil.COLLAPSE_TIME)
    422             uicore.animations.MorphScalar(self.charactersCont, 'top', startVal=self.charactersCont.top, endVal=charactersContTop, duration=csUtil.COLLAPSE_TIME)
    423         else:
    424             uicore.animations.StopAnimation(self.charactersCont, 'height')
    425             self.charactersCont.height = maxCurrentCharacterSlotHeight
    426             self.charactersCont.top = charactersContTop
    427 
    428     def GetSpaceForRedeemPanel(self):
    429         return min(self.redeemPanel.height, self.redeemPanel.GetCollapsedHeight())
    430 
    431     def AdjustFeatureBarPosition(self):
    432         shouldShipBeVisible = self.ShouldShipBeVisible()
    433         maxCurrentCharacterSlotHeight = self.GetMaxCharacterSlotHeight(shouldShipBeVisible)
    434         redeemPanelHeight = self.GetSpaceForRedeemPanel()
    435         availableHeight = int((uicore.desktop.height - maxCurrentCharacterSlotHeight) / 2.0) - redeemPanelHeight
    436         self.featureContainer.top = int(availableHeight / 2.0) + redeemPanelHeight - FEATURE_BAR_HEIGHT / 2
    437 
    438     def GetMaxCharacterSlotHeight(self, shipVisible = True):
    439         if self.characterSlotList:
    440             return max((slot.GetSlotHeight(shipVisible=shipVisible) for slot in self.characterSlotList))
    441         else:
    442             return max([ slot.GetSlotHeight(shipVisible=shipVisible) for slot in self.slotsByIdx.itervalues() ])
    443 
    444     def SetupCharacterSlots(self):
    445         self.characterSlotList = []
    446         self.slotsByCharID = {}
    447         self.slotsByIdx = {}
    448         self.slotsToDisplay = min(self.numSlotsOwnedByUser + 1, self.slotsToDisplay)
    449         paddingFromImage = SmallCharacterSlot.GetExtraWidth()
    450         spaceForEachSlot = (uicore.desktop.width - 2 * self.minSidePadding) / self.slotsToDisplay
    451         maxSize = SmallCharacterSlot.maxImageSize + paddingFromImage
    452         spaceForEachSlot = min(maxSize, spaceForEachSlot)
    453         occupiedSlots = len(self.chars[:self.slotsToDisplay])
    454         for idx in xrange(occupiedSlots):
    455             slot = SmallCharacterSlot(name='characterSlot_%s' % idx, parent=self.charactersCont, callback=self.EnterGameWithCharacter, deleteCallback=self.Terminate, undoDeleteCallback=self.UndoTermination, terminateCallback=self.Terminate, slotIdx=idx, width=spaceForEachSlot)
    456             slot.OnMouseEnter = (self.OnSlotOnMouseEnter, slot)
    457             slot.OnMouseExit = (self.OnSlotMouseExit, slot)
    458             self.characterSlotList.append(slot)
    459             self.slotsByIdx[idx] = slot
    460 
    461         for idx in xrange(occupiedSlots, self.slotsToDisplay):
    462             if idx > self.numSlotsOwnedByUser - 1:
    463                 callback = self.GoBuySlot
    464                 ownSlot = False
    465             else:
    466                 callback = self.CreateCharacter
    467                 ownSlot = True
    468             slot = SmallEmptySlot(name='emptySlot_%s' % idx, parent=self.charactersCont, callback=callback, slotIdx=idx, width=spaceForEachSlot, ownSlot=ownSlot)
    469             self.slotsByIdx[idx] = slot
    470             slot.OnMouseEnter = (self.OnSlotOnMouseEnter, slot)
    471             slot.OnMouseExit = (self.OnSlotMouseExit, slot)
    472 
    473         self.charactersCont.width = spaceForEachSlot * self.slotsToDisplay
    474         self.SetUIOffset()
    475 
    476     def SetUIOffset(self):
    477         cameraOffset = settings.user.ui.offsetUIwithCamera
    478         uiOffsetWithCamera = settings.user.ui.cameraOffset
    479         push = sm.GetService('window').GetCameraLeftOffset(self.charactersCont.width, align=uiconst.CENTER, left=0)
    480         self.uiOffset = (cameraOffset, uiOffsetWithCamera)
    481         self.charactersCont.left = push
    482         self.logo.left = push
    483 
    484     def LoadCharacterSlots(self):
    485         allSlots = self.characterSlotList[:]
    486         for characterInfo in self.chars[:self.slotsToDisplay]:
    487             charID = characterInfo.characterID
    488             characterSlot = allSlots.pop(0)
    489             self.LoadSlotForCharacter(charID, characterSlot)
    490             characterSlot.SetMouseExitState()
    491 
    492         if self.characterSlotList:
    493             maxShipIconHeight = max((slot.GetShipAndLocationContHeight() for slot in self.characterSlotList))
    494         else:
    495             shipPadding = CharLocation.paddingShipAlignmentTop + CharLocation.paddingShipAlignmentBottom
    496             maxShipIconHeight = CharLocation.minShipSize + CharLocation.locationContHeight + shipPadding
    497         for slot in self.slotsByIdx.itervalues():
    498             slot.SetShipContHeight(maxShipIconHeight)
    499 
    500         maxSlotHeight = self.GetMaxCharacterSlotHeight()
    501         self.maxFullSlotSize = maxSlotHeight
    502         self.charactersCont.height = maxSlotHeight
    503         self.ready = True
    504 
    505     def LoadSlotForCharacter(self, charID, characterSlot):
    506         self.slotsByCharID[charID] = characterSlot
    507         characterDetails = self.GetCharacterSelectionData().GetCharInfo(charID)
    508         characterSlot.LoadSlot(charID, characterDetails)
    509 
    510     def AddFeatureContainer(self):
    511         distanceFromBottom = self.GetSpaceForRedeemPanel()
    512         self.featureContainer = Container(parent=self.selectionScreen, name='featureContainer', align=uiconst.TOBOTTOM_NOPUSH, height=FEATURE_BAR_HEIGHT, top=distanceFromBottom)
    513         innerFeatureContainer = Container(parent=self.featureContainer, name='innerFeatureCont', align=uiconst.CENTER, width=834, height=FEATURE_BAR_HEIGHT)
    514         self.openStoreContainer = Container(name='openStoreContainer', parent=innerFeatureContainer, state=uiconst.UI_PICKCHILDREN, align=uiconst.CENTER, width=270, height=FEATURE_BAR_INNER_HEIGHT)
    515         OpenStoreButton(parent=self.openStoreContainer, align=uiconst.CENTER, onClick=uicore.cmd.ToggleAurumStore)
    516         self.adContainer = Container(name='adContainer', parent=innerFeatureContainer, align=uiconst.CENTERRIGHT, width=BANNER_WIDTH, height=FEATURE_BAR_INNER_HEIGHT)
    517         if self.surveyID:
    518             self.DisplaySurveyBanner()
    519         push = sm.GetService('window').GetCameraLeftOffset(innerFeatureContainer.width, align=uiconst.CENTER, left=0)
    520         innerFeatureContainer.left = push
    521 
    522     def DisplaySurveyBanner(self):
    523         with ExceptionEater('Failing to load survey banner'):
    524             if not getattr(self, 'adContainer', None) or self.adContainer.destroyed:
    525                 return
    526             if getattr(self, 'adSprite', None):
    527                 self.adSprite.Close()
    528                 self.adSprite = None
    529             self.adContainer.Flush()
    530             SurveyBanner(parent=self.adContainer, surveyID=self.surveyID)
    531             self.openStoreContainer.align = uiconst.CENTERLEFT
    532 
    533     def AdjustLogo(self):
    534         cl, ct, cw, ch = self.charactersCont.GetAbsolute()
    535         if ct > 0:
    536             availableHeightAbove = ct
    537         else:
    538             availableHeightAbove = int((uicore.desktop.height - self.maxFullSlotSize) / 2.0)
    539         logoContHeight = max(availableHeightAbove, MINIMUM_LOGOHEIGHT + LOGO_PADDING)
    540         self.logoCont.height = logoContHeight
    541         self.logo.display = True
    542         availableHeightForLogo = logoContHeight - LOGO_PADDING
    543         percentage = max(0.55, min(1.0, availableHeightForLogo / float(LOGO_HEIGHT)))
    544         newHeight = int(percentage * LOGO_HEIGHT)
    545         newWidth = int(percentage * LOGO_WIDTH)
    546         self.logo.height = newHeight
    547         self.logo.width = newWidth
    548 
    549     def OnSlotOnMouseEnter(self, slot, *args):
    550         if not slot.mouseOverState:
    551             sm.GetService('audio').SendUIEvent('character_hover_picture')
    552         for eachSlot in self.slotsByIdx.itervalues():
    553             if eachSlot.charID:
    554                 characterData = self.GetCharacterSelectionData().GetCharInfo(eachSlot.charID)
    555                 deletePrepTime = characterData.GetDeletePrepareTime()
    556                 if deletePrepTime:
    557                     continue
    558             if eachSlot == slot:
    559                 eachSlot.SetMouseOverState(animate=True)
    560             else:
    561                 eachSlot.SetMouseExitState(animate=True)
    562 
    563     def OnSlotMouseExit(self, slot, *args):
    564         if uicore.uilib.mouseOver.IsUnder(slot):
    565             return
    566         slot.SetMouseExitState(animate=True)
    567 
    568     def GoBuySlot(self, slotSelected):
    569         uicore.cmd.GetCommandAndExecute('OpenAccountManagement', origin='characterSelection')
    570 
    571     def CreateCharacter(self, slotSelected):
    572         if not self._CanLogin():
    573             return
    574         self.CreateNewCharacter()
    575 
    576     def _CanLogin(self):
    577         computerhash = sm.GetService('connection').GetComputerHash()
    578         if computerhash is not None:
    579             if not sm.RemoteSvc('multiLoginBlocker').Login(computerhash):
    580                 MultiLoginBlockedWindow.Open(origin=ORIGIN_CHARACTERSELECTION)
    581                 return False
    582         return True
    583 
    584     def EnterGameWithCharacter(self, slotSelected):
    585         characterData = self.GetCharacterSelectionData().GetCharInfo(slotSelected.charID)
    586         deletePrepTime = characterData.GetDeletePrepareTime()
    587         if deletePrepTime is not None:
    588             return
    589         if not self._CanLogin():
    590             return
    591         slotSelected.SetMouseOverState()
    592         slotSelected.PlaySelectedAnimation()
    593         self.ConfirmWithCharID(slotSelected.charID)
    594 
    595     def CreateNewCharacter(self):
    596         if not self.ready:
    597             prompt_player_fsd_dialog('Busy')
    598             return
    599         lowEnd = gfxsettings.GetDeviceClassification() == gfxsettings.DEVICE_LOW_END
    600         if lowEnd:
    601             msg2 = prompt_player_fsd_dialog('ReduceGraphicsSettings', {}, uiconst.YESNO, default=uiconst.ID_NO)
    602             if msg2 == uiconst.ID_YES:
    603                 self.ReduceCharacterGraphics()
    604         prompt_player_fsd_dialog('CCNewChar')
    605         uthread.new(sm.GetService('gameui').GoCharacterCreation)
    606 
    607     def CreateNewAvatar(self, charID):
    608         charData = self.GetCharacterSelectionData().GetCharInfo(charID)
    609         charDetails = charData.charDetails
    610         if charData.GetPaperDollState == paperdoll.State.force_recustomize:
    611             prompt_player_fsd_dialog('ForcedPaperDollRecustomization')
    612         uthread.new(sm.GetService('gameui').GoCharacterCreation, charID, charDetails.gender, charDetails.raceID, charDetails.bloodlineID, dollState=charData.GetPaperDollState())
    613 
    614     def Confirm(self):
    615         if not self.characterSlotList:
    616             return
    617         slot = self.characterSlotList[0]
    618         self.EnterGameWithCharacter(slot)
    619 
    620     def ConfirmWithCharID(self, charID, *_):
    621         log.LogInfo('Character selection: Character selection confirmation')
    622         if not self.ready:
    623             log.LogInfo('Character selection: Denied character selection confirmation, not ready')
    624             prompt_player_fsd_dialog('Busy')
    625             return
    626         isInSync = self.WaitForClockSynchroAndGetSynchroState()
    627         if not isInSync:
    628             prompt_player_fsd_dialog('ClockSynchroInProgress')
    629             return
    630         if sm.GetService('jumpQueue').GetPreparedQueueCharID() != charID:
    631             self.__Confirm(charID)
    632 
    633     def WaitForClockSynchroAndGetSynchroState(self):
    634         for x in xrange(300):
    635             if not sm.GetService('connection').IsClockSynchronizing():
    636                 return True
    637             if x > 30:
    638                 log.general.Log('Clock synchronization still in progress after %d seconds' % x, log.LGINFO)
    639             blue.pyos.synchro.SleepWallclock(1000)
    640 
    641         return not sm.GetService('connection').IsClockSynchronizing()
    642 
    643     def CheckCharacterLockStatus(self, charID):
    644         lockTypeID = sm.RemoteSvc('charUnboundMgr').GetCharacterLockType(charID)
    645         if lockTypeID is not None:
    646             if lockTypeID == appConst.charLockInTransferQueue:
    647                 raise UserError('CharacterTransferring')
    648             elif lockTypeID == appConst.charLockOnSale:
    649                 raise UserError('CharacterOnSale')
    650             else:
    651                 raise UserError('CharacterLocked')
    652 
    653     def EnterAsCharacter(self, charID, secondChoiceID, skipTutorial = False):
    654         MAX_RETRIES = 10
    655         RETRY_SECONDS = 6
    656         self.CheckCharacterLockStatus(charID)
    657         if secondChoiceID is not None:
    658             self.CheckCharacterLockStatus(secondChoiceID)
    659         for numTries in xrange(MAX_RETRIES):
    660             try:
    661                 sm.GetService('sessionMgr').PerformSessionChange('charsel', self.remoteCharacterSvc.SelectCharacterID, charID, secondChoiceID, skipTutorial)
    662                 launchdarkly.get_client().character_select(session.userid, charID, session.languageID)
    663                 return
    664             except UserError as e:
    665                 if e.msg == 'SystemCheck_SelectFailed_Loading' and numTries < MAX_RETRIES - 1:
    666                     log.LogNotice('System is currently loading. Retrying %s/%s' % (numTries, MAX_RETRIES))
    667                     blue.pyos.synchro.SleepWallclock(RETRY_SECONDS * 1000)
    668                 else:
    669                     self.EnableScreen()
    670                     raise
    671 
    672         self.EnableScreen()
    673 
    674     def __Confirm(self, charID, secondChoiceID = None):
    675         charData = self.GetCharacterSelectionData().GetCharInfo(charID)
    676         dollState = charData.GetPaperDollState()
    677         sm.GetService('cc').StoreCurrentDollState(dollState)
    678         if dollState in (paperdoll.State.force_recustomize, paperdoll.State.no_existing_customization):
    679             self.CreateNewAvatar(charID)
    680             return
    681         self.ready = False
    682         self.TryEnterGame(charID, secondChoiceID)
    683         if charID:
    684             mailCount = charData.GetUnreaddMailCount()
    685             if mailCount > 0:
    686                 uthread.new(sm.GetService('mailSvc').CheckNewMessages_thread, mailCount)
    687 
    688     def TryEnterGame(self, charID, secondChoiceID, skipTutorial = False):
    689         sm.GetService('audio').SendUIEvent('msg_OnLogin_play')
    690         sm.GetService('audio').SendUIEvent('msg_OnConnecting_play')
    691         self.ShowLoading()
    692         try:
    693             prompt_player_fsd_dialog('OnCharSel')
    694             sm.GetService('jumpQueue').PrepareQueueForCharID(charID)
    695             populationCap = moniker.Moniker('populationCap', (charID, appConst.groupCharacter))
    696             locations = populationCap.GetCharacterLoadSlot(charID)
    697             if len(locations) > 1:
    698                 selectedSolarSystemID = self.AskForAlternativeSystems(locations.keys())
    699                 if selectedSolarSystemID is None:
    700                     sm.GetService('jumpQueue').PrepareQueueForCharID(None)
    701                     self.ready = True
    702                     return
    703                 slotKey = locations[selectedSolarSystemID]
    704                 populationCap.MoveCharacterToNewSystem(charID, selectedSolarSystemID, slotKey)
    705             try:
    706                 self.selectionScreen.state = uiconst.UI_DISABLED
    707                 self.AnimateScreenOut(excludeSlotForCharID=charID)
    708                 self.EnterAsCharacter(charID, secondChoiceID, skipTutorial)
    709             except UserError as e:
    710                 self.EnableScreen()
    711                 if e.msg != 'SystemCheck_SelectFailed_Queued':
    712                     sm.GetService('jumpQueue').PrepareQueueForCharID(None)
    713                     raise
    714             except:
    715                 self.EnableScreen()
    716                 sm.GetService('jumpQueue').PrepareQueueForCharID(None)
    717                 raise
    718 
    719         except:
    720             self.EnableScreen()
    721             self.HideLoading()
    722             sm.GetService('loading').FadeOut()
    723             self.ready = True
    724             raise
    725 
    726     def AskForAlternativeSystems(self, neighbors):
    727         selectText = localization.GetByLabel('UI/CharacterSelection/SelectAlternativeSystem')
    728         validNeighbors = []
    729         for ssid in neighbors:
    730             systemItem = self.mapSvc.GetItem(ssid)
    731             regionID = self.mapSvc.GetRegionForSolarSystem(ssid)
    732             regionItem = self.mapSvc.GetItem(regionID)
    733             factionID = systemItem.factionID
    734             factionName = ''
    735             if factionID:
    736                 factionName = cfg.eveowners.Get(factionID).ownerName
    737             label = '%s<t>%s<t>%s<t>%s' % (systemItem.itemName,
    738              regionItem.itemName,
    739              self.mapSvc.GetSecurityStatus(ssid),
    740              factionName)
    741             validNeighborTuple = (label, ssid, None)
    742             validNeighbors.append(validNeighborTuple)
    743 
    744         loadingSvc = sm.StartService('loading')
    745         self.HideLoading()
    746         loadingSvc.FadeOut()
    747         scrollHeaders = [localization.GetByLabel('UI/Common/LocationTypes/SolarSystem'),
    748          localization.GetByLabel('UI/Common/LocationTypes/Region'),
    749          localization.GetByLabel('UI/Common/Security'),
    750          localization.GetByLabel('UI/Sovereignty/Sovereignty')]
    751         ret = uix.ListWnd(validNeighbors, None, localization.GetByLabel('UI/CharacterSelection/SystemCongested'), selectText, 1, scrollHeaders=scrollHeaders, minw=555, windowName='AlternativeSystemSelect')
    752         if ret:
    753             return ret[1]
    754 
    755     def Terminate(self, charID, *args):
    756         if not self.ready:
    757             prompt_player_fsd_dialog('Busy')
    758             return
    759         try:
    760             self.ready = 0
    761             characterData = self.GetCharacterSelectionData().GetCharInfo(charID)
    762             deletePrepTime = characterData.GetDeletePrepareTime()
    763             if deletePrepTime:
    764                 now = blue.os.GetWallclockTime()
    765                 if deletePrepTime < now:
    766                     self.DeleteCharacter(charID, characterData.charDetails.gender)
    767                 else:
    768                     timeLeft = deletePrepTime - now
    769                     infoMsg = localization.GetByLabel('UI/CharacterSelection/AlreadyInBiomassQueue', charID=charID, timeLef=timeLeft)
    770                     prompt_player_fsd_dialog('CustomInfo', {'info': infoMsg})
    771             else:
    772                 self.SubmitToBiomassQueue(charID)
    773         finally:
    774             self.ready = 1
    775 
    776     def DeleteCharacter(self, charID, gender):
    777         prompt_player_fsd_dialog('CCTerminate')
    778         if prompt_player_fsd_dialog('AskDeleteCharacter', {'charID': charID}, uiconst.YESNO) != uiconst.ID_YES:
    779             return
    780         self.ShowLoading()
    781         if gender == appConst.genderFemale:
    782             beginMsg = 'CCTerminateForGoodFemaleBegin'
    783             endMsg = 'CCTerminateForGoodFemale'
    784         else:
    785             beginMsg = 'CCTerminateForGoodMaleBegin'
    786             endMsg = 'CCTerminateForGoodMale'
    787         prompt_player_fsd_dialog(beginMsg)
    788         try:
    789             error = self.remoteCharacterSvc.DeleteCharacter(charID)
    790             prompt_player_fsd_dialog(endMsg)
    791         finally:
    792             self.HideLoading()
    793             self.ready = 1
    794 
    795         if error:
    796             prompt_player_fsd_dialog(error)
    797             return
    798         self.SetData(force=True)
    799         self.OnRefreshScreen()
    800 
    801     def SubmitToBiomassQueue(self, charID):
    802         if prompt_player_fsd_dialog('AskSubmitToBiomassQueue', {'charID': charID}, uiconst.YESNO) != uiconst.ID_YES:
    803             return
    804         ret = self.remoteCharacterSvc.PrepareCharacterForDelete(charID)
    805         if ret:
    806             prompt_player_fsd_dialog('SubmitToBiomassQueueConfirm', {'charID': charID,
    807              'when': ret - blue.os.GetWallclockTime()})
    808         self.UpdateSlot(charID)
    809 
    810     def UndoTermination(self, charID, *args):
    811         sm.RemoteSvc('charUnboundMgr').CancelCharacterDeletePrepare(charID)
    812         self.UpdateSlot(charID)
    813 
    814     def UpdateSlot(self, charID):
    815         self.GetCharacterSelectionData(force=True)
    816         slot = self.slotsByCharID.get(charID, None)
    817         if slot:
    818             characterDetails = self.GetCharacterSelectionData().GetCharInfo(charID)
    819             slot.RefreshCharacterDetails(characterDetails)
    820             slot.SetDeleteUI()
    821         else:
    822             self.OnRefreshScreen()
    823 
    824     def ShowLoading(self, forceOn = 0):
    825         try:
    826             self.loadingWheel.forcedOn = forceOn
    827             self.loadingWheel.Show()
    828         except:
    829             log.LogError('Failed to show the loading wheel')
    830 
    831     def HideLoading(self, forceOff = 0):
    832         try:
    833             if not self.loadingWheel.forcedOn or forceOff:
    834                 self.loadingWheel.Hide()
    835                 self.loadingWheel.forcedOn = 0
    836         except:
    837             log.LogError('Failed to hide the loading wheel')
    838 
    839     def _GetWarningHeight(self):
    840         if uicore.desktop.height < 810:
    841             return 1
    842         return WARNING_BANNER_HEIGHT
    843 
    844     def OnBannerAdDisplayed(self):
    845         self.adContainer.display = True
    846         self.openStoreContainer.align = uiconst.CENTERLEFT
    847 
    848     def OnBannerAdHidden(self):
    849         self.adContainer.display = False
    850         self.openStoreContainer.align = uiconst.CENTER
    851 
    852 
    853 @Component(ButtonEffect(opacityIdle=0.0, opacityHover=1.4, opacityMouseDown=1.7, bgElementFunc=lambda parent, _: parent.highlight, audioOnEntry=uiconst.SOUND_BUTTON_HOVER))
    854 
    855 class OpenStoreButton(Container):
    856     default_name = 'OpenStoreButton'
    857     default_width = 270
    858     default_height = 90
    859     default_state = uiconst.UI_NORMAL
    860 
    861     def ApplyAttributes(self, attributes):
    862         Container.ApplyAttributes(self, attributes)
    863         self.logo = Sprite(name='Logo', bgParent=self, texturePath='res:/UI/Texture/Vgs/storeLogo.png', align=uiconst.TOALL)
    864         self.highlight = Sprite(name='Highlight', bgParent=self, texturePath='res:/UI/Texture/Vgs/storeLogoGlow.png', align=uiconst.TOALL)
    865         if csUtil.IsMonochromeStyleEnabled():
    866             csUtil.SetMonochromeStyle(self.logo, True)
    867         self.onClick = attributes.onClick
    868 
    869     def OnClick(self):
    870         PlaySound(uiconst.SOUND_BUTTON_CLICK)
    871         self.onClick()