Poodletooth-iLand/otp/distributed/OTPClientRepository.py
2015-04-26 19:06:49 +03:00

1725 lines
66 KiB
Python

from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.distributed import DistributedSmoothNode
from direct.distributed.ClientRepositoryBase import ClientRepositoryBase
from direct.distributed.MsgTypes import *
from direct.distributed.PyDatagram import PyDatagram
from direct.distributed.PyDatagramIterator import PyDatagramIterator
from direct.fsm.ClassicFSM import ClassicFSM
from direct.fsm.State import State
from direct.gui.DirectGui import *
from direct.interval.IntervalGlobal import ivalMgr
from direct.showbase import LeakDetectors
from direct.showbase import MessengerLeakDetector
from direct.showbase import PythonUtil, GarbageReport, BulletinBoardWatcher
from direct.showbase.ContainerLeakDetector import ContainerLeakDetector
from direct.showbase.GarbageReportScheduler import GarbageReportScheduler
from direct.task import Task
import gc
import os
from pandac.PandaModules import *
import random
import string
import sys
import time
import types
from otp.avatar import Avatar
from otp.avatar import DistributedAvatar
from otp.avatar.DistributedPlayer import DistributedPlayer
from otp.distributed import DCClassImports
from otp.distributed import OtpDoGlobals
from otp.distributed.OtpDoGlobals import *
from otp.distributed.TelemetryLimiter import TelemetryLimiter
from otp.otpbase import OTPGlobals
from otp.otpbase import OTPLocalizer
from otp.otpgui import OTPDialog
from toontown.chat.ChatGlobals import *
class OTPClientRepository(ClientRepositoryBase):
notify = directNotify.newCategory('OTPClientRepository')
avatarLimit = 6
WishNameResult = Enum(['Failure',
'PendingApproval',
'Approved',
'Rejected'])
def __init__(self, serverVersion, playGame = None):
ClientRepositoryBase.__init__(self)
self.handler = None
self.__currentAvId = 0
self.createAvatarClass = None
self.systemMessageSfx = None
self.playToken = launcher.getPlayToken()
self.wantMagicWords = False
self.userSignature = base.config.GetString('signature', 'none')
self.parentMgr.registerParent(OTPGlobals.SPRender, base.render)
self.parentMgr.registerParent(OTPGlobals.SPHidden, NodePath())
self.timeManager = None
if config.GetBool('detect-leaks', 0) or config.GetBool('client-detect-leaks', 0):
self.startLeakDetector()
if config.GetBool('detect-messenger-leaks', 0) or config.GetBool('ai-detect-messenger-leaks', 0):
self.messengerLeakDetector = MessengerLeakDetector.MessengerLeakDetector('client messenger leak detector')
if config.GetBool('leak-messages', 0):
MessengerLeakDetector._leakMessengerObject()
if config.GetBool('run-garbage-reports', 0) or config.GetBool('client-run-garbage-reports', 0):
noneValue = -1.0
reportWait = config.GetFloat('garbage-report-wait', noneValue)
reportWaitScale = config.GetFloat('garbage-report-wait-scale', noneValue)
if reportWait == noneValue:
reportWait = 60.0 * 2.0
if reportWaitScale == noneValue:
reportWaitScale = None
self.garbageReportScheduler = GarbageReportScheduler(waitBetween=reportWait,
waitScale=reportWaitScale)
self._proactiveLeakChecks = config.GetBool('proactive-leak-checks', 1) or config.GetBool('client-proactive-leak-checks', 1)
self._crashOnProactiveLeakDetect = config.GetBool('crash-on-proactive-leak-detect', 1)
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, [
'noConnection',
'login',
'failedToConnect',
'failedToGetServerConstants']),
State('login',
self.enterLogin,
self.exitLogin, [
'noConnection',
'waitForGameList',
'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('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',
'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.music = None
self.gameDoneEvent = 'playGameDone'
self.playGame = playGame(self.gameFSM, self.gameDoneEvent)
self.shardListHandle = None
self.uberZoneInterest = None
self.__pendingGenerates = {}
self.__pendingMessages = {}
self.__doId2pendingInterest = {}
self.chatAgent = self.generateGlobalObject(OtpDoGlobals.OTP_DO_ID_CHAT_MANAGER, 'ChatAgent')
self.csm = None # To be set by subclass.
def hasPlayToken():
return self.playToken != None
def readDCFile(self, dcFileNames=None):
dcFile = self.getDcFile()
dcFile.clear()
self.dclassesByName = {}
self.dclassesByNumber = {}
self.hashVal = 0
try:
dcStream
except:
pass
else:
self.notify.info('Detected DC file stream, reading it...')
dcFileNames = [dcStream]
if isinstance(dcFileNames, str):
dcFileNames = [dcFileNames]
if dcFileNames is not None:
for dcFileName in dcFileNames:
if isinstance(dcFileName, StringStream):
readResult = dcFile.read(dcFileName, 'DC stream')
else:
readResult = dcFile.read(dcFileName)
if not readResult:
self.notify.error('Could not read DC file.')
else:
dcFile.readAll()
self.hashVal = DCClassImports.hashVal
for i in xrange(dcFile.getNumClasses()):
dclass = dcFile.getClass(i)
number = dclass.getNumber()
className = dclass.getName()
classDef = DCClassImports.dcImports.get(className)
if classDef is None:
self.notify.debug('No class definition for %s.' % className)
else:
if type(classDef) == types.ModuleType:
if not hasattr(classDef, className):
self.notify.warning('Module %s does not define class %s.' % (className, className))
continue
classDef = getattr(classDef, className)
if (type(classDef) != types.ClassType) and (type(classDef) != types.TypeType):
self.notify.error('Symbol %s is not a class name.' % className)
else:
dclass.setClassDef(classDef)
self.dclassesByName[className] = dclass
if number >= 0:
self.dclassesByNumber[number] = dclass
def startLeakDetector(self):
if hasattr(self, 'leakDetector'):
return False
firstCheckDelay = config.GetFloat('leak-detector-first-check-delay', 2 * 60.0)
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.handleConnecting
self.connect(self.serverList, successCallback=self._sendHello, failureCallback=self.failedToConnect)
def _sendHello(self):
datagram = PyDatagram()
datagram.addUint16(CLIENT_HELLO)
datagram.addUint32(self.hashVal)
datagram.addString(self.serverVersion)
self.send(datagram)
def handleConnecting(self, msgtype, di):
if msgtype == CLIENT_HELLO_RESP:
self._handleConnected()
else:
self.handleMessageType(msgtype, di)
@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(), WTSystem)
whisper.manage(base.marginManager)
if not self.systemMessageSfx:
self.systemMessageSfx = base.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):
launcher.setDisconnectDetailsNormal()
messenger.send(self.getConnectedEvent())
self.gotoFirstScreen()
def gotoFirstScreen(self):
self.startReaderPollTask()
#self.startHeartbeat()
self.loginFSM.request('login')
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
def enterLogin(self):
self.sendSetAvatarIdMsg(0)
self.loginDoneEvent = 'loginDone'
self.accept(self.loginDoneEvent, self.__handleLoginDone)
self.csm.performLogin(self.loginDoneEvent)
self.waitForDatabaseTimeout(requestName='WaitOnCSMLoginResponse')
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
def __handleLoginDone(self, doneStatus):
mode = doneStatus['mode']
if mode == 'success':
if hasattr(self, 'toontownTimeManager'):
timestamp = time.gmtime(doneStatus['timestamp'])
dateString = time.strftime(self.toontownTimeManager.formatStr, timestamp)
self.lastLoggedIn = self.toontownTimeManager.convertStrToToontownTime(dateString)
self.loginFSM.request('waitForGameList')
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 ClientServicesManager: ' + str(mode))
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
def exitLogin(self):
self.cleanupWaitingForDatabase()
self.ignore(self.loginDoneEvent)
del self.loginDoneEvent
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')
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():
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):
maxPop = config.GetInt('shard-mid-pop', 300)
for shard in self.activeDistrictMap.values():
if shard.available:
if shard.avatarCount < maxPop:
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')
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')
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()
if (self.bootedIndex is not None) and (self.bootedIndex in OTPLocalizer.CRBootedReasons):
message = OTPLocalizer.CRBootedReasons[self.bootedIndex]
elif self.bootedIndex == 155:
message = self.bootedText
elif self.bootedText is not None:
message = OTPLocalizer.CRBootedReasonUnknownCode % self.bootedIndex
else:
message = OTPLocalizer.CRLostConnection
reconnect = 1
if self.bootedIndex in (152, 127):
reconnect = 0
if self.bootedIndex == 152:
message = message % {'name': self.bootedText}
launcher.setDisconnectDetails(self.bootedIndex, message)
style = OTPDialog.Acknowledge
if reconnect:
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':
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 enterWaitForAvatarList(self):
self._requestAvatarList()
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
def _requestAvatarList(self):
self.csm.requestAvatars()
self.waitForDatabaseTimeout(requestName='WaitForAvatarList')
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
def exitWaitForAvatarList(self):
self.cleanupWaitingForDatabase()
self.handler = None
return
def handleAvatarsList(self, avatars):
self.avList = avatars
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 enterWaitForDeleteAvatarResponse(self, potAv):
self.csm.sendDeleteAvatar(potAv.id)
self.waitForDatabaseTimeout(requestName='WaitForDeleteAvatarResponse')
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
def exitWaitForDeleteAvatarResponse(self):
self.cleanupWaitingForDatabase()
@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):
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
self.csm.sendChooseAvatar(avId)
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
def handleAvatarResponseMsg(self, avatarId, di):
pass
@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 True:
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 __debug__ and not PythonUtil.configIsToday('temp-disable-leak-detection'):
logFunc = self.notify.error
allowExit = False
else:
logFunc = self.notify.warning
allowExit = False
if base.config.GetBool('direct-gui-edit', 0):
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',
'slowCloseShardCallback',
'tkLoop',
'manager-update',
'downloadStallTask',
'clientSleep',
jobMgr.TaskName,
self.GarbageCollectTaskName,
'garbageCollectStates',
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-fade',
'f9',
'meta-q',
'meta-q-repeat',
'meta-m',
'meta-q-repeat',
'meta-h',
'meta-h-repeat',
'control-f9',
'newDistributedDirectory',
'page_down',
'page_up',
'panda3d-render-error',
'PandaPaused',
'PandaRestarted',
'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.im_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 xrange(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
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 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)
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 = 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.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
@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)
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])
return
if base.config.GetBool('force-tutorial', 0):
self.gameFSM.request('tutorialQuestion', [hoodId, zoneId, avId])
return
else:
if hasattr(self, 'skipTutorialRequest') and self.skipTutorialRequest:
self.skipTutorialRequest = None
self.gameFSM.request('skipTutorialRequest', [hoodId, zoneId, avId])
return
else:
self.gameFSM.request('tutorialQuestion', [hoodId, zoneId, avId])
return
self.gameFSM.request('playGame', [hoodId, zoneId, avId])
def handlePlayGame(self, msgType, di):
if self.notify.getDebug():
self.notify.debug('handle play game got message type: ' + `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:
# TODO: Properly fix this:
try:
self.handleUpdateField(di)
except:
pass
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 getStartingDistrict(self):
district = None
if len(self.activeDistrictMap.keys()) == 0:
self.notify.info('no shards')
return
maxPop = config.GetInt('shard-mid-pop', 300)
# Join the least populated district.
for shard in self.activeDistrictMap.values():
if district:
if shard.avatarCount < district.avatarCount and shard.available:
if shard.avatarCount < maxPop:
district = shard
else:
if shard.available:
if shard.avatarCount < maxPop:
district = shard
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):
list = []
for s in self.activeDistrictMap.values():
if s.available:
list.append((s.doId, s.name, s.avatarCount, s.newAvatarCount,
s.invasionStatus))
return list
def getPlayerAvatars(self):
return [i for i in 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(0, 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 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:
di2 = DatagramIterator(di.getDatagram(), di.getCurrentIndex())
di2.getUint32() # Ignore the context.
handle = di2.getUint16()
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(ostream)
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 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):
return True
return False
def handleGenerateWithRequired(self, di, other=False):
doId = di.getUint32()
parentId = di.getUint32()
zoneId = di.getUint32()
classId = di.getUint16()
# Decide whether we should add this to the interest's pending
# generates, or process it right away:
for handle, interest in 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:
interest = None
if (not interest) or (not interest.events):
# This object can be generated right away:
return self.__doGenerate(doId, parentId, zoneId, classId, di, other)
# This object must be generated when the operation completes:
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:
return
# This interest has pending generates! Play them:
generates = self.__pendingGenerates[handle]
del self.__pendingGenerates[handle]
generates.sort(key=lambda i: i[3]) # Sort by classId.
for doId, parentId, zoneId, classId, dg, other in generates:
di = DatagramIterator(dg)
di.skipBytes(16)
self.__doGenerate(doId, parentId, zoneId, classId, di, other)
if doId in self.__doId2pendingInterest:
del self.__doId2pendingInterest[doId]
# Also play back any messages we missed:
self.__playBackMessages(handle)
def __playBackMessages(self, handle):
if handle not in self.__pendingMessages:
return
# Any pending messages? Play them:
for dg in self.__pendingMessages[handle]:
di = DatagramIterator(dg)
msgType = di.getUint16()
if self.handler is None:
self.handleMessageType(msgType, di)
else:
self.handler(msgType, di)
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 __doGenerate(self, doId, parentId, zoneId, classId, di, other):
dclass = self.dclassesByNumber[classId]
if self._isInvalidPlayerAvatarGenerate(doId, dclass, parentId, zoneId):
return
dclass.startGenerate()
if other:
self.generateWithRequiredOtherFields(dclass, doId, di, parentId, zoneId)
else:
self.generateWithRequiredFields(dclass, doId, di, parentId, zoneId)
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)
if doId == self.__currentAvId:
self.bootedIndex = 153
self.bootedText = ''
self.notify.warning("Avatar deleted! Closing connection...")
# disconnect now, don't wait for send/recv to fail
self.stopReaderPollTask()
self.lostConnection()
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)