Browse Source

Add Distributed Network samples

1: Simple client-server connection
2: Client-server connection with timeManager
3: Create distributed objects, sending and receiving messages
4: Distributed model file
5: Simple text chat
6: Simple smooth moving actor
Fireclaw 5 years ago
parent
commit
3d8f824081
39 changed files with 2077 additions and 0 deletions
  1. 123 0
      samples/networking/01-simple-connection/client.py
  2. 31 0
      samples/networking/01-simple-connection/server.py
  3. 81 0
      samples/networking/02-time-synced-connection/AIRepository.py
  4. 136 0
      samples/networking/02-time-synced-connection/client.py
  5. 34 0
      samples/networking/02-time-synced-connection/server.py
  6. 29 0
      samples/networking/03-distributed-node/AIDGameObject.py
  7. 25 0
      samples/networking/03-distributed-node/AIDGameObjectAI.py
  8. 86 0
      samples/networking/03-distributed-node/AIRepository.py
  9. 130 0
      samples/networking/03-distributed-node/ClientRepository.py
  10. 15 0
      samples/networking/03-distributed-node/DGameObject.py
  11. 20 0
      samples/networking/03-distributed-node/ServerRepository.py
  12. 136 0
      samples/networking/03-distributed-node/client.py
  13. 17 0
      samples/networking/03-distributed-node/sample.dc
  14. 18 0
      samples/networking/03-distributed-node/server.py
  15. 81 0
      samples/networking/04-distributed-model/AIRepository.py
  16. 116 0
      samples/networking/04-distributed-model/ClientRepository.py
  17. 61 0
      samples/networking/04-distributed-model/DModel.py
  18. 20 0
      samples/networking/04-distributed-model/ServerRepository.py
  19. 43 0
      samples/networking/04-distributed-model/client.py
  20. 3 0
      samples/networking/04-distributed-model/sample.dc
  21. 18 0
      samples/networking/04-distributed-model/server.py
  22. 81 0
      samples/networking/05-small-chat/AIRepository.py
  23. 104 0
      samples/networking/05-small-chat/ClientRepository.py
  24. 85 0
      samples/networking/05-small-chat/client.py
  25. 25 0
      samples/networking/05-small-chat/message.py
  26. 5 0
      samples/networking/05-small-chat/sample.dc
  27. 34 0
      samples/networking/05-small-chat/server.py
  28. 81 0
      samples/networking/06-simple-avatar/AIRepository.py
  29. 103 0
      samples/networking/06-simple-avatar/ClientRepository.py
  30. 50 0
      samples/networking/06-simple-avatar/DistributedSmoothActor.py
  31. 20 0
      samples/networking/06-simple-avatar/ServerRepository.py
  32. 151 0
      samples/networking/06-simple-avatar/client.py
  33. BIN
      samples/networking/06-simple-avatar/models/ralph-run.egg.pz
  34. BIN
      samples/networking/06-simple-avatar/models/ralph-walk.egg.pz
  35. BIN
      samples/networking/06-simple-avatar/models/ralph.egg.pz
  36. BIN
      samples/networking/06-simple-avatar/models/ralph.jpg
  37. 6 0
      samples/networking/06-simple-avatar/sample.dc
  38. 18 0
      samples/networking/06-simple-avatar/server.py
  39. 91 0
      samples/networking/direct.dc

+ 123 - 0
samples/networking/01-simple-connection/client.py

@@ -0,0 +1,123 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# initialize the engine
+base = ShowBase()
+
+# initialize the client
+
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import TextNode
+
+
+base.accept("escape", exit)
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+    return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
+                        parent=base.a2dTopLeft, align=TextNode.ALeft,
+                        pos=(0.08, -pos - 0.04), scale=.06)
+
+# Function to put title on the screen.
+def addTitle(text):
+    return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+                        parent=base.a2dBottomRight, align=TextNode.ARight,
+                        fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
+inst2 = addInstructions(0.06, "esc: Close the client")
+inst2 = addInstructions(0.12, "See console output")
+
+def setConnectedMessage():
+    title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
+
+base.accept("client-ready", setConnectedMessage)
+
+#
+# CLIENT
+#
+
+class GameClientRepository(ClientRepository):
+
+    def __init__(self):
+        dcFileNames = ['../direct.dc']
+
+        # a distributed object of our game.
+        self.distributedObject = None
+        self.aiDGameObect = None
+
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # uses another protocol then http you should change it accordingly.
+        # Make sure to pass the connectMethod to the  ClientRepository.__init__
+        # call too.  Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([self.url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def lostConnection(self):
+        """ This should be overridden by a derived class to handle an
+        unexpectedly lost connection to the gameserver. """
+        # Handle the disconnection from the server.  This can be a reconnect,
+        # simply exiting the application or anything else.
+        exit()
+
+    def connectFailure(self, statusCode, statusString):
+        """ Something went wrong """
+        # here we could create a reconnect task to try and connect again.
+        exit()
+
+    def connectSuccess(self):
+        """ Successfully connected.  But we still can't really do
+        anything until we've got the doID range. """
+
+        # Mark interest for zone 1, 2 and 3.  There won't be anything in them,
+        # it's just to display how it works for this small example.
+        self.setInterestZones([1, 2, 3])
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if self.haveCreateAuthority():
+            # we already have one
+            self.gotCreateReady()
+        else:
+            # Not yet, keep waiting a bit longer.
+            self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+    def gotCreateReady(self):
+        """ Ready to enter the world.  Expand our interest to include
+        any other zones """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore(self.uniqueName('createReady'))
+
+        print("Client Ready")
+        base.messenger.send("client-ready")
+
+# Start the client
+GameClientRepository()
+
+base.run()

+ 31 - 0
samples/networking/01-simple-connection/server.py

@@ -0,0 +1,31 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# the main server class
+class GameServerRepository(ServerRepository):
+    """The server repository class"""
+    def __init__(self):
+        """initialise the server class"""
+
+        # get the port number from the configuration file
+        # if it doesn't exist, we use 4400 as the default
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # list of all needed .dc files
+        dcFileNames = ['../direct.dc']
+
+        # initialise a threaded server on this machine with
+        # the port number and the dc filenames
+        ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
+
+# start the server
+GameServerRepository()
+
+base.run()

+ 81 - 0
samples/networking/02-time-synced-connection/AIRepository.py

@@ -0,0 +1,81 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+
+class AIRepository(ClientRepository):
+    def __init__(self):
+        """ The AI Repository usually lives on a server and is responsible for
+        server side logic that will handle game objects """
+
+        # List of all dc files that are of interest to this AI Repository
+        dcFileNames = ['../direct.dc']
+
+        # Initialize the repository.  We pass it the dc files and as this is an
+        # AI repository the dcSuffix AI.  This will make sure any later calls to
+        # createDistributedObject will use the correct version.
+        # The connectMethod
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            dcSuffix = 'AI',
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # doesn't use http you should change it accordingly. Make sure to pass
+        # the connectMethod to the  ClientRepository.__init__ call too.
+        # Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def connectFailure(self, statusCode, statusString):
+        """ something went wrong """
+        print("Couldn't connect. Make sure to run server.py first!")
+        raise(StandardError, statusString)
+
+    def connectSuccess(self):
+        """ Successfully connected.  But we still can't really do
+        anything until we've got the doID range. """
+        # The Client Repository will throw this event as soon as it has a doID
+        # range and would be able to create distributed objects
+        self.accept('createReady', self.gotCreateReady)
+
+    def lostConnection(self):
+        """ This should be overridden by a derived class to handle an
+         unexpectedly lost connection to the gameserver. """
+        exit()
+
+    def gotCreateReady(self):
+        """ Now we're ready to go! """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore('createReady')
+
+        # Create a Distributed Object by name.  This will look up the object in
+        # the dc files passed to the repository earlier
+        self.timeManager = self.createDistributedObject(
+            className = 'TimeManagerAI', # The Name of the Class we want to initialize
+            zoneId = 1) # The Zone this Object will live in
+
+        print("AI Repository Ready")
+
+    def deallocateChannel(self, doID):
+        """ This method will be called whenever a client disconnects from the
+        server.  The given doID is the ID of the client who left us. """
+        print("Client left us: ", doID)

+ 136 - 0
samples/networking/02-time-synced-connection/client.py

@@ -0,0 +1,136 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# initialize the engine
+base = ShowBase()
+
+# initialize the client
+
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import TextNode
+
+
+base.accept("escape", exit)
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+    return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
+                        parent=base.a2dTopLeft, align=TextNode.ALeft,
+                        pos=(0.08, -pos - 0.04), scale=.06)
+
+# Function to put title on the screen.
+def addTitle(text):
+    return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+                        parent=base.a2dBottomRight, align=TextNode.ARight,
+                        fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
+inst1 = addInstructions(0.06, "esc: Close the client")
+inst2 = addInstructions(0.12, "See console output")
+
+def setConnectedMessage():
+    title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
+
+base.accept("client-ready", setConnectedMessage)
+
+#
+# CLIENT
+#
+
+class GameClientRepository(ClientRepository):
+
+    def __init__(self):
+        dcFileNames = ['../direct.dc']
+
+        # a distributed object of our game.
+        self.distributedObject = None
+        self.aiDGameObect = None
+
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # uses another protocol then http you should change it accordingly.
+        # Make sure to pass the connectMethod to the  ClientRepository.__init__
+        # call too.  Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([self.url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def lostConnection(self):
+        """ This should be overridden by a derived class to handle an
+        unexpectedly lost connection to the gameserver. """
+        # Handle the disconnection from the server.  This can be a reconnect,
+        # simply exiting the application or anything else.
+        exit()
+
+    def connectFailure(self, statusCode, statusString):
+        """ Something went wrong """
+        # we could create a reconnect task to try and connect again.
+        exit()
+
+    def connectSuccess(self):
+        """ Successfully connected.  But we still can't really do
+        anything until we've got the doID range. """
+
+        # Make sure we have interest in the by the AIRepository defined
+        # TimeManager zone, so we always see it even if we switch to
+        # another zone.
+        self.setInterestZones([1])
+
+        # We must wait for the TimeManager to be fully created and
+        # synced before we can enter another zone and wait for the
+        # game object.  The uniqueName is important that we get the
+        # correct, our sync message from the TimeManager and not
+        # accidentaly a message from another client
+        self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+    def syncReady(self):
+        """ Now we've got the TimeManager manifested, and we're in
+        sync with the server time.  Now we can enter the world.  Check
+        to see if we've received our doIdBase yet. """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if self.haveCreateAuthority():
+            # we already have one
+            self.gotCreateReady()
+        else:
+            # Not yet, keep waiting a bit longer.
+            self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+    def gotCreateReady(self):
+        """ Ready to enter the world.  Expand our interest to include
+        any other zones """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore(self.uniqueName('createReady'))
+
+        print("Client Ready")
+        base.messenger.send("client-ready")
+
+# Start the client
+client = GameClientRepository()
+
+base.run()

+ 34 - 0
samples/networking/02-time-synced-connection/server.py

@@ -0,0 +1,34 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+from AIRepository import AIRepository
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# the main server class
+class GameServerRepository(ServerRepository):
+    """The server repository class"""
+    def __init__(self):
+        """initialise the server class"""
+
+        # get the port number from the configuration file
+        # if it doesn't exist, we use 4400 as the default
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # list of all needed .dc files
+        dcFileNames = ['../direct.dc']
+
+        # initialise a threaded server on this machine with
+        # the port number and the dc filenames
+        ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
+
+# start the server
+GameServerRepository()
+AIRepository()
+
+base.run()

+ 29 - 0
samples/networking/03-distributed-node/AIDGameObject.py

@@ -0,0 +1,29 @@
+from direct.distributed.DistributedObject import DistributedObject
+
+class AIDGameObject(DistributedObject):
+    """ This class is a DirectObject which will be created and managed by the
+    AI Repository. """
+
+    def __init__(self, cr):
+        DistributedObject.__init__(self, cr)
+
+    def announceGenerate(self):
+        """ The AI has created this object, so we send it's distributed object ID
+        over to the client.  That way the client can actually grab the object
+        and use it to communicate with the AI.  Alternatively store it in the
+        Client Repository in self.cr """
+        base.messenger.send(self.cr.uniqueName('AIDGameObjectGenerated'), [self.doId])
+        # call the base class method
+        DistributedObject.announceGenerate(self)
+
+    def d_requestDataFromAI(self):
+        """ Request some data from the AI and passing it some data from us. """
+        data = ("Some Data", 1, -1.25)
+        print("Sending game data:", data)
+        self.sendUpdate('messageRoundtripToAI', [data])
+
+    def messageRoundtripToClient(self, data):
+        """ Here we expect the answer from the AI from a previous
+        messageRoundtripToAI call """
+        print("Got Data:", data)
+        print("Roundtrip message complete")

+ 25 - 0
samples/networking/03-distributed-node/AIDGameObjectAI.py

@@ -0,0 +1,25 @@
+from direct.distributed.DistributedObjectAI import DistributedObjectAI
+
+class AIDGameObjectAI(DistributedObjectAI):
+    def __init__(self, aiRepository):
+        DistributedObjectAI.__init__(self, aiRepository)
+
+    def messageRoundtripToAI(self, data):
+        """ The client sent us some data to process.  So work with it and send
+        changed data back to the requesting client """
+        requesterId = self.air.getAvatarIdFromSender()
+        print("Got client data:", data, "from client with ID", requesterId)
+
+        # do something with the data
+        aiChangedData = (
+            data[0] + " from the AI",
+            data[1] + 1,
+            data[2])
+
+        print("Sending modified game data back:", aiChangedData)
+        self.d_messageRoundtripToClient(aiChangedData, requesterId)
+
+    def d_messageRoundtripToClient(self, data, requesterId):
+        """ Send the given data to the requesting client """
+        print("Send message to back to:", requesterId)
+        self.sendUpdateToAvatarId(requesterId, 'messageRoundtripToClient', [data])

+ 86 - 0
samples/networking/03-distributed-node/AIRepository.py

@@ -0,0 +1,86 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from AIDGameObjectAI import AIDGameObjectAI
+
+class AIRepository(ClientRepository):
+    def __init__(self):
+        """ The AI Repository usually lives on a server and is responsible for
+        server side logic that will handle game objects """
+
+        # List of all dc files that are of interest to this AI Repository
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        # Initialize the repository.  We pass it the dc files and as this is an
+        # AI repository the dcSuffix AI.  This will make sure any later calls to
+        # createDistributedObject will use the correct version.
+        # The connectMethod
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            dcSuffix = 'AI',
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # doesn't use http you should change it accordingly. Make sure to pass
+        # the connectMethod to the  ClientRepository.__init__ call too.
+        # Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def connectFailure(self, statusCode, statusString):
+        """ something went wrong """
+        print("Couldn't connect. Make sure to run server.py first!")
+        raise(StandardError, statusString)
+
+    def connectSuccess(self):
+        """ Successfully connected.  But we still can't really do
+        anything until we've got the doID range. """
+        # The Client Repository will throw this event as soon as it has a doID
+        # range and would be able to create distributed objects
+        self.accept('createReady', self.gotCreateReady)
+
+    def lostConnection(self):
+        """ This should be overridden by a derived class to handle an
+         unexpectedly lost connection to the gameserver. """
+        exit()
+
+    def gotCreateReady(self):
+        """ Now we're ready to go! """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore('createReady')
+
+        # Create a Distributed Object by name.  This will look up the object in
+        # the dc files passed to the repository earlier
+        self.timeManager = self.createDistributedObject(
+            className = 'TimeManagerAI', # The Name of the Class we want to initialize
+            zoneId = 1) # The Zone this Object will live in
+
+        self.gameDistObject = self.createDistributedObject(
+            className = 'AIDGameObjectAI',
+            zoneId = 2)
+
+        print("AI Repository Ready")
+
+    def deallocateChannel(self, doID):
+        """ This method will be called whenever a client disconnects from the
+        server.  The given doID is the ID of the client who left us. """
+        print("Client left us: ", doID)

+ 130 - 0
samples/networking/03-distributed-node/ClientRepository.py

@@ -0,0 +1,130 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from DGameObject import DGameObject
+
+class GameClientRepository(ClientRepository):
+
+    def __init__(self):
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        # a distributed object of our game.
+        self.distributedObject = None
+        self.aiDGameObect = None
+
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # uses another protocol then http you should change it accordingly.
+        # Make sure to pass the connectMethod to the  ClientRepository.__init__
+        # call too.  Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([self.url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def lostConnection(self):
+        """ This should be overridden by a derived class to handle an
+        unexpectedly lost connection to the gameserver. """
+        # Handle the disconnection from the server.  This can be a reconnect,
+        # simply exiting the application or anything else.
+        exit()
+
+    def connectFailure(self, statusCode, statusString):
+        """ Something went wrong """
+        exit()
+
+    def connectSuccess(self):
+        """ Successfully connected.  But we still can't really do
+        anything until we've got the doID range. """
+
+        # Make sure we have interest in the by the AIRepository defined
+        # TimeManager zone, so we always see it even if we switch to
+        # another zone.
+        self.setInterestZones([1])
+
+        # We must wait for the TimeManager to be fully created and
+        # synced before we can enter another zone and wait for the
+        # game object.  The uniqueName is important that we get the
+        # correct, our sync message from the TimeManager and not
+        # accidentaly a message from another client
+        self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+    def syncReady(self):
+        """ Now we've got the TimeManager manifested, and we're in
+        sync with the server time.  Now we can enter the world.  Check
+        to see if we've received our doIdBase yet. """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if self.haveCreateAuthority():
+            # we already have one
+            self.gotCreateReady()
+        else:
+            # Not yet, keep waiting a bit longer.
+            self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+    def gotCreateReady(self):
+        """ Ready to enter the world.  Expand our interest to include
+        any other zones """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore(self.uniqueName('createReady'))
+
+        self.join()
+
+        print("Client Ready")
+
+    def join(self):
+        """ Join a game/room/whatever """
+        self.accept(self.uniqueName('AIDGameObjectGenerated'), self.aiDGameObectGenerated)
+
+        # set our intersted zones to let the client see all distributed obects
+        # in those zones
+        self.setInterestZones([1, 2])
+
+        # Manifest a object on the server.  The object will have our "base" doId.
+        self.distributedObject = DGameObject(self)
+        self.createDistributedObject(
+            distObj = self.distributedObject,
+            zoneId = 2)
+
+        base.messenger.send("client-joined")
+        print("Joined")
+
+    def aiDGameObectGenerated(self, doId):
+        print("AIDGameObect was generated")
+        self.aiDGameObect = self.doId2do[doId]
+
+    def sendGameData(self):
+        if not self.distributedObject: return
+
+        print("send game data")
+
+        # send a message to the server
+        self.distributedObject.d_sendGameData()
+
+    def sendRoundtripToAI(self):
+        if not self.aiDGameObect: return
+
+        print("Initiate roundtrip message to AI Server")
+
+        self.aiDGameObect.d_requestDataFromAI()

+ 15 - 0
samples/networking/03-distributed-node/DGameObject.py

@@ -0,0 +1,15 @@
+from direct.distributed.DistributedObject import DistributedObject
+
+class DGameObject(DistributedObject):
+    def __init__(self, cr):
+        DistributedObject.__init__(self, cr)
+
+    def sendGameData(self, data):
+        """ Method that can be called from the clients with an sendUpdate call """
+        print(data)
+
+    def d_sendGameData(self):
+        """ A method to send an update message to the server.  The d_ stands
+        for distributed """
+        # send the message to the server
+        self.sendUpdate('sendGameData', [('ValueA', 123, 1.25)])

+ 20 - 0
samples/networking/03-distributed-node/ServerRepository.py

@@ -0,0 +1,20 @@
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+# the main server class
+class GameServerRepository(ServerRepository):
+    """The server repository class"""
+    def __init__(self):
+        """initialise the server class"""
+
+        # get the port number from the configuration file
+        # if it doesn't exist, we use 4400 as the default
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # list of all needed .dc files
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        # initialise a threaded server on this machine with
+        # the port number and the dc filenames
+        ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)

+ 136 - 0
samples/networking/03-distributed-node/client.py

@@ -0,0 +1,136 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# initialize the engine
+base = ShowBase()
+
+# initialize the client
+
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import TextNode
+
+
+base.accept("escape", exit)
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+    return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
+                        parent=base.a2dTopLeft, align=TextNode.ALeft,
+                        pos=(0.08, -pos - 0.04), scale=.06)
+
+# Function to put title on the screen.
+def addTitle(text):
+    return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+                        parent=base.a2dBottomRight, align=TextNode.ARight,
+                        fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
+inst1 = addInstructions(0.06, "esc: Close the client")
+inst2 = addInstructions(0.12, "See console output")
+
+def setConnectedMessage():
+    title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
+
+base.accept("client-ready", setConnectedMessage)
+
+#
+# CLIENT
+#
+
+class GameClientRepository(ClientRepository):
+
+    def __init__(self):
+        dcFileNames = ['../direct.dc']
+
+        # a distributed object of our game.
+        self.distributedObject = None
+        self.aiDGameObect = None
+
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # uses another protocol then http you should change it accordingly.
+        # Make sure to pass the connectMethod to the  ClientRepository.__init__
+        # call too.  Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([self.url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def lostConnection(self):
+        """ This should be overridden by a derived class to handle an
+        unexpectedly lost connection to the gameserver. """
+        # Handle the disconnection from the server.  This can be a reconnect,
+        # simply exiting the application or anything else.
+        exit()
+
+    def connectFailure(self, statusCode, statusString):
+        """ Something went wrong """
+        # we could create a reconnect task to try and connect again.
+        exit()
+
+    def connectSuccess(self):
+        """ Successfully connected.  But we still can't really do
+        anything until we've got the doID range. """
+
+        # Make sure we have interest in the by the AIRepository defined
+        # TimeManager zone, so we always see it even if we switch to
+        # another zone.
+        self.setInterestZones([1])
+
+        # We must wait for the TimeManager to be fully created and
+        # synced before we can enter another zone and wait for the
+        # game object.  The uniqueName is important that we get the
+        # correct, our sync message from the TimeManager and not
+        # accidentaly a message from another client
+        self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+    def syncReady(self):
+        """ Now we've got the TimeManager manifested, and we're in
+        sync with the server time.  Now we can enter the world.  Check
+        to see if we've received our doIdBase yet. """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if self.haveCreateAuthority():
+            # we already have one
+            self.gotCreateReady()
+        else:
+            # Not yet, keep waiting a bit longer.
+            self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+    def gotCreateReady(self):
+        """ Ready to enter the world.  Expand our interest to include
+        any other zones """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore(self.uniqueName('createReady'))
+
+        print("Client Ready")
+        base.messenger.send("client-ready")
+
+# Start the client
+client = GameClientRepository()
+
+base.run()

+ 17 - 0
samples/networking/03-distributed-node/sample.dc

@@ -0,0 +1,17 @@
+import DGameObject
+import AIDGameObject/AI
+
+struct gameDataModel {
+    string value_a;
+    uint8 value_b;
+    int8 value_c/100;
+}
+
+dclass DGameObject: DistributedObject {
+    sendGameData(gameDataModel data) broadcast;
+};
+
+dclass AIDGameObject: DistributedObject {
+    messageRoundtripToAI(gameDataModel data) p2p;
+    messageRoundtripToClient(gameDataModel data) p2p;
+}

+ 18 - 0
samples/networking/03-distributed-node/server.py

@@ -0,0 +1,18 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# import our own repositories
+from ServerRepository import GameServerRepository
+from AIRepository import AIRepository
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# instantiate the server
+GameServerRepository()
+
+# The AI Repository to manage server side (AI) clients
+AIRepository()
+
+# start the server
+base.run()

+ 81 - 0
samples/networking/04-distributed-model/AIRepository.py

@@ -0,0 +1,81 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+
+class AIRepository(ClientRepository):
+    def __init__(self):
+        """ The AI Repository usually lives on a server and is responsible for
+        server side logic that will handle game objects """
+
+        # List of all dc files that are of interest to this AI Repository
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        # Initialize the repository.  We pass it the dc files and as this is an
+        # AI repository the dcSuffix AI.  This will make sure any later calls to
+        # createDistributedObject will use the correct version.
+        # The connectMethod
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            dcSuffix = 'AI',
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # doesn't use http you should change it accordingly. Make sure to pass
+        # the connectMethod to the  ClientRepository.__init__ call too.
+        # Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def connectFailure(self, statusCode, statusString):
+        """ something went wrong """
+        print("Couldn't connect. Make sure to run server.py first!")
+        raise(StandardError, statusString)
+
+    def connectSuccess(self):
+        """ Successfully connected.  But we still can't really do
+        anything until we've got the doID range. """
+        # The Client Repository will throw this event as soon as it has a doID
+        # range and would be able to create distributed objects
+        self.accept('createReady', self.gotCreateReady)
+
+    def lostConnection(self):
+        """ This should be overridden by a derived class to handle an
+         unexpectedly lost connection to the gameserver. """
+        exit()
+
+    def gotCreateReady(self):
+        """ Now we're ready to go! """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore('createReady')
+
+        # Create a Distributed Object by name.  This will look up the object in
+        # the dc files passed to the repository earlier
+        self.timeManager = self.createDistributedObject(
+            className = 'TimeManagerAI', # The Name of the Class we want to initialize
+            zoneId = 1) # The Zone this Object will live in
+
+        print("AI Repository Ready")
+
+    def deallocateChannel(self, doID):
+        """ This method will be called whenever a client disconnects from the
+        server.  The given doID is the ID of the client who left us. """
+        print("Client left us: ", doID)

+ 116 - 0
samples/networking/04-distributed-model/ClientRepository.py

@@ -0,0 +1,116 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from random import random
+
+class GameClientRepository(ClientRepository):
+
+    def __init__(self):
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # uses another protocol then http you should change it accordingly.
+        # Make sure to pass the connectMethod to the  ClientRepository.__init__
+        # call too.  Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([self.url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def lostConnection(self):
+        """ This should be overridden by a derived class to handle an
+        unexpectedly lost connection to the gameserver. """
+        # Handle the disconnection from the server.  This can be a reconnect,
+        # simply exiting the application or anything else.
+        exit()
+
+    def connectFailure(self, statusCode, statusString):
+        """ Something went wrong """
+        exit()
+
+    def connectSuccess(self):
+        """ Successfully connected.  But we still can't really do
+        anything until we've got the doID range. """
+
+        # Make sure we have interest in the by the AIRepository defined
+        # TimeManager zone, so we always see it even if we switch to
+        # another zone.
+        self.setInterestZones([1])
+
+        # We must wait for the TimeManager to be fully created and
+        # synced before we can enter another zone and wait for the
+        # game object.  The uniqueName is important that we get the
+        # correct, our sync message from the TimeManager and not
+        # accidentaly a message from another client
+        self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+    def syncReady(self):
+        """ Now we've got the TimeManager manifested, and we're in
+        sync with the server time.  Now we can enter the world.  Check
+        to see if we've received our doIdBase yet. """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if self.haveCreateAuthority():
+            # we already have one
+            self.gotCreateReady()
+        else:
+            # Not yet, keep waiting a bit longer.
+            self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+    def gotCreateReady(self):
+        """ Ready to enter the world.  Expand our interest to include
+        any other zones """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore(self.uniqueName('createReady'))
+
+        self.join()
+
+        print("Client Ready")
+
+    def join(self):
+        """ Join a game/room/whatever """
+        # set our intersted zones to let the client see all distributed obects
+        # in those zones
+        self.setInterestZones([1, 2])
+
+        # Manifest a object on the server.  The object will have our "base" doId.
+        self.myDistributedModel = self.createDistributedObject(
+            className = "DModel",
+            zoneId = 2)
+
+        x = random()
+        z = random()
+        # set position for this local client
+        self.myDistributedModel.setPos(x, 10, z)
+        # make sure already connected clients will get notified of the
+        # position by calling the distributred (d_*) version of the method
+        self.myDistributedModel.d_setPos(x, 10, z)
+
+        base.messenger.send("client-joined")
+        print("Joined")
+
+    def modelReady(self, doId):
+        print("AIDGameObect was generated")
+        self.aiDGameObect = self.doId2do[doId]

+ 61 - 0
samples/networking/04-distributed-model/DModel.py

@@ -0,0 +1,61 @@
+from direct.distributed.DistributedNode import DistributedNode
+
+class DModel(DistributedNode):
+    def __init__(self, cr):
+        DistributedNode.__init__(self, cr)
+
+        # Load up the visible representation of this avatar.
+        self.model = loader.loadModel('smiley.egg')
+        self.model.reparentTo(self)
+
+    def announceGenerate(self):
+        """ This method is called after generate(), after all of the
+        required fields have been filled in.  At the time of this call,
+        the distributed object is ready for use. """
+
+        DistributedNode.announceGenerate(self)
+
+        # Now that the object has been fully manifested, we can parent
+        # it into the scene.
+        self.reparentTo(render)
+
+    def disable(self):
+        """ This method is called when the object is removed from the
+        scene, for instance because it left the zone.  It is balanced
+        against generate(): for each generate(), there will be a
+        corresponding disable().  Everything that was done in
+        generate() or announceGenerate() should be undone in disable().
+
+        After a disable(), the object might be cached in memory in case
+        it will eventually reappear.  The DistributedObject should be
+        prepared to receive another generate() for an object that has
+        already received disable().
+
+        Note that the above is only strictly true for *cacheable*
+        objects.  Most objects are, by default, non-cacheable; you
+        have to call obj.setCacheable(True) (usually in the
+        constructor) to make it cacheable.  Until you do this, your
+        non-cacheable object will always receive a delete() whenever
+        it receives a disable(), and it will never be stored in a
+        cache.
+        """
+
+        # Take it out of the scene graph.
+        self.detachNode()
+
+        DistributedNode.disable(self)
+
+    def delete(self):
+        """ This method is called after disable() when the object is to
+        be completely removed, for instance because the other user
+        logged off.  We will not expect to see this object again; it
+        will not be cached.  This is stronger than disable(), and the
+        object may remove any structures it needs to in order to allow
+        it to be completely deleted from memory.  This balances against
+        __init__(): every DistributedObject that is created will
+        eventually get delete() called for it exactly once. """
+
+        # Clean out self.model, so we don't have a circular reference.
+        self.model = None
+
+        DistributedNode.delete(self)

+ 20 - 0
samples/networking/04-distributed-model/ServerRepository.py

@@ -0,0 +1,20 @@
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+# the main server class
+class GameServerRepository(ServerRepository):
+    """The server repository class"""
+    def __init__(self):
+        """initialise the server class"""
+
+        # get the port number from the configuration file
+        # if it doesn't exist, we use 4400 as the default
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # list of all needed .dc files
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        # initialise a threaded server on this machine with
+        # the port number and the dc filenames
+        ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)

+ 43 - 0
samples/networking/04-distributed-model/client.py

@@ -0,0 +1,43 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# import our own repositories
+from ClientRepository import GameClientRepository
+
+# initialize the engine
+base = ShowBase()
+
+# initialize the client
+client = GameClientRepository()
+
+base.accept("escape", exit)
+
+
+
+
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import TextNode
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+    return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
+                        parent=base.a2dTopLeft, align=TextNode.ALeft,
+                        pos=(0.08, -pos - 0.04), scale=.06)
+
+# Function to put title on the screen.
+def addTitle(text):
+    return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+                        parent=base.a2dBottomRight, align=TextNode.ARight,
+                        fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
+inst1 = addInstructions(0.06, "esc: Close the client")
+inst2 = addInstructions(0.12, "See console output")
+
+def setConnectedMessage():
+    title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
+
+base.accept("client-joined", setConnectedMessage)
+
+# start the client
+base.run()

+ 3 - 0
samples/networking/04-distributed-model/sample.dc

@@ -0,0 +1,3 @@
+import DModel
+
+dclass DModel: DistributedNode { }

+ 18 - 0
samples/networking/04-distributed-model/server.py

@@ -0,0 +1,18 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# import our own repositories
+from ServerRepository import GameServerRepository
+from AIRepository import AIRepository
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# instantiate the server
+GameServerRepository()
+
+# The AI Repository to manage server side (AI) clients
+AIRepository()
+
+# start the server
+base.run()

+ 81 - 0
samples/networking/05-small-chat/AIRepository.py

@@ -0,0 +1,81 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+
+class AIRepository(ClientRepository):
+    def __init__(self):
+        """ The AI Repository usually lives on a server and is responsible for
+        server side logic that will handle game objects """
+
+        # List of all dc files that are of interest to this AI Repository
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        # Initialize the repository.  We pass it the dc files and as this is an
+        # AI repository the dcSuffix AI.  This will make sure any later calls to
+        # createDistributedObject will use the correct version.
+        # The connectMethod
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            dcSuffix = 'AI',
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # doesn't use http you should change it accordingly. Make sure to pass
+        # the connectMethod to the  ClientRepository.__init__ call too.
+        # Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def connectFailure(self, statusCode, statusString):
+        """ something went wrong """
+        print("Couldn't connect. Make sure to run server.py first!")
+        raise(StandardError, statusString)
+
+    def connectSuccess(self):
+        """ Successfully connected.  But we still can't really do
+        anything until we've got the doID range. """
+        # The Client Repository will throw this event as soon as it has a doID
+        # range and would be able to create distributed objects
+        self.accept('createReady', self.gotCreateReady)
+
+    def lostConnection(self):
+        """ This should be overridden by a derived class to handle an
+         unexpectedly lost connection to the gameserver. """
+        exit()
+
+    def gotCreateReady(self):
+        """ Now we're ready to go! """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore('createReady')
+
+        # Create a Distributed Object by name.  This will look up the object in
+        # the dc files passed to the repository earlier
+        self.timeManager = self.createDistributedObject(
+            className = 'TimeManagerAI', # The Name of the Class we want to initialize
+            zoneId = 1) # The Zone this Object will live in
+
+        print("AI Repository Ready")
+
+    def deallocateChannel(self, doID):
+        """ This method will be called whenever a client disconnects from the
+        server.  The given doID is the ID of the client who left us. """
+        print("Client left us: ", doID)

+ 104 - 0
samples/networking/05-small-chat/ClientRepository.py

@@ -0,0 +1,104 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from message import Message
+
+class GameClientRepository(ClientRepository):
+
+    def __init__(self):
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # uses another protocol then http you should change it accordingly.
+        # Make sure to pass the connectMethod to the  ClientRepository.__init__
+        # call too.  Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([self.url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def lostConnection(self):
+        ''' This should be overridden by a derived class to handle an
+        unexpectedly lost connection to the gameserver. '''
+        # Handle the disconnection from the server.  This can be a reconnect,
+        # simply exiting the application or anything else.
+        exit()
+
+    def connectFailure(self, statusCode, statusString):
+        ''' Something went wrong '''
+        # we could create a reconnect task to try and connect again.
+        exit()
+
+    def connectSuccess(self):
+        ''' Successfully connected.  But we still can't really do
+        anything until we've got the doID range. '''
+
+        # Make sure we have interest in the by the AIRepository defined
+        # TimeManager zone, so we always see it even if we switch to
+        # another zone.
+        self.setInterestZones([1])
+
+        # We must wait for the TimeManager to be fully created and
+        # synced before we can enter another zone and wait for the
+        # game object.  The uniqueName is important that we get the
+        # correct, our sync message from the TimeManager and not
+        # accidentaly a message from another client
+        self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+    def syncReady(self):
+        ''' Now we've got the TimeManager manifested, and we're in
+        sync with the server time.  Now we can enter the world.  Check
+        to see if we've received our doIdBase yet. '''
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if self.haveCreateAuthority():
+            # we already have one
+            self.gotCreateReady()
+        else:
+            # Not yet, keep waiting a bit longer.
+            self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+    def gotCreateReady(self):
+        ''' Ready to enter the world.  Expand our interest to include
+        any other zones '''
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore(self.uniqueName('createReady'))
+
+        # create a instance of the message class
+        msg = Message(self)
+        # and create the distributed Object with it
+        self.createDistributedObject(
+            distObj = msg, zoneId = 1)
+        # save the created Distributed Object
+        # in self.msg for later usage
+        self.msg = msg
+
+    def sendMessage(self, msgText):
+        '''Function to call the send function of the message class,
+        which sends the Message over the Network to the other users'''
+
+        sentText = "{}: {}".format(self.doIdBase, msgText)
+
+        self.msg.b_sendText(sentText)

+ 85 - 0
samples/networking/05-small-chat/client.py

@@ -0,0 +1,85 @@
+from direct.showbase.ShowBase import ShowBase
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from message import Message
+from ClientRepository import GameClientRepository
+from direct.gui.DirectGui import DirectButton, DirectEntry, DirectFrame
+from direct.gui import DirectGuiGlobals as DGG
+
+# initialize the engine
+base = ShowBase()
+
+client = GameClientRepository()
+
+# Setup the GUI
+
+# this frame will contain all our GUI elements
+frameMain = DirectFrame(frameColor = (0, 0, 0, 1))
+
+def clearText():
+    ''' Write an empty string in the textbox '''
+    txt_msg.enterText('')
+
+def setDefaultText():
+    ''' Write the default message in the textbox '''
+    txt_msg.enterText('Your Message')
+
+def send(args=None):
+    ''' Send the text written in the message textbox to the clients '''
+    client.sendMessage(txt_msg.get())
+    txt_msg.enterText('')
+
+# the Textbox where we write our Messages, which we
+# want to send over to the other users
+txt_msg = DirectEntry(
+    initialText = 'Your Message',
+    cursorKeys = True,
+    pos = (-0.9,0,-0.9),
+    scale = 0.1,
+    width = 14,
+    focusInCommand = clearText,
+    focusOutCommand = setDefaultText,
+    command = send,
+    parent = frameMain)
+
+# a button to initiate the sending of the message
+btn_send = DirectButton(
+    text = 'Send',
+    pos = (0.75,0,-0.9),
+    scale = 0.15,
+    command = send,
+    parent = frameMain)
+
+# This will show all sent messages
+txt_messages = DirectEntry(
+    cursorKeys = False,
+    pos = (-0.8,0,0.5),
+    scale = 0.1,
+    width = 14,
+    numLines = 10,
+    state = DGG.DISABLED,
+    parent = frameMain)
+
+# Function which will write the given text in the
+# textbox, where we store all send and recieved messages.
+# This Function will only write the last 10 messages and
+# cut of the erlier messages
+def setText(messageText):
+    # get all messages from the textbox
+    parts = txt_messages.get().split('\n')
+    if len(parts) >= 10:
+        cutParts = ''
+        # as the textbox can only hold 10 lines cut out the first entry
+        for i in range(1,len(parts)):
+            cutParts += parts[i] + '\n'
+        txt_messages.enterText(cutParts + messageText)
+    else:
+        txt_messages.enterText(txt_messages.get() + '\n' + messageText)
+
+# create a DirectObject instance, which will then catch the events and
+# handle them with the given functions
+base.accept('setText', setText)
+base.accept('escape', exit)
+
+# start the application
+base.run()

+ 25 - 0
samples/networking/05-small-chat/message.py

@@ -0,0 +1,25 @@
+from direct.distributed.DistributedObject import DistributedObject
+from direct.showbase.MessengerGlobal import messenger
+
+class Message(DistributedObject):
+    def __init__(self, clientRepo):
+        DistributedObject.__init__(self, clientRepo)
+
+    def sendText(self, messageText):
+        """Function which is caled for local changes only"""
+        # send an event, which will set the text on the
+        #print "got a message"
+        messenger.send("setText", [messageText])
+
+    def d_sendText(self, messageText):
+        """Function which is caled to send the message over the network
+        therfore the d_ suffix stands for distributed"""
+        #print "send message %s" % messageText
+        self.sendUpdate("sendText", [messageText])
+
+    def b_sendText(self, messageText):
+        """Function which combines the local and distributed functionality,
+        so the sendText and d_sendText functions are called.
+        The b_ suffix stands for both"""
+        self.sendText(messageText)
+        self.d_sendText(messageText)

+ 5 - 0
samples/networking/05-small-chat/sample.dc

@@ -0,0 +1,5 @@
+from message import Message
+
+dclass Message: DistributedObject {
+  sendText(string messageText) broadcast;
+}

+ 34 - 0
samples/networking/05-small-chat/server.py

@@ -0,0 +1,34 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+from AIRepository import AIRepository
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# the main server class
+class GameServerRepository(ServerRepository):
+    """The server repository class"""
+    def __init__(self):
+        """initialise the server class"""
+
+        # get the port number from the configuration file
+        # if it doesn't exist, we use 4400 as the default
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # list of all needed .dc files
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        # initialise a threaded server on this machine with
+        # the port number and the dc filenames
+        ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)
+
+# start the server
+GameServerRepository()
+AIRepository()
+
+base.run()

+ 81 - 0
samples/networking/06-simple-avatar/AIRepository.py

@@ -0,0 +1,81 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+
+class AIRepository(ClientRepository):
+    def __init__(self):
+        """ The AI Repository usually lives on a server and is responsible for
+        server side logic that will handle game objects """
+
+        # List of all dc files that are of interest to this AI Repository
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        # Initialize the repository.  We pass it the dc files and as this is an
+        # AI repository the dcSuffix AI.  This will make sure any later calls to
+        # createDistributedObject will use the correct version.
+        # The connectMethod
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            dcSuffix = 'AI',
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # doesn't use http you should change it accordingly. Make sure to pass
+        # the connectMethod to the  ClientRepository.__init__ call too.
+        # Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def connectFailure(self, statusCode, statusString):
+        """ something went wrong """
+        print("Couldn't connect. Make sure to run server.py first!")
+        raise(StandardError, statusString)
+
+    def connectSuccess(self):
+        """ Successfully connected.  But we still can't really do
+        anything until we've got the doID range. """
+        # The Client Repository will throw this event as soon as it has a doID
+        # range and would be able to create distributed objects
+        self.accept('createReady', self.gotCreateReady)
+
+    def lostConnection(self):
+        """ This should be overridden by a derived class to handle an
+         unexpectedly lost connection to the gameserver. """
+        exit()
+
+    def gotCreateReady(self):
+        """ Now we're ready to go! """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore('createReady')
+
+        # Create a Distributed Object by name.  This will look up the object in
+        # the dc files passed to the repository earlier
+        self.timeManager = self.createDistributedObject(
+            className = 'TimeManagerAI', # The Name of the Class we want to initialize
+            zoneId = 1) # The Zone this Object will live in
+
+        print("AI Repository Ready")
+
+    def deallocateChannel(self, doID):
+        """ This method will be called whenever a client disconnects from the
+        server.  The given doID is the ID of the client who left us. """
+        print("Client left us: ", doID)

+ 103 - 0
samples/networking/06-simple-avatar/ClientRepository.py

@@ -0,0 +1,103 @@
+from direct.distributed.ClientRepository import ClientRepository
+from panda3d.core import URLSpec, ConfigVariableInt, ConfigVariableString
+from DistributedSmoothActor import DistributedSmoothActor
+
+class GameClientRepository(ClientRepository):
+
+    def __init__(self):
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        # a distributed object of our game.
+        self.distributedObject = None
+        self.aiDGameObect = None
+
+        ClientRepository.__init__(
+            self,
+            dcFileNames = dcFileNames,
+            threadedNet = True)
+
+        # Set the same port as configured on the server to be able to connect
+        # to it
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # Set the IP or hostname of the server we want to connect to
+        hostname = ConfigVariableString('server-host', '127.0.0.1').getValue()
+
+        # Build the URL from the server hostname and port. If your server
+        # uses another protocol then http you should change it accordingly.
+        # Make sure to pass the connectMethod to the  ClientRepository.__init__
+        # call too.  Available connection methods are:
+        # self.CM_HTTP, self.CM_NET and self.CM_NATIVE
+        self.url = URLSpec('http://{}:{}'.format(hostname, tcpPort))
+
+        # Attempt a connection to the server
+        self.connect([self.url],
+                     successCallback = self.connectSuccess,
+                     failureCallback = self.connectFailure)
+
+    def lostConnection(self):
+        """ This should be overridden by a derived class to handle an
+        unexpectedly lost connection to the gameserver. """
+        # Handle the disconnection from the server.  This can be a reconnect,
+        # simply exiting the application or anything else.
+        exit()
+
+    def connectFailure(self, statusCode, statusString):
+        """ Something went wrong """
+        exit()
+
+    def connectSuccess(self):
+        """ Successfully connected.  But we still can't really do
+        anything until we've got the doID range. """
+
+        # Make sure we have interest in the by the AIRepository defined
+        # TimeManager zone, so we always see it even if we switch to
+        # another zone.
+        self.setInterestZones([1])
+
+        # We must wait for the TimeManager to be fully created and
+        # synced before we can enter another zone and wait for the
+        # game object.  The uniqueName is important that we get the
+        # correct, our sync message from the TimeManager and not
+        # accidentaly a message from another client
+        self.acceptOnce(self.uniqueName('gotTimeSync'), self.syncReady)
+
+    def syncReady(self):
+        """ Now we've got the TimeManager manifested, and we're in
+        sync with the server time.  Now we can enter the world.  Check
+        to see if we've received our doIdBase yet. """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if self.haveCreateAuthority():
+            # we already have one
+            self.gotCreateReady()
+        else:
+            # Not yet, keep waiting a bit longer.
+            self.accept(self.uniqueName('createReady'), self.gotCreateReady)
+
+    def gotCreateReady(self):
+        """ Ready to enter the world.  Expand our interest to include
+        any other zones """
+
+        # This method checks whether we actually have a valid doID range
+        # to create distributed objects yet
+        if not self.haveCreateAuthority():
+            # Not ready yet.
+            return
+
+        # we are ready now, so ignore further createReady events
+        self.ignore(self.uniqueName('createReady'))
+
+        self.join()
+
+        print("Client Ready")
+
+    def join(self):
+        """ Join a game/room/whatever """
+        # set our intersted zones to let the client see all distributed obects
+        # in those zones
+        self.setInterestZones([1, 2])
+
+        base.messenger.send('client-joined')
+        print("Joined")

+ 50 - 0
samples/networking/06-simple-avatar/DistributedSmoothActor.py

@@ -0,0 +1,50 @@
+from direct.distributed.DistributedSmoothNode import DistributedSmoothNode
+from panda3d.core import NodePath
+
+from direct.actor.Actor import Actor
+
+class DistributedSmoothActor(DistributedSmoothNode, Actor):
+    def __init__(self, cr):
+        Actor.__init__(self, "models/ralph",
+            {"run": "models/ralph-run",
+            "walk": "models/ralph-walk"})
+        DistributedSmoothNode.__init__(self, cr)
+        self.setCacheable(1)
+        self.setScale(.2)
+
+    def generate(self):
+        DistributedSmoothNode.generate(self)
+        self.activateSmoothing(True, False)
+        self.startSmooth()
+
+    def announceGenerate(self):
+        DistributedSmoothNode.announceGenerate(self)
+        self.reparentTo(render)
+
+    def disable(self):
+        # remove all anims, on all parts and all lods
+        self.stopSmooth()
+        if (not self.isEmpty()):
+            Actor.unloadAnims(self, None, None, None)
+        DistributedSmoothNode.disable(self)
+
+    def delete(self):
+        try:
+            self.DistributedActor_deleted
+        except:
+            self.DistributedActor_deleted = 1
+            DistributedSmoothNode.delete(self)
+            Actor.delete(self)
+
+    def start(self):
+        # Let the DistributedSmoothNode take care of broadcasting the
+        # position updates several times a second.
+        self.startPosHprBroadcast()
+
+    def loop(self, animName):
+        self.sendUpdate("loop", [animName])
+        return Actor.loop(self, animName)
+
+    def pose(self, animName, frame):
+        self.sendUpdate("pose", [animName, frame])
+        return Actor.pose(self, animName, frame)

+ 20 - 0
samples/networking/06-simple-avatar/ServerRepository.py

@@ -0,0 +1,20 @@
+# all imports needed by the server
+from direct.distributed.ServerRepository import ServerRepository
+from panda3d.core import ConfigVariableInt
+
+# the main server class
+class GameServerRepository(ServerRepository):
+    """The server repository class"""
+    def __init__(self):
+        """initialise the server class"""
+
+        # get the port number from the configuration file
+        # if it doesn't exist, we use 4400 as the default
+        tcpPort = ConfigVariableInt('server-port', 4400).getValue()
+
+        # list of all needed .dc files
+        dcFileNames = ['../direct.dc', 'sample.dc']
+
+        # initialise a threaded server on this machine with
+        # the port number and the dc filenames
+        ServerRepository.__init__(self, tcpPort, dcFileNames=dcFileNames, threadedNet=True)

+ 151 - 0
samples/networking/06-simple-avatar/client.py

@@ -0,0 +1,151 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+from panda3d.core import KeyboardButton, NodePath, PandaNode
+
+# import our own repositories
+from ClientRepository import GameClientRepository
+from DistributedSmoothActor import DistributedSmoothActor
+
+# initialize the engine
+base = ShowBase()
+
+base.disableMouse()
+
+class Avatar:
+    def __init__(self, cr):
+        self.cr = cr
+        self.ralph = DistributedSmoothActor(self.cr)
+
+        self.cr.createDistributedObject(
+            distObj = self.ralph,
+            zoneId = 2)
+
+        # Create a floater object, which floats 2 units above ralph.  We
+        # use this as a target for the camera to look at.
+
+        self.floater = NodePath(PandaNode("floater"))
+        self.floater.reparentTo(self.ralph)
+        self.floater.setZ(2.0)
+
+        # We will use this for checking if keyboard keys are pressed
+        self.isDown = base.mouseWatcherNode.isButtonDown
+
+        taskMgr.add(self.move, "moveTask")
+
+        # Set up the camera
+        base.camera.setPos(self.ralph.getX(), self.ralph.getY() + 10, 2)
+
+        # start the avatar
+        self.ralph.start()
+
+    # Accepts arrow keys to move either the player or the menu cursor,
+    # Also deals with grid checking and collision detection
+    def move(self, task):
+
+        # Get the time that elapsed since last frame.  We multiply this with
+        # the desired speed in order to find out with which distance to move
+        # in order to achieve that desired speed.
+        dt = globalClock.getDt()
+
+        # If the camera-left key is pressed, move camera left.
+        # If the camera-right key is pressed, move camera right.
+
+        if self.isDown(KeyboardButton.asciiKey(b"j")):
+            base.camera.setX(base.camera, -20 * dt)
+        if self.isDown(KeyboardButton.asciiKey(b"k")):
+            base.camera.setX(base.camera, +20 * dt)
+
+        # If a move-key is pressed, move ralph in the specified direction.
+
+        if self.isDown(KeyboardButton.asciiKey(b"a")):
+            self.ralph.setH(self.ralph.getH() + 300 * dt)
+        if self.isDown(KeyboardButton.asciiKey(b"d")):
+            self.ralph.setH(self.ralph.getH() - 300 * dt)
+        if self.isDown(KeyboardButton.asciiKey(b"w")):
+            self.ralph.setY(self.ralph, -20 * dt)
+        if self.isDown(KeyboardButton.asciiKey(b"s")):
+            self.ralph.setY(self.ralph, +10 * dt)
+
+        # update distributed position and rotation
+        #self.ralph.setDistPos(self.ralph.getX(), self.ralph.getY(), self.ralph.getZ())
+        #self.ralph.setDistHpr(self.ralph.getH(), self.ralph.getP(), self.ralph.getR())
+
+        # If ralph is moving, loop the run animation.
+        # If he is standing still, stop the animation.
+        currentAnim = self.ralph.getCurrentAnim()
+
+        if self.isDown(KeyboardButton.asciiKey(b"w")):
+            if currentAnim != "run":
+                self.ralph.loop("run")
+        elif self.isDown(KeyboardButton.asciiKey(b"s")):
+            # Play the walk animation backwards.
+            if currentAnim != "walk":
+                self.ralph.loop("walk")
+            self.ralph.setPlayRate(-1.0, "walk")
+        elif self.isDown(KeyboardButton.asciiKey(b"a")) or self.isDown(KeyboardButton.asciiKey(b"d")):
+            if currentAnim != "walk":
+                self.ralph.loop("walk")
+            self.ralph.setPlayRate(1.0, "walk")
+        else:
+            if currentAnim is not None:
+                self.ralph.stop()
+                self.ralph.pose("walk", 5)
+                self.isMoving = False
+
+        # If the camera is too far from ralph, move it closer.
+        # If the camera is too close to ralph, move it farther.
+
+        camvec = self.ralph.getPos() - base.camera.getPos()
+        camvec.setZ(0)
+        camdist = camvec.length()
+        camvec.normalize()
+        if camdist > 10.0:
+            base.camera.setPos(base.camera.getPos() + camvec * (camdist - 10))
+            camdist = 10.0
+        if camdist < 5.0:
+            base.camera.setPos(base.camera.getPos() - camvec * (5 - camdist))
+            camdist = 5.0
+
+        # The camera should look in ralph's direction,
+        # but it should also try to stay horizontal, so look at
+        # a floater which hovers above ralph's head.
+        base.camera.lookAt(self.floater)
+
+        return task.cont
+
+base.accept("escape", exit)
+
+# initialize the client
+client = GameClientRepository()
+
+
+from direct.gui.OnscreenText import OnscreenText
+from panda3d.core import TextNode
+
+# Function to put instructions on the screen.
+def addInstructions(pos, msg):
+    return OnscreenText(text=msg, style=1, fg=(0, 0, 0, 1), shadow=(1, 1, 1, 1),
+                        parent=base.a2dTopLeft, align=TextNode.ALeft,
+                        pos=(0.08, -pos - 0.04), scale=.06)
+
+# Function to put title on the screen.
+def addTitle(text):
+    return OnscreenText(text=text, style=1, pos=(-0.1, 0.09), scale=.08,
+                        parent=base.a2dBottomRight, align=TextNode.ARight,
+                        fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1))
+
+title = addTitle("Panda3D: Tutorial - Distributed Network (NOT CONNECTED)")
+inst1 = addInstructions(0.06, "W|A|S|D: Move avatar)")
+inst2 = addInstructions(0.12, "esc: Close the client")
+inst3 = addInstructions(0.24, "See console output")
+
+def clientJoined():
+    title["text"] = "Panda3D: Tutorial - Distributed Network (CONNECTED)"
+
+    # Setup our avatar
+    Avatar(client)
+
+base.accept("client-joined", clientJoined)
+
+# start the client
+base.run()

BIN
samples/networking/06-simple-avatar/models/ralph-run.egg.pz


BIN
samples/networking/06-simple-avatar/models/ralph-walk.egg.pz


BIN
samples/networking/06-simple-avatar/models/ralph.egg.pz


BIN
samples/networking/06-simple-avatar/models/ralph.jpg


+ 6 - 0
samples/networking/06-simple-avatar/sample.dc

@@ -0,0 +1,6 @@
+import DistributedSmoothActor
+
+dclass DistributedSmoothActor: DistributedSmoothNode {
+    loop(string animName) broadcast;
+    pose(string animName, int16 frame) broadcast;
+}

+ 18 - 0
samples/networking/06-simple-avatar/server.py

@@ -0,0 +1,18 @@
+# all imports needed by the engine itself
+from direct.showbase.ShowBase import ShowBase
+
+# import our own repositories
+from ServerRepository import GameServerRepository
+from AIRepository import AIRepository
+
+# initialize the engine
+base = ShowBase(windowType='none')
+
+# instantiate the server
+GameServerRepository()
+
+# The AI Repository to manage server side (AI) clients
+AIRepository()
+
+# start the server
+base.run()

+ 91 - 0
samples/networking/direct.dc

@@ -0,0 +1,91 @@
+// This is a sample dc file for some of the classes defined within the
+// direct source tree.  It is suggested that you copy this file into
+// your own project (or load it from the direct source tree) and build
+// on it with your own dc file for your own classes.
+
+keyword broadcast;
+keyword ram;
+keyword p2p;
+
+from direct.distributed import DistributedObject/AI
+from direct.distributed import TimeManager/AI
+from direct.distributed import DistributedNode/AI
+from direct.distributed import DistributedSmoothNode/AI
+
+struct BarrierData {
+  uint16 context;
+  string name;
+  uint32 avIds[];
+};
+
+// The most fundamental class
+dclass DistributedObject {
+  // These are used to support DistributedObjectAI.beginBarrier() and
+  // the matching DistributedObject.doneBarrier().  If you don't call
+  // these functions, you don't care about these distributed methods.
+  // (Actually, you probably don't care anyway.)
+  setBarrierData(BarrierData data[]) broadcast ram;
+  setBarrierReady(uint16 context);
+  setLocation(uint32 parentId, uint32 zoneId) broadcast ram;
+};
+
+dclass TimeManager: DistributedObject {
+  requestServerTime(uint8 context) p2p;
+  serverTime(uint8 context, int32 timestamp);
+};
+
+dclass DistributedNode: DistributedObject {
+  setX(int16 / 10) broadcast ram;
+  setY(int16 / 10) broadcast ram;
+  setZ(int16 / 10) broadcast ram;
+  setH(int16 % 360 / 10) broadcast ram;
+  setP(int16 % 360 / 10) broadcast ram;
+  setR(int16 % 360 / 10) broadcast ram;
+
+  setPos: setX, setY, setZ;
+  setHpr: setH, setP, setR;
+  setPosHpr: setX, setY, setZ, setH, setP, setR;
+  setXY: setX, setY;
+  setXZ: setX, setZ;
+  setXYH: setX, setY, setH;
+  setXYZH: setX, setY, setZ, setH;
+};
+
+dclass DistributedSmoothNode: DistributedNode {
+  // Component set pos and hpr functions.
+
+  setComponentL(uint64) broadcast ram;
+  setComponentX(int16 / 10) broadcast ram;
+  setComponentY(int16 / 10) broadcast ram;
+  setComponentZ(int16 / 10) broadcast ram;
+  setComponentH(int16 % 360 / 10) broadcast ram;
+  setComponentP(int16 % 360 / 10) broadcast ram;
+  setComponentR(int16 % 360 / 10) broadcast ram;
+  setComponentT(int16 timestamp) broadcast ram;
+
+  // Composite set pos and hpr functions.  These map to combinations
+  // of one or more of the above components.  They all include
+  // setComponentT(), which must be called last.
+  setSmStop: setComponentT;
+  setSmH: setComponentH, setComponentT;
+  setSmZ: setComponentZ, setComponentT;
+  setSmXY: setComponentX, setComponentY, setComponentT;
+  setSmXZ: setComponentX, setComponentZ, setComponentT;
+  setSmPos: setComponentX, setComponentY, setComponentZ, setComponentT;
+  setSmHpr: setComponentH, setComponentP, setComponentR, setComponentT;
+  setSmXYH: setComponentX, setComponentY, setComponentH, setComponentT;
+  setSmXYZH: setComponentX, setComponentY, setComponentZ, setComponentH, setComponentT;
+  setSmPosHpr: setComponentX, setComponentY, setComponentZ, setComponentH, setComponentP, setComponentR, setComponentT;
+  // special update if L (being location, such as zoneId) changes, send everything, intended to
+  // keep position and 'location' in sync
+  setSmPosHprL: setComponentL, setComponentX, setComponentY, setComponentZ, setComponentH, setComponentP, setComponentR, setComponentT;
+
+  clearSmoothing(int8 bogus) broadcast;
+
+  suggestResync(uint32 avId, int16 timestampA, int16 timestampB,
+                int32 serverTimeSec, uint16 serverTimeUSec,
+                uint16 / 100 uncertainty);
+  returnResync(uint32 avId, int16 timestampB,
+               int32 serverTimeSec, uint16 serverTimeUSec,
+               uint16 / 100 uncertainty);
+};