diff --git a/samples/networking/01-simple-connection/client.py b/samples/networking/01-simple-connection/client.py new file mode 100644 index 0000000000..2f5d1cef33 --- /dev/null +++ b/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() diff --git a/samples/networking/01-simple-connection/server.py b/samples/networking/01-simple-connection/server.py new file mode 100644 index 0000000000..51926a0feb --- /dev/null +++ b/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() diff --git a/samples/networking/02-time-synced-connection/AIRepository.py b/samples/networking/02-time-synced-connection/AIRepository.py new file mode 100644 index 0000000000..0b462f0557 --- /dev/null +++ b/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) diff --git a/samples/networking/02-time-synced-connection/client.py b/samples/networking/02-time-synced-connection/client.py new file mode 100644 index 0000000000..23ed1b3a0b --- /dev/null +++ b/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() diff --git a/samples/networking/02-time-synced-connection/server.py b/samples/networking/02-time-synced-connection/server.py new file mode 100644 index 0000000000..6f587de4cf --- /dev/null +++ b/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() diff --git a/samples/networking/03-distributed-node/AIDGameObject.py b/samples/networking/03-distributed-node/AIDGameObject.py new file mode 100644 index 0000000000..9c4aab012b --- /dev/null +++ b/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") diff --git a/samples/networking/03-distributed-node/AIDGameObjectAI.py b/samples/networking/03-distributed-node/AIDGameObjectAI.py new file mode 100644 index 0000000000..4fda4364fb --- /dev/null +++ b/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]) diff --git a/samples/networking/03-distributed-node/AIRepository.py b/samples/networking/03-distributed-node/AIRepository.py new file mode 100644 index 0000000000..a51dca16f9 --- /dev/null +++ b/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) diff --git a/samples/networking/03-distributed-node/ClientRepository.py b/samples/networking/03-distributed-node/ClientRepository.py new file mode 100644 index 0000000000..f413cf6ca8 --- /dev/null +++ b/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() diff --git a/samples/networking/03-distributed-node/DGameObject.py b/samples/networking/03-distributed-node/DGameObject.py new file mode 100644 index 0000000000..c3eb37a320 --- /dev/null +++ b/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)]) diff --git a/samples/networking/03-distributed-node/ServerRepository.py b/samples/networking/03-distributed-node/ServerRepository.py new file mode 100644 index 0000000000..59da3a6aa4 --- /dev/null +++ b/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) diff --git a/samples/networking/03-distributed-node/client.py b/samples/networking/03-distributed-node/client.py new file mode 100644 index 0000000000..23ed1b3a0b --- /dev/null +++ b/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() diff --git a/samples/networking/03-distributed-node/sample.dc b/samples/networking/03-distributed-node/sample.dc new file mode 100755 index 0000000000..651d79b35c --- /dev/null +++ b/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; +} diff --git a/samples/networking/03-distributed-node/server.py b/samples/networking/03-distributed-node/server.py new file mode 100644 index 0000000000..0aab404a46 --- /dev/null +++ b/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() diff --git a/samples/networking/04-distributed-model/AIRepository.py b/samples/networking/04-distributed-model/AIRepository.py new file mode 100644 index 0000000000..9e46f024d3 --- /dev/null +++ b/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) diff --git a/samples/networking/04-distributed-model/ClientRepository.py b/samples/networking/04-distributed-model/ClientRepository.py new file mode 100644 index 0000000000..f2b5ec7c82 --- /dev/null +++ b/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] diff --git a/samples/networking/04-distributed-model/DModel.py b/samples/networking/04-distributed-model/DModel.py new file mode 100644 index 0000000000..047e95ab85 --- /dev/null +++ b/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) diff --git a/samples/networking/04-distributed-model/ServerRepository.py b/samples/networking/04-distributed-model/ServerRepository.py new file mode 100644 index 0000000000..59da3a6aa4 --- /dev/null +++ b/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) diff --git a/samples/networking/04-distributed-model/client.py b/samples/networking/04-distributed-model/client.py new file mode 100644 index 0000000000..0a8373b469 --- /dev/null +++ b/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() diff --git a/samples/networking/04-distributed-model/sample.dc b/samples/networking/04-distributed-model/sample.dc new file mode 100755 index 0000000000..2a0e86b154 --- /dev/null +++ b/samples/networking/04-distributed-model/sample.dc @@ -0,0 +1,3 @@ +import DModel + +dclass DModel: DistributedNode { } diff --git a/samples/networking/04-distributed-model/server.py b/samples/networking/04-distributed-model/server.py new file mode 100644 index 0000000000..0aab404a46 --- /dev/null +++ b/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() diff --git a/samples/networking/05-small-chat/AIRepository.py b/samples/networking/05-small-chat/AIRepository.py new file mode 100644 index 0000000000..9e46f024d3 --- /dev/null +++ b/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) diff --git a/samples/networking/05-small-chat/ClientRepository.py b/samples/networking/05-small-chat/ClientRepository.py new file mode 100644 index 0000000000..3dd82bd88a --- /dev/null +++ b/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) diff --git a/samples/networking/05-small-chat/client.py b/samples/networking/05-small-chat/client.py new file mode 100755 index 0000000000..4a76cb26ed --- /dev/null +++ b/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() diff --git a/samples/networking/05-small-chat/message.py b/samples/networking/05-small-chat/message.py new file mode 100755 index 0000000000..879935e37e --- /dev/null +++ b/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) diff --git a/samples/networking/05-small-chat/sample.dc b/samples/networking/05-small-chat/sample.dc new file mode 100755 index 0000000000..08c6f4505f --- /dev/null +++ b/samples/networking/05-small-chat/sample.dc @@ -0,0 +1,5 @@ +from message import Message + +dclass Message: DistributedObject { + sendText(string messageText) broadcast; +} diff --git a/samples/networking/05-small-chat/server.py b/samples/networking/05-small-chat/server.py new file mode 100644 index 0000000000..874e41ec79 --- /dev/null +++ b/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() diff --git a/samples/networking/06-simple-avatar/AIRepository.py b/samples/networking/06-simple-avatar/AIRepository.py new file mode 100644 index 0000000000..9e46f024d3 --- /dev/null +++ b/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) diff --git a/samples/networking/06-simple-avatar/ClientRepository.py b/samples/networking/06-simple-avatar/ClientRepository.py new file mode 100644 index 0000000000..145242260b --- /dev/null +++ b/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") diff --git a/samples/networking/06-simple-avatar/DistributedSmoothActor.py b/samples/networking/06-simple-avatar/DistributedSmoothActor.py new file mode 100644 index 0000000000..516f43c9f7 --- /dev/null +++ b/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) diff --git a/samples/networking/06-simple-avatar/ServerRepository.py b/samples/networking/06-simple-avatar/ServerRepository.py new file mode 100644 index 0000000000..59da3a6aa4 --- /dev/null +++ b/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) diff --git a/samples/networking/06-simple-avatar/client.py b/samples/networking/06-simple-avatar/client.py new file mode 100644 index 0000000000..c19cd6b2d4 --- /dev/null +++ b/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() diff --git a/samples/networking/06-simple-avatar/models/ralph-run.egg.pz b/samples/networking/06-simple-avatar/models/ralph-run.egg.pz new file mode 100644 index 0000000000..e50ff41d24 Binary files /dev/null and b/samples/networking/06-simple-avatar/models/ralph-run.egg.pz differ diff --git a/samples/networking/06-simple-avatar/models/ralph-walk.egg.pz b/samples/networking/06-simple-avatar/models/ralph-walk.egg.pz new file mode 100644 index 0000000000..955352549c Binary files /dev/null and b/samples/networking/06-simple-avatar/models/ralph-walk.egg.pz differ diff --git a/samples/networking/06-simple-avatar/models/ralph.egg.pz b/samples/networking/06-simple-avatar/models/ralph.egg.pz new file mode 100644 index 0000000000..5749c11650 Binary files /dev/null and b/samples/networking/06-simple-avatar/models/ralph.egg.pz differ diff --git a/samples/networking/06-simple-avatar/models/ralph.jpg b/samples/networking/06-simple-avatar/models/ralph.jpg new file mode 100644 index 0000000000..198b4f5424 Binary files /dev/null and b/samples/networking/06-simple-avatar/models/ralph.jpg differ diff --git a/samples/networking/06-simple-avatar/sample.dc b/samples/networking/06-simple-avatar/sample.dc new file mode 100755 index 0000000000..1a4800bc29 --- /dev/null +++ b/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; +} diff --git a/samples/networking/06-simple-avatar/server.py b/samples/networking/06-simple-avatar/server.py new file mode 100644 index 0000000000..0aab404a46 --- /dev/null +++ b/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() diff --git a/samples/networking/direct.dc b/samples/networking/direct.dc new file mode 100755 index 0000000000..7e272a7344 --- /dev/null +++ b/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); +};