home *** CD-ROM | disk | FTP | other *** search
/ Visual Basic Game Programming for Teens / VBGPFT.cdr / DirectX8 / dx8a_sdk.exe / samples / multimedia / directinput / multimapper / multimapper.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2000-11-04  |  50.5 KB  |  1,322 lines

  1. //-----------------------------------------------------------------------------
  2. // File: MultiMapper.cpp
  3. //
  4. // Desc: This is a simple sample to demonstrate how to code using the DirectInput
  5. //       mapper feature.
  6. //
  7. // Copyright (C) 1995-2000 Microsoft Corporation. All Rights Reserved.
  8. //-----------------------------------------------------------------------------
  9.  
  10. #define INITGUID
  11. #include <windows.h>
  12. #include <basetsd.h>
  13. #include <tchar.h>
  14. #include <dinput.h>
  15. #include <d3d8types.h>  // included to get the D3DCOLOR_RGBA macro.
  16. #include <stdio.h>
  17. #include "resource.h"
  18.  
  19. // The following constants control the number of users the app 
  20. // supports, and the maximum number of devices it will tolerate.
  21. // Generally, these are used to allocate arrays, but they're also
  22. // used for bounding iterations on those arrays.
  23. #define MIN_USERS 1
  24. #define MAX_USERS 4
  25. #define MAX_DEVICES 25  // more than reasonable max
  26.  
  27. // The following constants control spacing and alignment 
  28. // for text display in the status area.
  29. #define LINE_SPACING 20 
  30. #define COL_SPACING  70
  31. #define FIRST_LINE   20 
  32.  
  33. // Controls the number of actions the app asks DirectInput to buffer.
  34. #define BUFFER_SIZE  16 
  35.  
  36. //
  37. // Internal return values
  38. //
  39.  
  40. // E_APPERR_DEVICESTAKEN is returned by the manager class when one player
  41. // on the machine has enough RECENT devices to prevent other players from 
  42. // playing. This return code is needed because this sample attempts to give 
  43. // all RECENT devices to that player.
  44. #define E_APPERR_DEVICESTAKEN MAKE_HRESULT(SEVERITY_ERROR,FACILITY_ITF,998) 
  45.  
  46. // E_APPERR_TOOMANYUSERS is returned by the manager class when the number of 
  47. // players exceeds the number of devices present on the system. For example, 
  48. // if you ask for 4 players on a machine that only has a keyboard and mouse,
  49. // you're 2 short of what you need. 
  50. #define E_APPERR_TOOMANYUSERS MAKE_HRESULT(SEVERITY_ERROR,FACILITY_ITF,999)
  51.  
  52. // Flags to control the creation of the manager class.
  53. #define APP_DICREATE_DEFAULT    FALSE 
  54. #define APP_DICREATE_FORCEINIT  TRUE
  55.  
  56. // Flags to control the destruction of the internal device lists.
  57. // There are cases (i.e. reassigning devices) where it makes sense
  58. // to reset the devices held by the manager class, while preserving 
  59. // ownership information.
  60. #define APP_CLEANUP_DEFAULT            FALSE
  61. #define APP_CLEANUP_PRESERVEASSIGNMENT TRUE
  62.  
  63.  
  64. //-----------------------------------------------------------------------------
  65. // Name: CInputDeviceManager
  66. // Desc: This is a class to wrap up the DirectInput functionality for the app. 
  67. //       All DirectInput initialization, enumeration, and management of 
  68. //       DirectInput devices in contained within this class.
  69. //-----------------------------------------------------------------------------
  70. class CInputDeviceManager
  71. {
  72.     LPDIRECTINPUT8       m_pDI;
  73.     LPDIRECTINPUTDEVICE8 m_ppdidDevices[MAX_USERS][MAX_DEVICES];
  74.     DWORD                 m_dwNumUsers;
  75.     TCHAR*               m_szUserNames;
  76.     HWND                 m_hWnd;    
  77.     DIACTIONFORMAT       m_diaf;
  78.     DWORD                m_dwCreateFlags;
  79.     DWORD                m_dwTotalDevices;
  80.     
  81.     // Callback for enumerating suitable devices
  82.     static BOOL CALLBACK EnumSuitableDevicesCB( LPCDIDEVICEINSTANCE, LPDIRECTINPUTDEVICE8, 
  83.                                          DWORD, DWORD, LPVOID );
  84.  
  85.     static BOOL CALLBACK BuildFlatListCB( LPCDIDEVICEINSTANCE, LPDIRECTINPUTDEVICE8, 
  86.                                           DWORD, DWORD, LPVOID );
  87.  
  88.  
  89. public:
  90.     // Creation methods
  91.     CInputDeviceManager();
  92.     HRESULT Create( HWND, DWORD*, DIACTIONFORMAT*, DWORD);
  93.  
  94.     // Cleanup
  95.     VOID    Cleanup();
  96.     VOID    CleanupDevices( DWORD );
  97.  
  98.     // Add a device to the list
  99.     VOID    AddDeviceForPlayer( const DWORD dwPlayerNum, 
  100.                                 const DIDEVICEINSTANCE* pdidi, 
  101.                                 const LPDIRECTINPUTDEVICE8 pdidDevice );
  102.     
  103.     // Assign devices to other users.
  104.     VOID    ReassignDevices();
  105.  
  106.     // Access the device list for a user.
  107.     HRESULT GetDevicesForPlayer( DWORD, LPDIRECTINPUTDEVICE8**);
  108.     
  109.     // Invoke the Default configuration UI.
  110.     HRESULT ConfigureDevices(IUnknown*, VOID*,DWORD);
  111.  
  112.     // Simple data retrieval.
  113.     DWORD GetNumUsers();
  114.     DWORD GetNumDevices();
  115. };
  116.  
  117. // The PLAYERDATA structure that contains all the relevant data
  118. // for a single player in the game. An array of these structures is used
  119. // to track the information for all players. This array is initialized 
  120. // in the window procedure when the WM_CREATE message is received. 
  121. typedef struct _PLAYERDATA {
  122.     BOOL  bTurningRight;
  123.     BOOL  bReverseThrust;
  124.     BOOL  bTurningLeft;
  125.     BOOL  bForwardThrust;
  126.     BOOL  bFiringWeapons;
  127.     BOOL  bEnableShields;
  128.     BOOL  bDisplayingMenu;
  129.     DWORD dwLRAxisData;
  130.     DWORD dwUDAxisData;
  131. } PLAYERDATA, *LPPLAYERDATA;
  132.  
  133. // The ENUMDATA structure is used to carry information needed by the 
  134. // EnumDevicesBySemantics callback to add devices to the manager class's
  135. // internal lists, build/set action maps, and return the status (device 
  136. // found or not) of the enumeration.
  137. typedef struct _ENUMDATA {
  138.     CInputDeviceManager* pDevMan;
  139.     DWORD                dwCreateFlags;
  140.     HWND                 hWnd;
  141.     DWORD                dwPlayerNum;
  142.     TCHAR*               pszPlayerName;
  143.     LPDIACTIONFORMAT     lpdiaf;
  144.     BOOL                 bRet;
  145. } ENUMDATA, *LPENUMDATA;
  146.  
  147.  
  148. // Global function prototypes.
  149. HRESULT CreateInputStuff(HWND, DWORD);
  150. BOOL ParsePlayerInput(HWND, LPDIRECTINPUTDEVICE8*, LPPLAYERDATA);
  151. void InvokeDefaultUI(HWND);
  152.  
  153. //-----------------------------------------------------------------------------
  154. // App-defined game actions for the DirecInput Mapper.
  155. //-----------------------------------------------------------------------------
  156.  
  157.  
  158. // The following constants are custom for each app, depending on what input the
  159. // app needs. This simple sample is pretending to be a space simulator game, so
  160. // relevant inputs are for turning left, thrusting, firing weapons, etc.. Also
  161. // note that some constants are defines for the axes, which are for recieving
  162. // axis data from joysticks and similiar analog devices.
  163. #define INPUT_LEFTRIGHT_AXIS   1L
  164. #define INPUT_UPDOWN_AXIS      2L
  165. #define INPUT_TURNLEFT         3L
  166. #define INPUT_TURNRIGHT        4L
  167. #define INPUT_FORWARDTHRUST    5L
  168. #define INPUT_REVERSETHRUST    6L
  169. #define INPUT_FIREWEAPONS      7L
  170. #define INPUT_ENABLESHIELD     8L
  171. #define INPUT_DISPLAYGAMEMENU  9L
  172. #define INPUT_QUITGAME        10L
  173.  
  174.  
  175. // The following array is the global, app-defined game actions, which map real
  176. // device input into a semantic. The first column is the app-defined semantics
  177. // as defined above. These are the constants the game actually sees in its
  178. // input loop. The second column is the physical action recieved by the device
  179. // which is to be mapped to the app-defined semantic. For instance, in the
  180. // array below, if the user hits the "Left" key on the keyboard, the app will
  181. // recieve an input code equal to INPUT_TURNLEFT. The last column is a text
  182. // string that DirectInput uses for displaying a configuration dialog box.
  183. #define NUMBER_OF_SEMANTICS 17
  184.  
  185. DIACTION g_rgGameAction[NUMBER_OF_SEMANTICS] =
  186. {
  187.     // Device input (joystick, etc.) that is pre-defined by DirectInput, 
  188.     // according to genre type. The genre for this app is space simulators.
  189.     { INPUT_LEFTRIGHT_AXIS,  DIAXIS_SPACESIM_LATERAL,         0, _T("Turn"), },
  190.     { INPUT_UPDOWN_AXIS,     DIAXIS_SPACESIM_MOVE,            0, _T("Move"), },
  191.     { INPUT_FIREWEAPONS,     DIBUTTON_SPACESIM_FIRE,          0, _T("Shoot"), },
  192.     { INPUT_ENABLESHIELD,    DIBUTTON_SPACESIM_GEAR,          0, _T("Enable shields"), },
  193.     { INPUT_DISPLAYGAMEMENU, DIBUTTON_SPACESIM_DISPLAY,       0, _T("Display"), },
  194.  
  195.     // Keyboard input mappings
  196.     { INPUT_TURNLEFT,        DIKEYBOARD_LEFT,    0, _T("Turn left"), },
  197.     { INPUT_TURNRIGHT,       DIKEYBOARD_RIGHT,   0, _T("Turn right"), },
  198.     { INPUT_FORWARDTHRUST,   DIKEYBOARD_UP,      0, _T("Forward thrust"), },
  199.     { INPUT_REVERSETHRUST,   DIKEYBOARD_DOWN,    0, _T("Reverse thrust"), },
  200.     { INPUT_FIREWEAPONS,     DIKEYBOARD_F,       0, _T("Shoot"), },
  201.     { INPUT_ENABLESHIELD,    DIKEYBOARD_S,       0, _T("Enable shields"), },
  202.     { INPUT_DISPLAYGAMEMENU, DIKEYBOARD_D, DIA_APPFIXED, _T("Display"), },
  203.     { INPUT_QUITGAME,        DIKEYBOARD_ESCAPE,  DIA_APPFIXED, _T("Quit Game"), },
  204.  
  205.     // Mouse input mappings
  206.     { INPUT_LEFTRIGHT_AXIS,  DIMOUSE_XAXIS,      0, _T("Turn"), },
  207.     { INPUT_UPDOWN_AXIS,     DIMOUSE_YAXIS,      0, _T("Move"), },
  208.     { INPUT_FIREWEAPONS,     DIMOUSE_BUTTON0,    0, _T("Shoot"), },
  209.     { INPUT_ENABLESHIELD,    DIMOUSE_BUTTON1,    0, _T("Enable shields"), },
  210. };
  211.  
  212. //-----------------------------------------------------------------------------
  213. // Globals for the app
  214. //-----------------------------------------------------------------------------
  215. GUID                 g_AppGuid    = { 0x451F8DEF, 0xA7E9, 0x4DF4, 0x9A, 0x6B,
  216.                                       0xF4, 0xb6,0xC7, 0x06, 0xF3, 0x98 };
  217. LPTSTR               g_strAppName = _T("DirectInput8 MultiMapper Sample");
  218. CInputDeviceManager* g_pInputDeviceManager = NULL;
  219.  
  220.  
  221. //-----------------------------------------------------------------------------
  222. // Name: CInputDeviceManager()
  223. // Desc: Constructor for the class
  224. //-----------------------------------------------------------------------------
  225. CInputDeviceManager::CInputDeviceManager()
  226. {
  227.     m_pDI                  = NULL;
  228.     m_dwCreateFlags        = 0;
  229.     m_dwTotalDevices       = 0;
  230.  
  231.     // Uses canned names for players.
  232.     m_szUserNames  = _T("Player 1\0Player 2\0Player 3\0Player 4\0\0");
  233.     ZeroMemory( m_ppdidDevices, sizeof(m_ppdidDevices) );
  234. }
  235.  
  236.  
  237. //-----------------------------------------------------------------------------
  238. // Name: Create()
  239. // Desc: Creation method for the class. Creates DirectInput, and enumerates 
  240. //       "suitable" devices for each player. By "suitable", we mean devices
  241. //       that work with the genre specified in the DIACTIONFORMAT structure.
  242. //       This method initializes the class in two ways, as controlled by 
  243. //       the dwFlags field. In the default mode, the method can assign any 
  244. //       devices recently used by each user upon startup. This invites a case
  245. //       where a user has "ownership" of enough devices to limit the number of
  246. //       players who can interact with the app. If this happens, the class is 
  247. //       reinitialized with the APP_DICREATE_FORCEINIT, which causes the class
  248. //       to allocate a single device to each user (with default configs).
  249. //-----------------------------------------------------------------------------
  250. HRESULT CInputDeviceManager::Create( HWND hWnd, DWORD* lpdwNumUsers, 
  251.                                      DIACTIONFORMAT* pdiaf, DWORD dwFlags )
  252. {
  253.     HRESULT hr;
  254.  
  255.     // Copy passed in arguments for internal use.
  256.     m_hWnd        = hWnd;
  257.     memcpy( &m_diaf, pdiaf, sizeof(DIACTIONFORMAT) );
  258.     m_dwNumUsers = *lpdwNumUsers;
  259.     m_dwCreateFlags = dwFlags;
  260.  
  261.     // Create the main DirectInput object.
  262.     hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, 
  263.                              IID_IDirectInput8, (VOID**)&m_pDI, NULL );
  264.     if( FAILED(hr) )
  265.     {
  266.         return E_FAIL;
  267.     }
  268.  
  269.     TCHAR* pszNameScan = m_szUserNames;
  270.     for(DWORD dwPlay = 0; dwPlay < m_dwNumUsers; dwPlay++)
  271.     {        
  272.         ENUMDATA ed;
  273.         ZeroMemory( &ed, sizeof(ed) );
  274.         ed.pDevMan = this;
  275.         ed.dwPlayerNum = dwPlay;
  276.         ed.pszPlayerName = pszNameScan;
  277.         ed.bRet = FALSE; // Will be TRUE if a device was found.
  278.         ed.lpdiaf = &m_diaf;
  279.         ed.hWnd = m_hWnd;
  280.         ed.dwCreateFlags = m_dwCreateFlags;
  281.  
  282.         if( m_dwCreateFlags == APP_DICREATE_DEFAULT)
  283.         {
  284.             // Enumerate "suitable" devices for this user.
  285.             hr = m_pDI->EnumDevicesBySemantics( pszNameScan, &m_diaf, 
  286.                                                 EnumSuitableDevicesCB, 
  287.                                                 (LPVOID)&ed, DIEDBSFL_THISUSER | DIEDBSFL_AVAILABLEDEVICES);
  288.         }
  289.         else // m_dwCreateFlags == APP_DICREATE_FORCEINIT
  290.         {
  291.             // Enumerate available devices for any user.
  292.             hr = m_pDI->EnumDevicesBySemantics( NULL, &m_diaf, 
  293.                                                 EnumSuitableDevicesCB, 
  294.                                                 (LPVOID)&ed, DIEDBSFL_AVAILABLEDEVICES);
  295.         }
  296.  
  297.         if( FAILED(hr))
  298.         {
  299.             return hr;
  300.         }
  301.         
  302.         if(!ed.bRet)
  303.         {
  304.             if(m_dwTotalDevices < m_dwNumUsers)
  305.                 return E_APPERR_TOOMANYUSERS;
  306.             else
  307.                 return E_APPERR_DEVICESTAKEN;
  308.         }
  309.     
  310.         // Scan to the next username in the multi-sz list.
  311.         pszNameScan += strlen(pszNameScan) + 1;
  312.     }
  313.  
  314.     return S_OK;
  315. }
  316.  
  317. //-----------------------------------------------------------------------------
  318. // Name: GetNumUsers()
  319. // Desc: Returns the number of users currently using the "game".
  320. //-----------------------------------------------------------------------------
  321. DWORD CInputDeviceManager::GetNumUsers()
  322. {
  323.     return m_dwNumUsers;
  324. }
  325.  
  326. //-----------------------------------------------------------------------------
  327. // Name: GetNumDevices()
  328. // Desc: Returns the number of devices present on the machine.
  329. //-----------------------------------------------------------------------------
  330. DWORD CInputDeviceManager::GetNumDevices()
  331. {
  332.     return m_dwTotalDevices;
  333. }
  334.  
  335.  
  336. //-----------------------------------------------------------------------------
  337. // Name: EnumSuitableDevicesCB()
  338. // Desc: Callback function for enumerating suitable devices. The manager class
  339. //       enumerates devices for each player, so this method is called a lot 
  340. //       (#_of_players * installed_devices).
  341. //       
  342. //       This method attempts to assign devices recently used by a player in a 
  343. //       previous instance to that player for this instance. As such, any device 
  344. //       that is enumerated with the DIEDBS_RECENTDEVICE flag is assigned to the 
  345. //       player for which the devices are being enumerated. Devices marked by 
  346. //       DIEDBS_NEWDEVICE and DIEDBS_MAPPEDPRI1 are added to a flat list.
  347. //       If no recent devices are found for the user, we take the topmost device
  348. //       in the flat list (the most suitable) and throw the others away.
  349. //-----------------------------------------------------------------------------
  350. BOOL CALLBACK CInputDeviceManager::EnumSuitableDevicesCB( LPCDIDEVICEINSTANCE  pdidi,
  351.                                                           LPDIRECTINPUTDEVICE8 pdidDevice, 
  352.                                                           DWORD  dwFlags,
  353.                                                           DWORD  dwRemainingDevices,
  354.                                                           LPVOID pContext )
  355. {
  356.     HRESULT hr;
  357.     ENUMDATA ed = *(LPENUMDATA)pContext;
  358.     DWORD dwBuildFlags;    
  359.  
  360.     // Temp array to hold non-recent/new devices for later allocation.
  361.     // A linked list would be a better choice, but for simplicity, we'll 
  362.     // use a simple array.
  363.     static LPDIRECTINPUTDEVICE8 lprgDevTemp[MAX_DEVICES]; 
  364.     static DIDEVICEINSTANCE     didTemp[MAX_DEVICES];
  365.     static int iIndex = 0;
  366.         
  367.  
  368.     // Devices of type DI8DEVTYPE_DEVICECTRL are specialized devices not generally
  369.     // considered appropriate to control game actions. We just ignore these.
  370.     if( DI8DEVTYPE_DEVICECTRL == GET_DIDEVICE_TYPE(pdidi->dwDevType) )
  371.     {
  372.         return DIENUM_CONTINUE;     
  373.     }
  374.  
  375.     if( dwFlags & DIEDBS_RECENTDEVICE )
  376.     {
  377.         // Set the cooperative level.
  378.         hr = pdidDevice->SetCooperativeLevel( ed.hWnd, DISCL_EXCLUSIVE|DISCL_FOREGROUND );
  379.         if( FAILED(hr) )
  380.         {
  381.             return DIENUM_CONTINUE;     
  382.         }
  383.  
  384.         // Set the action map for this player to remove it from DirecInput's internal
  385.         // list of available devices.
  386.         dwBuildFlags = (ed.dwCreateFlags == APP_DICREATE_DEFAULT) ? DIDBAM_DEFAULT : DIDBAM_HWDEFAULTS;
  387.         pdidDevice->BuildActionMap( ed.lpdiaf, ed.pszPlayerName, dwBuildFlags );
  388.         hr = pdidDevice->SetActionMap( ed.lpdiaf, ed.pszPlayerName, DIDSAM_DEFAULT ); 
  389.  
  390.         // Add the device to the device manager's internal list
  391.         ed.pDevMan->AddDeviceForPlayer( ed.dwPlayerNum, pdidi, pdidDevice );
  392.  
  393.         // If BuildActionMap fails, so will SetActionMap. Using a single error check here for simplicity.
  394.         // These may fail if no controls on this device are appropriate. However, other devices may be 
  395.         // just fine, so continue the enumeration.
  396.         if( FAILED(hr) )
  397.         {
  398.             return DIENUM_CONTINUE;
  399.         }
  400.         
  401. #ifdef _DEBUG
  402.         TCHAR str[MAX_PATH];
  403.         OutputDebugString(_T("\n--> !!!RECENT DEVICE ASSIGNED!!! <--\n"));
  404.         sprintf(str, _T("  Player == %s\n"), ed.pszPlayerName );
  405.         OutputDebugString(str);
  406.         sprintf(str, _T("  Device == %s\n"), (*pdidi).tszProductName );
  407.         OutputDebugString(str);
  408.                 
  409.         SYSTEMTIME st;
  410.         FILETIME   ft;  
  411.         // Convert the time to local time.
  412.         FileTimeToLocalFileTime(&ed.lpdiaf->ftTimeStamp, &ft);
  413.         // Convert the local file time from UTC to system time.
  414.         FileTimeToSystemTime(&ft, &st);
  415.  
  416.         // Build a string showing the date and time.
  417.         wsprintf( str, _T("  Timestamp == %02d/%02d/%d  %02d:%02d:%02d \n"), 
  418.                   st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond);
  419.         OutputDebugString(str);
  420.  
  421.         sprintf(str, _T("  dwFlags == %x\n"), dwFlags);
  422.         OutputDebugString(str);
  423. #endif
  424.         // Set return value in ENUMDATA struct to indicate success and 
  425.         // stop enumeration.
  426.         ((LPENUMDATA)pContext)->bRet = TRUE;
  427.     }
  428.     else if( dwFlags & ( DIEDBS_MAPPEDPRI1 | DIEDBS_NEWDEVICE ) )
  429.     {   // This is neither a RECENT nor a NEW device, add it to an internal list
  430.         // of devices. If we get to the end of the devices without finding a recently
  431.         // used or new device, we'll try one of these. 
  432.         lprgDevTemp[iIndex] = pdidDevice;
  433.         didTemp[iIndex]     = *(LPDIDEVICEINSTANCE)pdidi;
  434.         lprgDevTemp[iIndex++]->AddRef();
  435.  
  436. #ifdef _DEBUG
  437.         TCHAR str[MAX_PATH];
  438.         if( dwFlags & DIEDBS_NEWDEVICE )
  439.             OutputDebugString(_T("\n--> NEW DEVICE FOUND (not yet assigned) <--\n"));
  440.         else if ( dwFlags & DIEDBS_MAPPEDPRI1 )
  441.             OutputDebugString(_T("\n--> PRI1-CAPABLE DEVICE FOUND (not yet assigned) <--\n"));
  442.  
  443.         sprintf(str, _T("  Device == %s\n"), (*pdidi).tszProductName );
  444.         OutputDebugString(str);
  445. #endif
  446.     }
  447.  
  448.     // If no more devices will be enumerated, use one of the other appropriate devices 
  449.     // (according to DIEDBS_MAPPEDPRI1) for this user. For simplicity, we're just taking 
  450.     // the first one in the list. Real applications would probably iterate through action
  451.     // maps for these devices and choose the one with the most game-critical actions assigned. 
  452.     if( 0 == dwRemainingDevices &&  (((LPENUMDATA)pContext)->bRet != TRUE) ) 
  453.     {
  454.         dwBuildFlags = (ed.dwCreateFlags == APP_DICREATE_DEFAULT) ? DIDBAM_DEFAULT : DIDBAM_HWDEFAULTS;
  455.  
  456.         // Set the action map for this player to remove it from DirectInput's internal
  457.         // list of available devices.
  458.         if( lprgDevTemp[0] )
  459.         {
  460.             // Add the device to the device manager's internal list
  461.             ed.pDevMan->AddDeviceForPlayer( ed.dwPlayerNum, &didTemp[0], lprgDevTemp[0] );
  462.  
  463.             lprgDevTemp[0]->BuildActionMap( ed.lpdiaf, ed.pszPlayerName, dwBuildFlags );
  464.             hr = lprgDevTemp[0]->SetActionMap( ed.lpdiaf, ed.pszPlayerName, DIDSAM_DEFAULT ); 
  465.             // If BuildActionMap fails, so will SetActionMap. Using a single error check here for simplicity.
  466.             // These may fail if no controls on this device are appropriate. However, other devices may be 
  467.             // just fine, so continue the enumeration.
  468.             if( FAILED(hr) )
  469.                 return DIENUM_CONTINUE;
  470.  
  471.             ((LPENUMDATA)pContext)->bRet = TRUE;
  472.  
  473. #ifdef _DEBUG
  474.             TCHAR str[MAX_PATH];
  475.             OutputDebugString(_T("\n--> !!! DEVICE ASSIGNED !!! <--\n"));
  476.             sprintf(str, _T("  Player == %s\n"), ed.pszPlayerName );
  477.             OutputDebugString(str);
  478.             sprintf(str, _T("  Device == %s\n"), didTemp[0].tszProductName );
  479.             OutputDebugString(str);
  480.             
  481.             SYSTEMTIME st;
  482.             FILETIME   ft;  
  483.             // Convert the time to local time.
  484.             FileTimeToLocalFileTime(&ed.lpdiaf->ftTimeStamp, &ft);
  485.             // Convert the local file time from UTC to system time.
  486.             FileTimeToSystemTime(&ft, &st);
  487.  
  488.             // Build a string showing the date and time.
  489.             wsprintf( str, _T("  Timestamp == %02d/%02d/%d  %02d:%02d:%02d \n"), 
  490.                       st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond);
  491.             OutputDebugString(str);
  492.  
  493.             sprintf(str, _T("  dwFlags == %x\n"), dwFlags);
  494.             OutputDebugString(str);
  495. #endif
  496.         }
  497.  
  498.         // Release all of the remaining devices in the list.
  499.         for (int i=1 ; i<iIndex ; i++ )
  500.         {
  501.             if( lprgDevTemp[i] )
  502.             {
  503.                 lprgDevTemp[i]->Release();
  504.             }
  505.         }
  506.  
  507.         iIndex = 0; // reset counter for next pass.
  508.     }
  509.  
  510.     return DIENUM_CONTINUE;
  511. }
  512.  
  513.  
  514. //-----------------------------------------------------------------------------
  515. // Name: BuildFlatListCB()
  516. // Desc: Callback function for the enumeration invoked by ReassignDevices(). 
  517. //       This method just adds devices to a flat array that represents all the 
  518. //       devices present on the machine.
  519. //-----------------------------------------------------------------------------
  520. BOOL CALLBACK CInputDeviceManager::BuildFlatListCB( LPCDIDEVICEINSTANCE  pdidi,
  521.                                                     LPDIRECTINPUTDEVICE8 pdidDevice, 
  522.                                                     DWORD  dwFlags,
  523.                                                     DWORD  dwRemainingDevices,
  524.                                                     LPVOID pContext )
  525. {
  526.     static DWORD dwIndex = 0;
  527.     LPDIRECTINPUTDEVICE8* pdidDevArray;
  528.     pdidDevArray = (LPDIRECTINPUTDEVICE8*)pContext;
  529.  
  530.     pdidDevArray[dwIndex++] = pdidDevice;
  531.     pdidDevice->AddRef(); // Must AddRef any interfaces we keep.
  532.  
  533.     if(!dwRemainingDevices)
  534.     {
  535.         dwIndex = 0; 
  536.     }
  537.  
  538.     return DIENUM_CONTINUE;
  539. }
  540.  
  541.  
  542.  
  543. //-----------------------------------------------------------------------------
  544. // Name: AddDeviceForPlayer()
  545. // Desc: Adds a device to the internal array for a player. For gaming devices, 
  546. //       this method sets an axis deadzone. This could be done elsewhere, but 
  547. //       it makes sense to take care of it here since this is a centralized 
  548. //       location.
  549. //-----------------------------------------------------------------------------
  550. VOID CInputDeviceManager::AddDeviceForPlayer( const DWORD dwPlayerNum, 
  551.                                               const DIDEVICEINSTANCE* pdidi, 
  552.                                               const LPDIRECTINPUTDEVICE8 pdidDevice )
  553. {
  554.     DWORD                dwDeviceType = pdidi->dwDevType;
  555.  
  556.     // If this is a gaming device, set an axis deadzone.
  557.     if(GET_DIDEVICE_TYPE(dwDeviceType) & (~(DI8DEVTYPE_MOUSE & DI8DEVTYPE_KEYBOARD)))
  558.         {
  559.         DIPROPDWORD dipdw;
  560.         dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
  561.         dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
  562.         dipdw.diph.dwObj        = 0;
  563.         dipdw.diph.dwHow        = DIPH_DEVICE;
  564.         dipdw.dwData            = 1000;
  565.  
  566.         // If we can't set the deadzone, don't worry. Setting it is a good idea to 
  567.         // avoid jitter on axes, however.
  568.         pdidDevice->SetProperty( DIPROP_DEADZONE, &dipdw.diph );
  569.     }
  570.  
  571.     // Add the device to the end of the list for this user.
  572.     DWORD iDev = 0;
  573.     while( NULL != m_ppdidDevices[dwPlayerNum][iDev] ) iDev++;
  574.  
  575.     m_ppdidDevices[dwPlayerNum][iDev] = pdidDevice;
  576.     m_ppdidDevices[dwPlayerNum][iDev]->AddRef();  // Must increment the ref cout when duplicating interface pointers.
  577.  
  578.     m_dwTotalDevices++;
  579. }
  580.  
  581. //-----------------------------------------------------------------------------
  582. // Name: ReassignDevices()
  583. // Desc: Called after the the DirectInput Mapper Default UI returns from the 
  584. //       ConfigureDevices call. This method starts by flushing the manager 
  585. //       class's internal lists, then it creates a flat list of all devices
  586. //       on the machine (by calling EnumDevicesBySemantics with a NULL username).
  587. //       Once the list is built, it rolls through all devices to retrieve the
  588. //       current owner (via DIPROP_USERNAME). If the device has a username, the 
  589. //       method calls AddDeviceForPlayer method to give that device to the 
  590. //       appropriate user.
  591. //-----------------------------------------------------------------------------
  592. VOID CInputDeviceManager::ReassignDevices()
  593. {
  594.     LPDIRECTINPUTDEVICE8 prgAllDev[MAX_DEVICES]; // Flat array for all devices on the machine.
  595.     TCHAR szNameDev[MAX_PATH];
  596.     DWORD dwDev;
  597.  
  598.     // Clean-out the class-internal array of devices for each user.
  599.     CleanupDevices( APP_CLEANUP_PRESERVEASSIGNMENT );
  600.  
  601.     // Build a simple flat list of all devices currently attached to the machine. This
  602.     // array will be used to reassign devices to each user.
  603.     // 
  604.     // Using a NULL username and omitting the DIEDBSFL_THISUSER flag enumerates all devices.
  605.     ZeroMemory( prgAllDev, sizeof(prgAllDev) );
  606.     m_pDI->EnumDevicesBySemantics( NULL, &m_diaf, BuildFlatListCB, 
  607.                                    prgAllDev, DIEDBSFL_ATTACHEDONLY); 
  608.    
  609.     DIPROPSTRING dips;
  610.     dips.diph.dwSize       = sizeof(DIPROPSTRING); 
  611.     dips.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
  612.     dips.diph.dwObj        = 0; // device property 
  613.     dips.diph.dwHow        = DIPH_DEVICE; 
  614.  
  615.     // Now we've got an array with every device attached to the system. 
  616.     // Loop through them all and assign them to a player in the temp array.
  617.     dwDev = 0;
  618.     while( prgAllDev[dwDev] )
  619.     {
  620.         prgAllDev[dwDev]->GetProperty( DIPROP_USERNAME,
  621.                                        &dips.diph );
  622.         
  623.         // Convert the string from Unicode to ANSI.
  624.         WideCharToMultiByte( CP_ACP, 0, dips.wsz, -1,
  625.                              szNameDev, MAX_PATH,NULL, NULL);
  626.  
  627.         // Determine who this device is now assigned to (as a DWORD value)
  628.         // If the device is unassigned (i.e. no username), we skip it.
  629.         if( strlen(szNameDev) )
  630.         {
  631.             DWORD dwAssignedTo;
  632.             dwAssignedTo = (DWORD) ( (byte)szNameDev[strlen(szNameDev)-1] - ((byte)'1') );
  633.             
  634.             DIDEVICEINSTANCE didi;
  635.             ZeroMemory( &didi, sizeof(didi) );
  636.             didi.dwSize = sizeof(didi);
  637.             prgAllDev[dwDev]->GetDeviceInfo( &didi );
  638.  
  639.             // Get the device ready to go again for their user.
  640.             prgAllDev[dwDev]->BuildActionMap( &m_diaf, szNameDev, DIDBAM_DEFAULT );
  641.             prgAllDev[dwDev]->SetActionMap( &m_diaf, szNameDev, DIDSAM_DEFAULT );
  642.             prgAllDev[dwDev]->SetCooperativeLevel( m_hWnd, DISCL_EXCLUSIVE|DISCL_FOREGROUND );
  643.             
  644.             // Now add it for the player.
  645.             AddDeviceForPlayer( dwAssignedTo, &didi, prgAllDev[dwDev] );
  646.  
  647. #ifdef _DEBUG
  648.             TCHAR str[MAX_PATH];
  649.             sprintf(str, _T("--> DEVICE ASSIGNED <--\n") );
  650.             OutputDebugString(str);
  651.             sprintf(str, _T("  Player == %s\n"), szNameDev );
  652.             OutputDebugString(str);
  653.             sprintf(str, _T("  Device == %s\n"), didi.tszProductName );
  654.             OutputDebugString(str);
  655.             SYSTEMTIME st;
  656.             FILETIME   ft;  
  657.  
  658.             // Convert the time to local time.
  659.             FileTimeToLocalFileTime(&m_diaf.ftTimeStamp, &ft);
  660.  
  661.             // Convert the local file time from UTC to system time.
  662.             FileTimeToSystemTime(&ft, &st);
  663.     
  664.             // Build a string showing the date and time.
  665.             wsprintf( str, _T("  Timestamp == %02d/%02d/%d  %02d:%02d:%02d \n"), 
  666.                       st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond);
  667.             OutputDebugString(str);
  668. #endif
  669.         }
  670.  
  671.         dwDev++;
  672.     }
  673.  
  674.     // All devices have been assigned a to a user in the new array. 
  675.     // Clean up the local flat array
  676.     dwDev = 0;
  677.     while( prgAllDev[dwDev] )
  678.     {
  679.         prgAllDev[dwDev]->Release();
  680.         prgAllDev[dwDev] = NULL;
  681.         dwDev++;
  682.     }
  683. }
  684.  
  685.  
  686. //-----------------------------------------------------------------------------
  687. // Name: GetDevicesForPlayer()
  688. // Desc: Allows access to the private, internal list of devices for a given player.
  689. //-----------------------------------------------------------------------------
  690. HRESULT CInputDeviceManager::GetDevicesForPlayer(DWORD dwPlayer, LPDIRECTINPUTDEVICE8** ppDevices)
  691. {
  692.     *ppDevices = (LPDIRECTINPUTDEVICE8*)&m_ppdidDevices[dwPlayer][0];
  693.  
  694.     return S_OK;
  695. }
  696.  
  697.  
  698. //-----------------------------------------------------------------------------
  699. // Name: ConfigureDevices()
  700. // Desc: Wrapper function for the ConfigureDevices call. Before calling 
  701. //       ConfigureDevices, this function sets up a list of DIACTIONFORMATs and 
  702. //       user names, and creates a custom colorset with which the UI should 
  703. //       be displayed.
  704. //-----------------------------------------------------------------------------
  705. HRESULT CInputDeviceManager::ConfigureDevices( IUnknown* pSurface,
  706.                                                VOID* ConfigureDevicesCB,
  707.                                                DWORD dwFlags )
  708. {
  709.     HRESULT hr;
  710.  
  711.     // Initialize all the colors here
  712.     DICOLORSET dics;
  713.     ZeroMemory(&dics, sizeof(DICOLORSET));
  714.     dics.dwSize = sizeof(DICOLORSET);
  715.  
  716.        // Fill in all the params
  717.     DICONFIGUREDEVICESPARAMS dicdp;
  718.     ZeroMemory(&dicdp, sizeof(dicdp));
  719.     dicdp.dwSize = sizeof(dicdp);
  720.     dicdp.dwcUsers       = m_dwNumUsers;
  721.     dicdp.lptszUserNames = m_szUserNames;
  722.  
  723.     dicdp.dwcFormats     = 1;
  724.     dicdp.lprgFormats    = &m_diaf;
  725.     dicdp.hwnd           = m_hWnd;
  726.     dicdp.lpUnkDDSTarget = NULL;
  727.  
  728.     // Set UI color scheme
  729.     dicdp.dics.dwSize = sizeof(dics);
  730.     dicdp.dics.cTextFore        = D3DCOLOR_RGBA(255,255,255,255);
  731.     dicdp.dics.cTextHighlight   = D3DCOLOR_RGBA(60,191,241,255);
  732.     dicdp.dics.cCalloutLine     = D3DCOLOR_RGBA(255,255,255,128);
  733.     dicdp.dics.cCalloutHighlight= D3DCOLOR_RGBA(60,191,241,255);
  734.     dicdp.dics.cBorder          = D3DCOLOR_RGBA(140,152,140,128);
  735.     dicdp.dics.cControlFill     = D3DCOLOR_RGBA(113,0,0,128);
  736.     dicdp.dics.cHighlightFill   = D3DCOLOR_RGBA(0,0,0,128);
  737.     dicdp.dics.cAreaFill        = D3DCOLOR_RGBA(0,0,0,128);
  738.  
  739.  
  740.     // Unacquire all devices so that they don't control the game while 
  741.     // in the default Config UI.
  742.     DWORD dwDev = 0;
  743.     for( DWORD i = 0; i < m_dwNumUsers; i++ )
  744.     {
  745.         while( m_ppdidDevices[i][dwDev] )
  746.         {
  747.             m_ppdidDevices[i][dwDev]->Unacquire();
  748.             dwDev++;
  749.         }
  750.         dwDev = 0;
  751.     }
  752.  
  753.     hr = m_pDI->ConfigureDevices( NULL, &dicdp, dwFlags, NULL );
  754.  
  755.     return hr;
  756. }    
  757.     
  758.  
  759. //-----------------------------------------------------------------------------
  760. // Name: Cleanup()
  761. // Desc: Releases the DirectInput objects used by the class.
  762. //-----------------------------------------------------------------------------
  763. VOID CInputDeviceManager::Cleanup()
  764. {
  765.     CleanupDevices( APP_CLEANUP_DEFAULT );
  766.     
  767.     if( m_pDI )
  768.     {
  769.         m_pDI->Release();
  770.     }
  771.  
  772.     m_pDI = NULL;
  773. }
  774.  
  775. //-----------------------------------------------------------------------------
  776. // Name: CleanupDevices()
  777. // Desc: Unacquires and releases the DirectInput device objects used by the class.
  778. //-----------------------------------------------------------------------------
  779. VOID CInputDeviceManager::CleanupDevices(DWORD dwCleanFlags)
  780. {
  781.     DWORD dwD = 0;
  782.  
  783.     for( DWORD dwP = 0; dwP < m_dwNumUsers; dwP++ )
  784.     {
  785.         while( m_ppdidDevices[dwP][dwD] )
  786.         {     
  787.             m_ppdidDevices[dwP][dwD]->Unacquire();
  788.             if(APP_CLEANUP_DEFAULT == dwCleanFlags)
  789.             {
  790.                 m_ppdidDevices[dwP][dwD]->SetActionMap( &m_diaf, NULL, DIDSAM_NOUSER );
  791.             }
  792.             m_ppdidDevices[dwP][dwD]->Release();
  793.             m_ppdidDevices[dwP][dwD] = NULL;
  794.             dwD++;
  795.         }
  796.  
  797.         dwD = 0;
  798.     }
  799. }
  800.  
  801. //-----------------------------------------------------------------------------
  802. // Name: Cleanup()
  803. // Desc: Global cleanup function for the app. This function simply invokes the 
  804. //       manager class's internal cleanup method (to handle all of the DirectInput
  805. //       objects), then deletes the manager class itself.
  806. //-----------------------------------------------------------------------------
  807. VOID Cleanup()
  808. {
  809.     if( g_pInputDeviceManager )
  810.     {
  811.         g_pInputDeviceManager->Cleanup();
  812.         delete g_pInputDeviceManager;
  813.         g_pInputDeviceManager = NULL;
  814.     }
  815. }
  816.  
  817. //-----------------------------------------------------------------------------
  818. // Name: PaintPlayerStatus()
  819. // Desc: Paints a rudimentary UI to provide visual feedback based on each 
  820. //       player's actions on the devices they own.
  821. //-----------------------------------------------------------------------------
  822. void PaintPlayerStatus(HWND hWnd, DWORD dwPlayer, PLAYERDATA pd)
  823. {    
  824.     // Outout strings.
  825.     TCHAR* strBlank = _T("         ");
  826.     TCHAR* str;
  827.  
  828.     HDC hDC;
  829.     DWORD  dwY; // Y value for text display.
  830.     DWORD  dwX; // X value for text display.
  831.  
  832.     // Init text and DC. 
  833.     hDC = GetDC( hWnd );
  834.     SetBkColor( hDC, RGB(0,0,0) );
  835.     SetBkMode( hDC, OPAQUE );
  836.  
  837.     // Internally to the class, the Player number is zero-based. It's easier to 
  838.     // work with them here as one-based.
  839.     dwPlayer++;
  840.  
  841.     // Set text foreground color by player.
  842.     switch( dwPlayer )
  843.     {
  844.         case 1: 
  845.             // purple== Player 1
  846.             SetTextColor( hDC, RGB(225, 50, 255) );
  847.             str = _T("Player 1");
  848.             break;
  849.  
  850.         case 2: 
  851.             // red   == Player 2
  852.             SetTextColor( hDC, RGB(255, 40, 40) );
  853.             str = _T("Player 2");
  854.             break;
  855.  
  856.         case 3: 
  857.             // green == Player 3
  858.             SetTextColor( hDC, RGB(60, 255, 60) );
  859.             str = _T("Player 3");
  860.             break;
  861.  
  862.         case 4: 
  863.             // blue  == Player 4
  864.             SetTextColor( hDC, RGB(60, 60, 255) );
  865.             str = _T("Player 4");
  866.             break;
  867.     }
  868.  
  869.     // Generate the Y value at which to display text for this player. 
  870.     dwY = (dwPlayer * LINE_SPACING);
  871.     dwX = 0;
  872.  
  873.     TextOut( hDC, dwX, dwY, str, lstrlen(str) );
  874.     dwX += COL_SPACING;
  875.  
  876.     // The remainder of this function is simply to output the 
  877.     // results of gathering the input.
  878.     if(pd.bTurningLeft)
  879.         str = _T("Left   ");
  880.     else if (pd.bTurningRight)
  881.         str = _T("Right"); 
  882.     else 
  883.         str = strBlank;
  884.  
  885.     TextOut( hDC, dwX, dwY, str, lstrlen(str) );
  886.     dwX += COL_SPACING;
  887.  
  888.     if(pd.bForwardThrust)
  889.         str = _T("Fore");
  890.     else if(pd.bReverseThrust)
  891.         str = _T("Aft   ");
  892.     else
  893.         str = strBlank;
  894.     
  895.     TextOut( hDC, dwX, dwY, str, lstrlen(str) );
  896.     dwX += COL_SPACING;
  897.  
  898.     str =  pd.bFiringWeapons ? _T("Fire") : strBlank;
  899.     TextOut( hDC, dwX, dwY, str, lstrlen(str) );
  900.  
  901.     dwX += COL_SPACING;
  902.     str =  pd.bEnableShields ? _T("Up     ") : _T("Down");
  903.     TextOut( hDC, dwX, dwY, str, lstrlen(str) );
  904.  
  905.     ReleaseDC( hWnd, hDC );
  906. }
  907.  
  908.  
  909. //-----------------------------------------------------------------------------
  910. // Name: GameLoop()
  911. // Desc: This is the input loop for the app. Input is gathered from the 
  912. //       DirecInput devices, and output is displayed simply in the app's window.
  913. //-----------------------------------------------------------------------------
  914. BOOL GameLoop( HWND hWnd )
  915. {
  916.     BOOL bRet;
  917.     DIDEVICEOBJECTDATA didObjData;
  918.     ZeroMemory( &didObjData, sizeof(didObjData) );
  919.     static PLAYERDATA pd[MAX_USERS]; // State of each player
  920.     LPDIRECTINPUTDEVICE8* pdidDevices;
  921.     
  922.     DWORD dwNumPlayers = g_pInputDeviceManager->GetNumUsers();
  923.             
  924.     // Loop through all devices for all players and check game input.
  925.     for( DWORD i=0; i < dwNumPlayers; i++ )
  926.     {
  927.         // Get access to the list of input devices.
  928.         g_pInputDeviceManager->GetDevicesForPlayer( i, &pdidDevices );
  929.  
  930.         bRet = ParsePlayerInput(hWnd, pdidDevices, &pd[i]);
  931.         if( !bRet )
  932.         {
  933.             return bRet;
  934.         }
  935.  
  936.         // Remove any conflicts (in a game, you couldn't go left and right at the same
  937.         // time. Actual conflicts depend on the game logic, and not on the DirectInput
  938.         // semantic mappings.)
  939.         //
  940.         // !!NOTE!!: This imposes some logic that a game MIGHT want (i.e. can't go
  941.         //           forward and back at the same time). Your app might want to do
  942.         //           this differently. 
  943.         if( pd[i].bTurningLeft && pd[i].bTurningRight )
  944.         {
  945.             pd[i].bTurningLeft = pd[i].bTurningRight = FALSE;
  946.         }
  947.         if( pd[i].bForwardThrust && pd[i].bReverseThrust )
  948.         {
  949.             pd[i].bForwardThrust = pd[i].bReverseThrust = FALSE;
  950.         }
  951.  
  952.         // Paint the status for this device. This could just as easily be a call 
  953.         // to update the frame of your game. 
  954.         PaintPlayerStatus( hWnd, i, pd[i] );
  955.     }
  956.  
  957.     return TRUE;
  958. }
  959.  
  960.  
  961. //-----------------------------------------------------------------------------
  962. // Name: ParsePlayerInput()
  963. // Desc: Parses the actions buffered for the passed-in devices, and places the 
  964. //       results into the provided PLAYERDATA structure. This data represents
  965. //       the in game actions for a player.
  966. //-----------------------------------------------------------------------------
  967. BOOL ParsePlayerInput(HWND hWnd, LPDIRECTINPUTDEVICE8* ppdidDevices, LPPLAYERDATA ppd)
  968. {
  969.     HRESULT hr;
  970.     DWORD   dwItems;
  971.     DIDEVICEOBJECTDATA adod[BUFFER_SIZE];
  972.  
  973.     DWORD   dwDevice = 0;
  974.     while ( ppdidDevices[dwDevice] )
  975.     {
  976.         
  977.         // Poll the device to read the current state
  978.         if( FAILED(ppdidDevices[dwDevice]->Poll() ) )  
  979.         {
  980.             // DirectInput is telling us that the input stream has been
  981.             // interrupted. We aren't tracking any state between polls, so
  982.             // we don't have any special reset that needs to be done. We
  983.             // just re-acquire and try again.
  984.             hr = ppdidDevices[dwDevice]->Acquire();
  985.             if( DIERR_INPUTLOST  == hr) 
  986.             {
  987.                 hr = ppdidDevices[dwDevice]->Acquire();
  988.             }
  989.  
  990.             // hr may be DIERR_OTHERAPPHASPRIO or other errors.  This
  991.             // may occur when the app is minimized or in the process of 
  992.             // switching, so just try again later 
  993.             return TRUE; 
  994.         }        
  995.                 
  996.         // Retrieve the buffered actions from the device.
  997.         dwItems = BUFFER_SIZE;
  998.         hr = ppdidDevices[dwDevice]->GetDeviceData( sizeof(DIDEVICEOBJECTDATA),
  999.                                                     adod, &dwItems, 0 );
  1000.  
  1001.  
  1002.         if( SUCCEEDED(hr) )
  1003.         {           
  1004.             // Get the actions. The number of input events is stored in
  1005.             // dwItems, and all the events are stored in the "adod" array. Each
  1006.             // event has a type stored in uAppData, and actual data stored in
  1007.             // dwData.
  1008.             for( DWORD j=0; j<dwItems; j++ )
  1009.             {
  1010.                 // Non-axis data is recieved as "button pressed" or "button
  1011.                 // released". Parse input as such.
  1012.                 BOOL bState = (adod[j].dwData != 0 ) ? TRUE : FALSE;
  1013.  
  1014.                 switch (adod[j].uAppData)
  1015.                 {
  1016.                 case INPUT_LEFTRIGHT_AXIS: // Parse the left-right axis data
  1017.                     (*ppd).dwLRAxisData  = adod[j].dwData;
  1018.                     (*ppd).bTurningRight = (*ppd).bTurningLeft  = FALSE;                    
  1019.                     if( (int)(*ppd).dwLRAxisData > 0 )
  1020.                         (*ppd).bTurningRight = TRUE;
  1021.                     else if( (int)(*ppd).dwLRAxisData < 0 )
  1022.                         (*ppd).bTurningLeft = TRUE;
  1023.                     break;
  1024.  
  1025.                 case INPUT_UPDOWN_AXIS: // Parse the up-down axis data
  1026.                     (*ppd).dwUDAxisData   = adod[j].dwData;
  1027.                     (*ppd).bReverseThrust = (*ppd).bForwardThrust = FALSE;
  1028.  
  1029.                     if( (int)(*ppd).dwUDAxisData > 0 )
  1030.                         (*ppd).bReverseThrust = TRUE;
  1031.                     else if( (int)(*ppd).dwUDAxisData < 0 )
  1032.                         (*ppd).bForwardThrust = TRUE;
  1033.                     break;
  1034.                     
  1035.                 case INPUT_TURNLEFT:        (*ppd).bTurningLeft    = bState; break;
  1036.                 case INPUT_TURNRIGHT:       (*ppd).bTurningRight   = bState; break;
  1037.                 case INPUT_FORWARDTHRUST:   (*ppd).bForwardThrust  = bState; break;
  1038.                 case INPUT_REVERSETHRUST:   (*ppd).bReverseThrust  = bState; break;
  1039.                 case INPUT_FIREWEAPONS:     (*ppd).bFiringWeapons  = bState; break;
  1040.                 case INPUT_ENABLESHIELD:    (*ppd).bEnableShields  = bState; break;
  1041.                 
  1042.                 case INPUT_QUITGAME:        return FALSE;                
  1043.                 
  1044.                 case INPUT_DISPLAYGAMEMENU: 
  1045.                     (*ppd).bDisplayingMenu = bState; 
  1046.                     InvokeDefaultUI(hWnd);
  1047.                     return TRUE;
  1048.                 }
  1049.             }
  1050.         }
  1051.  
  1052.         // Go to the next device in the list.
  1053.         dwDevice++;
  1054.        }
  1055.  
  1056.     return TRUE;
  1057. }
  1058.  
  1059. //-----------------------------------------------------------------------------
  1060. // Name: InvokeDefaultUI()
  1061. // Desc: Global function to invoke the Default UI for the DirectInput mapper. 
  1062. //       This function uses the ConfigureDevices wrapper method for this task.
  1063. //       Note that when the UI is invoked in edit mode (DICD_EDIT), you should
  1064. //       assume that devices and the configuration of those devices will probably
  1065. //       change due to user interaction. After ConfigureDevices returns, this 
  1066. //       function calls the ReassignDevices method on the manager class to 
  1067. //       sort out which devices belong to whom, and rebuild action maps to reflect
  1068. //       any config changes that may have taken place.
  1069. //-----------------------------------------------------------------------------
  1070. void InvokeDefaultUI(HWND hWnd)
  1071. {
  1072.     g_pInputDeviceManager->ConfigureDevices(NULL, NULL, DICD_EDIT);
  1073.     g_pInputDeviceManager->ReassignDevices();
  1074. }
  1075.  
  1076.  
  1077. //-----------------------------------------------------------------------------
  1078. // Name: CreateInputStuff()
  1079. // Desc: Creates the DirectInput helper class. The DIACTIONFORMAT specifies what
  1080. //       type of devices we are looking for (via the genre). Note: genres are
  1081. //       defined in the docs and the dinput.h header file. The DIACTIONFORMAT
  1082. //       structure also is used to specify the game action array, defined
  1083. //       at the beginnning of this file.
  1084. //-----------------------------------------------------------------------------
  1085. HRESULT CreateInputStuff( HWND hWnd, DWORD dwNumUsers )
  1086. {
  1087.     HRESULT hr;
  1088.  
  1089.     // Setup action format for suitable input devices for this app
  1090.     DIACTIONFORMAT diaf;
  1091.     ZeroMemory( &diaf, sizeof(DIACTIONFORMAT) );
  1092.     diaf.dwSize        = sizeof(DIACTIONFORMAT);
  1093.     diaf.dwActionSize  = sizeof(DIACTION);
  1094.     diaf.dwDataSize    = NUMBER_OF_SEMANTICS * sizeof(DWORD);
  1095.     diaf.dwNumActions  = NUMBER_OF_SEMANTICS;
  1096.     diaf.guidActionMap = g_AppGuid;
  1097.     diaf.dwGenre       = DIVIRTUAL_SPACESIM;
  1098.     diaf.rgoAction     = g_rgGameAction;
  1099.     diaf.dwBufferSize  = BUFFER_SIZE;
  1100.     diaf.lAxisMin      = -100;
  1101.     diaf.lAxisMax      = 100;
  1102.     _tcscpy( diaf.tszActionMap, _T("MultiMapper Sample Application") );
  1103.  
  1104.     // Create a new input device manager
  1105.     g_pInputDeviceManager = new CInputDeviceManager();
  1106.     if( !g_pInputDeviceManager )
  1107.     {
  1108.         return E_OUTOFMEMORY;
  1109.     }
  1110.  
  1111.     hr = g_pInputDeviceManager->Create( hWnd, &dwNumUsers, &diaf, APP_DICREATE_DEFAULT );
  1112.  
  1113.     if( FAILED(hr) )
  1114.     {
  1115.         TCHAR msg[MAX_PATH];
  1116.         int iRet;
  1117.         
  1118.         switch(hr)
  1119.         {
  1120.         // It's possible that a single user could "own" too many devices for the other
  1121.         // players to get into the game. If so, we reinit the manager class to provide 
  1122.         // each user with a device that has a default configuration.
  1123.         case E_APPERR_DEVICESTAKEN:
  1124.             sprintf( msg, 
  1125.                  _T("You have entered more users than there are suitable devices, " \
  1126.                  "or some users are claiming too many devices.\n\n" \
  1127.                  "Click Yes to give each user a default device, or click No " \
  1128.                  "to close the application"));
  1129.  
  1130.             iRet = MessageBox( hWnd, msg, _T("Devices Are Taken"), 
  1131.                                MB_YESNO | MB_ICONEXCLAMATION );
  1132.  
  1133.             if(iRet == IDYES)
  1134.             {
  1135.                 g_pInputDeviceManager->Cleanup();
  1136.                 g_pInputDeviceManager->Create( hWnd, &dwNumUsers, &diaf, APP_DICREATE_FORCEINIT );
  1137.             }
  1138.             else
  1139.                 return E_FAIL;
  1140.             
  1141.             break;
  1142.  
  1143.         // Another common error is if more users are attempting to play than there are devices
  1144.         // attached to the machine. In this case, the number of players is automatically 
  1145.         // lowered to make playing the game possible. 
  1146.         case E_APPERR_TOOMANYUSERS:
  1147.             DWORD dwNumDevices = g_pInputDeviceManager->GetNumDevices();
  1148.             dwNumUsers = dwNumDevices;
  1149.             TCHAR* str = _T( "There are not enough devices attached to the system " \
  1150.                              "for the number of users you entered.\n\n" \
  1151.                              "The number of users has been automatically changed " \
  1152.                              "to %i (the number of devices available on the system).");
  1153.  
  1154.             sprintf( msg, str, dwNumDevices);
  1155.             MessageBox( hWnd, msg, _T("Too Many Users"), 
  1156.                         MB_OK | MB_ICONEXCLAMATION );
  1157.  
  1158.             g_pInputDeviceManager->Cleanup();
  1159.             g_pInputDeviceManager->Create( hWnd, &dwNumUsers, &diaf, APP_DICREATE_DEFAULT );                           
  1160.             break;
  1161.         }
  1162.     }
  1163.  
  1164.     return S_OK;
  1165. }
  1166.  
  1167.  
  1168. //-----------------------------------------------------------------------------
  1169. // Name: MainWndproc()
  1170. // Desc: Callback for all Windows messages
  1171. //-----------------------------------------------------------------------------
  1172. long FAR PASCAL MainWndproc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
  1173. {
  1174.     switch( msg )
  1175.     {
  1176.         case WM_PAINT:
  1177.         {
  1178.             // Output message to user
  1179.             CHAR* str;
  1180.             HDC hDC = GetDC( hWnd );
  1181.             
  1182.             SetTextColor( hDC, RGB(255,255,255) );
  1183.             SetBkColor( hDC, RGB(0,0,0) );
  1184.             SetBkMode( hDC, OPAQUE );
  1185.             
  1186.             str = _T("Name");
  1187.             TextOut( hDC, 0, 0, str, lstrlen(str) );
  1188.             str = _T("Turn");
  1189.             TextOut( hDC, COL_SPACING*1, 0, str, lstrlen(str) );
  1190.             str = _T("Thrust");
  1191.             TextOut( hDC, COL_SPACING*2, 0, str, lstrlen(str) );
  1192.             str = _T("Weapon");
  1193.             TextOut( hDC, COL_SPACING*3, 0, str, lstrlen(str) );
  1194.             str = _T("Shield");
  1195.             TextOut( hDC, COL_SPACING*4, 0, str, lstrlen(str) );
  1196.  
  1197.             str = _T("Looking for game input... press Escape to exit.");
  1198.             TextOut( hDC, 0, 140, str, lstrlen(str) );
  1199.  
  1200.             str = _T("Press D to display input device settings.");
  1201.             TextOut( hDC, 0, 158, str, lstrlen(str) );
  1202.  
  1203.             ReleaseDC( hWnd, hDC );
  1204.             break;
  1205.         }
  1206.  
  1207.         // Handling global keystrokes as WM_CHAR messages. In this case, it's 
  1208.         // actually easier to use Windows than a specialized action map.
  1209.         case WM_CHAR:
  1210.             if( VK_ESCAPE == (TCHAR)wParam )
  1211.                 SendMessage( hWnd, WM_DESTROY, 0, 0 );
  1212.             else if ( 0x64 == (TCHAR)wParam ) // 0x64 == 'D'
  1213.                 InvokeDefaultUI(hWnd);
  1214.             break;
  1215.  
  1216.         case WM_DESTROY:
  1217.             Cleanup();
  1218.             PostQuitMessage( 0 );
  1219.             break;
  1220.     }
  1221.  
  1222.     return DefWindowProc( hWnd, msg, wParam, lParam );
  1223. }
  1224.  
  1225. INT_PTR CALLBACK NumPlayerFunc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
  1226. {
  1227.     switch(uMsg)
  1228.     {
  1229.     case WM_INITDIALOG:
  1230.         SetFocus(GetDlgItem(hDlg, IDC_EDIT_NUMPLAYERS));
  1231.         SetDlgItemInt( hDlg, IDC_EDIT_NUMPLAYERS, MIN_USERS, FALSE);
  1232.         SendDlgItemMessage(hDlg, IDC_EDIT_NUMPLAYERS, EM_SETSEL, 0, -1);
  1233.         return FALSE;
  1234.  
  1235.     case WM_COMMAND:
  1236.         if (wParam == IDOK) 
  1237.         { 
  1238.             UINT nNumPlayers = 0;
  1239.             BOOL bSuccess;
  1240.             nNumPlayers= GetDlgItemInt( hDlg, IDC_EDIT_NUMPLAYERS, 
  1241.                                         &bSuccess, FALSE);
  1242.  
  1243.             if(bSuccess && (nNumPlayers >0 && nNumPlayers <=MAX_USERS))
  1244.                 EndDialog(hDlg, (INT)nNumPlayers); 
  1245.             else
  1246.             {
  1247.                 SetDlgItemInt( hDlg, IDC_EDIT_NUMPLAYERS, MIN_USERS, FALSE);
  1248.                 SetFocus(GetDlgItem(hDlg, IDC_EDIT_NUMPLAYERS));
  1249.                 SendDlgItemMessage(hDlg, IDC_EDIT_NUMPLAYERS, EM_SETSEL, 0, -1);
  1250.                 MessageBeep(MB_OK);
  1251.             }
  1252.             return TRUE; 
  1253.         } 
  1254.     }
  1255.     
  1256.     return FALSE;
  1257. }
  1258.  
  1259.  
  1260.  
  1261.  
  1262. //-----------------------------------------------------------------------------
  1263. // Name: WinMain()
  1264. // Desc: Application entry point
  1265. //-----------------------------------------------------------------------------
  1266. int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow )
  1267. {
  1268.     // Register the window class
  1269.     WNDCLASS wndClass = { CS_DBLCLKS, MainWndproc, 0, 0, hInstance, NULL,
  1270.                           LoadCursor( NULL, IDC_ARROW ), 
  1271.                           (HBRUSH)GetStockObject( BLACK_BRUSH ),
  1272.                           NULL, _T("IMapClass") };
  1273.     RegisterClass( &wndClass );
  1274.  
  1275.     // Create our main window
  1276.     HWND hWnd = CreateWindowEx( 0, _T("IMapClass"), _T("MultiMapper"),
  1277.                                 WS_VISIBLE|WS_POPUP|WS_CAPTION|WS_SYSMENU,
  1278.                                 200, 200, 400, 200, NULL, NULL, hInstance, NULL );
  1279.     if( NULL == hWnd )
  1280.     {
  1281.         return FALSE;
  1282.     }
  1283.  
  1284.     UpdateWindow( hWnd );
  1285.  
  1286.     UINT dwNumUsers;
  1287.     // Query the user(s) for the number of players.
  1288.     dwNumUsers = DialogBox( hInstance, 
  1289.                             MAKEINTRESOURCE(IDD_NUMPLAYERS),
  1290.                             hWnd,
  1291.                             (DLGPROC)NumPlayerFunc);
  1292.  
  1293.     // Create the DirectInput helper class
  1294.     if( FAILED( CreateInputStuff( hWnd, dwNumUsers ) ) )
  1295.     {
  1296.         SendMessage( hWnd, WM_DESTROY, 0, 0 );
  1297.         return 0;
  1298.     }
  1299.  
  1300.     // Traditional message loop to "run" the app
  1301.     while( TRUE )
  1302.     {
  1303.         MSG msg;
  1304.  
  1305.         if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
  1306.         {
  1307.             if( FALSE == GetMessage( &msg, NULL, 0, 0 ) )
  1308.                 return msg.wParam;
  1309.  
  1310.             TranslateMessage( &msg );
  1311.             DispatchMessage( &msg );
  1312.         }
  1313.         else
  1314.         {
  1315.             // Most apps use idle time for processing the game loop
  1316.             if( FALSE == GameLoop( hWnd ) )
  1317.                 SendMessage( hWnd, WM_DESTROY, 0, 0 );
  1318.         }
  1319.     }
  1320. }
  1321.  
  1322.