コード例 #1
0
    def __init__(self):
        dcFileNames = ['direct.dc', 'ralph.dc']

        AstronClientRepository.__init__(self, dcFileNames)
        FSM.__init__(self, 'RalphClientRepository')

        self.GameGlobalsId = GAME_GLOBALS_ID
        # Generate the LoginManager staticly, so it'll be always available.
        self.loginManager = self.generateGlobalObject(LOGIN_MANAGER_DO_ID,
                                                      'LoginManager')

        self.worldsInterest = None
        self.managerInterest = None
        self.areaInterest = None

        self.worlds = []
        self.itemId2worldId = {}

        # Set the background color to black
        base.win.setClearColor((0, 0, 0, 1))

        # HACK: Handlers for object generation with OTHER fields is missing from AstronClientRepository
        # so we'll add one of them here until the PR which adds the functions to ACR has been merged.
        # https://github.com/Astron/panda3d/pull/17/
        self.message_handlers[
            CLIENT_ENTER_OBJECT_REQUIRED_OTHER] = self.handleEnterObjectRequiredOther
コード例 #2
0
 def handleEnterObjectRequiredOwner(self, di):
     if self.loginFSM.getCurrentState().getName(
     ) == 'waitForSetAvatarResponse':
         doId = di.getUint32()
         parentId = di.getUint32()
         zoneId = di.getUint32()
         dclassId = di.getUint16()
         self.__handleSetAvatarResponse(doId, di)
     else:
         AstronClientRepository.handleEnterObjectRequiredOwner(self, di)
コード例 #3
0
 def handleQuietZoneUpdateField(self, di):
     di2 = DatagramIterator(di)
     doId = di2.getUint32()
     if doId in self.deferredDoIds:
         args, deferrable, dg0, updates = self.deferredDoIds[doId]
         dclass = args[2]
         if not dclass.getClassDef().neverDisable:
             return
     else:
         do = self.getDo(doId)
         if do:
             if not do.neverDisable:
                 return
     AstronClientRepository.handleUpdateField(self, di)
コード例 #4
0
 def __init__(self):
     # Basics
     ShowBase.__init__(self)
     base.disableMouse()
     self.accept("escape", self.disconnect)
     base.camera.set_pos(0, 0, 60)
     base.camera.look_at(0, 0, 0)
     # Game-relevant attributes
     self.has_avatar = False
     self.avatar_owner_view = False
     # Avatar controls
     # FIXME: These values will be off the kilter if keys are pressed when the client starts.
     self.movement_heading = 0
     self.movement_speed = 0
     self.accept("avatar", self.get_avatar)
     self.accept("distributed_avatar", self.get_distributed_avatar)
     self.accept("arrow_up", self.indicate_movement, [0, 1])
     self.accept("arrow_up-up", self.indicate_movement, [0, -1])
     self.accept("arrow_down", self.indicate_movement, [0, -1])
     self.accept("arrow_down-up", self.indicate_movement, [0, 1])
     self.accept("arrow_left", self.indicate_movement, [1, 0])
     self.accept("arrow_left-up", self.indicate_movement, [-1, 0])
     self.accept("arrow_right", self.indicate_movement, [-1, 0])
     self.accept("arrow_right-up", self.indicate_movement, [1, 0])
     # Create repository
     # FIXME: For some freaky reason, using the default method
     # (CM_HTTP) won't result in further callbacks being called
     # back. Does connection() fail?
     self.repo = AstronClientRepository(
         dcFileNames=["simple_example.dc"],
         connectMethod=AstronClientRepository.CM_NET)
     # Callback events. These names are "magic" (defined in AstronClientRepository)
     self.accept("CLIENT_HELLO_RESP", self.client_is_handshaked)
     self.accept("CLIENT_EJECT", self.ejected)
     self.accept("CLIENT_OBJECT_LEAVING", self.avatar_leaves)
     self.accept("CLIENT_OBJECT_LEAVING_OWNER", self.avatar_leaves_owner)
     self.accept("LOST_CONNECTION", self.lost_connection)
     # Connecting
     url = URLSpec()
     url.setServer("127.0.0.1")
     url.setPort(6667)
     # FIXME: No idea why this doesn't work instead...
     # url = URLSpec("127.0.0.1", 6667)
     self.notify.debug("Connecting...")
     self.repo.connect([url],
                       successCallback=self.connection_success,
                       failureCallback=self.connection_failure)
コード例 #5
0
    def __init__(self):
        dcFileNames = ['direct.dc', 'ralph.dc']

        AstronClientRepository.__init__(self, dcFileNames)
        FSM.__init__(self, 'RalphClientRepository')

        self.GameGlobalsId = GAME_GLOBALS_ID
        # Generate the LoginManager staticly, so it'll be always available.
        self.loginManager = self.generateGlobalObject(LOGIN_MANAGER_DO_ID, 'LoginManager')

        self.worldsInterest = None
        self.managerInterest = None
        self.areaInterest = None

        self.worlds = []
        self.itemId2worldId = {}

        # Set the background color to black
        base.win.setClearColor((0, 0, 0, 1))

        # HACK: Handlers for object generation with OTHER fields is missing from AstronClientRepository
        # so we'll add one of them here until the PR which adds the functions to ACR has been merged.
        # https://github.com/Astron/panda3d/pull/17/
        self.message_handlers[CLIENT_ENTER_OBJECT_REQUIRED_OTHER] = self.handleEnterObjectRequiredOther
コード例 #6
0
 def __init__(self):
     # Basics
     ShowBase.__init__(self)
     base.disableMouse()
     self.accept("escape", self.disconnect)
     base.camera.set_pos(0, 0, 60)
     base.camera.look_at(0, 0, 0)
     # Game-relevant attributes
     self.has_avatar = False
     self.avatar_owner_view = False
     # Avatar controls
     # FIXME: These values will be off the kilter if keys are pressed when the client starts.
     self.movement_heading = 0
     self.movement_speed = 0
     self.accept("avatar", self.get_avatar)
     self.accept("distributed_avatar", self.get_distributed_avatar)
     self.accept("arrow_up", self.indicate_movement, [0, 1])
     self.accept("arrow_up-up", self.indicate_movement, [0, -1])
     self.accept("arrow_down", self.indicate_movement, [0, -1])
     self.accept("arrow_down-up", self.indicate_movement, [0, 1])
     self.accept("arrow_left", self.indicate_movement, [1, 0])
     self.accept("arrow_left-up", self.indicate_movement, [-1, 0])
     self.accept("arrow_right", self.indicate_movement, [-1, 0])
     self.accept("arrow_right-up", self.indicate_movement, [1, 0])
     # Create repository
     # FIXME: For some freaky reason, using the default method
     # (CM_HTTP) won't result in further callbacks being called
     # back. Does connection() fail?
     self.repo = AstronClientRepository(dcFileNames = ["simple_example.dc"],
                                        connectMethod = AstronClientRepository.CM_NET)
     # Callback events. These names are "magic" (defined in AstronClientRepository)
     self.accept("CLIENT_HELLO_RESP", self.client_is_handshaked)
     self.accept("CLIENT_EJECT", self.ejected)
     self.accept("CLIENT_OBJECT_LEAVING", self.avatar_leaves)
     self.accept("CLIENT_OBJECT_LEAVING_OWNER", self.avatar_leaves_owner)
     self.accept("LOST_CONNECTION", self.lost_connection)
     # Connecting
     url = URLSpec()
     url.setServer("127.0.0.1")
     url.setPort(6667)
     # FIXME: No idea why this doesn't work instead...
     # url = URLSpec("127.0.0.1", 6667)
     self.notify.debug("Connecting...")
     self.repo.connect([url],
                       successCallback = self.connection_success,
                       failureCallback = self.connection_failure)
コード例 #7
0
    def __init__(self, serverVersion):
        self.serverVersion = serverVersion
        AstronClientRepository.__init__(
            self, ['phase_3/etc/direct.dc', 'phase_3/etc/toon.dc'])
        self.loginFSM = ClassicFSM('login', [
            State('off', self.enterOff, self.exitOff),
            State('connect', self.enterConnect, self.exitConnect),
            State('disconnect', self.enterDisconnect, self.exitDisconnect),
            State('avChoose', self.enterAvChoose, self.exitAvChoose),
            State('playingGame', self.enterPlayingGame, self.exitPlayingGame),
            State('serverUnavailable', self.enterServerUnavailable,
                  self.exitServerUnavailable),
            State('makeAToon', self.enterMakeAToon, self.exitMakeAToon),
            State('submitNewToon', self.enterSubmitNewToon,
                  self.exitSubmitNewToon),
            State('noShards', self.enterNoShards, self.exitNoShards),
            State('waitForSetAvatarResponse',
                  self.enterWaitForSetAvatarResponse,
                  self.exitWaitForSetAvatarResponse),
            State('waitForShardList', self.enterWaitForShardList,
                  self.exitWaitForShardList),
            State('ejected', self.enterEjected, self.exitEjected),
            State('districtReset', self.enterDistrictReset,
                  self.exitDistrictReset),
            State('died', self.enterDied, self.exitDied),
            State('betaInform', self.enterBetaInform, self.exitBetaInform)
        ], 'off', 'off')
        self.loginFSM.enterInitialState()
        self.gameFSM = ClassicFSM('game', [
            State('off', self.enterGameOff, self.exitGameOff),
            State('waitForGameEnterResponse',
                  self.enterWaitForGameEnterResponse,
                  self.exitWaitForGameEnterResponse),
            State('playGame', self.enterPlayGame, self.exitPlayGame),
            State('closeShard', self.enterCloseShard, self.exitCloseShard),
            State('switchShards', self.enterSwitchShards,
                  self.exitSwitchShards)
        ], 'off', 'off')
        self.gameFSM.enterInitialState()
        #self.taskNameAllocator = UniqueIdAllocator(0, 1000000000)
        self.avChooser = AvChooser(self.loginFSM)
        self.playGame = PlayGame(self.gameFSM, "playGameDone")
        self.hoodMgr = HoodMgr()
        self.makeAToon = MakeAToon()
        self.loginToken = os.environ.get("LOGIN_TOKEN")
        self.serverAddress = os.environ.get("GAME_SERVER")
        self.serverURL = URLSpec("http://%s" % self.serverAddress)
        self.parentMgr.registerParent(CIGlobals.SPRender, render)
        self.parentMgr.registerParent(CIGlobals.SPHidden, hidden)
        self.adminAccess = False
        self.localAvChoice = None
        self.SuitsActive = 0
        self.BossActive = 0
        self.accServerTimesNA = 0
        self.maxAccServerTimesNA = 10
        self.setZonesEmulated = 0
        self.old_setzone_interest_handle = None
        self.setZoneQueue = Queue()
        self.accept(self.SetZoneDoneEvent, self._handleEmuSetZoneDone)
        self.handler = None
        self.__currentAvId = 0
        self.myDistrict = None
        self.activeDistricts = {}
        self.shardListHandle = None
        self.uberZoneInterest = None
        self.isShowingPlayerIds = False
        self.doBetaInform = False
        self.dTutorial = None
        self.requestedName = None
        self.whisperNoise = base.loadSfx(
            'phase_3.5/audio/sfx/GUI_whisper_3.ogg')
        self.checkHttp()
        #self.http.addPreapprovedServerCertificateFilename(self.serverURL, Filename('phase_3/etc/gameserver.crt'))
        #self.tournamentMusicChunks = {}
        #self.threadedTaskChain = taskMgr.setupTaskChain("threadedTaskChainForSoundIntervals", numThreads = 2)

        self.attackMgr = base.cl_attackMgr

        base.minigame = None

        self.newToonSlot = None

        base.finalExitCallbacks.insert(0, self.__handleExit)

        self.accountName = os.environ.get('ACCOUNT_NAME', '')
        self.csm = self.generateGlobalObject(DO_ID_CLIENT_SERVICES_MANAGER,
                                             'ClientServicesManager')
        self.friendsManager = self.generateGlobalObject(
            DO_ID_FRIENDS_MANAGER, 'FriendsManager')
        self.uin = self.generateGlobalObject(DO_ID_UNIQUE_INTEREST_NOTIFIER,
                                             'UniqueInterestNotifier')
        self.statsManager = self.generateGlobalObject(DO_ID_STATS_MANAGER,
                                                      'StatsManager')

        self.pingToggle = False
        self.currentPing = None

        self.pingText = OnscreenText("",
                                     align=TextNode.ALeft,
                                     parent=base.a2dBottomLeft,
                                     fg=(1, 1, 1, 1),
                                     shadow=(0, 0, 0, 0.5),
                                     pos=(0.3, 0.09))
        self.pingText.setBin('gsg-popup', 1000)
        self.pingText.hide()

        SpeedHackChecker.startChecking()
        self.loginFSM.request('connect')
        return
コード例 #8
0
 def handlePlayGame(self, msgType, di):
     if msgType == CLIENT_ENTER_OBJECT_REQUIRED_OTHER_OWNER:
         self.handleGenerateWithRequiredOtherOwner(msgType, di)
     else:
         AstronClientRepository.handleDatagram(self, di)
コード例 #9
0
 def handleConnected(self):
     self.notify.info("Sending CLIENT_HELLO...")
     self.acceptOnce("CLIENT_HELLO_RESP", self.handleClientHelloResp)
     self.acceptOnce("CLIENT_EJECT", self.handleEjected)
     self.acceptOnce("LOST_CONNECTION", self.handleLostConnection)
     AstronClientRepository.sendHello(self, self.serverVersion)
コード例 #10
0
 def astronHandle(self, di):
     AstronClientRepository.handleDatagram(self, di)
コード例 #11
0
 def __init__(self, music, serverVersion):
     self.music = music
     self.serverVersion = serverVersion
     AstronClientRepository.__init__(self, ['phase_3/etc/direct.dc', 'phase_3/etc/toon.dc'])
     self.loginFSM = ClassicFSM('login', [State('off', self.enterOff, self.exitOff),
      State('connect', self.enterConnect, self.exitConnect),
      State('disconnect', self.enterDisconnect, self.exitDisconnect),
      State('avChoose', self.enterAvChoose, self.exitAvChoose),
      State('playingGame', self.enterPlayingGame, self.exitPlayingGame),
      State('serverUnavailable', self.enterServerUnavailable, self.exitServerUnavailable),
      State('makeAToon', self.enterMakeAToon, self.exitMakeAToon),
      State('submitNewToon', self.enterSubmitNewToon, self.exitSubmitNewToon),
      State('noShards', self.enterNoShards, self.exitNoShards),
      State('waitForSetAvatarResponse', self.enterWaitForSetAvatarResponse, self.exitWaitForSetAvatarResponse),
      State('waitForShardList', self.enterWaitForShardList, self.exitWaitForShardList),
      State('ejected', self.enterEjected, self.exitEjected),
      State('districtReset', self.enterDistrictReset, self.exitDistrictReset),
      State('died', self.enterDied, self.exitDied),
      State('betaInform', self.enterBetaInform, self.exitBetaInform)], 'off', 'off')
     self.loginFSM.enterInitialState()
     self.gameFSM = ClassicFSM('game', [State('off', self.enterGameOff, self.exitGameOff),
      State('waitForGameEnterResponse', self.enterWaitForGameEnterResponse, self.exitWaitForGameEnterResponse),
      State('playGame', self.enterPlayGame, self.exitPlayGame),
      State('closeShard', self.enterCloseShard, self.exitCloseShard),
      State('switchShards', self.enterSwitchShards, self.exitSwitchShards)], 'off', 'off')
     self.gameFSM.enterInitialState()
     self.avChooser = AvChooser(self.loginFSM)
     self.playGame = PlayGame(self.gameFSM, 'playGameDone')
     self.hoodMgr = HoodMgr()
     self.makeAToon = MakeAToon()
     self.loginToken = os.environ.get('LOGIN_TOKEN')
     self.serverAddress = os.environ.get('GAME_SERVER')
     self.serverURL = URLSpec('http://%s' % self.serverAddress)
     self.parentMgr.registerParent(CIGlobals.SPRender, render)
     self.parentMgr.registerParent(CIGlobals.SPHidden, hidden)
     self.adminAccess = False
     self.localAvChoice = None
     self.SuitsActive = 0
     self.BossActive = 0
     self.accServerTimesNA = 0
     self.maxAccServerTimesNA = 10
     self.setZonesEmulated = 0
     self.old_setzone_interest_handle = None
     self.setZoneQueue = Queue()
     self.accept(self.SetZoneDoneEvent, self._handleEmuSetZoneDone)
     self.handler = None
     self.__currentAvId = 0
     self.myDistrict = None
     self.activeDistricts = {}
     self.shardListHandle = None
     self.uberZoneInterest = None
     self.isShowingPlayerIds = False
     self.doBetaInform = True
     self.dTutorial = None
     self.requestedName = None
     self.whisperNoise = base.loadSfx('phase_3.5/audio/sfx/GUI_whisper_3.ogg')
     self.checkHttp()
     base.audio3d = Audio3DManager(base.sfxManagerList[0], camera)
     base.audio3d.setDropOffFactor(0)
     base.audio3d.setDopplerFactor(3.0)
     base.lifter = CollisionHandlerFloor()
     base.pusher = CollisionHandlerPusher()
     base.queue = CollisionHandlerQueue()
     base.minigame = None
     base.finalExitCallbacks.insert(0, self.__handleExit)
     self.csm = self.generateGlobalObject(DO_ID_CLIENT_SERVICES_MANAGER, 'ClientServicesManager')
     self.friendsManager = self.generateGlobalObject(DO_ID_FRIENDS_MANAGER, 'FriendsManager')
     SpeedHackChecker.startChecking()
     self.loginFSM.request('connect')
     return
コード例 #12
0
class SimpleClient(ShowBase):
    def __init__(self):
        # Basics
        ShowBase.__init__(self)
        base.disableMouse()
        self.accept("escape", self.disconnect)
        base.camera.set_pos(0, 0, 60)
        base.camera.look_at(0, 0, 0)
        # Game-relevant attributes
        self.has_avatar = False
        self.avatar_owner_view = False
        # Avatar controls
        # FIXME: These values will be off the kilter if keys are pressed when the client starts.
        self.movement_heading = 0
        self.movement_speed = 0
        self.accept("avatar", self.get_avatar)
        self.accept("distributed_avatar", self.get_distributed_avatar)
        self.accept("arrow_up", self.indicate_movement, [0, 1])
        self.accept("arrow_up-up", self.indicate_movement, [0, -1])
        self.accept("arrow_down", self.indicate_movement, [0, -1])
        self.accept("arrow_down-up", self.indicate_movement, [0, 1])
        self.accept("arrow_left", self.indicate_movement, [1, 0])
        self.accept("arrow_left-up", self.indicate_movement, [-1, 0])
        self.accept("arrow_right", self.indicate_movement, [-1, 0])
        self.accept("arrow_right-up", self.indicate_movement, [1, 0])
        # Create repository
        # FIXME: For some freaky reason, using the default method
        # (CM_HTTP) won't result in further callbacks being called
        # back. Does connection() fail?
        self.repo = AstronClientRepository(
            dcFileNames=["simple_example.dc"],
            connectMethod=AstronClientRepository.CM_NET)
        # Callback events. These names are "magic" (defined in AstronClientRepository)
        self.accept("CLIENT_HELLO_RESP", self.client_is_handshaked)
        self.accept("CLIENT_EJECT", self.ejected)
        self.accept("CLIENT_OBJECT_LEAVING", self.avatar_leaves)
        self.accept("CLIENT_OBJECT_LEAVING_OWNER", self.avatar_leaves_owner)
        self.accept("LOST_CONNECTION", self.lost_connection)
        # Connecting
        url = URLSpec()
        url.setServer("127.0.0.1")
        url.setPort(6667)
        # FIXME: No idea why this doesn't work instead...
        # url = URLSpec("127.0.0.1", 6667)
        self.notify.debug("Connecting...")
        self.repo.connect([url],
                          successCallback=self.connection_success,
                          failureCallback=self.connection_failure)

    #
    # Connection management (callbacks and helpers)
    #

    # Connection established. Send CLIENT_HELLO to progress from NEW to UNKNOWN.
    # Normally, there could be code here for things to do before entering making
    # the connection and actually interacting with the server.
    def connection_success(self, *args):
        self.repo.sendHello(version_string)

    def connection_failure(self):
        self.notify.error("Failed to connect")
        sys.exit()

    def lost_connection(self):
        self.notify.error("Lost connection")
        sys.exit()

    # Voluntarily end the connection.
    def disconnect(self):
        self.repo.disconnect()
        sys.exit()

    # Client was ejected
    def ejected(self, error_code, reason):
        self.notify.error("Ejected! %d: %s" % (error_code, reason))
        sys.exit()

    # Client has received CLIENT_HELLO_RESP and now is in state UNKNOWN.
    def client_is_handshaked(self, *args):
        login_manager = self.repo.generateGlobalObject(1234, 'LoginManager')
        # Attach map to scene graph
        self.map = self.loader.loadModel("map")
        self.map.reparent_to(self.render)
        # Log in and receive; leads to enter_owner (ownership of avatar)
        login_manager.login("guest", "guest")

    def avatar_leaves(self, do_id):
        print("Avatar leaving: " + str(do_id))

    def avatar_leaves_owner(self, do_id):
        print("AvatarOV leaving: " + str(do_id))

    #
    # Interface
    #

    # Adjust current intention and send it.
    def indicate_movement(self, heading, speed):
        if self.has_avatar:
            # FIXME: Not really graceful to just ignore this.
            # What if a button was already pressed when we got the OV?

            self.movement_heading += heading
            self.movement_speed += speed
            self.avatar_owner_view.indicateIntent(self.movement_heading,
                                                  self.movement_speed)
        else:
            print("Avatar not complete yet!")

    # A DistributedAvatarOV was created, here is it.
    def get_avatar(self, owner_view):
        print("Received avatar OV in client")
        self.avatar_owner_view = owner_view
        self.taskMgr.add(self.complete_avatar, 'complete avatar')

    def complete_avatar(self, task):
        try:
            avatar = self.repo.doId2do[self.avatar_owner_view.doId]
            base.camera.reparent_to(avatar)
            base.camera.set_pos(0, -20, 10)
            base.camera.look_at(0, 0, 0)
            self.has_avatar = True
        except KeyError:
            print("Couldn't complete avatar " +
                  str(self.avatar_owner_view.doId) + ", available DOs: " +
                  ", ".join([str(doId) for doId in self.repo.doId2do.keys()]))
            return Task.cont

    # A DistributedAvatar was created, here is it.
    def get_distributed_avatar(self, avatar):
        print("Received avatar " + str(avatar.doId))
        avatar.reparent_to(self.map)
コード例 #13
0
class SimpleClient(ShowBase):
    def __init__(self):
        # Basics
        ShowBase.__init__(self)
        base.disableMouse()
        self.accept("escape", self.disconnect)
        base.camera.set_pos(0, 0, 60)
        base.camera.look_at(0, 0, 0)
        # Game-relevant attributes
        self.has_avatar = False
        self.avatar_owner_view = False
        # Avatar controls
        # FIXME: These values will be off the kilter if keys are pressed when the client starts.
        self.movement_heading = 0
        self.movement_speed = 0
        self.accept("avatar", self.get_avatar)
        self.accept("distributed_avatar", self.get_distributed_avatar)
        self.accept("arrow_up", self.indicate_movement, [0, 1])
        self.accept("arrow_up-up", self.indicate_movement, [0, -1])
        self.accept("arrow_down", self.indicate_movement, [0, -1])
        self.accept("arrow_down-up", self.indicate_movement, [0, 1])
        self.accept("arrow_left", self.indicate_movement, [1, 0])
        self.accept("arrow_left-up", self.indicate_movement, [-1, 0])
        self.accept("arrow_right", self.indicate_movement, [-1, 0])
        self.accept("arrow_right-up", self.indicate_movement, [1, 0])
        # Create repository
        # FIXME: For some freaky reason, using the default method
        # (CM_HTTP) won't result in further callbacks being called
        # back. Does connection() fail?
        self.repo = AstronClientRepository(dcFileNames = ["simple_example.dc"],
                                           connectMethod = AstronClientRepository.CM_NET)
        # Callback events. These names are "magic" (defined in AstronClientRepository)
        self.accept("CLIENT_HELLO_RESP", self.client_is_handshaked)
        self.accept("CLIENT_EJECT", self.ejected)
        self.accept("CLIENT_OBJECT_LEAVING", self.avatar_leaves)
        self.accept("CLIENT_OBJECT_LEAVING_OWNER", self.avatar_leaves_owner)
        self.accept("LOST_CONNECTION", self.lost_connection)
        # Connecting
        url = URLSpec()
        url.setServer("127.0.0.1")
        url.setPort(6667)
        # FIXME: No idea why this doesn't work instead...
        # url = URLSpec("127.0.0.1", 6667)
        self.notify.debug("Connecting...")
        self.repo.connect([url],
                          successCallback = self.connection_success,
                          failureCallback = self.connection_failure)

    #
    # Connection management (callbacks and helpers)
    #

    # Connection established. Send CLIENT_HELLO to progress from NEW to UNKNOWN.
    # Normally, there could be code here for things to do before entering making
    # the connection and actually interacting with the server.
    def connection_success(self, *args):
        self.repo.sendHello(version_string)

    def connection_failure(self):
        self.notify.error("Failed to connect")
        sys.exit()

    def lost_connection(self):
        self.notify.error("Lost connection")
        sys.exit()

    # Voluntarily end the connection.
    def disconnect(self):
        self.repo.disconnect()
        sys.exit()
    
    # Client was ejected
    def ejected(self, error_code, reason):
        self.notify.error("Ejected! %d: %s" % (error_code, reason))
        sys.exit()

    # Client has received CLIENT_HELLO_RESP and now is in state UNKNOWN.
    def client_is_handshaked(self, *args):
        login_manager = self.repo.generateGlobalObject(1234, 'LoginManager')
        # Attach map to scene graph
        self.map = self.loader.loadModel("map")
        self.map.reparent_to(self.render)
        # Log in and receive; leads to enter_owner (ownership of avatar)
        login_manager.login("guest", "guest")

    def avatar_leaves(self, do_id):
        print("Avatar leaving: "+str(do_id))

    def avatar_leaves_owner(self, do_id):
        print("AvatarOV leaving: "+str(do_id))
        
    #
    # Interface
    #
    
    # Adjust current intention and send it.
    def indicate_movement(self, heading, speed):
        if self.has_avatar:
            # FIXME: Not really graceful to just ignore this.
            # What if a button was already pressed when we got the OV?
                        
            self.movement_heading += heading
            self.movement_speed += speed
            self.avatar_owner_view.indicateIntent(self.movement_heading, self.movement_speed)
        else:
            print("Avatar not complete yet!")

    # A DistributedAvatarOV was created, here is it.
    def get_avatar(self, owner_view):
        print("Received avatar OV in client")
        self.avatar_owner_view = owner_view
        self.taskMgr.add(self.complete_avatar, 'complete avatar')

    def complete_avatar(self, task):
        try:
            avatar = self.repo.doId2do[self.avatar_owner_view.doId]
            base.camera.reparent_to(avatar)
            base.camera.set_pos(0, -20, 10)
            base.camera.look_at(0, 0, 0)
            self.has_avatar = True
        except KeyError:
            print("Couldn't complete avatar "+str(self.avatar_owner_view.doId)+", available DOs: "+", ".join([str(doId) for doId in self.repo.doId2do.keys()]))
            return Task.cont

    # A DistributedAvatar was created, here is it.
    def get_distributed_avatar(self, avatar):
        print("Received avatar "+str(avatar.doId))
        avatar.reparent_to(self.map)