DirectPlay Messages

[Previous] [Next]

You're now ready to start creating all the game-specific DirectPlay code. You'll handle all Microsoft Windows messages in your program, but you also need to handle any DirectPlay messages received from other players. The MsgWaitForMultipleObjects routine, called at the top of the message-processing loop, handles player notifications received in the form of signaled events in the message loop that the WinMain function handles.

All you need to do is handle these messages when they are received. Often this consists of updating the other players' positions and verifying whether they've interacted with you somehow—for instance, checking that they didn't shoot or talk to your character. In the RoadRage code, you'll handle these types of messages as well as position messages sent so that each player's system can render the other players as they move through the virtual world.

The key issue here is understanding how to make the custom messages (message structures) required for your application, fill them with your data, and pass them to others connected to your game. You also need to know the counterpart to this, which is how to receive these messages, along with DirectPlay system messages, and what to do with them once you receive them. After all, although setting up your connection type and making the connection takes a great deal of code, a potentially more challenging (but fun) part is producing the code for passing the actual game-related messages to the other players connected to your session.

Passing Game Messages to Players

In your code, you need to define a player for each person connected to the gaming session. This PLAYER structure holds the player position and the rotational information so that the player can face in the desired direction. The structure also contains the RRnetID variable, which identifies the player when you create a message to pass to all the other players using the SendActionFlagMessage, SendActionWordMessage, or SendPositionMessage routine. (We'll cover these routines shortly.)

Here is the PLAYER structure:

 typedef struct player_typ {     float x;     float y;     float z;      float rot_angle;      int model_id;     int skin_tex_id;     BOOL walk_or_drive_mode;     int current_weapon;     int current_car;     int current_frame;     int current_sequence;     int health;     int armour;     int frags;     int ping;     DWORD RRnetID;     char name[256]; } PLAYER,*player_ptr; 

After creating the routines to handle the DirectPlay-based messaging you need to perform, along with setting up the CMyD3DApplication::MsgProc message handler routine to handle the user connecting, disconnecting, and respawning a character after it dies, you also need to add routines to handle messages for the DirectPlay control of the characters as well as the general motions for when the characters are at rest. Because the characters we've created for our game are Quake II_style characters, even when they are at rest, they move—for example, they breathe, shuffle their feet, or do some other basic repetitive action.

To handle DirectPlay messaging, we'll insert new case statements into the message-handling switch statement of CMyD3DApplication::MsgProc. The first item to check for is a WM_TIMER message. If the timer is the animation timer, loop through each player and animate it as well as its weapon (by using the AnimateCharacters routine). If the timer is the RESPAWN_TIMER, the player was killed, so it's regenerated with full health and at its starting position.

Finally, we need to add code to handle DirectPlay messages generated when a player disconnects from the game (ID_MULTIPLAYER_DISCONNECT) or connects to the game (ID_MULTIPLAYER_CONNECT).

 LRESULT CMyD3DApplication::MsgProc( HWND hWnd, UINT uMsg,                                     WPARAM wParam,                                     LPARAM lParam ) {     int MyPlayerNum;     int     nExitCode;     BOOL    bLaunchedByLobby;          switch( uMsg )     {                  case WM_TIMER:                           if(wParam == ANIMATION_TIMER)             {                 if( GetFramework() && GetbActive() &&                     GetbReady() && m_bWindowed )                 {                     if(IsRenderingOk == TRUE)                         AnimateCharacters();                     }             }             if(wParam == RESPAWN_TIMER)             {                 m_vEyePt.x = 700;                 m_vEyePt.y = 22;                 m_vEyePt.z = 700;                 MyHealth = 100;                 MyPlayerNum = GetPlayerNumber(MyRRnetID);                 player_list[MyPlayerNum].x = m_vEyePt.x;                 player_list[MyPlayerNum].y = m_vEyePt.y;                 player_list[MyPlayerNum].z = m_vEyePt.z;                 player_list[MyPlayerNum].rot_angle = 0;                 player_list[MyPlayerNum].bIsPlayerAlive = TRUE;                 player_list[MyPlayerNum].bStopAnimating = FALSE;                 player_list[MyPlayerNum].current_weapon = 0;                 player_list[MyPlayerNum].current_car = 0;                 player_list[MyPlayerNum].current_frame = 0;                 player_list[MyPlayerNum].current_sequence = 0;                 player_list[MyPlayerNum].bIsPlayerInWalkMode = TRUE;                 player_list[MyPlayerNum].health = 100;                 SendActionFlagMessage(MyRRnetID, TRUE,                                        APPMSG_RESPAWN);                                  KillTimer(hWnd, RESPAWN_TIMER);             }             break;                      case WM_COMMAND:             switch( LOWORD(wParam) )             {                 case MENU_ABOUT:                     Pause(TRUE);                     DialogBox(hInstApp, MAKEINTRESOURCE(IDD_ABOUT),                                hWnd, (DLGPROC)AppAbout);                     Pause(FALSE);                     break;                 case ID_MULTIPLAYER_DISCONNECT:                     PrintMessage(NULL,                          "MsgProc - ID_MULTIPLAYER_DISCONNECT",                          NULL, LOGFILE_ONLY);                     RrDestroyPlayer();                     break;                 case ID_MULTIPLAYER_CONNECT:                 if(multiplay_flag == TRUE)                     break;                 // See whether the session was launched from a                 // lobby server.                 hr = DPConnect_CheckForLobbyLaunch(&bLaunchedByLobby);                 if( FAILED(hr) )                 {                     if( hr == DPERR_USERCANCEL )                         return S_OK;                     return hr;                 }                 if( !bLaunchedByLobby )                 {                     // If not, the first step is to prompt the user                     // about the network connection and ask which                      // session he would like to join or whether he                     // wants to create a new session.                     nExitCode = DPConnect_StartDirectPlayConnect(                                                  hInstApp, FALSE );                     // See the above EXITCODE #defines for what                      // nExitCode can be.                      if( nExitCode == EXITCODE_QUIT )                     {                         // The user canceled the multiplayer                          // connect.                         // The sample will now quit.                         return E_ABORT;                     }                     if(nExitCode == EXITCODE_ERROR || g_pDP == NULL)                     {                         MessageBox( NULL,                                  "Multiplayer connect failed. ",                                 "You might need to reboot",                                     MB_OK | MB_ICONERROR );                         return E_FAIL;                     }                 }                 break;                  } 

To create a routine to handle all of the application-specific DirectPlay messages, we call the IDirectPlay4::Receive method. This method is defined as:

 HRESULT Receive(     LPDPID lpidFrom,     LPDPID lpidTo,     DWORD dwFlags,     LPVOID lpData,     LPDWORD lpdwDataSize ); 

ParameterDescription
lpidFrom Address of a variable to receive the sender's player ID. If the DPRECEIVE_FROMPLAYER flag is specified, this variable must be initialized with the player ID before calling this method.
lpidTo Address of a variable to receive the receiver's player ID. If the DPRECEIVE_TOPLAYER flag is specified, this variable must be initialized with the player ID before calling this method.
dwFlags One or more of the following control flags can be set. By default (dwFlags = 0), the first available message will be retrieved.

DPRECEIVE_ALL Returns the first available message. This is the default.

DPRECEIVE_PEEK Returns a message as specified by the other flags but doesn't remove it from the message queue. This flag must be specified if lpData is NULL.

DPRECEIVE_TOPLAYER and DPRECEIVE_FROMPLAYER If both DPRECEIVE_TOPLAYER and DPRECEIVE_FROMPLAYER are specified, Receive will only return messages that are (1) sent to the player specified by lpidTo and (2) sent from the player specified by lpidFrom. Both conditions must be met. If only DPRECEIVE_TOPLAYER is specified, Receive will only return messages sent to the player specified by lpidTo. If only DPRECEIVE_FROMPLAYER is specified, Receive will only return messages sent from the player specified by lpidFrom. If neither DPRECEIVE_TOPLAYER nor DPRECEIVE_FROMPLAYER is set, Receive will return the first available message.

lpData Pointer to a buffer where the message data is to be written. Set this parameter to NULL to request only the size of data. The lpdwDataSize parameter will be set to the size required to hold the data. If the message came from player ID DPID_SYSMSG, the application should cast lpData to DPMSG_GENERIC and check the dwType member to see what type of system message it is before processing it.
lpdwDataSize Pointer to a DWORD that is initialized to the size of the buffer before calling this method. After the method returns, this parameter will be set to the number of bytes copied into the buffer. If the buffer was too small (DPERR_BUFFERTOOSMALL), this parameter will be set to the buffer size required.

This method is used to get a DPLCONNECTION structure holding the information necessary to start and connect the application to a gaming session.

Once a message is received by the ProcessDirectPlayMessage routine, pass it (stored in the puMsgBuffer variable) to HandleSystemMessages, if the message is a system message (indicated by the DPID_SYSMSG ID) or HandleAppMessages, if the message is an application-specific (in our case, RoadRage) message.

This routine is defined as follows:

 HRESULT ProcessDirectPlayMessages( HWND hDlg ) {     DPID    idFrom;     DPID    idTo;     LPVOID  pvMsgBuffer;     DWORD   dwMsgBufferSize;     HRESULT hr;     if(g_pDP == NULL)         return FALSE;     // Read all messages in the queue.     dwMsgBufferSize = g_dwDPMsgBufferSize;     pvMsgBuffer     = g_pvDPMsgBuffer;          while( TRUE )     {         // See what's out there.         idFrom = 0;         idTo   = 0;         hr = g_pDP->Receive( &idFrom, &idTo, DPRECEIVE_ALL,                               pvMsgBuffer, &dwMsgBufferSize );         if( hr == DPERR_BUFFERTOOSMALL )         {             // The current buffer was too small,              // so reallocate it and try again.             SAFE_DELETE_ARRAY( pvMsgBuffer );             pvMsgBuffer = new BYTE[ dwMsgBufferSize ];             if( pvMsgBuffer == NULL )                 return E_OUTOFMEMORY;             // Save new buffer in global variables.             g_pvDPMsgBuffer     = pvMsgBuffer;             g_dwDPMsgBufferSize = dwMsgBufferSize;             continue; // Now that the buffer is bigger, try again.         }         if( DPERR_NOMESSAGES == hr )             return S_OK;         if( FAILED(hr) )             return hr;         // Handle the messages. Messages from DPID_SYSMSG are system          // messages; messages from elsewhere are application messages.         if( idFrom == DPID_SYSMSG )         {             hr = HandleSystemMessages( hDlg,                                    (DPMSG_GENERIC*)pvMsgBuffer,                                     dwMsgBufferSize, idFrom, idTo );             if( FAILED(hr) )                 return hr;         }         else         {             hr = HandleAppMessages( hDlg,                                     (GAMEMSG_GENERIC*)pvMsgBuffer,                                      dwMsgBufferSize, idFrom, idTo );             if( FAILED(hr) )                 return hr;         }     }     return S_OK; } 

The SendPlayerInfoMessage routine verifies that the mode is multiplayer mode. If it is, the routine creates a message containing the message type, the player number, the number of players in the gaming session, the player ID, and the player name. It then sends the message to all the other players (indicated by the DPID_ALLPLAYERS flag) by using the IDirectPlay4::SendEx method. This method is defined as follows:

 HRESULT SendEx(     DPID idFrom,     DPID idTo,     DWORD dwFlags,     LPVOID lpData,     DWORD dwDataSize,     DWORD dwPriority,     DWORD dwTimeout,     LPVOID lpContext,     LPDWORD lpdwMsgID ); 

ParameterDescription
idFrom ID of the sending player. The player ID must correspond to one of the local players on the computer from which the message is sent.
idTo The destination ID of the message. To send a message to another player, specify the ID of the player. To send a message to all the players in a group, specify the ID of the group. To send a message to all the players in the session, use the constant symbol DPID_ALLPLAYERS. To send a message to the server player, specify the constant symbol DPID_SERVERPLAYER. A player can't send a message to himself or herself.
dwFlags Indicates how the message should be sent. By default (dwFlags = 0), the message is sent nonguaranteed.

DPSEND_ASYNC Sends the message asynchronously. The function returns immediately and fills in the lpdwMsgID parameter. If this flag isn't specified, the function doesn't return until the message has been sent (and acknowledged, if sent guaranteed). A DPMSG_SENDCOMPLETE system message is posted when the send has completed. When this flag is used, the return value will be DPERR_PENDING if the message is put in the send queue. This is not a fatal error but rather a return value that lets you know the message didn't get sent immediately.

DPSEND_ENCRYPTED Sends the messages encrypted. This can be done only in a secure session. This flag can be used only if the DPSEND_GUARANTEED flag is also set. The message will be sent as a DPMSG_SECUREMESSAGE system message.

DPSEND_GUARANTEED Sends the message by using a guaranteed method of delivery if it is available.

DPSEND_NOSENDCOMPLETEMSG If this flag is set, no completion message is posted. This flag can be used only if the DPSEND_ASYNC flag is also set.

DPSEND_SIGNED Sends the message with a digital signature. This can be done only in a secure session. This flag can be used only if the DPSEND_GUARANTEED flag is also set. The message will be sent as a DPMSG_SECUREMESSAGE system message.

lpData Pointer to the data being sent.
dwDataSize Length of the data being sent.
dwPriority Priority of the message in the range from 0 through 65535. Zero is the lowest priority message. A larger number indicates a message with a higher priority. A message with a given priority will be sent only when there are no messages in the queue with a higher priority.

Not all service providers support this option. If it isn't supported, specifying a nonzero priority will return a DPERR_UNSUPPORTED error. Call GetCaps to determine whether this option is supported.

dwTimeout Optional application-supplied time-out, in milliseconds, that specifies a time limit for delivering the message. If the message can't be delivered within this time, it is automatically canceled and a DPMSG_SENDCOMPLETE message is posted. Zero indicates that no time-out should be used.

Not all service providers support this option. If it isn't supported, specifying a nonzero time-out will return a DPERR_UNSUPPORTED error. Call GetCaps to determine whether this option is supported.

lpContext An application-defined context that is returned to the application as part of the completion message when the send has been completed. Can be NULL.
lpdwMsgID Pointer to a DWORD that will be filled in with a DirectPlay-generated ID for the message. Use this ID to check the status of the message or to cancel it. Pass NULL if no message ID is needed. This parameter is returned only for asynchronous messages.

The SendPlayerInfoMessage routine follows:

 HRESULT SendPlayerInfoMessage(DWORD player_id, BYTE player_num,                  char *player_name, BYTE num_players, DWORD dwType) {     LPMSG_PLAYER_INFO lpPIMessage = NULL;     DWORD dwPIMessageSize = NULL;     HRESULT    hr;     FILE *fp;     DWORD player_name_length;     if(pCMyApp->multiplay_flag == FALSE)         return NULL;          player_name_length = lstrlen(player_name);     // Create space for message and player's name.      dwPIMessageSize = sizeof(MSG_PLAYER_INFO) + player_name_length;     lpPIMessage = (LPMSG_PLAYER_INFO) GlobalAllocPtr(GHND,                                                  dwPIMessageSize);     if (lpPIMessage == NULL)     {         hr = DPERR_OUTOFMEMORY;         goto FAILURE;     }     // Build message.         lpPIMessage->dwType  = dwType;     lpPIMessage->player_number = player_num;     lpPIMessage->num_players = num_players;     lpPIMessage->RRnetID = player_id;     lstrcpy(lpPIMessage->name, player_name);     fp = fopen("rrlogfile.txt","a");          fprintf( fp, "\n Send APPMSG_PLAYER_LIST -----\n");     fprintf( fp, "player_id     : %d\n", player_id);     fprintf( fp, "player_number : %d\n", player_num);     fprintf( fp, "num_players   : %d\n", num_players);     fprintf( fp, "-------------------\n\n");          fclose(fp);              // Send this string to all other players if you are the server.     hr = g_pDP->SendEx(pCMyApp->MyRRnetID, DPID_ALLPLAYERS,                         DPSEND_GUARANTEED,                         lpPIMessage, dwPIMessageSize , 1,                        time_out_period,                         NULL, NULL);          if(hr == DP_OK || DPERR_PENDING)         pCMyApp->num_packets_sent++;     else         goto FAILURE;     return (hr); FAILURE:     if (lpPIMessage)         GlobalFreePtr(lpPIMessage);     return (hr); } 

The next routine, SendActionDWordMessage, is similar to the SendPlayerInfoMessage routine, but it creates a message containing a DWORD for player actions (such as the APPMSG_HIT message when a player is hit) rather than player information:

 HRESULT SendActionDWordMessage(DWORD RRnetID, DWORD action_dword,                                DWORD dwType, DWORD SendTo) {     LPMSG_ACTIONDWORDINFO lpADWMessage = NULL;     DWORD dwADWMessageSize = 0;     HRESULT    hr;     if(pCMyApp->multiplay_flag == FALSE)         return NULL;          // Create space for message.      dwADWMessageSize = sizeof(MSG_ACTIONDWORDINFO);     lpADWMessage = (LPMSG_ACTIONDWORDINFO) GlobalAllocPtr(GHND,                                                 dwADWMessageSize);     if (lpADWMessage == NULL)     {         hr = DPERR_OUTOFMEMORY;         goto FAILURE;     }     // Build message.         lpADWMessage->dwType  = dwType;     lpADWMessage->RRnetID = pCMyApp->MyRRnetID;     lpADWMessage->action_dword = action_dword;     // Send this string to all other players.     hr = g_pDP->SendEx(RRnetID, SendTo, DPSEND_GUARANTEED,                        lpADWMessage, sizeof(MSG_ACTIONDWORDINFO),                        1, 0, NULL, NULL);     if(hr == DP_OK || DPERR_PENDING)         pCMyApp->num_packets_sent++;     else         goto FAILURE;     return (hr); FAILURE:     if (lpADWMessage)         GlobalFreePtr(lpADWMessage);     return (hr); } 

The next routine, SendActionFloatMessage, is again similar to the SendPlayerInfoMessage routine, but it creates a message-handling server and client messages (such as APPMSG_PING_TO_SERVER) rather than player information:

 HRESULT SendActionFloatMessage(DWORD RRnetID, float action_float,                                DWORD dwType, DWORD SendTo) {     LPMSG_ACTIONFLOATINFO lpADWMessage = NULL;     DWORD dwADWMessageSize = 0;     HRESULT    hr;     if(pCMyApp->multiplay_flag == FALSE)         return NULL;          // Create space for message.      dwADWMessageSize = sizeof(MSG_ACTIONFLOATINFO);     lpADWMessage = (LPMSG_ACTIONFLOATINFO) GlobalAllocPtr(GHND,                                                dwADWMessageSize);     if (lpADWMessage == NULL)     {         hr = DPERR_OUTOFMEMORY;         goto FAILURE;     }     // Build message.         lpADWMessage->dwType  = dwType;     lpADWMessage->RRnetID = RRnetID;     lpADWMessage->action_float = action_float;     // Send this message to all other players.     hr = g_pDP->SendEx(RRnetID, SendTo, DPSEND_ASYNC, lpADWMessage,                         sizeof(MSG_ACTIONFLOATINFO) , 1,                        time_out_period, NULL, NULL);     if(hr == DP_OK || DPERR_PENDING)         pCMyApp->num_packets_sent++;     else         goto FAILURE;     return (hr); FAILURE:     if (lpADWMessage)         GlobalFreePtr(lpADWMessage);     PrintMessage(NULL, "SendActionFloatMessage : FAILED", NULL,                  LOGFILE_ONLY);     return (hr); } 

Another routine we need to create is SendChatMessage. RoadRage provides the capability to send chat-style messages to other players in a gaming session (triggered by pressing the Tab key during a multiplayer gaming session). Once the user has typed a message, pressing Enter sends it to the other players. This capability adds to the fun when you're playing long distance because you can talk back and forth with the other players as the game progresses.

 HRESULT SendChatMessage(DWORD RRnetID, char *pChatStr) {     LPMSG_CHATSTRING    lpChatMessage    = NULL;     DWORD               dwChatMessageSize;     HRESULT             hr;     if(pCMyApp->multiplay_flag == FALSE)         return NULL;     // Create space for message plus string (string length included      // in message header).     dwChatMessageSize = sizeof(MSG_CHATSTRING) + lstrlen(pChatStr);     lpChatMessage = (LPMSG_CHATSTRING) GlobalAllocPtr(GHND,                                                 dwChatMessageSize);          if (lpChatMessage == NULL)     {         hr = DPERR_OUTOFMEMORY;         goto FAILURE;     }     // Build message.         lpChatMessage->dwType = APPMSG_CHATSTRING;     lstrcpy(lpChatMessage->szMsg, pChatStr);          // Send this string to all other players.     hr = g_pDP->SendEx(RRnetID, DPID_ALLPLAYERS, DPSEND_ASYNC,                         lpChatMessage, dwChatMessageSize, 1,                         time_out_period, NULL, NULL);     if(hr == DP_OK || DPERR_PENDING)         pCMyApp->num_packets_sent++;     else         goto FAILURE;     return (hr); FAILURE:     if (lpChatMessage)         GlobalFreePtr(lpChatMessage);     return (hr); } 

If a player has fired her character's gun, the first call is to StopDSound. This routine verifies that DirectSound is initialized, and if it is, calls SndObjStop.

The PlayDSound routine is then called. This routine calls SndObjPlay, which plays the sound by using the IDirectSoundBuffer_Play routine as long as DirectPlay is initialized.

Now that the sound associated with the character's action has been played, the DirectPlay task associated with this action can be performed. In this case, we need to send a message to the other players indicating we are firing our weapon.

To do this, we call the SendActionWordMessage routine indicating which gun is firing and specifying the APPMSG_FIRE message. This routine is one of three routines we define for the three message types we'll be sending or receiving.

The SendActionWordMessage routine is defined as follows:

 HRESULT SendActionWordMessage(DWORD RRnetID, int action_word,                               DWORD dwType) {     LPMSG_ACTIONWORDINFO lpAWMessage = NULL;     DWORD dwAWMessageSize = 0;     HRESULT    hr;     if(pCMyApp->multiplay_flag == FALSE)         return NULL;          // Create space for message.      dwAWMessageSize = sizeof(MSG_ACTIONWORDINFO);     lpAWMessage = (LPMSG_ACTIONWORDINFO) GlobalAllocPtr(GHND,                                                    dwAWMessageSize);     if (lpAWMessage == NULL)     {         hr = DPERR_OUTOFMEMORY;         goto FAILURE;     }     // Build message.         lpAWMessage->dwType  = dwType;     lpAWMessage->RRnetID = pCMyApp->MyRRnetID;     lpAWMessage->action_word = action_word;     // Send this string to all other players.     hr = g_pDP->SendEx(RRnetID, DPID_ALLPLAYERS, DPSEND_ASYNC,                        lpAWMessage, sizeof(MSG_ACTIONWORDINFO), 2,                        time_out_period, NULL, NULL);     if(hr == DP_OK || DPERR_PENDING)         pCMyApp->num_packets_sent++;     else         goto FAILURE;     return (hr); FAILURE:     if (lpAWMessage)         GlobalFreePtr(lpAWMessage);     return (hr); } 

The SendActionWordMessage routine verifies that we're in a multiplayer game. If we are, it allocates space for our message and then constructs it. The three parameters we specify for this message are dwType, RRnetID, and action_word. These parameters define the type of message we are sending, who is sending it (defined by our ID), and the action word that specifies which action our character is performing. In this case, we have passed APPMSG_FIRE to this routine, so we're indicating that we're firing our weapon.

The UpdatePosCallBack routine, used to update our position in the 3D world, first calls ReceiveMessage, which loops until it successfully reads a message. Once a message is read, ReceiveMessage determines whether the message is a system message or an application message. Finally, we call the SendPositionMessage routine to send out a message telling the other players where we are. (We'll go over this routine shortly.)

The UpdatePosCallBack routine is defined as follows:

 BOOL CALLBACK UpdatePosCallBack(UINT uTimerID,          UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2) {     int player_num;     if(pCMyApp->multiplay_flag == FALSE)         return FALSE;     ProcessDirectPlayMessages( NULL );     player_num = GetPlayerNumber(pCMyApp->MyRRnetID);     if(player_num == RRnetID_ERROR)     {         PrintMessage(NULL,"IsPlayerHit - UpdatePosCallBack FAILED",                      NULL, LOGFILE_ONLY);         return FALSE;     }     SendPositionMessage(pCMyApp->MyRRnetID,                          pCMyApp->m_vEyePt.x,                         pCMyApp->m_vEyePt.y,                         pCMyApp->m_vEyePt.z,                         pCMyApp->angy,                         pCMyApp->have_i_moved_flag );     return TRUE; } 

The SendPositionMessage routine takes the (x, y, z) location and view angle of the player. As long as we're in multiplayer mode, this routine allocates space for our position message and fills it with the position and the view angle, our player's ID, and the APPMSG_POSITION message type to indicate that the new message is a position message. By passing this message, we tell the other systems connected to the gaming session our newest position so that their 3D representations of our character can be rendered and animated.

 HRESULT SendPositionMessage(DWORD RRnetID, float x, float y, float z,                             float view_angle, BOOL IsRunning) {     LPMSG_POS lpPosMessage     = NULL;     DWORD     dwPosMessageSize = NULL;     HRESULT   hr;     if(pCMyApp->multiplay_flag == FALSE)         return NULL;          // Create space for message.      dwPosMessageSize = sizeof(MSG_POS);     lpPosMessage = (LPMSG_POS) GlobalAllocPtr(GHND,                                               dwPosMessageSize);     if (lpPosMessage == NULL)     {         hr = DPERR_OUTOFMEMORY;         goto FAILURE;     }     // Build message.         lpPosMessage->dwType  = APPMSG_POSITION;     lpPosMessage->x_pos = x;     lpPosMessage->y_pos = 0;     lpPosMessage->z_pos = z;     lpPosMessage->view_angle = (WORD)view_angle;     lpPosMessage->IsRunning  = IsRunning;      hr = g_pDP->SendEx(RRnetID, DPID_ALLPLAYERS, DPSEND_ASYNC,                         lpPosMessage, sizeof(MSG_POS) , 1,                        time_out_period, NULL, NULL);     if(hr == DP_OK || DPERR_PENDING)         pCMyApp->num_packets_sent++;     else         goto FAILURE;     return (hr); FAILURE:     if (lpPosMessage)         GlobalFreePtr(lpPosMessage);     return (hr); } 

The HandleSystemMessage we called in the ReceiveMessage routine earlier checks the message type of any system message we receive. If the message type is DPSYS_CREATEPLAYERORGROUP, we create a new player, getting the player name and initializing the player data for this new player. The data passed via this message includes the player position, name, weapon number, and so on.

 //------------------------------------------------------------------- // Name: HandleSystemMessages // Desc: Evaluates system messages and performs appropriate actions //------------------------------------------------------------------- HRESULT HandleSystemMessages( HWND hDlg, DPMSG_GENERIC* pMsg,                                DWORD dwMsgSize,                                DPID idFrom, DPID idTo ) {       LPSTR    lpszStr = NULL;     LPDPMSG_CREATEPLAYERORGROUP              lp = (LPDPMSG_CREATEPLAYERORGROUP) pMsg;     LPSTR    lpszPlayerName;     LPSTR    szDisplayFormat = "%s has joined";                  FILE *fp;     int i;     int player;          switch( pMsg->dwType )     {         case DPSYS_SESSIONLOST:             // Non-host message. This message is sent to all              // players when the host exits the game.             PrintMessage(NULL, "DPSYS_SESSIONLOST - host has quit",                           NULL, LOGFILE_ONLY);             if( g_bHostPlayer )                 return E_FAIL; // Sanity check             AddDpChatMessageToDisplay( "Host has quit the game" );             PostQuitMessage( DPERR_SESSIONLOST );             break;         case DPSYS_HOST:             PrintMessage(NULL, "DPSYS_HOST - you are now the host",                           NULL, LOGFILE_ONLY);             g_bHostPlayer = TRUE;             pCMyApp->MyPing = 0;             AddDpChatMessageToDisplay( "You are now the host" );             break;         case DPSYS_CREATEPLAYERORGROUP:             DPMSG_CREATEPLAYERORGROUP* pCreateMsg;             pCreateMsg = (DPMSG_CREATEPLAYERORGROUP*) pMsg;             // Update the number of active players.             g_dwNumberOfActivePlayers++;             // Get pointer to player name.             if (lp->dpnName.lpszShortNameA)                 lpszPlayerName = lp->dpnName.lpszShortNameA;             else                 lpszPlayerName = "unknown";                          for(i = 0; i < MAX_NUM_PLAYERS; i++)             {                 if( pCMyApp->player_list[i].bIsPlayerValid == FALSE )                 {                     player = i;                     pCMyApp->player_list[i].bIsPlayerValid = TRUE;                     break;                 }             }                      fp = fopen("rrlogfile.txt","a");             fprintf( fp, "\n receive DPSYS_CREATEPLAYERORGROUP \n");             fprintf( fp, "player_id     : %d\n", lp->dpId);             fprintf( fp, "player_number : %d\n", player);             fprintf( fp, "-------------------\n\n");             fclose(fp);             pCMyApp->player_list[player].x = 500;             pCMyApp->player_list[player].y = 12;             pCMyApp->player_list[player].z = 500;             pCMyApp->player_list[player].rot_angle = 0;             pCMyApp->player_list[player].model_id = 0;             pCMyApp->player_list[player].skin_tex_id = 27;             pCMyApp->player_list[player].ping = 0;             pCMyApp->player_list[player].health = 100;             pCMyApp->player_list[player].bIsPlayerAlive = TRUE;             pCMyApp->player_list[player].bStopAnimating = FALSE;             pCMyApp->player_list[player].current_weapon = 0;             pCMyApp->player_list[player].current_car = 0;             pCMyApp->player_list[player].current_frame = 0;             pCMyApp->player_list[player].current_sequence = 0;             pCMyApp->player_list[player].bIsPlayerInWalkMode = TRUE;             pCMyApp->player_list[player].RRnetID = lp->dpId;             pCMyApp->num_players++;             strcpy(pCMyApp->player_list[player].name,                    lpszPlayerName);                                  if(g_bHostPlayer == TRUE)             {                 for(i = 0; i < MAX_NUM_PLAYERS; i++)                 {                     if(pCMyApp->player_list[i].bIsPlayerValid ==                         TRUE)                     {                                             SendPlayerInfoMessage(                             pCMyApp->player_list[i].RRnetID,                             i,                             pCMyApp->player_list[i].name,                                 pCMyApp->num_players,                              APPMSG_PLAYER_LIST );                     }                 }                                                                            strcpy(buffer, lpszPlayerName);                 strcat(buffer, " has joined the game");                 AddDpChatMessageToDisplay( buffer );             }             DisplayNumberPlayersInGame( hDlg );             break; 

If the message type is DPSYS_DESTROYPLAYERORGROUP, indicating a player or group is leaving the gaming session, we call GetPlayerNumber, which determines which player has the RRnetID value we're looking for. We then call AddDpChatMessageToDisplay to create and post the message "Player [player name] has left the game" to all the other players to notify them that we're exiting the game. Next we decrement the global variable g_dwNumberOfActivePlayers, which defines the number of players in the game, and call DisplayNumberPlayersInGame to display this information.

         case DPSYS_DESTROYPLAYERORGROUP:             DPMSG_DESTROYPLAYERORGROUP* pDeleteMsg;             pDeleteMsg = (DPMSG_DESTROYPLAYERORGROUP*) pMsg;             fp = fopen("rrlogfile.txt","a");             fprintf( fp, "\n receive DPSYS_DESTROYPLAYERORGROUP \n");             player = GetPlayerNumber(pDeleteMsg->dpId);             if(player == RRnetID_ERROR)             {                 PrintMessage(NULL,                     "IsPlayerHit - DPSYS_DESTROYPLAYERORGROUP FAILED",                     NULL, LOGFILE_ONLY);                 break;             }             pCMyApp->player_list[player].bIsPlayerValid = FALSE;                          fprintf( fp, "player_id     : %d\n", pDeleteMsg->dpId);             fprintf( fp, "player_number : %d\n", player);             fprintf( fp, "-------------------\n\n");             fclose(fp);             strcpy(buffer, pCMyApp->player_list[player].name);             strcat(buffer, " has left the game");             AddDpChatMessageToDisplay( buffer );             // Update the number of active players.             g_dwNumberOfActivePlayers--;             DisplayNumberPlayersInGame( hDlg );             break;     }     return S_OK; } 

If the message type is DPSYS_HOST, we know that the current session host has exited the session. This means that we now need to become the host to allow everyone else to continue playing if the person who initiated the session decides to exit. To indicate that we are now the host, we set our bIsHost flag to TRUE.

         case DPSYS_HOST:             PrintMessage(NULL, "DPSYS_HOST - you are now the host",                           NULL, LOGFILE_ONLY);             g_bHostPlayer = TRUE;             pCMyApp->MyPing = 0;             AddDpChatMessageToDisplay( "You are now the host" );             break; 

Besides handling the system messages, we need to handle the application-specific messages. Application-specific messages are those that we create for our application rather than those that are generated by DirectPlay when certain events occur. We handle any application-specific messages with HandleApplicationMessage, which we call in the ReceiveMessage routine (discussed earlier) whenever an application-specific message is received.

The HandleAppMessage routine is rather long, so I won't show it all here. You can find it in the file dpmessages.cpp on the companion CD.

The first application-specific message we receive in the RoadRage code (and handled by the HandleApplicationMessage routine) is the APPMSG_GESTURE message. When this message is received, we determine which player made this gesture by calling GetPlayerNumber. This simple routine finds the player that matches the net ID (RRnetID) that was passed in.

GetPlayerNumber is defined as follows:

 int GetPlayerNumber(DWORD dpid) {     int i;     for(i = 0; i < pCMyApp->num_players; i++)     {         if(pCMyApp->player_list[i].bIsPlayerValid == TRUE)         {             if(pCMyApp->player_list[i].RRnetID == dpid)                  return i;                      }     }          PrintMessage(NULL, "GetPlayerNumber: Failed to get player ID",                  NULL, LOGFILE_ONLY);     return RRnetID_ERROR; } 

We then set the current animation sequence to the starting frame of the animation sequence for the gesture sequence so that this motion can be played.

NOTE
In Quake terminology, a gesture is a specific motion made by the 3D virtual character. When creating your own Quake character (using a tool such as the Quake 2 Modeller contained on this book's CD), you can decide what you want this motion to look like.

The next RoadRage application message we can receive is the APPMSG_CHANGE_WEAPON_TO message. When this message is received, we determine which player is switching weapons. We then set the weapon of that player to the new weapon so that the player will be rendered and animated with the correct weapon.

         case APPMSG_CHANGE_WEAPON_TO:             lpActionWord = (LPMSG_ACTIONWORDINFO)pMsg;             player_num = GetPlayerNumber(idFrom);             //lpActionWord->RRnetID);             if(player_num == RRnetID_ERROR)             {                 PrintMessage(NULL,                     "IsPlayerHit - APPMSG_CHANGE_WEAPON_TO FAILED",                     NULL, LOGFILE_ONLY);                 break;             }             pCMyApp->player_list[player_num].current_weapon =                 lpActionWord->action_word;                     pCMyApp->num_packets_received++;             break; 

The next application message we can receive is the APPMSG_FIRE message, which indicates that someone is firing a weapon using the GetPlayerNumber routine. When this message is received, we determine which player is firing and what weapon she has. We then set up the sequence of frames for firing this weapon so that the player model is animated correctly for firing the current weapon. Finally, we play the DirectSound sound associated with the firing of this weapon.

         case APPMSG_FIRE:             lpActionWord = (LPMSG_ACTIONWORDINFO)pMsg;             player_num   = GetPlayerNumber(lpActionWord->RRnetID);             if(player_num == RRnetID_ERROR)             {                 PrintMessage(NULL,"IsPlayerHit - APPMSG_FIRE FAILED",                              NULL, LOGFILE_ONLY);                 break;             }             curr_wep = lpActionWord->action_word;                                  pCMyApp->player_list[player_num].bIsFiring = TRUE;             if(pCMyApp->player_list[player_num].current_sequence != 2)             {                 if(pCMyApp->player_list[player_num].bIsPlayerAlive ==                    TRUE)                     pCMyApp->SetPlayerAnimationSequence(player_num, 2);                              }             pCMyApp->StopBuffer(TRUE, 0);             pCMyApp->PlaySound(0, NULL);             pCMyApp->num_packets_received++;             break; 

We can also receive the APPMSG_WALK_OR_DRIVE application message, which is sent when we switch between driving and walking mode in RoadRage. This code block sets the flag for the player switching modes so that it is rendered properly. Two other messages we can receive are APPMSG_JUMP and APPMSG_CROUCH. These messages will trigger the character to perform the walk or jump motions in the same manner as the APPMSG_GESTURE message. For details on this code, see the code for this chapter on the companion CD.

         case APPMSG_WALK_OR_DRIVE:             lpActionFlag = (LPMSG_ACTIONFLAGINFO)pMsg;             player_num = GetPlayerNumber(lpActionFlag->RRnetID);             if(player_num == RRnetID_ERROR)             {                 PrintMessage(NULL,                     "IsPlayerHit - APPMSG_WALK_OR_DRIVE FAILED",                     NULL, LOGFILE_ONLY);                 break;             }             pCMyApp->player_list[player_num].bIsPlayerInWalkMode =                 lpActionFlag->action_flag;             pCMyApp->num_packets_received++;             break;             case APPMSG_RESPAWN:             lpActionFlag = (LPMSG_ACTIONFLAGINFO)pMsg;             player_num = GetPlayerNumber(lpActionFlag->RRnetID);             if(player_num == RRnetID_ERROR)             {                 PrintMessage(NULL,                     "IsPlayerHit - APPMSG_RESPAWN FAILED", NULL,                     LOGFILE_ONLY);                 break;             }             pCMyApp->player_list[player_num].bIsPlayerAlive = TRUE;             pCMyApp->player_list[player_num].bStopAnimating = FALSE;             pCMyApp->player_list[player_num].current_weapon = 0;             pCMyApp->player_list[player_num].current_car = 0;             pCMyApp->player_list[player_num].current_frame = 0;             pCMyApp->player_list[player_num].current_sequence = 0;             pCMyApp->player_list[player_num].bIsPlayerInWalkMode =                 TRUE;             pCMyApp->player_list[player_num].health = 100;             pCMyApp->num_packets_received++;                 break;             case APPMSG_JUMP:             pCMyApp->num_packets_received++;             break;                      case APPMSG_CROUCH:             pCMyApp->num_packets_received++;             break; 

We can also receive the APPMSG_POSITION RoadRage application message, which is sent whenever a player moves. We first determine which player this message is from by calling the GetPlayerName routine and passing it the players network ID (RRnetID).

The APPMSG_POSITION message also contains the (x, y, z) position information in the x_pos, y_pos, and z_pos members. Additionally, the view_angle member is used to determine the direction the player is facing. Finally, if the IsRunning member is set to TRUE, we set up the character animation to play the running motion so that the character runs through our virtual world as we reposition him according to the player's selection of motion via the keyboard or joystick.

         case APPMSG_POSITION:                         lpPos = (LPMSG_POS)pMsg;                 player_num = GetPlayerNumber(idFrom);             //lpPos->RRnetID);             if(player_num == RRnetID_ERROR)             {                 PrintMessage(NULL,                     "IsPlayerHit - APPMSG_POSITION FAILED", NULL,                     LOGFILE_ONLY);                 break;             }             pCMyApp->player_list[player_num].x = lpPos->x_pos;                 pCMyApp->player_list[player_num].y = lpPos->y_pos+12;             pCMyApp->player_list[player_num].z = lpPos->z_pos;             pCMyApp->player_list[player_num].rot_angle =                 lpPos->view_angle;             pCMyApp->player_list[player_num].bIsRunning =                 lpPos->IsRunning;                          if(lpPos->IsRunning == TRUE)             {                     if(pCMyApp->player_list[player_num].current_sequence                    != 1)                 {                     if(pCMyApp->player_list[player_num].bIsPlayerAlive                        == TRUE)                         pCMyApp->SetPlayerAnimationSequence(                             player_num, 1);                     }             }             pCMyApp->num_packets_received++;             break; 

The final message we need to handle for the RoadRage program is the APPMSG_CHATSTRING message. This message allows us to pass textual messages to other players so that we can talk to them during a multiplayer session.

The code to handle a chat string message follows:

         case APPMSG_CHATSTRING:             LPMSG_CHATSTRING   lpszStr = (LPMSG_CHATSTRING) pMsg;             // Post string to chat window.             if (lpszStr)             {                 if (lpszStr->szMsg[0] != 13 )                 {                     AddDpChatMessageToDisplay(lpszStr->szMsg);                     pCMyApp->num_packets_received++;                 }             }             break; 

To create the chat string, call the AddDpChatMessageToDisplay routine. This routine copies the message string passed into the routine into the next line of the chat message we are readying to display to the other players.

 void AddDpChatMessageToDisplay(char *msg) {     int i;     if(DpChatMessage_counter < 4)     {         strcpy(DpChatMessages[DpChatMessage_counter], msg);         DpChatMessage_counter++;     }     else     {         for(i = 0; i < 3; i++)             strcpy(DpChatMessages[i], DpChatMessages[i+1] );         strcpy(DpChatMessages[3], msg);                  } } 

The routine to display the message is DisplayIncomingDpChatMessages. It displays the lines of the chat message (up to four) by outputting the lines to the screen.

 void DisplayIncomingDpChatMessages() {     int i;     for(i = 0; i < 4; i++)     {         if(DpChatMessages[i])             pCMyApp->OutputText(20,60+i*20, DpChatMessages[i]);     } } 

The SendActionFlagMessage routine is the final routine for handling the RoadRage application-specific messages. This message is used when we enter or exit the car as we switch between walking and driving mode. As with the other message-handling routines, this routine first verifies that we are connected to a multiplayer session. If we are, it allocates space for a new message, and as long as the allocation is successful, it fills the message. The slots we fill are the dwType, MyRRnetID, and the action_flag slots. Once constructed, this message is sent to all the players in the gaming session.

 HRESULT SendActionFlagMessage(DWORD RRnetID, BOOL action_flag,                                DWORD dwType) {     LPMSG_ACTIONFLAGINFO lpAFMessage = NULL;     DWORD dwAFMessageSize = NULL;     HRESULT    hr;          if(pCMyApp->multiplay_flag == FALSE)         return NULL;          // Create space for message plus.      dwAFMessageSize = sizeof(MSG_ACTIONFLAGINFO);     lpAFMessage = (LPMSG_ACTIONFLAGINFO) GlobalAllocPtr(GHND,                                                  dwAFMessageSize);     if (lpAFMessage == NULL)     {         hr = DPERR_OUTOFMEMORY;         goto FAILURE;     }     // Build message.         lpAFMessage->dwType = dwType;     lpAFMessage->RRnetID = pCMyApp->MyRRnetID;     lpAFMessage->action_flag = action_flag;          // Send this string to all other players if you are the server.     hr = g_pDP->SendEx(RRnetID, DPID_ALLPLAYERS, DPSEND_GUARANTEED,                         lpAFMessage, sizeof(MSG_ACTIONFLAGINFO) , 3,                         time_out_period, NULL, NULL);     if(hr == DP_OK || DPERR_PENDING)         pCMyApp->num_packets_sent++;     else         goto FAILURE;     return (hr); FAILURE:     if (lpAFMessage)         GlobalFreePtr(lpAFMessage);     return (hr);  } 

We've now covered all the messages, both system and application, that we need to handle for RoadRage. Remember that the SendActionWordMessage routine supports messages indicating a gun is firing using the APPMSG_FIRE message. Once the calls are made to generate the animated sequence for firing the weapon, this routine calls the IsPlayerHit routine as the final step.

The IsPlayerHit routine determines whether the person firing hit a player. If a player is hit, a pop-up message is displayed. IsPlayerHit then calls SendActionWordMessage to send an APPMSG_HIT message indicating that a player was hit. Although this routine is fairly long, it's pretty basic. The majority of the body just determines whether or not a character has been hit.

This routine is defined as follows:

 //------------------------------------------------------------------- // Name: CMyD3DApplication::IsPlayerHit // Desc: Determines whether one of the network players //       is hit when you fire your weapon //------------------------------------------------------------------- void CMyD3DApplication::IsPlayerHit() {     int i;     int player_num;     DWORD player_id;     if(num_players == 0)         return;     for(i = 0; i < num_players; i++)     {         player_id = player_list[i].RRnetID;                  if( (player_list[i].RRnetID != MyRRnetID) &&             (player_list[i].bIsPlayerValid == TRUE) &&             (player_id > 0) )         {                 gv_dx = player_list[i].x -m_vEyePt.x;             gv_dz = player_list[i].z -m_vEyePt.z;             gv_gradient     = gv_dz / gv_dx;              gv_target_angle = -1 * ((float)180 / (float)3.14159) *                                      (float)atan(gv_gradient);             gv_distance     = (float)sqrt(gv_dx * gv_dx +                                           gv_dz * gv_dz);                  gv_temp_angle    = (float)angy;                  if((gv_dx >= 0) && (gv_dz >= 0))             {                 gv_quadrant = 0; //quadrant 1    0 to 90                 gv_target_angle = 90 + gv_target_angle;              }             if((gv_dx >= 0) && (gv_dz < 0))                 gv_quadrant = 90; //quadrant 2   90 to 180              if((gv_dx < 0) && (gv_dz < 0))             {                 gv_quadrant = 180; //quadrant 3  180 to 270                 gv_target_angle = 90 + gv_target_angle;              }             if((gv_dx < 0) && (gv_dz >= 0))                 gv_quadrant = 270; //quadrant 4  270 to 360                      gv_target_angle += gv_quadrant;             if (gv_temp_angle >= 360)                 gv_temp_angle = gv_temp_angle - 360;             if (gv_temp_angle < 0)                 gv_temp_angle = gv_temp_angle + 360;             if (gv_target_angle >= 360)                 gv_target_angle = gv_target_angle - 360;             if (gv_target_angle < 0)                 gv_target_angle = gv_target_angle + 360;             gv_da         = gv_temp_angle - gv_target_angle;             gv_hit_width  = (float)(abs((int)((float)tan(k * gv_da) *                                               gv_distance)));              if(gv_hit_width < 10)             {                 player_list[i].health -= 20;                 if(player_list[i].health >= 0)                 {                     player_num = GetPlayerNumber(MyRRnetID);                     if((player_num >= 0) &&                        (player_num < MAX_NUM_PLAYERS))                     {                         SendActionDWordMessage(MyRRnetID,                                    player_list[i].RRnetID,                                    APPMSG_HIT,                                   DPID_ALLPLAYERS);                     }                     else                         PrintMessage(NULL,                             "IsPlayerHit - GetPlayerNumber FAILED",                              NULL, LOGFILE_ONLY);                     }                                  if(player_list[i].health <= 0)                 {                     if(player_list[i].bIsPlayerAlive == TRUE)                     {                         SetPlayerAnimationSequence(i, 6);                             player_list[i].bIsPlayerAlive = FALSE;                         player_list[player_num].frags++;                     }                 }                 else                     SetPlayerAnimationSequence(i, 4);             }         }                  } // end for i loop     } 

The final routine we need to create is the CMyD3DApplication::SetPlayerAnimationSequence routine. This member function is used to set the current animation sequence we want to have a particular player's model use. The sequence_number variable specifies which of the motions we want to have the animated character perform (based on our input—or someone else's, depending on the player number).

The start_frame, which is the first frame in the animation sequence, is stored in the current_frame of the desired player so that we can play this sequence. Remember that play means to load each model and render it sequentially to create an animated motion.

 void CMyD3DApplication::SetPlayerAnimationSequence(int player_number,                                                int sequence_number) {     int model_id;     int start_frame;     if(player_number >= num_players)     {         PrintMessage(NULL,                      "SetPlayerAnimationSequence player_"                      "number too large FAILED",                       NULL, LOGFILE_ONLY);         return;     }     player_list[player_number].current_sequence = sequence_number;          if(sequence_number > 6)     {         PrintMessage(NULL,                      "SetPlayerAnimationSequence - sequence_number "                      "too large FAILED", NULL, LOGFILE_ONLY);         return;     }     model_id = player_list[player_number].model_id;     if(model_id > MAX_NUM_PLAYERS)     {         PrintMessage(NULL,                      "SetPlayerAnimationSequence - model_id "                      "too large FAILED",                       NULL, LOGFILE_ONLY);         return;     }     start_frame =         pmdata[model_id].sequence_start_frame[sequence_number];         player_list[player_number].current_frame = start_frame; } 

With that, we've implemented nearly all the code necessary to turn RoadRage into a multiplayer game that supports all the available connection techniques (network, modem, and so on). A final necessary step is to create the code for allowing a character to exit from a multiplayer gaming session.

Ending the Game

When the game is complete (for example, if the user exits), you need to clean up. Simply destroy your player using the IDirectPlay4::DestroyPlayer method, which deletes a local player from the session. This method also removes any pending messages from the message queue that were intended for this player and removes the player from any groups she belonged to. Both the application that created the player and the session host can destroy the player. Here's the declaration for the IDirectPlay4::DestroyPlayer method:

 HRESULT DestroyPlayer (     DPID idPlayer   ); 

This function has only one parameter, idPlayer, which is the player ID of the locally owned player to remove from the session. When the session host calls DestroyPlayer, the DPID can be that of any player, including the local player and any remote players.

You can then call IDirectPlay4::Close to close your DirectPlay connection. This method will move any groups that were created locally so that the session host owns them. IDirectPlay4::Close also will destroy any locally created players and send the DPMSG_DELETEPLAYERFROMGROUP and DPMSG_DESTROYPLAYERORGROUP system messages to the other players in the session. The definition of the DPMSG_DELETEPLAYERFROMGROUP structure follows:

 typedef struct{     DWORD dwType;      DPID dpIdGroup;       DPID dpIdPlayer;  } DPMSG_ADDPLAYERTOGROUP, FAR *LPDPMSG_ADDPLAYERTOGROUP; typedef DPMSG_ADDPLAYERTOGROUP DPMSG_DELETEPLAYERFROMGROUP; 

ParameterDescription
dwType Identifies the message; this member is DPSYS_DELETEPLAYERFROMGROUP
dpIdGroup ID of the group from which the player was removed
dpIdPlayer ID of the player that was removed from the group

And here's the definition of the DPMSG_DESTROYPLAYERORGROUP structure:

 typedef struct{     DWORD  dwType;       DWORD  dwPlayerType;      DPID   dpId;      LPVOID lpLocalData;      DWORD  dwLocalDataSize;      LPVOID lpRemoteDate;      DWORD  dwRemoteDataSize;      DPNAME dpnName;      DPID   dpIdParent;      DWORD  dwFlags;  } DPMSG_DESTROYPLAYERORGROUP, FAR *LPDPMSG_DESTROYPLAYERORGROUP; 

ParameterDescription
dwType A DWORD that identifies the message. This member is DPSYS_DESTROYPLAYERORGROUP.
dwPlayerType A DWORD that identifies whether the message applies to a player (DPPLAYERTYPE_PLAYER) or a group (DPPLAYERTYPE_GROUP).
dpId ID of a player or group that's been destroyed.
lpLocalData Pointer to the local data associated with this player or group.
dwLocalDataSize Size, in bytes, of the local data.
lpRemoteData Pointer to the remote data associated with this player or group.
dwRemoteDataSize Size, in bytes, of the remote data.
dpnName Structure containing the name of the player or group.
dpIdParent ID of the parent group if the group being destroyed is a subgroup. (The group being destroyed was created by a call to IDirectPlay4::CreateGroupInGroup; otherwise, the value is 0.)
dwFlags Player or group flags. This parameter takes one or more of the following values:

DPGROUP_HIDDEN Set when a hidden group is destroyed

DPGROUP_STAGINGAREA Set when a group that is a staging area is destroyed

DPPLAYER_SERVERPLAYER Set when the player being destroyed is a server player for client/server communications

DPPLAYER_SPECTATOR Set when the player destroyed is a spectator; a spectator player behaves exactly like a normal player except that the player is flagged as a spectator

The following code shows how to handle this:

 void RrDestroyPlayer() {     if(pCMyApp->multiplay_flag == FALSE)         return;     timeKillEvent(pCMyApp->UpdatePosTimer);     timeKillEvent(pCMyApp->FireWeaponTimer);     if(g_pDP == NULL)     {         PrintMessage(NULL, "RrDestroyPlayer FAILED - g_pDP = NULL",                       NULL, LOGFILE_ONLY);         return;     }     else         PrintMessage(NULL, "RrDestroyPlayer  - g_pDP ok ",                       NULL, LOGFILE_ONLY);     if(g_pDP->DestroyPlayer( g_LocalPlayerDPID ) != DP_OK)     {         PrintMessage(NULL, "RrDestroyPlayer FAILED - DestroyPlayer",                       NULL, LOGFILE_ONLY);         return;     }     else         PrintMessage(NULL, "RrDestroyPlayer - DestroyPlayer ok",                       NULL, LOGFILE_ONLY);     if(g_pDP->Close() != DP_OK)     {         PrintMessage(NULL, "RrDestroyPlayer FAILED - Close",                       NULL, LOGFILE_ONLY);         return;     }     else         PrintMessage(NULL, "RrDestroyPlayer - Close ok",                       NULL, LOGFILE_ONLY);    SAFE_DELETE_ARRAY( g_pDPLConnection );    SAFE_RELEASE( g_pDPLobby );     PrintMessage(NULL, "RrDestroyPlayer - disconnected ok",                   NULL, LOGFILE_ONLY);             pCMyApp->multiplay_flag = FALSE;     pCMyApp->num_players = 0; } 

As the final steps of the WinMain routine (and thus the program), we call the CoUninitialize function as well as CloseHandle for the registry key and message event that we created at the beginning of the game. The CoUninitialize function closes the COM library.

Finally, the calls to CloseHandle, passing the registry key and message event handle, will invalidate the object handles you pass and decrement each object's handle count. When the last handle of the object is closed, the object is removed from the system.



Inside Direct3D
Inside Direct3D (Dv-Mps Inside)
ISBN: 0735606137
EAN: 2147483647
Year: 1999
Pages: 131

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net