2448 lines
101 KiB
Python
2448 lines
101 KiB
Python
import sys
|
|
import time
|
|
import random
|
|
import gc
|
|
import os
|
|
from panda3d.core import *
|
|
from direct.gui.DirectGui import *
|
|
from otp.distributed.OtpDoGlobals import *
|
|
from direct.interval.IntervalGlobal import ivalMgr
|
|
from direct.directnotify.DirectNotifyGlobal import directNotify
|
|
from direct.distributed.ClientRepositoryBase import ClientRepositoryBase
|
|
from direct.fsm.ClassicFSM import ClassicFSM
|
|
from direct.fsm.State import State
|
|
from direct.task import Task
|
|
from direct.distributed import DistributedSmoothNode
|
|
from direct.showbase import PythonUtil, GarbageReport
|
|
from direct.showbase.ContainerLeakDetector import ContainerLeakDetector
|
|
from direct.showbase import MessengerLeakDetector
|
|
from direct.showbase.GarbageReportScheduler import GarbageReportScheduler
|
|
from direct.showbase import LeakDetectors
|
|
from direct.distributed.PyDatagram import PyDatagram
|
|
from otp.avatar.DistributedPlayer import DistributedPlayer
|
|
from otp.login import LoginTTSpecificDevAccount
|
|
from otp.login.CreateAccountScreen import CreateAccountScreen
|
|
from otp.login import LoginScreen
|
|
from otp.otpgui import OTPDialog
|
|
from otp.otpbase import OTPLocalizer
|
|
from otp.login import LoginGSAccount
|
|
from otp.login import LoginGoAccount
|
|
from otp.login.LoginWebPlayTokenAccount import LoginWebPlayTokenAccount
|
|
from otp.login.LoginDISLTokenAccount import LoginDISLTokenAccount
|
|
from otp.login import LoginTTAccount
|
|
from otp.login import LoginAstronAccount
|
|
from otp.login import HTTPUtil
|
|
from otp.otpbase import OTPGlobals
|
|
from otp.otpbase import OTPLauncherGlobals
|
|
from otp.uberdog import OtpAvatarManager
|
|
from otp.distributed import OtpDoGlobals
|
|
from otp.distributed.TelemetryLimiter import TelemetryLimiter
|
|
from otp.ai.GarbageLeakServerEventAggregator import GarbageLeakServerEventAggregator
|
|
from .PotentialAvatar import PotentialAvatar
|
|
from enum import IntEnum
|
|
|
|
class OTPClientRepository(ClientRepositoryBase):
|
|
notify = directNotify.newCategory('OTPClientRepository')
|
|
avatarLimit = 6
|
|
WishNameResult = IntEnum('WishNameResult', ('Failure',
|
|
'PendingApproval',
|
|
'Approved',
|
|
'Rejected'))
|
|
|
|
def __init__(self, serverVersion, launcher = None, playGame = None):
|
|
ClientRepositoryBase.__init__(self)
|
|
self.handler = None
|
|
self.launcher = launcher
|
|
base.launcher = launcher
|
|
self.__currentAvId = 0
|
|
self.productName = ConfigVariableString('product-name', 'DisneyOnline-US').value
|
|
self.createAvatarClass = None
|
|
self.systemMessageSfx = None
|
|
reg_deployment = ''
|
|
if self.productName == 'DisneyOnline-US':
|
|
if self.launcher:
|
|
if self.launcher.isDummy():
|
|
reg_deployment = self.launcher.getDeployment()
|
|
else:
|
|
reg_deployment = self.launcher.getRegistry('DEPLOYMENT')
|
|
if reg_deployment != 'UK' and reg_deployment != 'AP':
|
|
reg_deployment = self.launcher.getRegistry('GAME_DEPLOYMENT')
|
|
|
|
self.notify.info('reg_deployment=%s' % reg_deployment)
|
|
|
|
if reg_deployment == 'UK':
|
|
self.productName = 'DisneyOnline-UK'
|
|
elif reg_deployment == 'AP':
|
|
self.productName = 'DisneyOnline-AP'
|
|
|
|
self.blue = None
|
|
if self.launcher:
|
|
self.blue = self.launcher.getBlue()
|
|
|
|
fakeBlue = ConfigVariableString('fake-blue', '').value
|
|
if fakeBlue:
|
|
self.blue = fakeBlue
|
|
|
|
self.playToken = None
|
|
if self.launcher:
|
|
self.playToken = self.launcher.getPlayToken()
|
|
|
|
fakePlayToken = ConfigVariableString('fake-playtoken', '').value
|
|
if fakePlayToken:
|
|
self.playToken = fakePlayToken
|
|
|
|
self.DISLToken = None
|
|
if self.launcher:
|
|
self.DISLToken = self.launcher.getDISLToken()
|
|
|
|
fakeDISLToken = ConfigVariableString('fake-DISLToken', '').value
|
|
fakeDISLPlayerName = ConfigVariableString('fake-DISL-PlayerName', '').value
|
|
if fakeDISLToken:
|
|
self.DISLToken = fakeDISLToken
|
|
elif fakeDISLPlayerName:
|
|
defaultId = 42
|
|
defaultNumAvatars = 4
|
|
defaultNumAvatarSlots = 4
|
|
defaultNumConcur = 1
|
|
subCount = ConfigVariableInt('fake-DISL-NumSubscriptions', 1).value
|
|
playerAccountId = ConfigVariableInt('fake-DISL-PlayerAccountId', defaultId).value
|
|
self.DISLToken = ('ACCOUNT_NAME=%s' % fakeDISLPlayerName +
|
|
'&ACCOUNT_NUMBER=%s' % playerAccountId +
|
|
'&ACCOUNT_NAME_APPROVAL=%s' % ConfigVariableString('fake-DISL-PlayerNameApproved', 'YES').value +
|
|
'&SWID=%s' % ConfigVariableString('fake-DISL-SWID', '{1763AC36-D73F-41C2-A54A-B579E58B69C8}').value +
|
|
'&FAMILY_NUMBER=%s' % ConfigVariableString('fake-DISL-FamilyAccountId', '-1').value +
|
|
'&familyAdmin=%s' % ConfigVariableString('fake-DISL-FamilyAdmin', '1').value +
|
|
'&PIRATES_ACCESS=%s' % ConfigVariableString('fake-DISL-PiratesAccess', 'FULL').value +
|
|
'&PIRATES_MAX_NUM_AVATARS=%s' % ConfigVariableInt('fake-DISL-MaxAvatars', defaultNumAvatars).value +
|
|
'&PIRATES_NUM_AVATAR_SLOTS=%s' % ConfigVariableInt('fake-DISL-MaxAvatarSlots', defaultNumAvatarSlots).value +
|
|
'&expires=%s' % ConfigVariableString('fake-DISL-expire', '1577898000').value +
|
|
'&OPEN_CHAT_ENABLED=%s' % ConfigVariableString('fake-DISL-OpenChatEnabled', 'YES').value +
|
|
'&CREATE_FRIENDS_WITH_CHAT=%s' % ConfigVariableString('fake-DISL-CreateFriendsWithChat', 'YES').value +
|
|
'&CHAT_CODE_CREATION_RULE=%s' % ConfigVariableString('fake-DISL-ChatCodeCreation', 'YES').value +
|
|
'&FAMILY_MEMBERS=%s' % ConfigVariableString('fake-DISL-FamilyMembers').value + '&PIRATES_SUB_COUNT=%s' % subCount)
|
|
for i in range(subCount):
|
|
self.DISLToken += ('&PIRATES_SUB_%s_ACCESS=%s' % (i, ConfigVariableString('fake-DISL-Sub-%s-Access' % i, 'FULL').value) +
|
|
'&PIRATES_SUB_%s_ACTIVE=%s' % (i, ConfigVariableString('fake-DISL-Sub-%s-Active' % i, 'YES').value) +
|
|
'&PIRATES_SUB_%s_ID=%s' % (i, ConfigVariableInt('fake-DISL-Sub-%s-Id' % i, playerAccountId).value + ConfigVariableInt('fake-DISL-Sub-Id-Offset', 0).value) +
|
|
'&PIRATES_SUB_%s_LEVEL=%s' % (i, ConfigVariableInt('fake-DISL-Sub-%s-Level' % i, 3).value) +
|
|
'&PIRATES_SUB_%s_NAME=%s' % (i, ConfigVariableString('fake-DISL-Sub-%s-Name' % i, fakeDISLPlayerName).value) +
|
|
'&PIRATES_SUB_%s_NUM_AVATARS=%s' % (i, ConfigVariableInt('fake-DISL-Sub-%s-NumAvatars' % i, defaultNumAvatars).value) +
|
|
'&PIRATES_SUB_%s_NUM_CONCUR=%s' % (i, ConfigVariableInt('fake-DISL-Sub-%s-NumConcur' % i, defaultNumConcur).value) +
|
|
'&PIRATES_SUB_%s_OWNERID=%s' % (i, ConfigVariableInt('fake-DISL-Sub-%s-OwnerId' % i, playerAccountId).value) +
|
|
'&PIRATES_SUB_%s_FOUNDER=%s' % (i, ConfigVariableString('fake-DISL-Sub-%s-Founder' % i, 'YES').value))
|
|
|
|
self.DISLToken += ('&WL_CHAT_ENABLED=%s' % ConfigVariableString('fake-DISL-WLChatEnabled', 'YES').value +
|
|
'&valid=true')
|
|
if base.logPrivateInfo:
|
|
print(self.DISLToken)
|
|
|
|
self.requiredLogin = ConfigVariableString('required-login', 'auto').value
|
|
if self.requiredLogin == 'auto':
|
|
self.notify.info('required-login auto.')
|
|
elif self.requiredLogin == 'green':
|
|
self.notify.error('The green code is out of date')
|
|
elif self.requiredLogin == 'blue':
|
|
if not self.blue:
|
|
self.notify.error('The tcr does not have the required blue login')
|
|
elif self.requiredLogin == 'playToken':
|
|
if not self.playToken:
|
|
self.notify.error('The tcr does not have the required playToken login')
|
|
elif self.requiredLogin == 'DISLToken':
|
|
if not self.DISLToken:
|
|
self.notify.error('The tcr does not have the required DISL token login')
|
|
elif self.requiredLogin == 'gameServer':
|
|
self.notify.info('Using game server name/password.')
|
|
self.DISLToken = None
|
|
else:
|
|
self.notify.error('The required-login was not recognized.')
|
|
|
|
self.wantMagicWords = False
|
|
if self.launcher and hasattr(self.launcher, 'http'):
|
|
self.http = self.launcher.http
|
|
else:
|
|
self.http = HTTPClient()
|
|
|
|
self.accountOldAuth = ConfigVariableBool('account-old-auth', 0).value
|
|
self.accountOldAuth = ConfigVariableBool('%s-account-old-auth' % game.name,
|
|
self.accountOldAuth).value
|
|
self.useNewTTDevLogin = ConfigVariableBool('use-tt-specific-dev-login', False).value
|
|
if __astron__:
|
|
self.loginInterface = LoginAstronAccount.LoginAstronAccount(self)
|
|
self.notify.info('loginInterface: LoginAstronAccount')
|
|
elif self.useNewTTDevLogin:
|
|
self.loginInterface = LoginTTSpecificDevAccount.LoginTTSpecificDevAccount(self)
|
|
self.notify.info('loginInterface: LoginTTSpecificDevAccount')
|
|
elif self.accountOldAuth:
|
|
self.loginInterface = LoginGSAccount.LoginGSAccount(self)
|
|
self.notify.info('loginInterface: LoginGSAccount')
|
|
elif self.blue:
|
|
self.loginInterface = LoginGoAccount.LoginGoAccount(self)
|
|
self.notify.info('loginInterface: LoginGoAccount')
|
|
elif self.playToken:
|
|
self.loginInterface = LoginWebPlayTokenAccount(self)
|
|
self.notify.info('loginInterface: LoginWebPlayTokenAccount')
|
|
elif self.DISLToken:
|
|
self.loginInterface = LoginDISLTokenAccount(self)
|
|
self.notify.info('loginInterface: LoginDISLTokenAccount')
|
|
else:
|
|
self.loginInterface = LoginTTAccount.LoginTTAccount(self)
|
|
self.notify.info('loginInterface: LoginTTAccount')
|
|
|
|
self.secretChatAllowed = ConfigVariableBool('allow-secret-chat', 0).value
|
|
self.openChatAllowed = ConfigVariableBool('allow-open-chat', 0).value
|
|
self.secretChatNeedsParentPassword = ConfigVariableBool('secret-chat-needs-parent-password', 0).value or (self.launcher and self.launcher.getNeedPwForSecretKey())
|
|
self.parentPasswordSet = ConfigVariableBool('parent-password-set', 0).value or (self.launcher and self.launcher.getParentPasswordSet())
|
|
self.userSignature = ConfigVariableString('signature', 'none').value
|
|
self.freeTimeExpiresAt = -1
|
|
self.__isPaid = 0
|
|
self.periodTimerExpired = 0
|
|
self.periodTimerStarted = None
|
|
self.periodTimerSecondsRemaining = None
|
|
self.parentMgr.registerParent(OTPGlobals.SPRender, base.render)
|
|
self.parentMgr.registerParent(OTPGlobals.SPHidden, NodePath())
|
|
self.timeManager = None
|
|
if ConfigVariableBool('detect-leaks', 0).value or ConfigVariableBool('client-detect-leaks', 0).value:
|
|
self.startLeakDetector()
|
|
|
|
if ConfigVariableBool('detect-messenger-leaks', 0).value or ConfigVariableBool('ai-detect-messenger-leaks', 0).value:
|
|
self.messengerLeakDetector = MessengerLeakDetector.MessengerLeakDetector('client messenger leak detector')
|
|
if ConfigVariableBool('leak-messages', 0).value:
|
|
MessengerLeakDetector._leakMessengerObject()
|
|
|
|
if ConfigVariableBool('run-garbage-reports', 0).value or ConfigVariableBool('client-run-garbage-reports', 0).value:
|
|
noneValue = -1.0
|
|
reportWait = ConfigVariableDouble('garbage-report-wait', noneValue).value
|
|
reportWaitScale = ConfigVariableDouble('garbage-report-wait-scale', noneValue).value
|
|
if reportWait == noneValue:
|
|
reportWait = 60.0 * 2.0
|
|
if reportWaitScale == noneValue:
|
|
reportWaitScale = None
|
|
self.garbageReportScheduler = GarbageReportScheduler(waitBetween=reportWait,
|
|
waitScale=reportWaitScale)
|
|
|
|
self._proactiveLeakChecks = ConfigVariableBool('proactive-leak-checks', 1).value or ConfigVariableBool('client-proactive-leak-checks', 1).value
|
|
self._crashOnProactiveLeakDetect = ConfigVariableBool('crash-on-proactive-leak-detect', 1).value
|
|
self.activeDistrictMap = {}
|
|
self.telemetryLimiter = TelemetryLimiter()
|
|
self.serverVersion = serverVersion
|
|
self.waitingForDatabase = None
|
|
self.loginFSM = ClassicFSM('loginFSM', [
|
|
State('loginOff',
|
|
self.enterLoginOff,
|
|
self.exitLoginOff, [
|
|
'connect']),
|
|
State('connect',
|
|
self.enterConnect,
|
|
self.exitConnect, [
|
|
'login',
|
|
'failedToConnect',
|
|
'failedToGetServerConstants']),
|
|
State('login',
|
|
self.enterLogin,
|
|
self.exitLogin, [
|
|
'noConnection',
|
|
'waitForGameList',
|
|
'createAccount',
|
|
'reject',
|
|
'failedToConnect',
|
|
'shutdown']),
|
|
State('createAccount',
|
|
self.enterCreateAccount,
|
|
self.exitCreateAccount, [
|
|
'noConnection',
|
|
'waitForGameList',
|
|
'login',
|
|
'reject',
|
|
'failedToConnect',
|
|
'shutdown']),
|
|
State('failedToConnect',
|
|
self.enterFailedToConnect,
|
|
self.exitFailedToConnect, [
|
|
'connect',
|
|
'shutdown']),
|
|
State('failedToGetServerConstants',
|
|
self.enterFailedToGetServerConstants,
|
|
self.exitFailedToGetServerConstants, [
|
|
'connect',
|
|
'shutdown',
|
|
'noConnection']),
|
|
State('shutdown',
|
|
self.enterShutdown,
|
|
self.exitShutdown, [
|
|
'loginOff']),
|
|
State('waitForGameList',
|
|
self.enterWaitForGameList,
|
|
self.exitWaitForGameList, [
|
|
'noConnection',
|
|
'waitForShardList',
|
|
'missingGameRootObject']),
|
|
State('missingGameRootObject',
|
|
self.enterMissingGameRootObject,
|
|
self.exitMissingGameRootObject, [
|
|
'waitForGameList',
|
|
'shutdown']),
|
|
State('waitForShardList',
|
|
self.enterWaitForShardList,
|
|
self.exitWaitForShardList, [
|
|
'noConnection',
|
|
'waitForAvatarList',
|
|
'noShards']),
|
|
State('noShards',
|
|
self.enterNoShards,
|
|
self.exitNoShards, [
|
|
'noConnection',
|
|
'noShardsWait',
|
|
'shutdown']),
|
|
State('noShardsWait',
|
|
self.enterNoShardsWait,
|
|
self.exitNoShardsWait, [
|
|
'noConnection',
|
|
'waitForShardList',
|
|
'shutdown']),
|
|
State('reject',
|
|
self.enterReject,
|
|
self.exitReject, []),
|
|
State('noConnection',
|
|
self.enterNoConnection,
|
|
self.exitNoConnection, [
|
|
'login',
|
|
'connect',
|
|
'shutdown']),
|
|
State('afkTimeout',
|
|
self.enterAfkTimeout,
|
|
self.exitAfkTimeout, [
|
|
'waitForAvatarList',
|
|
'shutdown']),
|
|
State('periodTimeout',
|
|
self.enterPeriodTimeout,
|
|
self.exitPeriodTimeout, [
|
|
'shutdown']),
|
|
State('waitForAvatarList',
|
|
self.enterWaitForAvatarList,
|
|
self.exitWaitForAvatarList, [
|
|
'noConnection',
|
|
'chooseAvatar',
|
|
'shutdown']),
|
|
State('chooseAvatar',
|
|
self.enterChooseAvatar,
|
|
self.exitChooseAvatar, [
|
|
'noConnection',
|
|
'createAvatar',
|
|
'waitForAvatarList',
|
|
'waitForSetAvatarResponse',
|
|
'waitForDeleteAvatarResponse',
|
|
'shutdown',
|
|
'login']),
|
|
State('createAvatar',
|
|
self.enterCreateAvatar,
|
|
self.exitCreateAvatar, [
|
|
'noConnection',
|
|
'chooseAvatar',
|
|
'waitForSetAvatarResponse',
|
|
'shutdown']),
|
|
State('waitForDeleteAvatarResponse',
|
|
self.enterWaitForDeleteAvatarResponse,
|
|
self.exitWaitForDeleteAvatarResponse, [
|
|
'noConnection',
|
|
'chooseAvatar',
|
|
'shutdown']),
|
|
State('rejectRemoveAvatar',
|
|
self.enterRejectRemoveAvatar,
|
|
self.exitRejectRemoveAvatar, [
|
|
'noConnection',
|
|
'chooseAvatar',
|
|
'shutdown']),
|
|
State('waitForSetAvatarResponse',
|
|
self.enterWaitForSetAvatarResponse,
|
|
self.exitWaitForSetAvatarResponse, [
|
|
'noConnection',
|
|
'playingGame',
|
|
'shutdown']),
|
|
State('playingGame',
|
|
self.enterPlayingGame,
|
|
self.exitPlayingGame, [
|
|
'noConnection',
|
|
'waitForAvatarList',
|
|
'login',
|
|
'shutdown',
|
|
'afkTimeout',
|
|
'periodTimeout',
|
|
'noShards'])],
|
|
'loginOff', 'loginOff')
|
|
self.gameFSM = ClassicFSM('gameFSM', [
|
|
State('gameOff',
|
|
self.enterGameOff,
|
|
self.exitGameOff, [
|
|
'waitOnEnterResponses']),
|
|
State('waitOnEnterResponses',
|
|
self.enterWaitOnEnterResponses,
|
|
self.exitWaitOnEnterResponses, [
|
|
'playGame',
|
|
'tutorialQuestion',
|
|
'gameOff']),
|
|
State('tutorialQuestion',
|
|
self.enterTutorialQuestion,
|
|
self.exitTutorialQuestion, [
|
|
'playGame',
|
|
'gameOff']),
|
|
State('playGame',
|
|
self.enterPlayGame,
|
|
self.exitPlayGame, [
|
|
'gameOff',
|
|
'closeShard',
|
|
'switchShards']),
|
|
State('switchShards',
|
|
self.enterSwitchShards,
|
|
self.exitSwitchShards, [
|
|
'gameOff',
|
|
'waitOnEnterResponses']),
|
|
State('closeShard',
|
|
self.enterCloseShard,
|
|
self.exitCloseShard, [
|
|
'gameOff',
|
|
'waitOnEnterResponses'])],
|
|
'gameOff', 'gameOff')
|
|
self.loginFSM.getStateNamed('playingGame').addChild(self.gameFSM)
|
|
self.loginFSM.enterInitialState()
|
|
self.loginScreen = None
|
|
self.music = None
|
|
self.gameDoneEvent = 'playGameDone'
|
|
self.playGame = playGame(self.gameFSM, self.gameDoneEvent)
|
|
self.shardListHandle = None
|
|
self.uberZoneInterest = None
|
|
self.wantSwitchboard = ConfigVariableBool('want-switchboard', 0).value
|
|
self.wantSwitchboardHacks = ConfigVariableBool('want-switchboard-hacks', 0).value
|
|
self.__pendingGenerates = {}
|
|
self.__pendingMessages = {}
|
|
self.__doId2pendingInterest = {}
|
|
self.centralLogger = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_CENTRAL_LOGGER, 'CentralLogger')
|
|
if __astron__:
|
|
self.astronLoginManager = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_ASTRON_LOGIN_MANAGER, 'AstronLoginManager')
|
|
|
|
def startLeakDetector(self):
|
|
if hasattr(self, 'leakDetector'):
|
|
return False
|
|
firstCheckDelay = ConfigVariableDouble('leak-detector-first-check-delay', 2 * 60.0).value
|
|
self.leakDetector = ContainerLeakDetector('client container leak detector', firstCheckDelay=firstCheckDelay)
|
|
self.objectTypesLeakDetector = LeakDetectors.ObjectTypesLeakDetector()
|
|
self.garbageLeakDetector = LeakDetectors.GarbageLeakDetector()
|
|
self.renderLeakDetector = LeakDetectors.SceneGraphLeakDetector(render)
|
|
self.hiddenLeakDetector = LeakDetectors.SceneGraphLeakDetector(hidden)
|
|
self.cppMemoryUsageLeakDetector = LeakDetectors.CppMemoryUsage()
|
|
self.taskLeakDetector = LeakDetectors.TaskLeakDetector()
|
|
self.messageListenerTypesLeakDetector = LeakDetectors.MessageListenerTypesLeakDetector()
|
|
return True
|
|
|
|
def getGameDoId(self):
|
|
return self.GameGlobalsId
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterLoginOff(self):
|
|
self.handler = self.handleMessageType
|
|
self.shardListHandle = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitLoginOff(self):
|
|
self.handler = None
|
|
return
|
|
|
|
def getServerVersion(self):
|
|
return self.serverVersion
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterConnect(self, serverList):
|
|
self.serverList = serverList
|
|
dialogClass = OTPGlobals.getGlobalDialogClass()
|
|
self.connectingBox = dialogClass(message=OTPLocalizer.CRConnecting)
|
|
self.connectingBox.show()
|
|
self.renderFrame()
|
|
self.handler = self.handleMessageType
|
|
self.connect(self.serverList, successCallback=self._handleConnected, failureCallback=self.failedToConnect)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def failedToConnect(self, statusCode, statusString):
|
|
self.loginFSM.request('failedToConnect', [statusCode, statusString])
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitConnect(self):
|
|
self.connectingBox.cleanup()
|
|
del self.connectingBox
|
|
|
|
def handleSystemMessage(self, di):
|
|
message = ClientRepositoryBase.handleSystemMessage(self, di)
|
|
whisper = WhisperPopup(message, OTPGlobals.getInterfaceFont(), WhisperPopup.WTSystem)
|
|
whisper.manage(base.marginManager)
|
|
if not self.systemMessageSfx:
|
|
self.systemMessageSfx = base.loader.loadSfx('phase_3/audio/sfx/clock03.ogg')
|
|
if self.systemMessageSfx:
|
|
base.playSfx(self.systemMessageSfx)
|
|
|
|
def getConnectedEvent(self):
|
|
return 'OTPClientRepository-connected'
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def _handleConnected(self):
|
|
self.launcher.setDisconnectDetailsNormal()
|
|
messenger.send(self.getConnectedEvent())
|
|
self.gotoFirstScreen()
|
|
|
|
def gotoFirstScreen(self):
|
|
self.startReaderPollTask()
|
|
if not __astron__:
|
|
self.startHeartbeat()
|
|
self.loginFSM.request('login')
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterLogin(self):
|
|
self.sendSetAvatarIdMsg(0)
|
|
self.loginDoneEvent = 'loginDone'
|
|
self.loginScreen = LoginScreen.LoginScreen(self, self.loginDoneEvent)
|
|
self.accept(self.loginDoneEvent, self.__handleLoginDone)
|
|
self.loginScreen.load()
|
|
self.loginScreen.enter()
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def __handleLoginDone(self, doneStatus):
|
|
mode = doneStatus['mode']
|
|
if mode == 'success':
|
|
self.loginFSM.request('waitForGameList')
|
|
elif mode == 'getChatPassword':
|
|
self.loginFSM.request('parentPassword')
|
|
elif mode == 'freeTimeExpired':
|
|
self.loginFSM.request('freeTimeInform')
|
|
elif mode == 'createAccount':
|
|
self.loginFSM.request('createAccount', [{'back': 'login',
|
|
'backArgs': []}])
|
|
elif mode == 'reject':
|
|
self.loginFSM.request('reject')
|
|
elif mode == 'quit':
|
|
self.loginFSM.request('shutdown')
|
|
elif mode == 'failure':
|
|
self.loginFSM.request('failedToConnect', [-1, '?'])
|
|
else:
|
|
self.notify.error('Invalid doneStatus mode from loginScreen: ' + str(mode))
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitLogin(self):
|
|
if self.loginScreen:
|
|
self.loginScreen.exit()
|
|
self.loginScreen.unload()
|
|
self.loginScreen = None
|
|
self.renderFrame()
|
|
self.ignore(self.loginDoneEvent)
|
|
del self.loginDoneEvent
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterCreateAccount(self, createAccountDoneData = {'back': 'login',
|
|
'backArgs': []}):
|
|
self.createAccountDoneData = createAccountDoneData
|
|
self.createAccountDoneEvent = 'createAccountDone'
|
|
self.createAccountScreen = None
|
|
self.createAccountScreen = CreateAccountScreen(self, self.createAccountDoneEvent)
|
|
self.accept(self.createAccountDoneEvent, self.__handleCreateAccountDone)
|
|
self.createAccountScreen.load()
|
|
self.createAccountScreen.enter()
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def __handleCreateAccountDone(self, doneStatus):
|
|
mode = doneStatus['mode']
|
|
if mode == 'success':
|
|
self.loginFSM.request('waitForGameList')
|
|
elif mode == 'reject':
|
|
self.loginFSM.request('reject')
|
|
elif mode == 'cancel':
|
|
self.loginFSM.request(self.createAccountDoneData['back'], self.createAccountDoneData['backArgs'])
|
|
elif mode == 'failure':
|
|
self.loginFSM.request(self.createAccountDoneData['back'], self.createAccountDoneData['backArgs'])
|
|
elif mode == 'quit':
|
|
self.loginFSM.request('shutdown')
|
|
else:
|
|
self.notify.error('Invalid doneStatus mode from CreateAccountScreen: ' + str(mode))
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitCreateAccount(self):
|
|
if self.createAccountScreen:
|
|
self.createAccountScreen.exit()
|
|
self.createAccountScreen.unload()
|
|
self.createAccountScreen = None
|
|
self.renderFrame()
|
|
self.ignore(self.createAccountDoneEvent)
|
|
del self.createAccountDoneEvent
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterFailedToConnect(self, statusCode, statusString):
|
|
self.handler = self.handleMessageType
|
|
messenger.send('connectionIssue')
|
|
url = self.serverList[0]
|
|
self.notify.warning('Failed to connect to %s (%s %s). Notifying user.' % (url.cStr(), statusCode, statusString))
|
|
if statusCode == 1403 or statusCode == 1405 or statusCode == 1400:
|
|
message = OTPLocalizer.CRNoConnectProxyNoPort % (url.getServer(), url.getPort(), url.getPort())
|
|
style = OTPDialog.CancelOnly
|
|
else:
|
|
message = OTPLocalizer.CRNoConnectTryAgain % (url.getServer(), url.getPort())
|
|
style = OTPDialog.TwoChoice
|
|
dialogClass = OTPGlobals.getGlobalDialogClass()
|
|
self.failedToConnectBox = dialogClass(message=message, doneEvent='failedToConnectAck', text_wordwrap=18, style=style)
|
|
self.failedToConnectBox.show()
|
|
self.notify.info(message)
|
|
self.accept('failedToConnectAck', self.__handleFailedToConnectAck)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def __handleFailedToConnectAck(self):
|
|
doneStatus = self.failedToConnectBox.doneStatus
|
|
if doneStatus == 'ok':
|
|
self.loginFSM.request('connect', [self.serverList])
|
|
messenger.send('connectionRetrying')
|
|
elif doneStatus == 'cancel':
|
|
self.loginFSM.request('shutdown')
|
|
else:
|
|
self.notify.error('Unrecognized doneStatus: ' + str(doneStatus))
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitFailedToConnect(self):
|
|
self.handler = None
|
|
self.ignore('failedToConnectAck')
|
|
self.failedToConnectBox.cleanup()
|
|
del self.failedToConnectBox
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterFailedToGetServerConstants(self, e):
|
|
self.handler = self.handleMessageType
|
|
messenger.send('connectionIssue')
|
|
url = 'N/A'
|
|
statusCode = 0
|
|
if isinstance(e, HTTPUtil.ConnectionError):
|
|
statusCode = e.statusCode
|
|
self.notify.warning('Got status code %s from connection to %s.' % (statusCode, url.cStr()))
|
|
else:
|
|
self.notify.warning("Didn't get status code from connection to %s." % url.cStr())
|
|
if statusCode == 1403 or statusCode == 1400:
|
|
message = OTPLocalizer.CRServerConstantsProxyNoPort % (url.cStr(), url.getPort())
|
|
style = OTPDialog.CancelOnly
|
|
elif statusCode == 1405:
|
|
message = OTPLocalizer.CRServerConstantsProxyNoCONNECT % url.cStr()
|
|
style = OTPDialog.CancelOnly
|
|
else:
|
|
message = OTPLocalizer.CRServerConstantsTryAgain % url.cStr()
|
|
style = OTPDialog.TwoChoice
|
|
dialogClass = OTPGlobals.getGlobalDialogClass()
|
|
self.failedToGetConstantsBox = dialogClass(message=message, doneEvent='failedToGetConstantsAck', text_wordwrap=18, style=style)
|
|
self.failedToGetConstantsBox.show()
|
|
self.accept('failedToGetConstantsAck', self.__handleFailedToGetConstantsAck)
|
|
self.notify.warning('Failed to get account server constants. Notifying user.')
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def __handleFailedToGetConstantsAck(self):
|
|
doneStatus = self.failedToGetConstantsBox.doneStatus
|
|
if doneStatus == 'ok':
|
|
self.loginFSM.request('connect', [self.serverList])
|
|
messenger.send('connectionRetrying')
|
|
elif doneStatus == 'cancel':
|
|
self.loginFSM.request('shutdown')
|
|
else:
|
|
self.notify.error('Unrecognized doneStatus: ' + str(doneStatus))
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitFailedToGetServerConstants(self):
|
|
self.handler = None
|
|
self.ignore('failedToGetConstantsAck')
|
|
self.failedToGetConstantsBox.cleanup()
|
|
del self.failedToGetConstantsBox
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterShutdown(self, errorCode = None):
|
|
self.handler = self.handleMessageType
|
|
self.sendDisconnect()
|
|
self.notify.info('Exiting cleanly')
|
|
base.exitShow(errorCode)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitShutdown(self):
|
|
if hasattr(self, 'garbageWatcher'):
|
|
self.garbageWatcher.destroy()
|
|
del self.garbageWatcher
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterWaitForGameList(self):
|
|
self.gameDoDirectory = self.addTaggedInterest(self.GameGlobalsId, OTP_ZONE_ID_MANAGEMENT, self.ITAG_PERM, 'game directory', event='GameList_Complete')
|
|
self.acceptOnce('GameList_Complete', self.waitForGetGameListResponse)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def waitForGetGameListResponse(self):
|
|
if self.isGameListCorrect():
|
|
if ConfigVariableBool('game-server-tests', 0).value:
|
|
from otp.distributed import GameServerTestSuite
|
|
GameServerTestSuite.GameServerTestSuite(self)
|
|
self.loginFSM.request('waitForShardList')
|
|
else:
|
|
self.loginFSM.request('missingGameRootObject')
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def isGameListCorrect(self):
|
|
return 1
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitWaitForGameList(self):
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterMissingGameRootObject(self):
|
|
self.notify.warning('missing some game root objects.')
|
|
self.handler = self.handleMessageType
|
|
dialogClass = OTPGlobals.getGlobalDialogClass()
|
|
self.missingGameRootObjectBox = dialogClass(message=OTPLocalizer.CRMissingGameRootObject, doneEvent='missingGameRootObjectBoxAck', style=OTPDialog.TwoChoice)
|
|
self.missingGameRootObjectBox.show()
|
|
self.accept('missingGameRootObjectBoxAck', self.__handleMissingGameRootObjectAck)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def __handleMissingGameRootObjectAck(self):
|
|
doneStatus = self.missingGameRootObjectBox.doneStatus
|
|
if doneStatus == 'ok':
|
|
self.loginFSM.request('waitForGameList')
|
|
elif doneStatus == 'cancel':
|
|
self.loginFSM.request('shutdown')
|
|
else:
|
|
self.notify.error('Unrecognized doneStatus: ' + str(doneStatus))
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitMissingGameRootObject(self):
|
|
self.handler = None
|
|
self.ignore('missingGameRootObjectBoxAck')
|
|
self.missingGameRootObjectBox.cleanup()
|
|
del self.missingGameRootObjectBox
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterWaitForShardList(self):
|
|
if not self.isValidInterestHandle(self.shardListHandle):
|
|
self.shardListHandle = self.addTaggedInterest(self.GameGlobalsId, OTP_ZONE_ID_DISTRICTS, self.ITAG_PERM, 'LocalShardList', event='ShardList_Complete')
|
|
self.acceptOnce('ShardList_Complete', self._wantShardListComplete)
|
|
else:
|
|
self._wantShardListComplete()
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def _wantShardListComplete(self):
|
|
if self._shardsAreReady():
|
|
self.loginFSM.request('waitForAvatarList')
|
|
else:
|
|
self.loginFSM.request('noShards')
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def _shardsAreReady(self):
|
|
for shard in list(self.activeDistrictMap.values()):
|
|
if shard.available:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitWaitForShardList(self):
|
|
self.ignore('ShardList_Complete')
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterNoShards(self):
|
|
messenger.send('connectionIssue')
|
|
self.handler = self.handleMessageType
|
|
dialogClass = OTPGlobals.getGlobalDialogClass()
|
|
self.noShardsBox = dialogClass(message=OTPLocalizer.CRNoDistrictsTryAgain, doneEvent='noShardsAck', style=OTPDialog.TwoChoice)
|
|
self.noShardsBox.show()
|
|
self.accept('noShardsAck', self.__handleNoShardsAck)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def __handleNoShardsAck(self):
|
|
doneStatus = self.noShardsBox.doneStatus
|
|
if doneStatus == 'ok':
|
|
messenger.send('connectionRetrying')
|
|
self.loginFSM.request('noShardsWait')
|
|
elif doneStatus == 'cancel':
|
|
self.loginFSM.request('shutdown')
|
|
else:
|
|
self.notify.error('Unrecognized doneStatus: ' + str(doneStatus))
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitNoShards(self):
|
|
self.handler = None
|
|
self.ignore('noShardsAck')
|
|
self.noShardsBox.cleanup()
|
|
del self.noShardsBox
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterNoShardsWait(self):
|
|
dialogClass = OTPGlobals.getGlobalDialogClass()
|
|
self.connectingBox = dialogClass(message=OTPLocalizer.CRConnecting)
|
|
self.connectingBox.show()
|
|
self.renderFrame()
|
|
self.noShardsWaitTaskName = 'noShardsWait'
|
|
|
|
def doneWait(task, self = self):
|
|
self.loginFSM.request('waitForShardList')
|
|
|
|
if __dev__:
|
|
delay = 0.0
|
|
else:
|
|
delay = 6.5 + random.random() * 2.0
|
|
taskMgr.doMethodLater(delay, doneWait, self.noShardsWaitTaskName)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitNoShardsWait(self):
|
|
taskMgr.remove(self.noShardsWaitTaskName)
|
|
del self.noShardsWaitTaskName
|
|
self.connectingBox.cleanup()
|
|
del self.connectingBox
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterReject(self):
|
|
self.handler = self.handleMessageType
|
|
self.notify.warning('Connection Rejected')
|
|
launcher.setPandaErrorCode(13)
|
|
sys.exit()
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitReject(self):
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterNoConnection(self):
|
|
messenger.send('connectionIssue')
|
|
self.resetInterestStateForConnectionLoss()
|
|
self.shardListHandle = None
|
|
self.handler = self.handleMessageType
|
|
self.__currentAvId = 0
|
|
self.stopHeartbeat()
|
|
self.stopReaderPollTask()
|
|
gameUsername = launcher.getValue('GAME_USERNAME', base.cr.userName)
|
|
if self.bootedIndex != None and self.bootedIndex in OTPLocalizer.CRBootedReasons:
|
|
message = OTPLocalizer.CRBootedReasons[self.bootedIndex] % {'name': gameUsername}
|
|
elif self.bootedText != None:
|
|
message = OTPLocalizer.CRBootedReasonUnknownCode % self.bootedIndex
|
|
else:
|
|
message = OTPLocalizer.CRLostConnection
|
|
reconnect = 1
|
|
if self.bootedIndex in (152, 127):
|
|
reconnect = 0
|
|
self.launcher.setDisconnectDetails(self.bootedIndex, message)
|
|
style = OTPDialog.Acknowledge
|
|
if reconnect and self.loginInterface.supportsRelogin():
|
|
message += OTPLocalizer.CRTryConnectAgain
|
|
style = OTPDialog.TwoChoice
|
|
dialogClass = OTPGlobals.getGlobalDialogClass()
|
|
self.lostConnectionBox = dialogClass(doneEvent='lostConnectionAck', message=message, text_wordwrap=18, style=style)
|
|
self.lostConnectionBox.show()
|
|
self.accept('lostConnectionAck', self.__handleLostConnectionAck)
|
|
self.notify.warning('Lost connection to server. Notifying user.')
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def __handleLostConnectionAck(self):
|
|
if self.lostConnectionBox.doneStatus == 'ok' and self.loginInterface.supportsRelogin():
|
|
self.loginFSM.request('connect', [self.serverList])
|
|
else:
|
|
self.loginFSM.request('shutdown')
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitNoConnection(self):
|
|
self.handler = None
|
|
self.ignore('lostConnectionAck')
|
|
self.lostConnectionBox.cleanup()
|
|
messenger.send('connectionRetrying')
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterAfkTimeout(self):
|
|
self.sendSetAvatarIdMsg(0)
|
|
msg = OTPLocalizer.AfkForceAcknowledgeMessage
|
|
dialogClass = OTPGlobals.getDialogClass()
|
|
self.afkDialog = dialogClass(text=msg, command=self.__handleAfkOk, style=OTPDialog.Acknowledge)
|
|
self.handler = self.handleMessageType
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def __handleAfkOk(self, value):
|
|
self.loginFSM.request('waitForAvatarList')
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitAfkTimeout(self):
|
|
if self.afkDialog:
|
|
self.afkDialog.cleanup()
|
|
self.afkDialog = None
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterPeriodTimeout(self):
|
|
self.sendSetAvatarIdMsg(0)
|
|
self.sendDisconnect()
|
|
msg = OTPLocalizer.PeriodForceAcknowledgeMessage
|
|
dialogClass = OTPGlobals.getDialogClass()
|
|
self.periodDialog = dialogClass(text=msg, command=self.__handlePeriodOk, style=OTPDialog.Acknowledge)
|
|
self.handler = self.handleMessageType
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def __handlePeriodOk(self, value):
|
|
base.exitShow()
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitPeriodTimeout(self):
|
|
if self.periodDialog:
|
|
self.periodDialog.cleanup()
|
|
self.periodDialog = None
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterWaitForAvatarList(self):
|
|
if not __astron__:
|
|
self.handler = self.handleWaitForAvatarList
|
|
self._requestAvatarList()
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def _requestAvatarList(self):
|
|
self.sendGetAvatarsMsg()
|
|
self.waitForDatabaseTimeout(requestName='WaitForAvatarList')
|
|
self.acceptOnce(OtpAvatarManager.OtpAvatarManager.OnlineEvent, self._requestAvatarList)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def sendGetAvatarsMsg(self):
|
|
if __astron__:
|
|
self.astronLoginManager.sendRequestAvatarList()
|
|
else:
|
|
datagram = PyDatagram()
|
|
datagram.addUint16(CLIENT_GET_AVATARS)
|
|
self.send(datagram)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitWaitForAvatarList(self):
|
|
self.cleanupWaitingForDatabase()
|
|
self.ignore(OtpAvatarManager.OtpAvatarManager.OnlineEvent)
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def handleWaitForAvatarList(self, msgType, di):
|
|
if msgType == CLIENT_GET_AVATARS_RESP:
|
|
self.handleGetAvatarsRespMsg(di)
|
|
elif msgType == CLIENT_GET_AVATARS_RESP2:
|
|
pass
|
|
else:
|
|
self.handleMessageType(msgType, di)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def handleGetAvatarsRespMsg(self, di):
|
|
returnCode = di.getUint8()
|
|
if returnCode == 0:
|
|
avatarTotal = di.getUint16()
|
|
avList = []
|
|
for i in range(0, avatarTotal):
|
|
avNum = di.getUint32()
|
|
avNames = ['',
|
|
'',
|
|
'',
|
|
'']
|
|
avNames[0] = di.getString()
|
|
avNames[1] = di.getString()
|
|
avNames[2] = di.getString()
|
|
avNames[3] = di.getString()
|
|
avDNA = di.getBlob()
|
|
avPosition = di.getUint8()
|
|
aname = di.getUint8()
|
|
potAv = PotentialAvatar(avNum, avNames, avDNA, avPosition, aname)
|
|
avList.append(potAv)
|
|
|
|
self.avList = avList
|
|
self.loginFSM.request('chooseAvatar', [self.avList])
|
|
else:
|
|
self.notify.error('Bad avatar list return code: ' + str(returnCode))
|
|
self.loginFSM.request('shutdown')
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def handleGetAvatarsResp2Msg(self, di):
|
|
returnCode = di.getUint8()
|
|
if returnCode == 0:
|
|
avatarTotal = di.getUint16()
|
|
avList = []
|
|
for i in range(0, avatarTotal):
|
|
avNum = di.getUint32()
|
|
avNames = ['',
|
|
'',
|
|
'',
|
|
'']
|
|
avNames[0] = di.getString()
|
|
avDNA = None
|
|
avPosition = di.getUint8()
|
|
aname = None
|
|
potAv = PotentialAvatar(avNum, avNames, avDNA, avPosition, aname)
|
|
avList.append(potAv)
|
|
|
|
self.avList = avList
|
|
self.loginFSM.request('chooseAvatar', [self.avList])
|
|
else:
|
|
self.notify.error('Bad avatar list return code: ' + str(returnCode))
|
|
self.loginFSM.request('shutdown')
|
|
return
|
|
|
|
if __astron__:
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def handleAvatarListResponse(self, avatarList):
|
|
avList = []
|
|
for avNum, avName, avDNA, avPosition, nameState in avatarList:
|
|
avNames = ['',
|
|
'',
|
|
'',
|
|
'']
|
|
avNames[0] = avName
|
|
if nameState == 2: # Pending
|
|
avNames[1] = avName
|
|
elif nameState == 3: # Approved
|
|
avNames[2] = avName
|
|
elif nameState == 4: # Rejected
|
|
avNames[3] = avName
|
|
|
|
aname = int(nameState == 1)
|
|
potAv = PotentialAvatar(avNum, avNames, avDNA, avPosition, aname)
|
|
avList.append(potAv)
|
|
|
|
self.avList = avList
|
|
self.loginFSM.request('chooseAvatar', [self.avList])
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterChooseAvatar(self, avList):
|
|
pass
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitChooseAvatar(self):
|
|
pass
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterCreateAvatar(self, avList, index, newDNA = None):
|
|
pass
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitCreateAvatar(self):
|
|
pass
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def sendCreateAvatarMsg(self, avDNA, avName, avPosition):
|
|
if __astron__:
|
|
self.astronLoginManager.sendCreateAvatar(avDNA, avName, avPosition)
|
|
else:
|
|
datagram = PyDatagram()
|
|
datagram.addUint16(CLIENT_CREATE_AVATAR)
|
|
datagram.addUint16(0)
|
|
datagram.addBlob(avDNA.makeNetString())
|
|
datagram.addUint8(avPosition)
|
|
self.newName = avName
|
|
self.newDNA = avDNA
|
|
self.newPosition = avPosition
|
|
self.send(datagram)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def sendCreateAvatar2Msg(self, avClass, avDNA, avName, avPosition):
|
|
className = avClass.__name__
|
|
dclass = self.dclassesByName[className]
|
|
datagram = PyDatagram()
|
|
datagram.addUint16(CLIENT_CREATE_AVATAR2)
|
|
datagram.addUint16(0)
|
|
datagram.addUint8(avPosition)
|
|
datagram.addUint16(dclass.getNumber())
|
|
self.newName = avName
|
|
self.newDNA = avDNA
|
|
self.newPosition = avPosition
|
|
self.send(datagram)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterWaitForDeleteAvatarResponse(self, potAv):
|
|
if not __astron__:
|
|
self.handler = self.handleWaitForDeleteAvatarResponse
|
|
self.sendDeleteAvatarMsg(potAv.id)
|
|
self.waitForDatabaseTimeout(requestName='WaitForDeleteAvatarResponse')
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def sendDeleteAvatarMsg(self, avId):
|
|
if __astron__:
|
|
self.astronLoginManager.sendRequestRemoveAvatar(avId)
|
|
else:
|
|
datagram = PyDatagram()
|
|
datagram.addUint16(CLIENT_DELETE_AVATAR)
|
|
datagram.addUint32(avId)
|
|
self.send(datagram)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitWaitForDeleteAvatarResponse(self):
|
|
self.cleanupWaitingForDatabase()
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def handleWaitForDeleteAvatarResponse(self, msgType, di):
|
|
if msgType == CLIENT_DELETE_AVATAR_RESP:
|
|
self.handleGetAvatarsRespMsg(di)
|
|
else:
|
|
self.handleMessageType(msgType, di)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterRejectRemoveAvatar(self, reasonCode):
|
|
self.notify.warning('Rejected removed avatar. (%s)' % (reasonCode,))
|
|
self.handler = self.handleMessageType
|
|
dialogClass = OTPGlobals.getGlobalDialogClass()
|
|
self.rejectRemoveAvatarBox = dialogClass(message='%s\n(%s)' % (OTPLocalizer.CRRejectRemoveAvatar, reasonCode), doneEvent='rejectRemoveAvatarAck', style=OTPDialog.Acknowledge)
|
|
self.rejectRemoveAvatarBox.show()
|
|
self.accept('rejectRemoveAvatarAck', self.__handleRejectRemoveAvatar)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def __handleRejectRemoveAvatar(self):
|
|
self.loginFSM.request('chooseAvatar')
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitRejectRemoveAvatar(self):
|
|
self.handler = None
|
|
self.ignore('rejectRemoveAvatarAck')
|
|
self.rejectRemoveAvatarBox.cleanup()
|
|
del self.rejectRemoveAvatarBox
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterWaitForSetAvatarResponse(self, potAv):
|
|
if not __astron__:
|
|
self.handler = self.handleWaitForSetAvatarResponse
|
|
self.sendSetAvatarMsg(potAv)
|
|
self.waitForDatabaseTimeout(requestName='WaitForSetAvatarResponse')
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitWaitForSetAvatarResponse(self):
|
|
self.cleanupWaitingForDatabase()
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def sendSetAvatarMsg(self, potAv):
|
|
self.sendSetAvatarIdMsg(potAv.id)
|
|
self.avData = potAv
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def sendSetAvatarIdMsg(self, avId):
|
|
if avId != self.__currentAvId:
|
|
self.__currentAvId = avId
|
|
if __astron__:
|
|
self.astronLoginManager.sendRequestPlayAvatar(avId)
|
|
else:
|
|
datagram = PyDatagram()
|
|
datagram.addUint16(CLIENT_SET_AVATAR)
|
|
datagram.addUint32(avId)
|
|
self.send(datagram)
|
|
if avId == 0:
|
|
self.stopPeriodTimer()
|
|
else:
|
|
self.startPeriodTimer()
|
|
|
|
if not __astron__:
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def handleAvatarResponseMsg(self, di):
|
|
pass
|
|
else:
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def handleAvatarResponseMsg(self, avatarId, di):
|
|
pass
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def handleWaitForSetAvatarResponse(self, msgType, di):
|
|
if msgType == CLIENT_GET_AVATAR_DETAILS_RESP:
|
|
self.handleAvatarResponseMsg(di)
|
|
elif msgType == CLIENT_GET_PET_DETAILS_RESP:
|
|
self.handleAvatarResponseMsg(di)
|
|
elif msgType == CLIENT_GET_FRIEND_LIST_RESP:
|
|
self.handleGetFriendsList(di)
|
|
elif msgType == CLIENT_GET_FRIEND_LIST_EXTENDED_RESP:
|
|
self.handleGetFriendsListExtended(di)
|
|
elif msgType == CLIENT_FRIEND_ONLINE:
|
|
self.handleFriendOnline(di)
|
|
elif msgType == CLIENT_FRIEND_OFFLINE:
|
|
self.handleFriendOffline(di)
|
|
else:
|
|
self.handleMessageType(msgType, di)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterPlayingGame(self):
|
|
pass
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitPlayingGame(self):
|
|
self.notify.info('sending clientLogout')
|
|
messenger.send('clientLogout')
|
|
|
|
@report(types=['args'], dConfigParam='teleport')
|
|
def detectLeaks(self, okTasks = None, okEvents = None):
|
|
if not __dev__ or configIsToday('allow-unclean-exit'):
|
|
return
|
|
leakedTasks = self.detectLeakedTasks(okTasks)
|
|
leakedEvents = self.detectLeakedEvents(okEvents)
|
|
leakedIvals = self.detectLeakedIntervals()
|
|
leakedGarbage = self.detectLeakedGarbage()
|
|
if leakedTasks or leakedEvents or leakedIvals or leakedGarbage:
|
|
errorCode = base.getExitErrorCode()
|
|
if errorCode >= OTPLauncherGlobals.NonErrorExitStateStart and errorCode <= OTPLauncherGlobals.NonErrorExitStateEnd:
|
|
logFunc = self.notify.warning
|
|
allowExit = True
|
|
elif __debug__ and not PythonUtil.configIsToday('temp-disable-leak-detection'):
|
|
logFunc = self.notify.error
|
|
allowExit = False
|
|
else:
|
|
logFunc = self.notify.warning
|
|
allowExit = False
|
|
if ConfigVariableBool('direct-gui-edit', 0).value:
|
|
logFunc('There are leaks: %s tasks, %s events, %s ivals, %s garbage cycles\nLeaked Events may be due to direct gui editing' % (leakedTasks,
|
|
leakedEvents,
|
|
leakedIvals,
|
|
leakedGarbage))
|
|
else:
|
|
logFunc('There are leaks: %s tasks, %s events, %s ivals, %s garbage cycles' % (leakedTasks,
|
|
leakedEvents,
|
|
leakedIvals,
|
|
leakedGarbage))
|
|
if allowExit:
|
|
self.notify.info('Allowing client to leave, panda error code %s' % errorCode)
|
|
else:
|
|
base.userExit()
|
|
else:
|
|
self.notify.info('There are no leaks detected.')
|
|
|
|
def detectLeakedGarbage(self, callback = None):
|
|
if not __debug__:
|
|
return 0
|
|
self.notify.info('checking for leaked garbage...')
|
|
if gc.garbage:
|
|
self.notify.warning('garbage already contains %d items' % len(gc.garbage))
|
|
report = GarbageReport.GarbageReport('logout', verbose=True)
|
|
numCycles = report.getNumCycles()
|
|
if numCycles:
|
|
msg = "You can't leave until you take out your garbage. See report above & base.garbage"
|
|
self.notify.info(msg)
|
|
report.destroy()
|
|
return numCycles
|
|
|
|
def detectLeakedTasks(self, extraTasks = None):
|
|
allowedTasks = ['dataLoop',
|
|
'resetPrevTransform',
|
|
'doLaterProcessor',
|
|
'eventManager',
|
|
'readerPollTask',
|
|
'heartBeat',
|
|
'gridZoneLoop',
|
|
'igLoop',
|
|
'audioLoop',
|
|
'asyncLoad',
|
|
'collisionLoop',
|
|
'shadowCollisionLoop',
|
|
'ivalLoop',
|
|
'downloadSequence',
|
|
'patchAndHash',
|
|
'launcher-download',
|
|
'launcher-download-multifile',
|
|
'launcher-decompressFile',
|
|
'launcher-decompressMultifile',
|
|
'launcher-extract',
|
|
'launcher-patch',
|
|
'slowCloseShardCallback',
|
|
'tkLoop',
|
|
'manager-update',
|
|
'downloadStallTask',
|
|
'clientSleep',
|
|
jobMgr.TaskName,
|
|
self.GarbageCollectTaskName,
|
|
'RedownloadNewsTask',
|
|
TelemetryLimiter.TaskName]
|
|
if extraTasks is not None:
|
|
allowedTasks.extend(extraTasks)
|
|
problems = []
|
|
for task in taskMgr.getTasks():
|
|
if not hasattr(task, 'name'):
|
|
continue
|
|
if task.name in allowedTasks:
|
|
continue
|
|
else:
|
|
if hasattr(task, 'debugInitTraceback'):
|
|
print(task.debugInitTraceback)
|
|
problems.append(task.name)
|
|
|
|
if problems:
|
|
print(taskMgr)
|
|
msg = "You can't leave until you clean up your tasks: {"
|
|
for task in problems:
|
|
msg += '\n ' + task
|
|
|
|
msg += '}\n'
|
|
self.notify.info(msg)
|
|
return len(problems)
|
|
else:
|
|
return 0
|
|
return
|
|
|
|
def detectLeakedEvents(self, extraHooks = None):
|
|
allowedHooks = ['destroy-DownloadWatcherBar',
|
|
'destroy-DownloadWatcherText',
|
|
'destroy-fade',
|
|
'f9',
|
|
'control-f9',
|
|
'launcherAllPhasesComplete',
|
|
'launcherPercentPhaseComplete',
|
|
'newDistributedDirectory',
|
|
'page_down',
|
|
'page_up',
|
|
'panda3d-render-error',
|
|
'PandaPaused',
|
|
'PandaRestarted',
|
|
'phaseComplete-3',
|
|
'press-mouse2-fade',
|
|
'print-fade',
|
|
'release-mouse2-fade',
|
|
'resetClock',
|
|
'window-event',
|
|
'TCRSetZoneDone',
|
|
'aspectRatioChanged',
|
|
'newDistributedDirectory',
|
|
CConnectionRepository.getOverflowEventName(),
|
|
self._getLostConnectionEvent(),
|
|
'render-texture-targets-changed',
|
|
'gotExtraFriendHandles']
|
|
if hasattr(loader, 'hook'):
|
|
allowedHooks.append(loader.hook)
|
|
if extraHooks is not None:
|
|
allowedHooks.extend(extraHooks)
|
|
problems = []
|
|
for hook in messenger.getEvents():
|
|
if hook not in allowedHooks:
|
|
problems.append(hook)
|
|
|
|
if problems:
|
|
msg = "You can't leave until you clean up your messenger hooks: {"
|
|
for hook in problems:
|
|
whoAccepts = messenger.whoAccepts(hook)
|
|
msg += '\n %s' % hook
|
|
for obj in whoAccepts:
|
|
msg += '\n OBJECT:%s, %s %s' % (obj, obj.__class__, whoAccepts[obj])
|
|
if hasattr(obj, 'getCreationStackTraceCompactStr'):
|
|
msg += '\n CREATIONSTACKTRACE:%s' % obj.getCreationStackTraceCompactStr()
|
|
else:
|
|
try:
|
|
value = whoAccepts[obj]
|
|
callback = value[0]
|
|
guiObj = callback.__self__
|
|
if hasattr(guiObj, 'getCreationStackTraceCompactStr'):
|
|
msg += '\n CREATIONSTACKTRACE:%s' % guiObj.getCreationStackTraceCompactStr()
|
|
except:
|
|
pass
|
|
|
|
msg += '\n}\n'
|
|
self.notify.warning(msg)
|
|
return len(problems)
|
|
else:
|
|
return 0
|
|
return
|
|
|
|
def detectLeakedIntervals(self):
|
|
numIvals = ivalMgr.getNumIntervals()
|
|
if numIvals > 0:
|
|
print("You can't leave until you clean up your intervals: {")
|
|
for i in range(ivalMgr.getMaxIndex()):
|
|
ival = None
|
|
if i < len(ivalMgr.ivals):
|
|
ival = ivalMgr.ivals[i]
|
|
if ival == None:
|
|
ival = ivalMgr.getCInterval(i)
|
|
if ival:
|
|
print(ival)
|
|
if hasattr(ival, 'debugName'):
|
|
print(ival.debugName)
|
|
if hasattr(ival, 'debugInitTraceback'):
|
|
print(ival.debugInitTraceback)
|
|
|
|
print('}')
|
|
self.notify.info("You can't leave until you clean up your intervals.")
|
|
return numIvals
|
|
else:
|
|
return 0
|
|
return
|
|
|
|
def _abandonShard(self):
|
|
self.notify.error('%s must override _abandonShard' % self.__class__.__name__)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterGameOff(self):
|
|
self.uberZoneInterest = None
|
|
if not hasattr(self, 'cleanGameExit'):
|
|
self.cleanGameExit = True
|
|
if self.cleanGameExit:
|
|
if self.isShardInterestOpen():
|
|
self.notify.error('enterGameOff: shard interest is still open')
|
|
elif self.isShardInterestOpen():
|
|
self.notify.warning('unclean exit, abandoning shard')
|
|
self._abandonShard()
|
|
self.cleanupWaitAllInterestsComplete()
|
|
del self.cleanGameExit
|
|
self.cache.flush()
|
|
self.doDataCache.flush()
|
|
self.handler = self.handleMessageType
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitGameOff(self):
|
|
self.handler = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterWaitOnEnterResponses(self, shardId, hoodId, zoneId, avId):
|
|
self.cleanGameExit = False
|
|
if not __astron__:
|
|
self.handler = self.handleWaitOnEnterResponses
|
|
self.handlerArgs = {'hoodId': hoodId,
|
|
'zoneId': zoneId,
|
|
'avId': avId}
|
|
if shardId is not None:
|
|
district = self.activeDistrictMap.get(shardId)
|
|
else:
|
|
district = None
|
|
if not district:
|
|
self.distributedDistrict = self.getStartingDistrict()
|
|
if self.distributedDistrict is None:
|
|
self.loginFSM.request('noShards')
|
|
return
|
|
shardId = self.distributedDistrict.doId
|
|
else:
|
|
self.distributedDistrict = district
|
|
self.notify.info('Entering shard %s' % shardId)
|
|
localAvatar.setLocation(shardId, zoneId)
|
|
base.localAvatar.defaultShard = shardId
|
|
self.waitForDatabaseTimeout(requestName='WaitOnEnterResponses')
|
|
self.handleSetShardComplete()
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def handleWaitOnEnterResponses(self, msgType, di):
|
|
if msgType == CLIENT_GET_FRIEND_LIST_RESP:
|
|
self.handleGetFriendsList(di)
|
|
elif msgType == CLIENT_GET_FRIEND_LIST_EXTENDED_RESP:
|
|
self.handleGetFriendsListExtended(di)
|
|
elif msgType == CLIENT_FRIEND_ONLINE:
|
|
self.handleFriendOnline(di)
|
|
elif msgType == CLIENT_FRIEND_OFFLINE:
|
|
self.handleFriendOffline(di)
|
|
elif msgType == CLIENT_GET_PET_DETAILS_RESP:
|
|
self.handleGetAvatarDetailsResp(di)
|
|
else:
|
|
self.handleMessageType(msgType, di)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def handleSetShardComplete(self):
|
|
hoodId = self.handlerArgs['hoodId']
|
|
zoneId = self.handlerArgs['zoneId']
|
|
avId = self.handlerArgs['avId']
|
|
self.uberZoneInterest = self.addInterest(base.localAvatar.defaultShard, OTPGlobals.UberZone, 'uberZone', 'uberZoneInterestComplete')
|
|
self.acceptOnce('uberZoneInterestComplete', self.uberZoneInterestComplete)
|
|
self.waitForDatabaseTimeout(20, requestName='waitingForUberZone')
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def uberZoneInterestComplete(self):
|
|
self.__gotTimeSync = 0
|
|
self.cleanupWaitingForDatabase()
|
|
if self.timeManager == None:
|
|
self.notify.info('TimeManager is not present.')
|
|
DistributedSmoothNode.globalActivateSmoothing(0, 0)
|
|
self.gotTimeSync()
|
|
else:
|
|
DistributedSmoothNode.globalActivateSmoothing(1, 0)
|
|
h = HashVal()
|
|
hashPrcVariables(h)
|
|
pyc = HashVal()
|
|
if not __dev__:
|
|
self.hashFiles(pyc)
|
|
self.timeManager.d_setSignature(self.userSignature, h.asBin(), pyc.asBin())
|
|
self.timeManager.sendCpuInfo()
|
|
if self.timeManager.synchronize('startup'):
|
|
self.accept('gotTimeSync', self.gotTimeSync)
|
|
self.waitForDatabaseTimeout(requestName='uberZoneInterest-timeSync')
|
|
else:
|
|
self.notify.info('No sync from TimeManager.')
|
|
self.gotTimeSync()
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitWaitOnEnterResponses(self):
|
|
self.ignore('uberZoneInterestComplete')
|
|
self.cleanupWaitingForDatabase()
|
|
self.handler = None
|
|
self.handlerArgs = None
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterCloseShard(self, loginState = None):
|
|
self.notify.info('Exiting shard')
|
|
if loginState is None:
|
|
loginState = 'waitForAvatarList'
|
|
self._closeShardLoginState = loginState
|
|
base.cr.setNoNewInterests(True)
|
|
return
|
|
|
|
def _removeLocalAvFromStateServer(self):
|
|
self.sendSetAvatarIdMsg(0)
|
|
self._removeAllOV()
|
|
callback = Functor(self.loginFSM.request, self._closeShardLoginState)
|
|
if base.slowCloseShard:
|
|
taskMgr.doMethodLater(base.slowCloseShardDelay * 0.5, Functor(self.removeShardInterest, callback), 'slowCloseShard')
|
|
else:
|
|
self.removeShardInterest(callback)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def _removeAllOV(self):
|
|
ownerDoIds = list(self.doId2ownerView.keys())
|
|
for doId in ownerDoIds:
|
|
self.disableDoId(doId, ownerView=True)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def isShardInterestOpen(self):
|
|
self.notify.error('%s must override isShardInterestOpen' % self.__class__.__name__)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def removeShardInterest(self, callback, task = None):
|
|
self._removeCurrentShardInterest(Functor(self._removeShardInterestComplete, callback))
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def _removeShardInterestComplete(self, callback):
|
|
self.cleanGameExit = True
|
|
self.cache.flush()
|
|
self.doDataCache.flush()
|
|
if base.slowCloseShard:
|
|
taskMgr.doMethodLater(base.slowCloseShardDelay * 0.5, Functor(self._callRemoveShardInterestCallback, callback), 'slowCloseShardCallback')
|
|
else:
|
|
self._callRemoveShardInterestCallback(callback, None)
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def _callRemoveShardInterestCallback(self, callback, task):
|
|
callback()
|
|
return Task.done
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def _removeCurrentShardInterest(self, callback):
|
|
self.notify.error('%s must override _removeCurrentShardInterest' % self.__class__.__name__)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitCloseShard(self):
|
|
del self._closeShardLoginState
|
|
base.cr.setNoNewInterests(False)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterTutorialQuestion(self, hoodId, zoneId, avId):
|
|
pass
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitTutorialQuestion(self):
|
|
pass
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterPlayGame(self, hoodId, zoneId, avId):
|
|
if self.music:
|
|
self.music.stop()
|
|
self.music = None
|
|
self.garbageLeakLogger = GarbageLeakServerEventAggregator(self)
|
|
self.handler = self.handlePlayGame
|
|
self.accept(self.gameDoneEvent, self.handleGameDone)
|
|
base.transitions.noFade()
|
|
self.playGame.load()
|
|
try:
|
|
loader.endBulkLoad('localAvatarPlayGame')
|
|
except:
|
|
pass
|
|
|
|
self.playGame.enter(hoodId, zoneId, avId)
|
|
|
|
def checkScale(task):
|
|
return Task.cont
|
|
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def handleGameDone(self):
|
|
if self.timeManager:
|
|
self.timeManager.setDisconnectReason(OTPGlobals.DisconnectSwitchShards)
|
|
doneStatus = self.playGame.getDoneStatus()
|
|
how = doneStatus['how']
|
|
shardId = doneStatus['shardId']
|
|
hoodId = doneStatus['hoodId']
|
|
zoneId = doneStatus['zoneId']
|
|
avId = doneStatus['avId']
|
|
if how == 'teleportIn':
|
|
self.gameFSM.request('switchShards', [shardId,
|
|
hoodId,
|
|
zoneId,
|
|
avId])
|
|
else:
|
|
self.notify.error('Exited shard with unexpected mode %s' % how)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitPlayGame(self):
|
|
taskMgr.remove('globalScaleCheck')
|
|
self.handler = None
|
|
self.playGame.exit()
|
|
self.playGame.unload()
|
|
self.ignore(self.gameDoneEvent)
|
|
self.garbageLeakLogger.destroy()
|
|
del self.garbageLeakLogger
|
|
return
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def gotTimeSync(self):
|
|
self.notify.info('gotTimeSync')
|
|
self.ignore('gotTimeSync')
|
|
self.__gotTimeSync = 1
|
|
self.moveOnFromUberZone()
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def moveOnFromUberZone(self):
|
|
if not self.__gotTimeSync:
|
|
self.notify.info('Waiting for time sync.')
|
|
return
|
|
hoodId = self.handlerArgs['hoodId']
|
|
zoneId = self.handlerArgs['zoneId']
|
|
avId = self.handlerArgs['avId']
|
|
if not self.SupportTutorial or base.localAvatar.tutorialAck:
|
|
self.gameFSM.request('playGame', [hoodId, zoneId, avId])
|
|
elif ConfigVariableBool('force-tutorial', 1).value:
|
|
if hasattr(self, 'skipTutorialRequest') and self.skipTutorialRequest:
|
|
self.gameFSM.request('playGame', [hoodId, zoneId, avId])
|
|
self.gameFSM.request('skipTutorialRequest', [hoodId, zoneId, avId])
|
|
else:
|
|
self.gameFSM.request('tutorialQuestion', [hoodId, zoneId, avId])
|
|
else:
|
|
self.gameFSM.request('playGame', [hoodId, zoneId, avId])
|
|
|
|
if not __astron__:
|
|
def handlePlayGame(self, msgType, di):
|
|
if self.notify.getDebug():
|
|
self.notify.debug('handle play game got message type: ' + repr(msgType))
|
|
if msgType == CLIENT_CREATE_OBJECT_REQUIRED:
|
|
self.handleGenerateWithRequired(di)
|
|
elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
|
|
self.handleGenerateWithRequiredOther(di)
|
|
elif msgType == CLIENT_OBJECT_UPDATE_FIELD:
|
|
self.handleUpdateField(di)
|
|
elif msgType == CLIENT_OBJECT_DISABLE_RESP:
|
|
self.handleDisable(di)
|
|
elif msgType == CLIENT_OBJECT_DELETE_RESP:
|
|
self.handleDelete(di)
|
|
elif msgType == CLIENT_GET_FRIEND_LIST_RESP:
|
|
self.handleGetFriendsList(di)
|
|
elif msgType == CLIENT_GET_FRIEND_LIST_EXTENDED_RESP:
|
|
self.handleGetFriendsListExtended(di)
|
|
elif msgType == CLIENT_FRIEND_ONLINE:
|
|
self.handleFriendOnline(di)
|
|
elif msgType == CLIENT_FRIEND_OFFLINE:
|
|
self.handleFriendOffline(di)
|
|
elif msgType == CLIENT_GET_AVATAR_DETAILS_RESP:
|
|
self.handleGetAvatarDetailsResp(di)
|
|
elif msgType == CLIENT_GET_PET_DETAILS_RESP:
|
|
self.handleGetAvatarDetailsResp(di)
|
|
else:
|
|
self.handleMessageType(msgType, di)
|
|
else:
|
|
def handlePlayGame(self, msgType, di):
|
|
if self.notify.getDebug():
|
|
self.notify.debug('handle play game got message type: ' + repr(msgType))
|
|
if self.__recordObjectMessage(msgType, di):
|
|
return
|
|
if msgType == CLIENT_ENTER_OBJECT_REQUIRED:
|
|
self.handleGenerateWithRequired(di)
|
|
elif msgType == CLIENT_ENTER_OBJECT_REQUIRED_OTHER:
|
|
self.handleGenerateWithRequired(di, other=True)
|
|
elif msgType == CLIENT_OBJECT_SET_FIELD:
|
|
self.handleUpdateField(di)
|
|
elif msgType == CLIENT_OBJECT_LEAVING:
|
|
self.handleDelete(di)
|
|
else:
|
|
self.handleMessageType(msgType, di)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def enterSwitchShards(self, shardId, hoodId, zoneId, avId):
|
|
self._switchShardParams = [shardId,
|
|
hoodId,
|
|
zoneId,
|
|
avId]
|
|
localAvatar.setLeftDistrict()
|
|
self.removeShardInterest(self._handleOldShardGone)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def _handleOldShardGone(self):
|
|
self.gameFSM.request('waitOnEnterResponses', self._switchShardParams)
|
|
|
|
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
|
|
def exitSwitchShards(self):
|
|
pass
|
|
|
|
def isFreeTimeExpired(self):
|
|
if self.accountOldAuth:
|
|
return 0
|
|
if ConfigVariableBool('free-time-expired', 0).value:
|
|
return 1
|
|
if ConfigVariableBool('unlimited-free-time', 0).value:
|
|
return 0
|
|
if self.freeTimeExpiresAt == -1:
|
|
return 0
|
|
if self.freeTimeExpiresAt == 0:
|
|
return 1
|
|
if self.freeTimeExpiresAt < -1:
|
|
self.notify.warning('freeTimeExpiresAt is less than -1 (%s)' % self.freeTimeExpiresAt)
|
|
if self.freeTimeExpiresAt < time.time():
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
def freeTimeLeft(self):
|
|
if self.freeTimeExpiresAt == -1 or self.freeTimeExpiresAt == 0:
|
|
return 0
|
|
secsLeft = self.freeTimeExpiresAt - time.time()
|
|
return max(0, secsLeft)
|
|
|
|
def isWebPlayToken(self):
|
|
return self.playToken != None
|
|
|
|
def isBlue(self):
|
|
return self.blue != None
|
|
|
|
def isPaid(self):
|
|
paidStatus = ConfigVariableString('force-paid-status', '').value
|
|
if not paidStatus:
|
|
return self.__isPaid
|
|
elif paidStatus == 'paid':
|
|
return 1
|
|
elif paidStatus == 'unpaid':
|
|
return 0
|
|
elif paidStatus == 'FULL':
|
|
return OTPGlobals.AccessFull
|
|
elif paidStatus == 'VELVET':
|
|
return OTPGlobals.AccessVelvetRope
|
|
else:
|
|
return 0
|
|
|
|
def setIsPaid(self, isPaid):
|
|
self.__isPaid = isPaid
|
|
|
|
def allowFreeNames(self):
|
|
return ConfigVariableInt('allow-free-names', 1).value
|
|
|
|
def allowSecretChat(self):
|
|
return self.secretChatAllowed or self.productName == 'Terra-DMC' and self.isBlue() and self.secretChatAllowed
|
|
|
|
def allowWhiteListChat(self):
|
|
if hasattr(self, 'whiteListChatEnabled') and self.whiteListChatEnabled:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def allowAnyTypedChat(self):
|
|
if self.allowSecretChat() or self.allowWhiteListChat() or self.allowOpenChat():
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def allowOpenChat(self):
|
|
return self.openChatAllowed
|
|
|
|
def isParentPasswordSet(self):
|
|
return self.parentPasswordSet
|
|
|
|
def needParentPasswordForSecretChat(self):
|
|
return self.isPaid() and self.secretChatNeedsParentPassword or self.productName == 'Terra-DMC' and self.isBlue() and self.secretChatNeedsParentPassword
|
|
|
|
def logAccountInfo(self):
|
|
self.notify.info('*** ACCOUNT INFO ***')
|
|
self.notify.info('username: %s' % self.userName)
|
|
if base.logPrivateInfo:
|
|
if self.blue:
|
|
self.notify.info('paid: %s (blue)' % self.isPaid())
|
|
else:
|
|
self.notify.info('paid: %s' % self.isPaid())
|
|
if not self.isPaid():
|
|
if self.isFreeTimeExpired():
|
|
self.notify.info('free time is expired')
|
|
else:
|
|
secs = self.freeTimeLeft()
|
|
self.notify.info('free time left: %s' % PythonUtil.formatElapsedSeconds(secs))
|
|
if self.periodTimerSecondsRemaining != None:
|
|
self.notify.info('period time left: %s' % PythonUtil.formatElapsedSeconds(self.periodTimerSecondsRemaining))
|
|
return
|
|
|
|
def getStartingDistrict(self):
|
|
district = None
|
|
if len(list(self.activeDistrictMap.keys())) == 0:
|
|
self.notify.info('no shards')
|
|
return
|
|
if base.fillShardsToIdealPop:
|
|
lowPop, midPop, highPop = base.getShardPopLimits()
|
|
self.notify.debug('low: %s mid: %s high: %s' % (lowPop, midPop, highPop))
|
|
for s in list(self.activeDistrictMap.values()):
|
|
if s.available and s.avatarCount < lowPop:
|
|
self.notify.debug('%s: pop %s' % (s.name, s.avatarCount))
|
|
if district is None:
|
|
district = s
|
|
elif s.avatarCount > district.avatarCount or s.avatarCount == district.avatarCount and s.name > district.name:
|
|
district = s
|
|
|
|
if district is None:
|
|
self.notify.debug('all shards over cutoff, picking lowest-population shard')
|
|
for s in list(self.activeDistrictMap.values()):
|
|
if s.available:
|
|
self.notify.debug('%s: pop %s' % (s.name, s.avatarCount))
|
|
if district is None or s.avatarCount < district.avatarCount:
|
|
district = s
|
|
|
|
if district is not None:
|
|
self.notify.debug('chose %s: pop %s' % (district.name, district.avatarCount))
|
|
return district
|
|
|
|
def getShardName(self, shardId):
|
|
try:
|
|
return self.activeDistrictMap[shardId].name
|
|
except:
|
|
return None
|
|
|
|
return None
|
|
|
|
def isShardAvailable(self, shardId):
|
|
try:
|
|
return self.activeDistrictMap[shardId].available
|
|
except:
|
|
return 0
|
|
|
|
def listActiveShards(self):
|
|
activeShards = []
|
|
for s in list(self.activeDistrictMap.values()):
|
|
if s.available:
|
|
activeShards.append((s.doId,
|
|
s.name,
|
|
s.avatarCount,
|
|
s.newAvatarCount))
|
|
|
|
return activeShards
|
|
|
|
def getPlayerAvatars(self):
|
|
return [ i for i in list(self.doId2do.values()) if isinstance(i, DistributedPlayer) ]
|
|
|
|
def queryObjectField(self, dclassName, fieldName, doId, context = 0):
|
|
dclass = self.dclassesByName.get(dclassName)
|
|
if dclass is not None:
|
|
fieldId = dclass.getFieldByName(fieldName).getNumber()
|
|
self.queryObjectFieldId(doId, fieldId, context)
|
|
return
|
|
|
|
def lostConnection(self):
|
|
ClientRepositoryBase.lostConnection(self)
|
|
self.loginFSM.request('noConnection')
|
|
|
|
def waitForDatabaseTimeout(self, extraTimeout = 0, requestName = 'unknown'):
|
|
OTPClientRepository.notify.debug('waiting for database timeout %s at %s' % (requestName, globalClock.getFrameTime()))
|
|
self.cleanupWaitingForDatabase()
|
|
globalClock.tick()
|
|
taskMgr.doMethodLater((OTPGlobals.DatabaseDialogTimeout + extraTimeout) * choice(__dev__, 10, 1), self.__showWaitingForDatabase, 'waitingForDatabase', extraArgs=[requestName])
|
|
|
|
def cleanupWaitingForDatabase(self):
|
|
if self.waitingForDatabase:
|
|
self.waitingForDatabase.hide()
|
|
self.waitingForDatabase.cleanup()
|
|
self.waitingForDatabase = None
|
|
taskMgr.remove('waitingForDatabase')
|
|
return
|
|
|
|
def __showWaitingForDatabase(self, requestName):
|
|
messenger.send('connectionIssue')
|
|
OTPClientRepository.notify.info('timed out waiting for %s at %s' % (requestName, globalClock.getFrameTime()))
|
|
dialogClass = OTPGlobals.getDialogClass()
|
|
self.waitingForDatabase = dialogClass(text=OTPLocalizer.CRToontownUnavailable, dialogName='WaitingForDatabase', buttonTextList=[OTPLocalizer.CRToontownUnavailableCancel], style=OTPDialog.CancelOnly, command=self.__handleCancelWaiting)
|
|
self.waitingForDatabase.show()
|
|
taskMgr.remove('waitingForDatabase')
|
|
taskMgr.doMethodLater(OTPGlobals.DatabaseGiveupTimeout, self.__giveUpWaitingForDatabase, 'waitingForDatabase', extraArgs=[requestName])
|
|
return Task.done
|
|
|
|
def __giveUpWaitingForDatabase(self, requestName):
|
|
OTPClientRepository.notify.info('giving up waiting for %s at %s' % (requestName, globalClock.getFrameTime()))
|
|
self.cleanupWaitingForDatabase()
|
|
self.loginFSM.request('noConnection')
|
|
return Task.done
|
|
|
|
def __handleCancelWaiting(self, value):
|
|
self.loginFSM.request('shutdown')
|
|
|
|
def renderFrame(self):
|
|
gsg = base.win.getGsg()
|
|
if gsg:
|
|
render2d.prepareScene(gsg)
|
|
base.graphicsEngine.renderFrame()
|
|
|
|
def resetPeriodTimer(self, secondsRemaining):
|
|
self.periodTimerExpired = 0
|
|
self.periodTimerSecondsRemaining = secondsRemaining
|
|
|
|
def recordPeriodTimer(self, task):
|
|
freq = 60.0
|
|
elapsed = globalClock.getRealTime() - self.periodTimerStarted
|
|
self.runningPeriodTimeRemaining = self.periodTimerSecondsRemaining - elapsed
|
|
self.notify.debug('periodTimeRemaining: %s' % self.runningPeriodTimeRemaining)
|
|
launcher.recordPeriodTimeRemaining(self.runningPeriodTimeRemaining)
|
|
taskMgr.doMethodLater(freq, self.recordPeriodTimer, 'periodTimerRecorder')
|
|
return Task.done
|
|
|
|
def startPeriodTimer(self):
|
|
if self.periodTimerStarted == None and self.periodTimerSecondsRemaining != None:
|
|
self.periodTimerStarted = globalClock.getRealTime()
|
|
taskMgr.doMethodLater(self.periodTimerSecondsRemaining, self.__periodTimerExpired, 'periodTimerCountdown')
|
|
for warning in OTPGlobals.PeriodTimerWarningTime:
|
|
if self.periodTimerSecondsRemaining > warning:
|
|
taskMgr.doMethodLater(self.periodTimerSecondsRemaining - warning, self.__periodTimerWarning, 'periodTimerCountdown')
|
|
|
|
self.runningPeriodTimeRemaining = self.periodTimerSecondsRemaining
|
|
self.recordPeriodTimer(None)
|
|
return
|
|
|
|
def stopPeriodTimer(self):
|
|
if self.periodTimerStarted != None:
|
|
elapsed = globalClock.getRealTime() - self.periodTimerStarted
|
|
self.periodTimerSecondsRemaining -= elapsed
|
|
self.periodTimerStarted = None
|
|
taskMgr.remove('periodTimerCountdown')
|
|
taskMgr.remove('periodTimerRecorder')
|
|
return
|
|
|
|
def __periodTimerWarning(self, task):
|
|
base.localAvatar.setSystemMessage(0, OTPLocalizer.PeriodTimerWarning)
|
|
return Task.done
|
|
|
|
def __periodTimerExpired(self, task):
|
|
self.notify.info("User's period timer has just expired!")
|
|
self.stopPeriodTimer()
|
|
self.periodTimerExpired = 1
|
|
self.periodTimerStarted = None
|
|
self.periodTimerSecondsRemaining = None
|
|
messenger.send('periodTimerExpired')
|
|
return Task.done
|
|
|
|
if not __astron__:
|
|
def handleMessageType(self, msgType, di):
|
|
if msgType == CLIENT_GO_GET_LOST:
|
|
self.handleGoGetLost(di)
|
|
elif msgType == CLIENT_HEARTBEAT:
|
|
self.handleServerHeartbeat(di)
|
|
elif msgType == CLIENT_SYSTEM_MESSAGE:
|
|
self.handleSystemMessage(di)
|
|
elif msgType == CLIENT_SYSTEMMESSAGE_AKNOWLEDGE:
|
|
self.handleSystemMessageAknowledge(di)
|
|
elif msgType == CLIENT_CREATE_OBJECT_REQUIRED:
|
|
self.handleGenerateWithRequired(di)
|
|
elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER:
|
|
self.handleGenerateWithRequiredOther(di)
|
|
elif msgType == CLIENT_CREATE_OBJECT_REQUIRED_OTHER_OWNER:
|
|
self.handleGenerateWithRequiredOtherOwner(di)
|
|
elif msgType == CLIENT_OBJECT_UPDATE_FIELD:
|
|
self.handleUpdateField(di)
|
|
elif msgType == CLIENT_OBJECT_DISABLE:
|
|
self.handleDisable(di)
|
|
elif msgType == CLIENT_OBJECT_DISABLE_OWNER:
|
|
self.handleDisable(di, ownerView=True)
|
|
elif msgType == CLIENT_OBJECT_DELETE_RESP:
|
|
self.handleDelete(di)
|
|
elif msgType == CLIENT_DONE_INTEREST_RESP:
|
|
self.gotInterestDoneMessage(di)
|
|
elif msgType == CLIENT_GET_STATE_RESP:
|
|
pass
|
|
elif msgType == CLIENT_OBJECT_LOCATION:
|
|
self.gotObjectLocationMessage(di)
|
|
elif msgType == CLIENT_SET_WISHNAME_RESP:
|
|
self.gotWishnameResponse(di)
|
|
else:
|
|
currentLoginState = self.loginFSM.getCurrentState()
|
|
if currentLoginState:
|
|
currentLoginStateName = currentLoginState.getName()
|
|
else:
|
|
currentLoginStateName = 'None'
|
|
currentGameState = self.gameFSM.getCurrentState()
|
|
if currentGameState:
|
|
currentGameStateName = currentGameState.getName()
|
|
else:
|
|
currentGameStateName = 'None'
|
|
else:
|
|
def handleMessageType(self, msgType, di):
|
|
if self.__recordObjectMessage(msgType, di):
|
|
return
|
|
if msgType == CLIENT_EJECT:
|
|
self.handleGoGetLost(di)
|
|
elif msgType == CLIENT_HEARTBEAT:
|
|
self.handleServerHeartbeat(di)
|
|
elif msgType == CLIENT_ENTER_OBJECT_REQUIRED:
|
|
self.handleGenerateWithRequired(di)
|
|
elif msgType == CLIENT_ENTER_OBJECT_REQUIRED_OTHER:
|
|
self.handleGenerateWithRequired(di, other=True)
|
|
elif msgType == CLIENT_ENTER_OBJECT_REQUIRED_OTHER_OWNER:
|
|
self.handleGenerateWithRequiredOtherOwner(di)
|
|
elif msgType == CLIENT_OBJECT_SET_FIELD:
|
|
self.handleUpdateField(di)
|
|
elif msgType == CLIENT_OBJECT_LEAVING:
|
|
self.handleDisable(di)
|
|
elif msgType == CLIENT_OBJECT_LEAVING_OWNER:
|
|
self.handleDisable(di, ownerView=True)
|
|
elif msgType == CLIENT_DONE_INTEREST_RESP:
|
|
self.gotInterestDoneMessage(di)
|
|
elif msgType == CLIENT_OBJECT_LOCATION:
|
|
self.gotObjectLocationMessage(di)
|
|
else:
|
|
currentLoginState = self.loginFSM.getCurrentState()
|
|
if currentLoginState:
|
|
currentLoginStateName = currentLoginState.getName()
|
|
else:
|
|
currentLoginStateName = 'None'
|
|
currentGameState = self.gameFSM.getCurrentState()
|
|
if currentGameState:
|
|
currentGameStateName = currentGameState.getName()
|
|
else:
|
|
currentGameStateName = 'None'
|
|
|
|
def gotInterestDoneMessage(self, di):
|
|
if self.deferredGenerates:
|
|
dg = Datagram(di.getDatagram())
|
|
di = DatagramIterator(dg, di.getCurrentIndex())
|
|
self.deferredGenerates.append((CLIENT_DONE_INTEREST_RESP, (dg, di)))
|
|
else:
|
|
if __astron__:
|
|
# Play back generates, if necessary.
|
|
# First, create a new DatagramIterator using
|
|
# the Datagram from DatagramIterator di, and
|
|
# the current index of DatagramIterator di:
|
|
di2 = DatagramIterator(di.getDatagram(), di.getCurrentIndex())
|
|
|
|
# Get the context. This is never actually used,
|
|
# however none of this will work unless we get it.
|
|
ctx = di2.getUint32()
|
|
|
|
# Now, get the handle:
|
|
handle = di2.getUint16()
|
|
|
|
# Finally, play back the generates:
|
|
self.__playBackGenerates(handle)
|
|
|
|
self.handleInterestDoneMessage(di)
|
|
|
|
def gotObjectLocationMessage(self, di):
|
|
if self.deferredGenerates:
|
|
dg = Datagram(di.getDatagram())
|
|
di = DatagramIterator(dg, di.getCurrentIndex())
|
|
di2 = DatagramIterator(dg, di.getCurrentIndex())
|
|
doId = di2.getUint32()
|
|
if doId in self.deferredDoIds:
|
|
self.deferredDoIds[doId][3].append((CLIENT_OBJECT_LOCATION, (dg, di)))
|
|
else:
|
|
self.handleObjectLocation(di)
|
|
else:
|
|
self.handleObjectLocation(di)
|
|
|
|
def sendWishName(self, avId, name):
|
|
datagram = PyDatagram()
|
|
datagram.addUint16(CLIENT_SET_WISHNAME)
|
|
datagram.addUint32(avId)
|
|
datagram.addString(name)
|
|
self.send(datagram)
|
|
|
|
def sendWishNameAnonymous(self, name):
|
|
self.sendWishName(0, name)
|
|
|
|
def getWishNameResultMsg(self):
|
|
return 'OTPCR.wishNameResult'
|
|
|
|
def gotWishnameResponse(self, di):
|
|
avId = di.getUint32()
|
|
returnCode = di.getUint16()
|
|
pendingName = ''
|
|
approvedName = ''
|
|
rejectedName = ''
|
|
if returnCode == 0:
|
|
pendingName = di.getString()
|
|
approvedName = di.getString()
|
|
rejectedName = di.getString()
|
|
if approvedName:
|
|
name = approvedName
|
|
elif pendingName:
|
|
name = pendingName
|
|
elif rejectedName:
|
|
name = rejectedName
|
|
else:
|
|
name = ''
|
|
WNR = self.WishNameResult
|
|
if returnCode:
|
|
result = WNR.Failure
|
|
elif rejectedName:
|
|
result = WNR.Rejected
|
|
elif pendingName:
|
|
result = WNR.PendingApproval
|
|
elif approvedName:
|
|
result = WNR.Approved
|
|
messenger.send(self.getWishNameResultMsg(), [result, avId, name])
|
|
|
|
def replayDeferredGenerate(self, msgType, extra):
|
|
if msgType == CLIENT_DONE_INTEREST_RESP:
|
|
dg, di = extra
|
|
self.handleInterestDoneMessage(di)
|
|
elif msgType == CLIENT_OBJECT_LOCATION:
|
|
dg, di = extra
|
|
self.handleObjectLocation(di)
|
|
else:
|
|
ClientRepositoryBase.replayDeferredGenerate(self, msgType, extra)
|
|
|
|
@exceptionLogged(append=False)
|
|
def handleDatagram(self, di):
|
|
if self.notify.getDebug():
|
|
print('ClientRepository received datagram:')
|
|
di.getDatagram().dumpHex(Notify.out())
|
|
msgType = self.getMsgType()
|
|
if msgType == 65535:
|
|
self.lostConnection()
|
|
return
|
|
if self.handler == None:
|
|
self.handleMessageType(msgType, di)
|
|
else:
|
|
self.handler(msgType, di)
|
|
self.considerHeartbeat()
|
|
return
|
|
|
|
def askAvatarKnown(self, avId):
|
|
return 0
|
|
|
|
def hashFiles(self, pyc):
|
|
for dir in sys.path:
|
|
if dir == '':
|
|
dir = '.'
|
|
if os.path.isdir(dir):
|
|
for filename in os.listdir(dir):
|
|
if filename.endswith('.pyo') or filename.endswith('.pyc') or filename.endswith('.py') or filename == 'library.zip':
|
|
pathname = Filename.fromOsSpecific(os.path.join(dir, filename))
|
|
hv = HashVal()
|
|
hv.hashFile(pathname)
|
|
pyc.mergeWith(hv)
|
|
|
|
def queueRequestAvatarInfo(self, avId):
|
|
pass
|
|
|
|
def identifyFriend(self, doId):
|
|
pass
|
|
|
|
def identifyPlayer(self, playerId):
|
|
pass
|
|
|
|
def identifyAvatar(self, doId):
|
|
info = self.doId2do.get(doId)
|
|
if info:
|
|
return info
|
|
else:
|
|
info = self.identifyFriend(doId)
|
|
return info
|
|
|
|
def sendDisconnect(self):
|
|
if self.isConnected():
|
|
datagram = PyDatagram()
|
|
datagram.addUint16(CLIENT_DISCONNECT)
|
|
self.send(datagram)
|
|
self.notify.info('Sent disconnect message to server')
|
|
self.disconnect()
|
|
self.stopHeartbeat()
|
|
|
|
def _isPlayerDclass(self, dclass):
|
|
return False
|
|
|
|
def _isValidPlayerLocation(self, parentId, zoneId):
|
|
return True
|
|
|
|
def _isInvalidPlayerAvatarGenerate(self, doId, dclass, parentId, zoneId):
|
|
if self._isPlayerDclass(dclass):
|
|
if not self._isValidPlayerLocation(parentId, zoneId):
|
|
base.cr.centralLogger.writeClientEvent('got generate for player avatar %s in invalid location (%s, %s)' % (doId, parentId, zoneId))
|
|
return True
|
|
return False
|
|
|
|
if not __astron__:
|
|
def handleGenerateWithRequired(self, di):
|
|
parentId = di.getUint32()
|
|
zoneId = di.getUint32()
|
|
classId = di.getUint16()
|
|
doId = di.getUint32()
|
|
dclass = self.dclassesByNumber[classId]
|
|
if self._isInvalidPlayerAvatarGenerate(doId, dclass, parentId, zoneId):
|
|
return
|
|
dclass.startGenerate()
|
|
distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId)
|
|
dclass.stopGenerate()
|
|
|
|
def handleGenerateWithRequiredOther(self, di):
|
|
parentId = di.getUint32()
|
|
zoneId = di.getUint32()
|
|
classId = di.getUint16()
|
|
doId = di.getUint32()
|
|
dclass = self.dclassesByNumber[classId]
|
|
if self._isInvalidPlayerAvatarGenerate(doId, dclass, parentId, zoneId):
|
|
return
|
|
deferrable = getattr(dclass.getClassDef(), 'deferrable', False)
|
|
if not self.deferInterval or self.noDefer:
|
|
deferrable = False
|
|
now = globalClock.getFrameTime()
|
|
if self.deferredGenerates or deferrable:
|
|
if self.deferredGenerates or now - self.lastGenerate < self.deferInterval:
|
|
self.deferredGenerates.append((CLIENT_CREATE_OBJECT_REQUIRED_OTHER, doId))
|
|
dg = Datagram(di.getDatagram())
|
|
di = DatagramIterator(dg, di.getCurrentIndex())
|
|
self.deferredDoIds[doId] = ((parentId,
|
|
zoneId,
|
|
classId,
|
|
doId,
|
|
di),
|
|
deferrable,
|
|
dg,
|
|
[])
|
|
if len(self.deferredGenerates) == 1:
|
|
taskMgr.remove('deferredGenerate')
|
|
taskMgr.doMethodLater(self.deferInterval, self.doDeferredGenerate, 'deferredGenerate')
|
|
else:
|
|
self.lastGenerate = now
|
|
self.doGenerate(parentId, zoneId, classId, doId, di)
|
|
else:
|
|
self.doGenerate(parentId, zoneId, classId, doId, di)
|
|
|
|
def handleGenerateWithRequiredOtherOwner(self, di):
|
|
classId = di.getUint16()
|
|
doId = di.getUint32()
|
|
parentId = di.getUint32()
|
|
zoneId = di.getUint32()
|
|
dclass = self.dclassesByNumber[classId]
|
|
dclass.startGenerate()
|
|
distObj = self.generateWithRequiredOtherFieldsOwner(dclass, doId, di)
|
|
dclass.stopGenerate()
|
|
|
|
def handleQuietZoneGenerateWithRequired(self, di):
|
|
parentId = di.getUint32()
|
|
zoneId = di.getUint32()
|
|
classId = di.getUint16()
|
|
doId = di.getUint32()
|
|
dclass = self.dclassesByNumber[classId]
|
|
dclass.startGenerate()
|
|
distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId)
|
|
dclass.stopGenerate()
|
|
|
|
def handleQuietZoneGenerateWithRequiredOther(self, di):
|
|
parentId = di.getUint32()
|
|
zoneId = di.getUint32()
|
|
classId = di.getUint16()
|
|
doId = di.getUint32()
|
|
dclass = self.dclassesByNumber[classId]
|
|
dclass.startGenerate()
|
|
distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
|
|
dclass.stopGenerate()
|
|
else:
|
|
def handleGenerateWithRequired(self, di, other=False):
|
|
doId = di.getUint32()
|
|
parentId = di.getUint32()
|
|
zoneId = di.getUint32()
|
|
classId = di.getUint16()
|
|
|
|
# Determine whether or not we should add this generate
|
|
# to the pending generates, or just generate it right away.
|
|
for handle, interest in list(self._interests.items()):
|
|
if parentId != interest.parentId:
|
|
continue
|
|
|
|
if isinstance(interest.zoneIdList, list):
|
|
if zoneId not in interest.zoneIdList:
|
|
continue
|
|
else:
|
|
if zoneId != interest.zoneIdList:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
self.notify.warning('Received generate for %d from %d:%d, which is not a part of any existing interests!' % (doId, parentId, zoneId))
|
|
interest = None
|
|
|
|
if not interest or not interest.events:
|
|
# Generate this object right away.
|
|
return self.__generateObject(doId, parentId, zoneId, classId, di, other)
|
|
|
|
# Wait on the interest events to complete before generating.
|
|
pending = self.__pendingGenerates.setdefault(handle, [])
|
|
pending.append((doId, parentId, zoneId, classId, Datagram(di.getDatagram()), other))
|
|
self.__doId2pendingInterest[doId] = handle
|
|
|
|
def __playBackGenerates(self, handle):
|
|
if handle not in self.__pendingGenerates:
|
|
# Nothing to play back!
|
|
return
|
|
|
|
# We will now play back this interest's pending generates.
|
|
# First, get a copy of the pending generates, and remove
|
|
# them from self.__pendingGenerates:
|
|
generates = self.__pendingGenerates[handle]
|
|
del self.__pendingGenerates[handle]
|
|
|
|
# Sort generates by classId:
|
|
generates.sort(key=lambda x: x[3])
|
|
|
|
# Generate the objects:
|
|
for doId, parentId, zoneId, classId, dg, other in generates:
|
|
# Set up a DatagramIterator using Datagram dg:
|
|
di = DatagramIterator(dg)
|
|
|
|
# Skip 16 bytes to move past the header.
|
|
# For the record: MsgType(2), zoneId, doId, parentId (3x4), classId (2)
|
|
di.skipBytes(16)
|
|
|
|
# Generate the object:
|
|
self.__generateObject(doId, parentId, zoneId, classId, di, other)
|
|
|
|
# Delete this object's doId from
|
|
# __doId2pendingInterest if it exists:
|
|
if doId in self.__doId2pendingInterest:
|
|
del self.__doId2pendingInterest[doId]
|
|
|
|
# Now that we have generated the object, if there
|
|
# are any messages to play back, do so now:
|
|
self.__playBackMessages(handle)
|
|
|
|
def __playBackMessages(self, handle):
|
|
if handle not in self.__pendingMessages:
|
|
# Nothing to play back!
|
|
return
|
|
|
|
# We will now play back any pending messages.
|
|
# First, loop through all the Datagram instances
|
|
# in __pendingMessages:
|
|
for dg in self.__pendingMessages[handle]:
|
|
# Set up a DatagramIterator using Datagram dg:
|
|
di = DatagramIterator(dg)
|
|
|
|
# Get the msgType:
|
|
msgType = di.getUint16()
|
|
|
|
# If self.handler is set, use that. Otherwise,
|
|
# use self.handleMessageType:
|
|
if self.handler:
|
|
self.handler(msgType, di)
|
|
else:
|
|
self.handleMessageType(msgType, di)
|
|
|
|
# We can now remove the handle from __pendingMessages:
|
|
del self.__pendingMessages[handle]
|
|
|
|
def __recordObjectMessage(self, msgType, di):
|
|
if msgType not in (CLIENT_OBJECT_SET_FIELD, CLIENT_OBJECT_LEAVING, CLIENT_OBJECT_LOCATION):
|
|
return False
|
|
|
|
di2 = DatagramIterator(di.getDatagram(), di.getCurrentIndex())
|
|
doId = di2.getUint32()
|
|
if doId not in self.__doId2pendingInterest:
|
|
return False
|
|
|
|
pending = self.__pendingMessages.setdefault(self.__doId2pendingInterest[doId], [])
|
|
pending.append(Datagram(di.getDatagram()))
|
|
return True
|
|
|
|
def __generateObject(self, doId, parentId, zoneId, classId, di, other):
|
|
# Get our dclass:
|
|
dclass = self.dclassesByNumber[classId]
|
|
|
|
# Is this an invalid player avatar generate?
|
|
if self._isInvalidPlayerAvatarGenerate(doId, dclass, parentId, zoneId):
|
|
# Yup, ignore this.
|
|
return
|
|
|
|
# Start the generation process:
|
|
dclass.startGenerate()
|
|
|
|
# Is this an other generate?
|
|
if other:
|
|
# Yup. In this case, we'll use generateWithRequiredOtherFields.
|
|
distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
|
|
else:
|
|
# Nah. In this case, we'll use generateWithRequiredFields.
|
|
distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId)
|
|
|
|
# We're done.
|
|
dclass.stopGenerate()
|
|
|
|
def handleGenerateWithRequiredOtherOwner(self, di):
|
|
doId = di.getUint32()
|
|
parentId = di.getUint32()
|
|
zoneId = di.getUint32()
|
|
classId = di.getUint16()
|
|
dclass = self.dclassesByNumber[classId]
|
|
dclass.startGenerate()
|
|
distObj = self.generateWithRequiredOtherFieldsOwner(dclass, doId, di)
|
|
dclass.stopGenerate()
|
|
|
|
def handleQuietZoneGenerateWithRequired(self, di):
|
|
doId = di.getUint32()
|
|
parentId = di.getUint32()
|
|
zoneId = di.getUint32()
|
|
classId = di.getUint16()
|
|
dclass = self.dclassesByNumber[classId]
|
|
dclass.startGenerate()
|
|
distObj = self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId)
|
|
dclass.stopGenerate()
|
|
|
|
def handleQuietZoneGenerateWithRequiredOther(self, di):
|
|
doId = di.getUint32()
|
|
parentId = di.getUint32()
|
|
zoneId = di.getUint32()
|
|
classId = di.getUint16()
|
|
dclass = self.dclassesByNumber[classId]
|
|
dclass.startGenerate()
|
|
distObj = self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
|
|
dclass.stopGenerate()
|
|
|
|
def handleDisable(self, di, ownerView = False):
|
|
doId = di.getUint32()
|
|
if not self.isLocalId(doId):
|
|
self.disableDoId(doId, ownerView)
|
|
|
|
def sendSetLocation(self, doId, parentId, zoneId):
|
|
datagram = PyDatagram()
|
|
datagram.addUint16(CLIENT_OBJECT_LOCATION)
|
|
datagram.addUint32(doId)
|
|
datagram.addUint32(parentId)
|
|
datagram.addUint32(zoneId)
|
|
self.send(datagram)
|
|
|
|
def sendHeartbeat(self):
|
|
datagram = PyDatagram()
|
|
datagram.addUint16(CLIENT_HEARTBEAT)
|
|
self.send(datagram)
|
|
self.lastHeartbeat = globalClock.getRealTime()
|
|
self.considerFlush()
|
|
|
|
def isLocalId(self, id):
|
|
try:
|
|
return localAvatar.doId == id
|
|
except:
|
|
self.notify.debug('In isLocalId(), localAvatar not created yet')
|
|
return False
|
|
|
|
ITAG_PERM = 'perm'
|
|
ITAG_AVATAR = 'avatar'
|
|
ITAG_SHARD = 'shard'
|
|
ITAG_WORLD = 'world'
|
|
ITAG_GAME = 'game'
|
|
|
|
def addTaggedInterest(self, parentId, zoneId, mainTag, desc, otherTags = [], event = None):
|
|
return self.addInterest(parentId, zoneId, desc, event)
|