コード例 #1
0
 def __init__(self):
     base.cr = ClientRepository(
         dcFileNames=['phase_3/etc/direct.dc', 'phase_3/etc/toon.dc'],
         dcSuffix='AI')
     base.cr.connect([url],
                     successCallback=self.connectSuccess,
                     failureCallback=self.connectFailure)
     base.cTrav = CollisionTraverser()
     self.skeleton = 0
     if base.config.GetBool('want-suits', True):
         base.accept("SpawnSuit", self.createSuit)
     self.activeInvasion = False
     self.invasionSize = 0
     self.difficulty = ""
     self.title = DirectLabel(text="Server Menu",
                              pos=(-0.05, -0.1, -0.1),
                              scale=0.1,
                              relief=None,
                              text_fg=(1, 1, 1, 1),
                              parent=base.a2dTopRight,
                              text_align=TextNode.ARight)
     self.Suits = []
     base.pathNodes = []
     self.SuitCount = 0
     self.automaticSuits = 0
     self.hoodUtil = HoodUtil(base.cr)
     self.tournament = SuitTournament()
     self.lastChoice = None
     self.zoneAllocator = UniqueIdAllocator(50, 500)
コード例 #2
0
    def handleSetDoIdrange(self, di):
        self.doIdBase = di.getUint32()
        self.doIdLast = self.doIdBase + di.getUint32()
        self.doIdAllocator = UniqueIdAllocator(self.doIdBase,
                                               self.doIdLast - 1)

        self.ourChannel = self.doIdBase

        self.createReady()
コード例 #3
0
    def handleSetDoIdrange(self, di):
        self.doIdBase = di.getUint32()
        self.doIdLast = self.doIdBase + di.getUint32()
        self.doIdAllocator = UniqueIdAllocator(self.doIdBase, self.doIdLast - 1)

        self.ourChannel = self.doIdBase

        self.createReady()
コード例 #4
0
class ClientRepository(ClientRepositoryBase):
    """
    This is the open-source ClientRepository as provided by CMU.  It
    communicates with the ServerRepository in this same directory.

    If you are looking for the VR Studio's implementation of the
    client repository, look to OTPClientRepository (elsewhere).
    """
    notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")

    # This is required by DoCollectionManager, even though it's not
    # used by this implementation.
    GameGlobalsId = 0

    doNotDeallocateChannel = True

    def __init__(self,
                 dcFileNames=None,
                 dcSuffix='',
                 connectMethod=None,
                 threadedNet=None):
        ClientRepositoryBase.__init__(self,
                                      dcFileNames=dcFileNames,
                                      dcSuffix=dcSuffix,
                                      connectMethod=connectMethod,
                                      threadedNet=threadedNet)
        self.setHandleDatagramsInternally(False)

        base.finalExitCallbacks.append(self.shutdown)

        # The doId allocator.  The CMU LAN server may choose to
        # send us a block of doIds.  If it chooses to do so, then we
        # may create objects, using those doIds.
        self.doIdAllocator = None
        self.doIdBase = 0
        self.doIdLast = 0

        # The doIdBase of the client message currently being
        # processed.
        self.currentSenderId = None

        # Explicitly-requested interest zones.
        self.interestZones = []

    def handleSetDoIdrange(self, di):
        self.doIdBase = di.getUint32()
        self.doIdLast = self.doIdBase + di.getUint32()
        self.doIdAllocator = UniqueIdAllocator(self.doIdBase,
                                               self.doIdLast - 1)

        self.ourChannel = self.doIdBase

        self.createReady()

    def createReady(self):
        # Now that we've got a doId range, we can safely generate new
        # distributed objects.
        messenger.send('createReady', taskChain='default')
        messenger.send(self.uniqueName('createReady'), taskChain='default')

    def handleRequestGenerates(self, di):
        # When new clients join the zone of an object, they need to hear
        # about it, so we send out all of our information about objects in
        # that particular zone.

        zone = di.getUint32()
        for obj in self.doId2do.values():
            if obj.zoneId == zone:
                if (self.isLocalId(obj.doId)):
                    self.resendGenerate(obj)

    def resendGenerate(self, obj):
        """ Sends the generate message again for an already-generated
        object, presumably to inform any newly-arrived clients of this
        object's current state. """

        # get the list of "ram" fields that aren't
        # required.  These are fields whose values should
        # persist even if they haven't been received
        # lately, so we have to re-broadcast these values
        # in case the new client hasn't heard their latest
        # values.
        extraFields = []
        for i in range(obj.dclass.getNumInheritedFields()):
            field = obj.dclass.getInheritedField(i)
            if field.hasKeyword('broadcast') and field.hasKeyword(
                    'ram') and not field.hasKeyword('required'):
                if field.asMolecularField():
                    # It's a molecular field; this means
                    # we have to pack the components.
                    # Fortunately, we'll find those
                    # separately through the iteration, so
                    # we can ignore this field itself.
                    continue

                extraFields.append(field.getName())

        datagram = self.formatGenerate(obj, extraFields)
        self.send(datagram)

    def handleGenerate(self, di):
        self.currentSenderId = di.getUint32()
        zoneId = di.getUint32()
        classId = di.getUint16()
        doId = di.getUint32()

        # Look up the dclass
        dclass = self.dclassesByNumber[classId]

        distObj = self.doId2do.get(doId)
        if distObj and distObj.dclass == dclass:
            # We've already got this object.  Probably this is just a
            # repeat-generate, synthesized for the benefit of someone
            # else who just entered the zone.  Accept the new updates,
            # but don't make a formal generate.
            assert (self.notify.debug("performing generate-update for %s %s" %
                                      (dclass.getName(), doId)))
            dclass.receiveUpdateBroadcastRequired(distObj, di)
            dclass.receiveUpdateOther(distObj, di)
            return

        assert (self.notify.debug("performing generate for %s %s" %
                                  (dclass.getName(), doId)))
        dclass.startGenerate()
        # Create a new distributed object, and put it in the dictionary
        distObj = self.generateWithRequiredOtherFields(dclass, doId, di, 0,
                                                       zoneId)
        dclass.stopGenerate()

    def allocateDoId(self):
        """ Returns a newly-allocated doId.  Call freeDoId() when the
        object has been deleted. """

        return self.doIdAllocator.allocate()

    def reserveDoId(self, doId):
        """ Removes the indicate doId from the available pool, as if
        it had been explicitly allocated.  You may pass it to
        freeDoId() later if you wish. """

        self.doIdAllocator.initialReserveId(doId)
        return doId

    def freeDoId(self, doId):
        """ Returns a doId back into the free pool for re-use. """

        assert self.isLocalId(doId)
        self.doIdAllocator.free(doId)

    def storeObjectLocation(self, object, parentId, zoneId):
        # The CMU implementation doesn't use the DoCollectionManager
        # much.
        object.parentId = parentId
        object.zoneId = zoneId

    def createDistributedObject(self,
                                className=None,
                                distObj=None,
                                zoneId=0,
                                optionalFields=None,
                                doId=None,
                                reserveDoId=False):
        """ To create a DistributedObject, you must pass in either the
        name of the object's class, or an already-created instance of
        the class (or both).  If you pass in just a class name (to the
        className parameter), then a default instance of the object
        will be created, with whatever parameters the default
        constructor supplies.  Alternatively, if you wish to create
        some initial values different from the default, you can create
        the instance yourself and supply it to the distObj parameter,
        then that instance will be used instead.  (It should be a
        newly-created object, not one that has already been manifested
        on the network or previously passed through
        createDistributedObject.)  In either case, the new
        DistributedObject is returned from this method.
        
        This method will issue the appropriate network commands to
        make this object appear on all of the other clients.

        You should supply an initial zoneId in which to manifest the
        object.  The fields marked "required" or "ram" will be
        broadcast to all of the other clients; if you wish to
        broadcast additional field values at this time as well, pass a
        list of field names in the optionalFields parameters.

        Normally, doId is None, to mean allocate a new doId for the
        object.  If you wish to use a particular doId, pass it in
        here.  If you also pass reserveDoId = True, this doId will be
        reserved from the allocation pool using self.reserveDoId().
        You are responsible for ensuring this doId falls within the
        client's allowable doId range and has not already been
        assigned to another object.  """

        if not className:
            if not distObj:
                self.notify.error(
                    "Must specify either a className or a distObj.")
            className = distObj.__class__.__name__

        if doId is None:
            doId = self.allocateDoId()
        elif reserveDoId:
            self.reserveDoId(doId)

        dclass = self.dclassesByName.get(className)
        if not dclass:
            self.notify.error("Unknown distributed class: %s" %
                              (distObj.__class__))
        classDef = dclass.getClassDef()
        if classDef == None:
            self.notify.error("Could not create an undefined %s object." %
                              (dclass.getName()))

        if not distObj:
            distObj = classDef(self)
        if not isinstance(distObj, classDef):
            self.notify.error("Object %s is not an instance of %s" %
                              (distObj.__class__.__name__, classDef.__name__))

        distObj.dclass = dclass
        distObj.doId = doId
        self.doId2do[doId] = distObj
        distObj.generateInit()
        distObj._retrieveCachedData()
        distObj.generate()
        distObj.setLocation(0, zoneId)
        distObj.announceGenerate()
        datagram = self.formatGenerate(distObj, optionalFields)
        self.send(datagram)
        return distObj

    def formatGenerate(self, distObj, extraFields):
        """ Returns a datagram formatted for sending the generate message for the indicated object. """
        return distObj.dclass.clientFormatGenerateCMU(distObj, distObj.doId,
                                                      distObj.zoneId,
                                                      extraFields)

    def sendDeleteMsg(self, doId):
        datagram = PyDatagram()
        datagram.addUint16(OBJECT_DELETE_CMU)
        datagram.addUint32(doId)
        self.send(datagram)

    def sendDisconnect(self):
        if self.isConnected():
            # Tell the game server that we're going:
            datagram = PyDatagram()
            # Add message type
            datagram.addUint16(CLIENT_DISCONNECT_CMU)
            # Send the message
            self.send(datagram)
            self.notify.info("Sent disconnect message to server")
            self.disconnect()
        self.stopHeartbeat()

    def setInterestZones(self, interestZoneIds):
        """ Changes the set of zones that this particular client is
        interested in hearing about. """

        datagram = PyDatagram()
        # Add message type
        datagram.addUint16(CLIENT_SET_INTEREST_CMU)

        for zoneId in interestZoneIds:
            datagram.addUint32(zoneId)

        # send the message
        self.send(datagram)
        self.interestZones = interestZoneIds[:]

    def setObjectZone(self, distObj, zoneId):
        """ Moves the object into the indicated zone. """
        distObj.b_setLocation(0, zoneId)
        assert distObj.zoneId == zoneId

        # Tell all of the clients monitoring the new zone that we've
        # arrived.
        self.resendGenerate(distObj)

    def sendSetLocation(self, doId, parentId, zoneId):
        datagram = PyDatagram()
        datagram.addUint16(OBJECT_SET_ZONE_CMU)
        datagram.addUint32(doId)
        datagram.addUint32(zoneId)
        self.send(datagram)

    def sendHeartbeat(self):
        datagram = PyDatagram()
        # Add message type
        datagram.addUint16(CLIENT_HEARTBEAT_CMU)
        # Send it!
        self.send(datagram)
        self.lastHeartbeat = globalClock.getRealTime()
        # This is important enough to consider flushing immediately
        # (particularly if we haven't run readerPollTask recently).
        self.considerFlush()

    def isLocalId(self, doId):
        """ Returns true if this doId is one that we're the owner of,
        false otherwise. """

        return ((doId >= self.doIdBase) and (doId < self.doIdLast))

    def haveCreateAuthority(self):
        """ Returns true if this client has been assigned a range of
        doId's it may use to create objects, false otherwise. """

        return (self.doIdLast > self.doIdBase)

    def getAvatarIdFromSender(self):
        """ Returns the doIdBase of the client that originally sent
        the current update message.  This is only defined when
        processing an update message or a generate message. """
        return self.currentSenderId

    def handleDatagram(self, di):
        if self.notify.getDebug():
            print "ClientRepository received datagram:"
            di.getDatagram().dumpHex(ostream)

        msgType = self.getMsgType()
        self.currentSenderId = None

        # These are the sort of messages we may expect from the public
        # Panda server.

        if msgType == SET_DOID_RANGE_CMU:
            self.handleSetDoIdrange(di)
        elif msgType == OBJECT_GENERATE_CMU:
            self.handleGenerate(di)
        elif msgType == OBJECT_UPDATE_FIELD_CMU:
            self.handleUpdateField(di)
        elif msgType == OBJECT_DISABLE_CMU:
            self.handleDisable(di)
        elif msgType == OBJECT_DELETE_CMU:
            self.handleDelete(di)
        elif msgType == REQUEST_GENERATES_CMU:
            self.handleRequestGenerates(di)
        else:
            self.handleMessageType(msgType, di)

        # If we're processing a lot of datagrams within one frame, we
        # may forget to send heartbeats.  Keep them coming!
        self.considerHeartbeat()

    def handleMessageType(self, msgType, di):
        self.notify.error("unrecognized message type %s" % (msgType))

    def handleUpdateField(self, di):
        # The CMU update message starts with an additional field, not
        # present in the Disney update message: the doIdBase of the
        # original sender.  Extract that and call up to the parent.
        self.currentSenderId = di.getUint32()
        ClientRepositoryBase.handleUpdateField(self, di)

    def handleDisable(self, di):
        # Receives a list of doIds.
        while di.getRemainingSize() > 0:
            doId = di.getUint32()

            # We should never get a disable message for our own object.
            assert not self.isLocalId(doId)
            self.disableDoId(doId)

    def handleDelete(self, di):
        # Receives a single doId.
        doId = di.getUint32()
        self.deleteObject(doId)

    def deleteObject(self, doId):
        """
        Removes the object from the client's view of the world.  This
        should normally not be called directly except in the case of
        error recovery, since the server will normally be responsible
        for deleting and disabling objects as they go out of scope.

        After this is called, future updates by server on this object
        will be ignored (with a warning message).  The object will
        become valid again the next time the server sends a generate
        message for this doId.

        This is not a distributed message and does not delete the
        object on the server or on any other client.
        """
        if doId in self.doId2do:
            # If it is in the dictionary, remove it.
            obj = self.doId2do[doId]
            # Remove it from the dictionary
            del self.doId2do[doId]
            # Disable, announce, and delete the object itself...
            # unless delayDelete is on...
            obj.deleteOrDelay()
            if self.isLocalId(doId):
                self.freeDoId(doId)
        elif self.cache.contains(doId):
            # If it is in the cache, remove it.
            self.cache.delete(doId)
            if self.isLocalId(doId):
                self.freeDoId(doId)
        else:
            # Otherwise, ignore it
            self.notify.warning("Asked to delete non-existent DistObj " +
                                str(doId))

    def stopTrackRequestDeletedDO(self, *args):
        # No-op.  Not entirely sure what this does on the VR Studio side.
        pass

    def sendUpdate(self, distObj, fieldName, args):
        """ Sends a normal update for a single field. """
        dg = distObj.dclass.clientFormatUpdate(fieldName, distObj.doId, args)
        self.send(dg)

    def sendUpdateToChannel(self, distObj, channelId, fieldName, args):
        """ Sends a targeted update of a single field to a particular
        client.  The top 32 bits of channelId is ignored; the lower 32
        bits should be the client Id of the recipient (i.e. the
        client's doIdbase).  The field update will be sent to the
        indicated client only.  The field must be marked clsend or
        p2p, and may not be marked broadcast. """

        datagram = distObj.dclass.clientFormatUpdate(fieldName, distObj.doId,
                                                     args)
        dgi = PyDatagramIterator(datagram)

        # Reformat the packed datagram to change the message type and
        # add the target id.
        dgi.getUint16()

        dg = PyDatagram()
        dg.addUint16(CLIENT_OBJECT_UPDATE_FIELD_TARGETED_CMU)
        dg.addUint32(channelId & 0xffffffff)
        dg.appendData(dgi.getRemainingBytes())

        self.send(dg)
コード例 #5
0
class ClientRepository(ClientRepositoryBase):
    """
    This is the open-source ClientRepository as provided by CMU.  It
    communicates with the ServerRepository in this same directory.

    If you are looking for the VR Studio's implementation of the
    client repository, look to OTPClientRepository (elsewhere).
    """
    notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository")

    # This is required by DoCollectionManager, even though it's not
    # used by this implementation.
    GameGlobalsId = 0

    doNotDeallocateChannel = True
    
    def __init__(self, dcFileNames = None, dcSuffix = '', connectMethod = None,
                 threadedNet = None):
        ClientRepositoryBase.__init__(self, dcFileNames = dcFileNames, dcSuffix = dcSuffix, connectMethod = connectMethod, threadedNet = threadedNet)
        self.setHandleDatagramsInternally(False)

        base.finalExitCallbacks.append(self.shutdown)

        # The doId allocator.  The CMU LAN server may choose to
        # send us a block of doIds.  If it chooses to do so, then we
        # may create objects, using those doIds.
        self.doIdAllocator = None
        self.doIdBase = 0
        self.doIdLast = 0

        # The doIdBase of the client message currently being
        # processed.
        self.currentSenderId = None

        # Explicitly-requested interest zones.
        self.interestZones = []

    def handleSetDoIdrange(self, di):
        self.doIdBase = di.getUint32()
        self.doIdLast = self.doIdBase + di.getUint32()
        self.doIdAllocator = UniqueIdAllocator(self.doIdBase, self.doIdLast - 1)

        self.ourChannel = self.doIdBase

        self.createReady()

    def createReady(self):
        # Now that we've got a doId range, we can safely generate new
        # distributed objects.
        messenger.send('createReady', taskChain = 'default')
        messenger.send(self.uniqueName('createReady'), taskChain = 'default')

    def handleRequestGenerates(self, di):
        # When new clients join the zone of an object, they need to hear
        # about it, so we send out all of our information about objects in
        # that particular zone.

        zone = di.getUint32()
        for obj in self.doId2do.values():
            if obj.zoneId == zone:
                if (self.isLocalId(obj.doId)):
                    self.resendGenerate(obj)

    def resendGenerate(self, obj):
        """ Sends the generate message again for an already-generated
        object, presumably to inform any newly-arrived clients of this
        object's current state. """

        # get the list of "ram" fields that aren't
        # required.  These are fields whose values should
        # persist even if they haven't been received
        # lately, so we have to re-broadcast these values
        # in case the new client hasn't heard their latest
        # values.
        extraFields = []
        for i in range(obj.dclass.getNumInheritedFields()):
            field = obj.dclass.getInheritedField(i)
            if field.hasKeyword('broadcast') and field.hasKeyword('ram') and not field.hasKeyword('required'):
                if field.asMolecularField():
                    # It's a molecular field; this means
                    # we have to pack the components.
                    # Fortunately, we'll find those
                    # separately through the iteration, so
                    # we can ignore this field itself.
                    continue

                extraFields.append(field.getName())

        datagram = self.formatGenerate(obj, extraFields)
        self.send(datagram)

    def handleGenerate(self, di):
        self.currentSenderId = di.getUint32()
        zoneId = di.getUint32()
        classId = di.getUint16()
        doId = di.getUint32()

        # Look up the dclass
        dclass = self.dclassesByNumber[classId]

        distObj = self.doId2do.get(doId)
        if distObj and distObj.dclass == dclass:
            # We've already got this object.  Probably this is just a
            # repeat-generate, synthesized for the benefit of someone
            # else who just entered the zone.  Accept the new updates,
            # but don't make a formal generate.
            assert(self.notify.debug("performing generate-update for %s %s" % (dclass.getName(), doId)))
            dclass.receiveUpdateBroadcastRequired(distObj, di)
            dclass.receiveUpdateOther(distObj, di)
            return

        assert(self.notify.debug("performing generate for %s %s" % (dclass.getName(), doId)))
        dclass.startGenerate()
        # Create a new distributed object, and put it in the dictionary
        distObj = self.generateWithRequiredOtherFields(dclass, doId, di, 0, zoneId)
        dclass.stopGenerate()

    def allocateDoId(self):
        """ Returns a newly-allocated doId.  Call freeDoId() when the
        object has been deleted. """

        return self.doIdAllocator.allocate()

    def reserveDoId(self, doId):
        """ Removes the indicate doId from the available pool, as if
        it had been explicitly allocated.  You may pass it to
        freeDoId() later if you wish. """

        self.doIdAllocator.initialReserveId(doId)
        return doId

    def freeDoId(self, doId):
        """ Returns a doId back into the free pool for re-use. """

        assert self.isLocalId(doId)
        self.doIdAllocator.free(doId)

    def storeObjectLocation(self, object, parentId, zoneId):
        # The CMU implementation doesn't use the DoCollectionManager
        # much.
        object.parentId = parentId
        object.zoneId = zoneId

    def createDistributedObject(self, className = None, distObj = None,
                                zoneId = 0, optionalFields = None,
                                doId = None, reserveDoId = False):

        """ To create a DistributedObject, you must pass in either the
        name of the object's class, or an already-created instance of
        the class (or both).  If you pass in just a class name (to the
        className parameter), then a default instance of the object
        will be created, with whatever parameters the default
        constructor supplies.  Alternatively, if you wish to create
        some initial values different from the default, you can create
        the instance yourself and supply it to the distObj parameter,
        then that instance will be used instead.  (It should be a
        newly-created object, not one that has already been manifested
        on the network or previously passed through
        createDistributedObject.)  In either case, the new
        DistributedObject is returned from this method.
        
        This method will issue the appropriate network commands to
        make this object appear on all of the other clients.

        You should supply an initial zoneId in which to manifest the
        object.  The fields marked "required" or "ram" will be
        broadcast to all of the other clients; if you wish to
        broadcast additional field values at this time as well, pass a
        list of field names in the optionalFields parameters.

        Normally, doId is None, to mean allocate a new doId for the
        object.  If you wish to use a particular doId, pass it in
        here.  If you also pass reserveDoId = True, this doId will be
        reserved from the allocation pool using self.reserveDoId().
        You are responsible for ensuring this doId falls within the
        client's allowable doId range and has not already been
        assigned to another object.  """

        if not className:
            if not distObj:
                self.notify.error("Must specify either a className or a distObj.")
            className = distObj.__class__.__name__

        if doId is None:
            doId = self.allocateDoId()
        elif reserveDoId:
            self.reserveDoId(doId)
            
        dclass = self.dclassesByName.get(className)
        if not dclass:
            self.notify.error("Unknown distributed class: %s" % (distObj.__class__))
        classDef = dclass.getClassDef()
        if classDef == None:
            self.notify.error("Could not create an undefined %s object." % (
                dclass.getName()))

        if not distObj:
            distObj = classDef(self)
        if not isinstance(distObj, classDef):
            self.notify.error("Object %s is not an instance of %s" % (distObj.__class__.__name__, classDef.__name__))

        distObj.dclass = dclass
        distObj.doId = doId
        self.doId2do[doId] = distObj
        distObj.generateInit()
        distObj._retrieveCachedData()
        distObj.generate()
        distObj.setLocation(0, zoneId)
        distObj.announceGenerate()
        datagram = self.formatGenerate(distObj, optionalFields)
        self.send(datagram)
        return distObj

    def formatGenerate(self, distObj, extraFields):
        """ Returns a datagram formatted for sending the generate message for the indicated object. """
        return distObj.dclass.clientFormatGenerateCMU(distObj, distObj.doId, distObj.zoneId, extraFields)

    def sendDeleteMsg(self, doId):
        datagram = PyDatagram()
        datagram.addUint16(OBJECT_DELETE_CMU)
        datagram.addUint32(doId)
        self.send(datagram)

    def sendDisconnect(self):
        if self.isConnected():
            # Tell the game server that we're going:
            datagram = PyDatagram()
            # Add message type
            datagram.addUint16(CLIENT_DISCONNECT_CMU)
            # Send the message
            self.send(datagram)
            self.notify.info("Sent disconnect message to server")
            self.disconnect()
        self.stopHeartbeat()

    def setInterestZones(self, interestZoneIds):
        """ Changes the set of zones that this particular client is
        interested in hearing about. """

        datagram = PyDatagram()
        # Add message type
        datagram.addUint16(CLIENT_SET_INTEREST_CMU)

        for zoneId in interestZoneIds:
            datagram.addUint32(zoneId)

        # send the message
        self.send(datagram)
        self.interestZones = interestZoneIds[:]

    def setObjectZone(self, distObj, zoneId):
        """ Moves the object into the indicated zone. """
        distObj.b_setLocation(0, zoneId)
        assert distObj.zoneId == zoneId

        # Tell all of the clients monitoring the new zone that we've
        # arrived.
        self.resendGenerate(distObj)

    def sendSetLocation(self, doId, parentId, zoneId):
        datagram = PyDatagram()
        datagram.addUint16(OBJECT_SET_ZONE_CMU)
        datagram.addUint32(doId)
        datagram.addUint32(zoneId)
        self.send(datagram)

    def sendHeartbeat(self):
        datagram = PyDatagram()
        # Add message type
        datagram.addUint16(CLIENT_HEARTBEAT_CMU)
        # Send it!
        self.send(datagram)
        self.lastHeartbeat = globalClock.getRealTime()
        # This is important enough to consider flushing immediately
        # (particularly if we haven't run readerPollTask recently).
        self.considerFlush()

    def isLocalId(self, doId):
        """ Returns true if this doId is one that we're the owner of,
        false otherwise. """
        
        return ((doId >= self.doIdBase) and (doId < self.doIdLast))

    def haveCreateAuthority(self):
        """ Returns true if this client has been assigned a range of
        doId's it may use to create objects, false otherwise. """
        
        return (self.doIdLast > self.doIdBase)

    def getAvatarIdFromSender(self):
        """ Returns the doIdBase of the client that originally sent
        the current update message.  This is only defined when
        processing an update message or a generate message. """
        return self.currentSenderId

    def handleDatagram(self, di):
        if self.notify.getDebug():
            print "ClientRepository received datagram:"
            di.getDatagram().dumpHex(ostream)

        msgType = self.getMsgType()
        self.currentSenderId = None

        # These are the sort of messages we may expect from the public
        # Panda server.

        if msgType == SET_DOID_RANGE_CMU:
            self.handleSetDoIdrange(di)
        elif msgType == OBJECT_GENERATE_CMU:
            self.handleGenerate(di)
        elif msgType == OBJECT_UPDATE_FIELD_CMU:
            self.handleUpdateField(di)
        elif msgType == OBJECT_DISABLE_CMU:
            self.handleDisable(di)
        elif msgType == OBJECT_DELETE_CMU:
            self.handleDelete(di)
        elif msgType == REQUEST_GENERATES_CMU:
            self.handleRequestGenerates(di)
        else:
            self.handleMessageType(msgType, di)

        # If we're processing a lot of datagrams within one frame, we
        # may forget to send heartbeats.  Keep them coming!
        self.considerHeartbeat()

    def handleMessageType(self, msgType, di):
        self.notify.error("unrecognized message type %s" % (msgType))

    def handleUpdateField(self, di):
        # The CMU update message starts with an additional field, not
        # present in the Disney update message: the doIdBase of the
        # original sender.  Extract that and call up to the parent.
        self.currentSenderId = di.getUint32()
        ClientRepositoryBase.handleUpdateField(self, di)

    def handleDisable(self, di):
        # Receives a list of doIds.
        while di.getRemainingSize() > 0:
            doId = di.getUint32()

            # We should never get a disable message for our own object.
            assert not self.isLocalId(doId)
            self.disableDoId(doId)

    def handleDelete(self, di):
        # Receives a single doId.
        doId = di.getUint32()
        self.deleteObject(doId)

    def deleteObject(self, doId):
        """
        Removes the object from the client's view of the world.  This
        should normally not be called directly except in the case of
        error recovery, since the server will normally be responsible
        for deleting and disabling objects as they go out of scope.

        After this is called, future updates by server on this object
        will be ignored (with a warning message).  The object will
        become valid again the next time the server sends a generate
        message for this doId.

        This is not a distributed message and does not delete the
        object on the server or on any other client.
        """
        if doId in self.doId2do:
            # If it is in the dictionary, remove it.
            obj = self.doId2do[doId]
            # Remove it from the dictionary
            del self.doId2do[doId]
            # Disable, announce, and delete the object itself...
            # unless delayDelete is on...
            obj.deleteOrDelay()
            if self.isLocalId(doId):
                self.freeDoId(doId)
        elif self.cache.contains(doId):
            # If it is in the cache, remove it.
            self.cache.delete(doId)
            if self.isLocalId(doId):
                self.freeDoId(doId)
        else:
            # Otherwise, ignore it
            self.notify.warning(
                "Asked to delete non-existent DistObj " + str(doId))

    def stopTrackRequestDeletedDO(self, *args):
        # No-op.  Not entirely sure what this does on the VR Studio side.
        pass

    def sendUpdate(self, distObj, fieldName, args):
        """ Sends a normal update for a single field. """
        dg = distObj.dclass.clientFormatUpdate(
            fieldName, distObj.doId, args)
        self.send(dg)

    def sendUpdateToChannel(self, distObj, channelId, fieldName, args):

        """ Sends a targeted update of a single field to a particular
        client.  The top 32 bits of channelId is ignored; the lower 32
        bits should be the client Id of the recipient (i.e. the
        client's doIdbase).  The field update will be sent to the
        indicated client only.  The field must be marked clsend or
        p2p, and may not be marked broadcast. """

        datagram = distObj.dclass.clientFormatUpdate(
            fieldName, distObj.doId, args)
        dgi = PyDatagramIterator(datagram)

        # Reformat the packed datagram to change the message type and
        # add the target id.
        dgi.getUint16()
        
        dg = PyDatagram()
        dg.addUint16(CLIENT_OBJECT_UPDATE_FIELD_TARGETED_CMU)
        dg.addUint32(channelId & 0xffffffff)
        dg.appendData(dgi.getRemainingBytes())

        self.send(dg)
コード例 #6
0
class AIRepository:
    eventLoggerNotify = DirectNotify().newCategory("EventLogger")

    def __init__(self):
        base.cr = ClientRepository(
            dcFileNames=['phase_3/etc/direct.dc', 'phase_3/etc/toon.dc'],
            dcSuffix='AI')
        base.cr.connect([url],
                        successCallback=self.connectSuccess,
                        failureCallback=self.connectFailure)
        base.cTrav = CollisionTraverser()
        self.skeleton = 0
        if base.config.GetBool('want-suits', True):
            base.accept("SpawnSuit", self.createSuit)
        self.activeInvasion = False
        self.invasionSize = 0
        self.difficulty = ""
        self.title = DirectLabel(text="Server Menu",
                                 pos=(-0.05, -0.1, -0.1),
                                 scale=0.1,
                                 relief=None,
                                 text_fg=(1, 1, 1, 1),
                                 parent=base.a2dTopRight,
                                 text_align=TextNode.ARight)
        self.Suits = []
        base.pathNodes = []
        self.SuitCount = 0
        self.automaticSuits = 0
        self.hoodUtil = HoodUtil(base.cr)
        self.tournament = SuitTournament()
        self.lastChoice = None
        self.zoneAllocator = UniqueIdAllocator(50, 500)

    def allocateZone(self):
        return self.zoneAllocator.allocate()

    def freeZone(self, zone):
        self.zoneAllocator.free(zone)

    def logServerEvent(self, type, msg):
        self.eventLoggerNotify.info("%s: %s" % (type.upper(), msg))

    def killAllSuits(self):
        for suit in self.Suits:
            if not suit.distSuit.isDead() and suit.head != "vp":
                suit.distSuit.b_setHealth(0)

    def enableAutoSuits(self):
        self.automaticSuits = 1
        random_wait = random.uniform(5, 60)
        taskMgr.doMethodLater(random_wait, self.autoSuiter, "autoSuitSpawn")
        base.accept("control", self.disableAutoSuits)
        print "AutoSuit: Auto suits enabled."

    def disableAutoSuits(self):
        self.automaticSuits = 0
        taskMgr.remove("autoSuitSpawn")
        base.accept("control", self.enableAutoSuits)
        print "AutoSuit: Auto suits disabled."

    def isBossActive(self):
        for suit in self.Suits:
            if suit.head == "vp":
                return True
        return False

    def autoSuiter(self, task):
        random_choice = random.randint(0, 7)
        if self.lastChoice == 0 or self.lastChoice == 1 or self.lastChoice == 2 and self.SuitCount > 0:
            random_choice = random.randint(2, 6)
        elif self.lastChoice == 7:
            random_choice = random.randint(1, 6)

        if random_choice == 0 or random_choice == 1 or random_choice == 2:
            random_delay = random.randint(40, 80)
            choice = "invasion"
        elif random_choice == 3 or random_choice == 4 or random_choice == 5 or random_choice == 6:
            random_delay = random.randint(5, 20)
            choice = "suit"
        elif random_choice == 7:
            choice = "tournament"
            random_delay = random.randint(360, 700)
        self.lastChoice = random_choice
        if self.lastChoice == 7 and self.activeInvasion or self.SuitCount > 0:
            self.lastChoice = 1
            random_delay = random.randint(5, 80)
        if self.toonsAreInZone(20):
            self.createAutoSuit(choice)
            print "AutoSuit: Creating auto suit."
        else:
            random_delay = random.randint(20, 80)
            print "AutoSuit: Can't create an auto suit with no toons playing. Changing delay time."
        print "AutoSuit: Delay is %s seconds." % random_delay
        task.delayTime = random_delay
        return task.again

    def toonsArePlaying(self):
        for key in base.cr.doId2do.keys():
            obj = base.cr.doId2do[key]
            if obj.__class__.__name__ == "DistributedToon":
                return True
        return False

    def toonsAreInZone(self, zone):
        for key in base.cr.doId2do.keys():
            obj = base.cr.doId2do[key]
            if obj.__class__.__name__ == "DistributedToon":
                if obj.zoneId == zone:
                    return True
        return False

    def createAutoSuit(self, choice):
        if choice == "invasion":
            if self.SuitCount < 20 and not self.tournament.inTournament and not self.activeInvasion:
                # Spawn invasion
                random_diff = random.randint(0, 2)
                if random_diff == 0:
                    self.difficulty = "easy"
                elif random_diff == 1:
                    self.diffiuclty = "normal"
                elif random_diff == 2:
                    self.difficulty = "hard"
                random_size = random.randint(0, 2)
                if random_size == 0:
                    self.size = "small"
                elif random_size == 1:
                    self.size = "medium"
                elif random_size == 2:
                    self.size = "large"
                self.suit = "ABC"
                taskMgr.add(self.startInvasion,
                            "startInvasion",
                            extraArgs=[0],
                            appendTask=True)
        elif choice == "suit":
            if self.SuitCount < 25 and not self.tournament.inTournament:
                # Spawn suit
                random_type = random.randint(0, 2)
                if random_type == 0:
                    type = "A"
                elif random_type == 1:
                    type = "B"
                elif random_type == 2:
                    type = "C"
                self.createSuit(type)
        elif choice == "tournament":
            if self.SuitCount == 0 and not self.tournament.inTournament and not self.activeInvasion:
                # Spawn tournament
                self.tournament.initiateTournament()
            else:
                self.lastChoice = 1

        return None

    def callBackup(self, difficulty):
        self.difficulty = difficulty
        self.suit = "ABC"
        self.size = "medium"
        self.killAllSuits()
        taskMgr.doMethodLater(8,
                              self.startInvasion,
                              "startBackupInvasion",
                              extraArgs=[1],
                              appendTask=True)

    def sendSysMessage(self, textEntered):
        pkg = PyDatagram()
        pkg.addUint16(SYS_MSG)
        pkg.addString("SYSTEM: " + textEntered)
        base.sr.sendDatagram(pkg)

    def connectFailure(self):
        notify.warning(
            "failure connecting to gameserver, AI server will not initiate.")

    def connectSuccess(self):
        base.acceptOnce('createReady', self.createReady)

    def startInvasion(self, skeleton, task):
        if not self.activeInvasion and not self.tournament.inTournament:
            self.sendSysMessage("A " + ToontownGlobals.Suit +
                                " Invasion has begun in Toontown Central!!!")
        self.activeInvasion = True
        if self.isFullInvasion():
            return Task.done

        suitsNow = random.randint(0, 7)
        for suit in range(suitsNow):
            if self.isFullInvasion():
                break
            if self.suit == "ABC":
                random_suitType = random.randint(0, 2)
                if random_suitType == 0:
                    self.createSuit("A", skeleton=skeleton)
                elif random_suitType == 1:
                    self.createSuit("B", skeleton=skeleton)
                elif random_suitType == 2:
                    self.createSuit("C", skeleton=skeleton)
            else:
                self.createSuit(self.suit, skeleton=skeleton)

        task.delayTime = 4
        return Task.again

    def isFullInvasion(self):
        if self.size == "large":
            if self.SuitCount >= 20:
                return True
            else:
                return False
        elif self.size == "medium":
            if self.SuitCount >= 13:
                return True
            else:
                return False
        elif self.size == "small":
            if self.SuitCount >= 5:
                return True
            else:
                return False

    def createReady(self):
        self.hoodUtil.load("TT", AI=1)
        base.cr.setInterestZones([10, 20, 30])
        self.timeMgr = base.cr.createDistributedObject(
            className="TimeManagerAI", zoneId=10)
        self.createCChars()
        self.createMinigames()

    def createCChars(self):
        if base.config.GetBool('want-classic-chars', True):
            if base.config.GetBool('want-mickey', True):
                MickeyNPCBase(base.cr)
            if base.config.GetBool('want-goofy', True):
                GoofyNPCBase(base.cr)

    def createMinigames(self):
        if base.config.GetBool('want-minigames', True):
            if base.config.GetBool('want-race-game', True):
                mg1 = base.cr.createDistributedObject(
                    className="DistributedMinigameStationAI", zoneId=30)
                mg1.b_setStation(ToontownGlobals.RaceGame)
                mg1.b_setLocationPoint(0)
            if base.config.GetBool('want-uno-game', True):
                mg2 = base.cr.createDistributedObject(
                    className="DistributedMinigameStationAI", zoneId=30)
                mg2.b_setStation(ToontownGlobals.UnoGame)
                mg2.b_setLocationPoint(1)
            if base.config.GetBool('want-sneaky-game', True):
                mg3 = base.cr.createDistributedObject(
                    className="DistributedMinigameStationAI", zoneId=30)
                mg3.b_setStation(ToontownGlobals.SneakyGame)
                mg3.b_setLocationPoint(2)

    def createSuit(self, type, head=None, team=None, anySuit=1, skeleton=0):
        if self.SuitCount == 0 and not self.activeInvasion and not self.tournament.inTournament:
            self.sendSysMessage("A " + ToontownGlobals.Suit +
                                " is flying down in Toontown Central!")
        self.SuitCount += 1
        if not self.activeInvasion:
            self.invasionSize = 0
        if self.SuitCount == 1:
            if self.tournament.inTournament:
                if self.tournament.getRound() == 1:
                    self.hoodUtil.enableSuitEffect(0)
                    pkg = PyDatagram()
                    pkg.addUint16(SUITS_ACTIVE)
                    pkg.addUint32(self.invasionSize)
                    base.sr.sendDatagram(pkg)
            else:
                self.hoodUtil.enableSuitEffect(0)
                pkg = PyDatagram()
                pkg.addUint16(SUITS_ACTIVE)
                pkg.addUint32(self.invasionSize)
                base.sr.sendDatagram(pkg)
        if anySuit:
            if self.difficulty == "hard":
                if type == "B":
                    self.random_head = random.randint(7, 8)
                elif type == "C":
                    self.random_head = random.randint(7, 8)
                elif type == "A":
                    self.random_head = random.randint(8, 13)
            elif self.difficulty == "normal":
                if type == "B":
                    self.random_head = random.randint(3, 6)
                elif type == "C":
                    self.random_head = random.randint(4, 6)
                elif type == "A":
                    self.random_head = random.randint(4, 7)
            elif self.difficulty == "easy":
                if type == "B":
                    self.random_head = random.randint(0, 2)
                elif type == "C":
                    self.random_head = random.randint(0, 3)
                elif type == "A":
                    self.random_head = random.randint(0, 3)
            elif self.difficulty == "all" or self.difficulty == "":
                if type == "B" or type == "C":
                    self.random_head = random.randint(0, 8)
                elif type == "A":
                    self.random_head = random.randint(0, 13)
            if type == "C":
                if self.random_head == 0:
                    head = 'coldcaller'
                    team = 's'
                elif self.random_head == 1:
                    head = 'shortchange'
                    team = 'm'
                elif self.random_head == 2:
                    head = 'bottomfeeder'
                    team = 'l'
                elif self.random_head == 3:
                    head = 'flunky'
                    team = 'c'
                elif self.random_head == 4:
                    head = 'tightwad'
                    team = 'm'
                elif self.random_head == 5:
                    head = 'micromanager'
                    team = 'c'
                elif self.random_head == 6:
                    head = 'gladhander'
                    team = 's'
                elif self.random_head == 7:
                    head = 'moneybags'
                    team = 'm'
                elif self.random_head == 8:
                    head = 'corporateraider'
                    team = 'c'
            if type == "B":
                if self.random_head == 0:
                    head = 'pencilpusher'
                    team = 'c'
                elif self.random_head == 1:
                    head = 'bloodsucker'
                    team = 'l'
                elif self.random_head == 2:
                    head = 'telemarketer'
                    team = 's'
                elif self.random_head == 3:
                    head = 'ambulancechaser'
                    team = 'l'
                elif self.random_head == 4:
                    head = 'beancounter'
                    team = 'm'
                elif self.random_head == 5:
                    head = 'downsizer'
                    team = 'c'
                elif self.random_head == 6:
                    head = 'movershaker'
                    team = 's'
                elif self.random_head == 7:
                    head = 'spindoctor'
                    team = 'l'
                elif self.random_head == 8:
                    head = 'loanshark'
                    team = 'm'
            if type == "A":
                if self.random_head == 0:
                    head = 'pennypincher'
                    team = 'm'
                elif self.random_head == 1:
                    head = 'yesman'
                    team = 'c'
                elif self.random_head == 2:
                    head = 'doubletalker'
                    team = 'l'
                elif self.random_head == 3:
                    head = 'namedropper'
                    team = 's'
                elif self.random_head == 4:
                    head = 'backstabber'
                    team = 'l'
                elif self.random_head == 5:
                    head = 'numbercruncher'
                    team = 'm'
                elif self.random_head == 6:
                    head = 'headhunter'
                    team = 'c'
                elif self.random_head == 7:
                    head = 'twoface'
                    team = 's'
                elif self.random_head == 8:
                    head = 'legaleagle'
                    team = 'l'
                elif self.random_head == 9:
                    head = 'mingler'
                    team = 's'
                elif self.random_head == 10:
                    head = 'bigcheese'
                    team = 'c'
                elif self.random_head == 11:
                    head = 'bigwig'
                    team = 'l'
                elif self.random_head == 12:
                    head = 'robberbaron'
                    team = 'm'
                elif self.random_head == 13:
                    head = 'mrhollywood'
                    team = 's'
        if head == "vp":
            self.handleBossSpawned()
        SuitBase(base.cr, type, head, team, skeleton)

    def handleBossSpawned(self):
        bosses = 0
        for suit in self.Suits:
            if suit.head == "vp":
                bosses += 1
        if bosses == 0:
            self.sendBossSpawned()

    def sendBossSpawned(self):
        pkg = PyDatagram()
        pkg.addUint16(BOSS_SPAWNED)
        base.sr.sendDatagram(pkg)

    def deadSuit(self):
        self.SuitCount -= 1
        if self.SuitCount < 0:
            self.SuitCount = 0
        if self.tournament.inTournament:
            self.tournament.handleDeadSuit()
            return
        if self.SuitCount == 0:
            if self.activeInvasion:
                self.activeInvasion = False
            self.hoodUtil.disableSuitEffect()
            pkg = PyDatagram()
            pkg.addUint16(SUITS_INACTIVE)
            base.sr.sendDatagram(pkg)
コード例 #7
0
class ClientRepository(ClientRepositoryBase):
    notify = DirectNotifyGlobal.directNotify.newCategory('ClientRepository')
    GameGlobalsId = 0
    doNotDeallocateChannel = True

    def __init__(self, dcFileNames=None, dcSuffix='', connectMethod=None, threadedNet=None):
        ClientRepositoryBase.__init__(self, dcFileNames=dcFileNames, dcSuffix=dcSuffix, connectMethod=connectMethod, threadedNet=threadedNet)
        self.setHandleDatagramsInternally(False)
        base.finalExitCallbacks.append(self.shutdown)
        self.doIdAllocator = None
        self.doIdBase = 0
        self.doIdLast = 0
        self.currentSenderId = None
        self.interestZones = []
        return

    def handleSetDoIdrange(self, di):
        self.doIdBase = di.getUint32()
        self.doIdLast = self.doIdBase + di.getUint32()
        self.doIdAllocator = UniqueIdAllocator(self.doIdBase, self.doIdLast - 1)
        self.ourChannel = self.doIdBase
        self.createReady()

    def createReady(self):
        messenger.send('createReady', taskChain='default')
        messenger.send(self.uniqueName('createReady'), taskChain='default')

    def handleRequestGenerates(self, di):
        zone = di.getUint32()
        for obj in self.doId2do.values():
            if obj.zoneId == zone:
                if self.isLocalId(obj.doId):
                    self.resendGenerate(obj)

    def resendGenerate(self, obj):
        extraFields = []
        for i in range(obj.dclass.getNumInheritedFields()):
            field = obj.dclass.getInheritedField(i)
            if field.hasKeyword('broadcast') and field.hasKeyword('ram') and not field.hasKeyword('required'):
                if field.asMolecularField():
                    continue
                extraFields.append(field.getName())

        datagram = self.formatGenerate(obj, extraFields)
        self.send(datagram)

    def handleGenerate(self, di):
        self.currentSenderId = di.getUint32()
        zoneId = di.getUint32()
        classId = di.getUint16()
        doId = di.getUint32()
        dclass = self.dclassesByNumber[classId]
        distObj = self.doId2do.get(doId)
        if distObj and distObj.dclass == dclass:
            dclass.receiveUpdateBroadcastRequired(distObj, di)
            dclass.receiveUpdateOther(distObj, di)
            return
        dclass.startGenerate()
        distObj = self.generateWithRequiredOtherFields(dclass, doId, di, 0, zoneId)
        dclass.stopGenerate()

    def allocateDoId(self):
        return self.doIdAllocator.allocate()

    def reserveDoId(self, doId):
        self.doIdAllocator.initialReserveId(doId)
        return doId

    def freeDoId(self, doId):
        self.doIdAllocator.free(doId)

    def storeObjectLocation(self, object, parentId, zoneId):
        object.parentId = parentId
        object.zoneId = zoneId

    def createDistributedObject(self, className=None, distObj=None, zoneId=0, optionalFields=None, doId=None, reserveDoId=False):
        if not className:
            if not distObj:
                self.notify.error('Must specify either a className or a distObj.')
            className = distObj.__class__.__name__
        if doId is None:
            doId = self.allocateDoId()
        else:
            if reserveDoId:
                self.reserveDoId(doId)
        dclass = self.dclassesByName.get(className)
        if not dclass:
            self.notify.error('Unknown distributed class: %s' % distObj.__class__)
        classDef = dclass.getClassDef()
        if classDef == None:
            self.notify.error('Could not create an undefined %s object.' % dclass.getName())
        if not distObj:
            distObj = classDef(self)
        if not isinstance(distObj, classDef):
            self.notify.error('Object %s is not an instance of %s' % (distObj.__class__.__name__, classDef.__name__))
        distObj.dclass = dclass
        distObj.doId = doId
        self.doId2do[doId] = distObj
        distObj.generateInit()
        distObj._retrieveCachedData()
        distObj.generate()
        distObj.setLocation(0, zoneId)
        distObj.announceGenerate()
        datagram = self.formatGenerate(distObj, optionalFields)
        self.send(datagram)
        return distObj

    def formatGenerate(self, distObj, extraFields):
        return distObj.dclass.clientFormatGenerateCMU(distObj, distObj.doId, distObj.zoneId, extraFields)

    def sendDeleteMsg(self, doId):
        datagram = PyDatagram()
        datagram.addUint16(OBJECT_DELETE_CMU)
        datagram.addUint32(doId)
        self.send(datagram)

    def sendDisconnect(self):
        if self.isConnected():
            datagram = PyDatagram()
            datagram.addUint16(CLIENT_DISCONNECT_CMU)
            self.send(datagram)
            self.notify.info('Sent disconnect message to server')
            self.disconnect()
        self.stopHeartbeat()

    def setInterestZones(self, interestZoneIds):
        datagram = PyDatagram()
        datagram.addUint16(CLIENT_SET_INTEREST_CMU)
        for zoneId in interestZoneIds:
            datagram.addUint32(zoneId)

        self.send(datagram)
        self.interestZones = interestZoneIds[:]

    def setObjectZone(self, distObj, zoneId):
        distObj.b_setLocation(0, zoneId)
        self.resendGenerate(distObj)

    def sendSetLocation(self, doId, parentId, zoneId):
        datagram = PyDatagram()
        datagram.addUint16(OBJECT_SET_ZONE_CMU)
        datagram.addUint32(doId)
        datagram.addUint32(zoneId)
        self.send(datagram)

    def sendHeartbeat(self):
        datagram = PyDatagram()
        datagram.addUint16(CLIENT_HEARTBEAT_CMU)
        self.send(datagram)
        self.lastHeartbeat = globalClock.getRealTime()
        self.considerFlush()

    def isLocalId(self, doId):
        return doId >= self.doIdBase and doId < self.doIdLast

    def haveCreateAuthority(self):
        return self.doIdLast > self.doIdBase

    def getAvatarIdFromSender(self):
        return self.currentSenderId

    def handleDatagram(self, di):
        if self.notify.getDebug():
            print 'ClientRepository received datagram:'
            di.getDatagram().dumpHex(ostream)
        msgType = self.getMsgType()
        self.currentSenderId = None
        if msgType == SET_DOID_RANGE_CMU:
            self.handleSetDoIdrange(di)
        else:
            if msgType == OBJECT_GENERATE_CMU:
                self.handleGenerate(di)
            else:
                if msgType == OBJECT_UPDATE_FIELD_CMU:
                    self.handleUpdateField(di)
                else:
                    if msgType == OBJECT_DISABLE_CMU:
                        self.handleDisable(di)
                    else:
                        if msgType == OBJECT_DELETE_CMU:
                            self.handleDelete(di)
                        else:
                            if msgType == REQUEST_GENERATES_CMU:
                                self.handleRequestGenerates(di)
                            else:
                                self.handleMessageType(msgType, di)
        self.considerHeartbeat()
        return

    def handleMessageType(self, msgType, di):
        self.notify.error('unrecognized message type %s' % msgType)

    def handleUpdateField(self, di):
        self.currentSenderId = di.getUint32()
        ClientRepositoryBase.handleUpdateField(self, di)

    def handleDisable(self, di):
        while di.getRemainingSize() > 0:
            doId = di.getUint32()
            self.disableDoId(doId)

    def handleDelete(self, di):
        doId = di.getUint32()
        self.deleteObject(doId)

    def deleteObject(self, doId):
        if doId in self.doId2do:
            obj = self.doId2do[doId]
            del self.doId2do[doId]
            obj.deleteOrDelay()
            if self.isLocalId(doId):
                self.freeDoId(doId)
        else:
            if self.cache.contains(doId):
                self.cache.delete(doId)
                if self.isLocalId(doId):
                    self.freeDoId(doId)
            else:
                self.notify.warning('Asked to delete non-existent DistObj ' + str(doId))

    def stopTrackRequestDeletedDO(self, *args):
        pass

    def sendUpdate(self, distObj, fieldName, args):
        dg = distObj.dclass.clientFormatUpdate(fieldName, distObj.doId, args)
        self.send(dg)

    def sendUpdateToChannel(self, distObj, channelId, fieldName, args):
        datagram = distObj.dclass.clientFormatUpdate(fieldName, distObj.doId, args)
        dgi = PyDatagramIterator(datagram)
        dgi.getUint16()
        dg = PyDatagram()
        dg.addUint16(CLIENT_OBJECT_UPDATE_FIELD_TARGETED_CMU)
        dg.addUint32(channelId & 4294967295L)
        dg.appendData(dgi.getRemainingBytes())
        self.send(dg)