Poodletooth-iLand/otp/distributed/OTPClientRepository.py
2015-07-05 19:59:25 -04:00

1410 lines
54 KiB
Python
Executable file

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.task import Task
from panda3d.core import *
from otp.avatar import Avatar, DistributedAvatar
from otp.avatar.DistributedPlayer import DistributedPlayer
from otp.distributed import OtpDoGlobals
from otp.distributed.OtpDoGlobals import *
from otp.distributed.TelemetryLimiter import TelemetryLimiter
from otp.otpbase import OTPGlobals, OTPLocalizer
from otp.otpgui import OTPDialog
from otp.nametag.NametagConstants import *
import sys, time, types, random
class OTPClientRepository(ClientRepositoryBase):
notify = directNotify.newCategory('OTPClientRepository')
avatarLimit = 6
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.parentMgr.registerParent(OTPGlobals.SPRender, base.render)
self.parentMgr.registerParent(OTPGlobals.SPHidden, NodePath())
self.timeManager = None
self._proactiveLeakChecks = 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']),
State('login',
self.enterLogin,
self.exitLogin, [
'noConnection',
'waitForGameList',
'reject',
'failedToConnect',
'shutdown']),
State('failedToConnect',
self.enterFailedToConnect,
self.exitFailedToConnect, [
'connect',
'shutdown']),
State('shutdown',
self.enterShutdown,
self.exitShutdown, [
'loginOff']),
State('waitForGameList',
self.enterWaitForGameList,
self.exitWaitForGameList, [
'noConnection',
'waitForShardList']),
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
if isinstance(dcFileNames, types.StringTypes):
# If we were given a single string, make it a list.
dcFileNames = [dcFileNames]
dcImports = {}
if dcFileNames == None:
try:
# For Nirai
readResult = dcFile.read(dcStream, '__dc__')
del __builtin__.dcStream
except NameError:
readResult = dcFile.readAll()
if not readResult:
self.notify.error("Could not read dc file.")
else:
searchPath = getModelPath().getValue()
for dcFileName in dcFileNames:
pathname = Filename(dcFileName)
vfs.resolveFilename(pathname, searchPath)
readResult = dcFile.read(pathname)
if not readResult:
self.notify.error("Could not read dc file: %s" % (pathname))
self.hashVal = dcFile.getHash()
# Now import all of the modules required by the DC file.
for n in range(dcFile.getNumImportModules()):
moduleName = dcFile.getImportModule(n)[:]
# Maybe the module name is represented as "moduleName/AI".
suffix = moduleName.split('/')
moduleName = suffix[0]
suffix=suffix[1:]
if self.dcSuffix in suffix:
moduleName += self.dcSuffix
elif self.dcSuffix == 'UD' and 'AI' in suffix: #HACK:
moduleName += 'AI'
importSymbols = []
for i in range(dcFile.getNumImportSymbols(n)):
symbolName = dcFile.getImportSymbol(n, i)
# Maybe the symbol name is represented as "symbolName/AI".
suffix = symbolName.split('/')
symbolName = suffix[0]
suffix=suffix[1:]
if self.dcSuffix in suffix:
symbolName += self.dcSuffix
elif self.dcSuffix == 'UD' and 'AI' in suffix: #HACK:
symbolName += 'AI'
importSymbols.append(symbolName)
self.importModule(dcImports, moduleName, importSymbols)
# Now get the class definition for the classes named in the DC
# file.
for i in range(dcFile.getNumClasses()):
dclass = dcFile.getClass(i)
number = dclass.getNumber()
className = dclass.getName() + self.dcSuffix
# Does the class have a definition defined in the newly
# imported namespace?
classDef = dcImports.get(className)
if classDef is None and self.dcSuffix == 'UD': #HACK:
className = dclass.getName() + 'AI'
classDef = dcImports.get(className)
# Also try it without the dcSuffix.
if classDef == None:
className = dclass.getName()
classDef = 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
# Owner Views
if self.hasOwnerView():
ownerDcSuffix = self.dcSuffix + 'OV'
# dict of class names (without 'OV') that have owner views
ownerImportSymbols = {}
# Now import all of the modules required by the DC file.
for n in range(dcFile.getNumImportModules()):
moduleName = dcFile.getImportModule(n)
# Maybe the module name is represented as "moduleName/AI".
suffix = moduleName.split('/')
moduleName = suffix[0]
suffix=suffix[1:]
if ownerDcSuffix in suffix:
moduleName = moduleName + ownerDcSuffix
importSymbols = []
for i in range(dcFile.getNumImportSymbols(n)):
symbolName = dcFile.getImportSymbol(n, i)
# Check for the OV suffix
suffix = symbolName.split('/')
symbolName = suffix[0]
suffix=suffix[1:]
if ownerDcSuffix in suffix:
symbolName += ownerDcSuffix
importSymbols.append(symbolName)
ownerImportSymbols[symbolName] = None
self.importModule(dcImports, moduleName, importSymbols)
# Now get the class definition for the owner classes named
# in the DC file.
for i in range(dcFile.getNumClasses()):
dclass = dcFile.getClass(i)
if ((dclass.getName()+ownerDcSuffix) in ownerImportSymbols):
number = dclass.getNumber()
className = dclass.getName() + ownerDcSuffix
# Does the class have a definition defined in the newly
# imported namespace?
classDef = dcImports.get(className)
if classDef is None:
self.notify.error("No class definition for %s." % className)
else:
if type(classDef) == types.ModuleType:
if not hasattr(classDef, className):
self.notify.error("Module %s does not define class %s." % (className, className))
classDef = getattr(classDef, className)
dclass.setOwnerClassDef(classDef)
self.dclassesByName[className] = dclass
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':
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))
dialogClass = OTPGlobals.getGlobalDialogClass()
self.failedToConnectBox = dialogClass(message=OTPLocalizer.CRNoConnectTryAgain % (url.getServer(), url.getPort()), doneEvent='failedToConnectAck', text_wordwrap=18, style=OTPDialog.TwoChoice)
self.failedToConnectBox.show()
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 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):
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.loginFSM.request, ['waitForShardList'])
@report(types=['args', 'deltaStamp'], dConfigParam='teleport')
def exitWaitForGameList(self):
self.handler = None
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')
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()
self.removeShardInterest(Functor(self.loginFSM.request, self._closeShardLoginState))
@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()
callback()
@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.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 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):
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 identifyFriend(self, doId):
return self.identifyAvatar(doId)
def identifyAvatar(self, doId):
info = self.doId2do.get(doId)
return info if info else self.identifyFriend(doId)
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)