Vegetarians beware, we're about to discuss the meat of DirectPlay: Messaging. This is not for the faint of heart! Get out the BBQ because we're gonna cook us up a mess of juicy code!
Mmmmmm, DirectPlay burger.
There are two types of messages that we'll encounter, user-defined messages, and system messages. A user-defined message is one that we've created ourselves for the purposes of exchanging game related information between players. A system message is one that's been created by DirectPlay itself for the purpose of maintaining DirectPlay state information.
For example, a user-defined message might be used to send ball movement data between the two players in a game of NetPONG. A system message could be used by DirectPlay to inform the host that a new player has joined the session.
System messages are sent automatically by DirectPlay when appropriate, but we can create and send user-defined messages whenever we so choose:
Const MSG_CHATTEXT = 0 Dim dpMsg As DirectPlayMessage 'Create a message object Set dpMsg = dp.CreateMessage dpMsg.WriteLong MSG_CHATTEXT dpMsg.WriteString strChatText dp.SendEx lngPlayerID, DPID_ALLPLAYERS, DPSEND_GUARANTEED, dpMsg, 0, 0, 0
Enter the DirectPlayMessage object. Initialize a new one with the DirectPlay4.CreateMessage method and then we're ready to go. DirectPlayMessage objects expose methods that allow us to write data using various data types (Long, Integers, Strings, whatever). In the code above, we're first writing a Long using the constant MSG_CHATTEXT. It is customary to start off a message with a Long indicating the message type (you must create and keep track of these constants yourself!) so that it can be sorted and handled appropriately--as you'll see later. After we've written the message type, we can proceed to fill the message with our desired data. In this case, we're sending a "chat string" which could be used for player-player communication during a game, for example. Assume that strChatText contains the string we're trying to send.
The DirectPlay4.SendEx method is used to actually send the message object after it has been loaded with data. The first parameter accepted by SendEx is the "playerID" of the sender.. remember, we stored this value back when we created the player? The second parameter indicates which player(s) we would like to send this message to. We can specify any single player (or "group"... see SDK!) or use the DPID_ALLPLAYERS constant to send the message to every player in the session.
The third parameter is where we indicate the "Send Flags" we'd like to use. There are quite a few, including DPSEND_ENCRYPT and DPSEND_SIGNED, but for the most part we'll likely be using DPSEND_GUARANTEED and DPSEND_ASYNC. Use DPSEND_GAURANTEED when you need to be sure that your message gets through, use DPSEND_ASYNC when you're sending messages that are not vital, and are possibly made obsolete by subsequent messages.
For example, when the host decides to "start" the game, guaranteed messages should be sent to all of the players in the session since it is imperative that they receive this information. Once inside a game, however, data is often sent in a redundant fashion. For example, in a game of StarCraft (or any other Real Time Strategy game), if the player loses one packet describing the current position of his enemy's troops, it is of little consequence since another packet will soon be along to replace it, and the previous information would have been obsolete even if it had been received. In situations like this, sending messages asynchronously is advantageous since they can be sent somewhat more quickly and in a rapid-fire fashion.
The fourth parameter accepted by the DirectPlay4.SendEx method must be the DirectPlayMessage object that we wish to send. The fifth, sixth, and seventh parameters are used for Priority, TimeOut, and Context. They will not be discussed here. There's just too darn much stuff to get through!
So, you know how to pack and send a message... it seems logical now to learn how to receive and unpack one!
Dim lngFromPlayerID As Long Dim lngToPlayerID As Long Dim lngMsgType As Long Dim dpMsg As DirectPlayMessage Dim lngMsgCount As Long lngMsgCount = dp.GetMessageCount(lngPlayerID) Do While lngMsgCount > 0 Set dpMsg = dp.Receive(lngFromPlayerID, lngToPlayerID, DPRECEIVE_ALL) lngMsgType = dpMsg.ReadLong() lngMsgCount = lngMsgCount - 1
First, we use the DirectPlay4.GetMessageCount method to retrieve the number of messages we have waiting for us. We must pass our "PlayerID" to the GetMessageCount function so that DirectPlay knows which player we're receiving messages for (yes, you can have more than one player active at a time!).
Next, we loop through and process each of these messages one by one. A new DirectPlayMessage object is set up, and is initialized by the DirectPlay4.Receive method which receives the next message in the queue. The Receive method returns some other data in addition to the DirectPlayMessage object. If we pass it a couple of Longs, it returns the "PlayerID" of the sender as well as the "PlayerID" of the intended recipient. The third parameter, the receive flags, allows us to indicate which messages we'd like to receive. Passing the constant DPRECEIVE_ALL indicates that we'd like to receive ANY message. With other constants, however, we can indicate that we'd like to receive messages only from a certain player (ie. the value of the first parameter), or messages sent to a certain player (second parameter).
Moving right along... once we've obtained the new message object (however we've done it), we can extract the first Long from it. This Long should contain the "message type" value described previously. For example, a value of MSG_CHATTEXT would indicate that this was intended as a chat text message, since this is the constant we defined earlier for this purpose! Also, we must decrement our message counter so that we only loop as many times as we have messages to process.
Now that we have all of this info from the message, we can process it. First we must distinguish between system messages and user-defined messages:
If lngFromPlayerID = DPID_SYSMSG Then '...handle system messages... Else '...handle user-defined messages... End If
The lngFromPlayerID variable now contains the "PlayerID" of the message's sender. This value will be equal to the constant DPID_SYSMSG if this message was NOT user defined. We can therefore use this IF statement to handle system messages, and use an ELSE to handle any other case--a user-defined message.
Whether it be a system or user-defined message, it can be further sorted according to its message type (the first Long). A Select Case statement is ideal for this:
Select Case lngMsgType Case DPSYS_DESTROYPLAYERORGROUP, DPSYS_CREATEPLAYERORGROUP '...a new player has joined/left the session, take action... End Select
Above we witness the handling of two possible system messages. If a player joins or leaves a session, this code will be activated. We can then take steps to accommodate the occurrence.
Select Case lngMsgType Case MSG_CHATTEXT '...we have receive chat text, take action... strChatText = dpMsg.ReadString() End Select
Here we see the handling of a user-defined message. Once we know that this is a chat text message, we can extract the text string it contains. This can then be stored in some variable (here we use a predefined string, strChatText) and used appropriately.