//-----------------------------------------------------------------------------
// File: MultiMapper.cpp
//
// Desc: This is a simple sample to demonstrate how to code using the DirectInput
//       mapper feature.
//
// Copyright (C) 1995-2000 Microsoft Corporation. All Rights Reserved.
//-----------------------------------------------------------------------------

#define INITGUID
#include <windows.h>
#include <basetsd.h>
#include <tchar.h>
#include <dinput.h>
#include <d3d8types.h>  // included to get the D3DCOLOR_RGBA macro.
#include <stdio.h>
#include "resource.h"

// The following constants control the number of users the app 
// supports, and the maximum number of devices it will tolerate.
// Generally, these are used to allocate arrays, but they're also
// used for bounding iterations on those arrays.
#define MIN_USERS 1
#define MAX_USERS 4
#define MAX_DEVICES 25  // more than reasonable max

// The following constants control spacing and alignment 
// for text display in the status area.
#define LINE_SPACING 20 
#define COL_SPACING  70
#define FIRST_LINE   20 

// Controls the number of actions the app asks DirectInput to buffer.
#define BUFFER_SIZE  16 

//
// Internal return values
//

// E_APPERR_DEVICESTAKEN is returned by the manager class when one player
// on the machine has enough RECENT devices to prevent other players from 
// playing. This return code is needed because this sample attempts to give 
// all RECENT devices to that player.
#define E_APPERR_DEVICESTAKEN MAKE_HRESULT(SEVERITY_ERROR,FACILITY_ITF,998) 

// E_APPERR_TOOMANYUSERS is returned by the manager class when the number of 
// players exceeds the number of devices present on the system. For example, 
// if you ask for 4 players on a machine that only has a keyboard and mouse,
// you're 2 short of what you need. 
#define E_APPERR_TOOMANYUSERS MAKE_HRESULT(SEVERITY_ERROR,FACILITY_ITF,999)

// Flags to control the creation of the manager class.
#define APP_DICREATE_DEFAULT    FALSE 
#define APP_DICREATE_FORCEINIT  TRUE

// Flags to control the destruction of the internal device lists.
// There are cases (i.e. reassigning devices) where it makes sense
// to reset the devices held by the manager class, while preserving 
// ownership information.
#define APP_CLEANUP_DEFAULT            FALSE
#define APP_CLEANUP_PRESERVEASSIGNMENT TRUE


//-----------------------------------------------------------------------------
// Name: CInputDeviceManager
// Desc: This is a class to wrap up the DirectInput functionality for the app. 
//       All DirectInput initialization, enumeration, and management of 
//       DirectInput devices in contained within this class.
//-----------------------------------------------------------------------------
class CInputDeviceManager
{
    LPDIRECTINPUT8       m_pDI;
    LPDIRECTINPUTDEVICE8 m_ppdidDevices[MAX_USERS][MAX_DEVICES];
	DWORD				 m_dwNumUsers;
    TCHAR*               m_szUserNames;
    HWND                 m_hWnd;    
    DIACTIONFORMAT       m_diaf;
    DWORD                m_dwCreateFlags;
    DWORD                m_dwTotalDevices;
    
    // Callback for enumerating suitable devices
    static BOOL CALLBACK EnumSuitableDevicesCB( LPCDIDEVICEINSTANCE, LPDIRECTINPUTDEVICE8, 
                                         DWORD, DWORD, LPVOID );

    static BOOL CALLBACK BuildFlatListCB( LPCDIDEVICEINSTANCE, LPDIRECTINPUTDEVICE8, 
                                          DWORD, DWORD, LPVOID );


public:
	// Creation methods
    CInputDeviceManager();
    HRESULT Create( HWND, DWORD*, DIACTIONFORMAT*, DWORD);

    // Cleanup
    VOID    Cleanup();
    VOID    CleanupDevices( DWORD );

    // Add a device to the list
    VOID    AddDeviceForPlayer( const DWORD dwPlayerNum, 
                                const DIDEVICEINSTANCE* pdidi, 
					            const LPDIRECTINPUTDEVICE8 pdidDevice );
    
    // Assign devices to other users.
    VOID    ReassignDevices();

    // Access the device list for a user.
    HRESULT GetDevicesForPlayer( DWORD, LPDIRECTINPUTDEVICE8**);
	
    // Invoke the Default configuration UI.
	HRESULT ConfigureDevices(IUnknown*, VOID*,DWORD);

    // Simple data retrieval.
    DWORD GetNumUsers();
    DWORD GetNumDevices();
};

// The PLAYERDATA structure that contains all the relevant data
// for a single player in the game. An array of these structures is used
// to track the information for all players. This array is initialized 
// in the window procedure when the WM_CREATE message is received. 
typedef struct _PLAYERDATA {
    BOOL  bTurningRight;
    BOOL  bReverseThrust;
    BOOL  bTurningLeft;
    BOOL  bForwardThrust;
    BOOL  bFiringWeapons;
    BOOL  bEnableShields;
    BOOL  bDisplayingMenu;
    DWORD dwLRAxisData;
    DWORD dwUDAxisData;
} PLAYERDATA, *LPPLAYERDATA;

// The ENUMDATA structure is used to carry information needed by the 
// EnumDevicesBySemantics callback to add devices to the manager class's
// internal lists, build/set action maps, and return the status (device 
// found or not) of the enumeration.
typedef struct _ENUMDATA {
    CInputDeviceManager* pDevMan;
    DWORD                dwCreateFlags;
    HWND                 hWnd;
    DWORD                dwPlayerNum;
    TCHAR*               pszPlayerName;
    LPDIACTIONFORMAT     lpdiaf;
    BOOL                 bRet;
} ENUMDATA, *LPENUMDATA;


// Global function prototypes.
HRESULT CreateInputStuff(HWND, DWORD);
BOOL ParsePlayerInput(HWND, LPDIRECTINPUTDEVICE8*, LPPLAYERDATA);
void InvokeDefaultUI(HWND);

//-----------------------------------------------------------------------------
// App-defined game actions for the DirecInput Mapper.
//-----------------------------------------------------------------------------


// The following constants are custom for each app, depending on what input the
// app needs. This simple sample is pretending to be a space simulator game, so
// relevant inputs are for turning left, thrusting, firing weapons, etc.. Also
// note that some constants are defines for the axes, which are for recieving
// axis data from joysticks and similiar analog devices.
#define INPUT_LEFTRIGHT_AXIS   1L
#define INPUT_UPDOWN_AXIS      2L
#define INPUT_TURNLEFT         3L
#define INPUT_TURNRIGHT        4L
#define INPUT_FORWARDTHRUST    5L
#define INPUT_REVERSETHRUST    6L
#define INPUT_FIREWEAPONS      7L
#define INPUT_ENABLESHIELD     8L
#define INPUT_DISPLAYGAMEMENU  9L
#define INPUT_QUITGAME        10L


// The following array is the global, app-defined game actions, which map real
// device input into a semantic. The first column is the app-defined semantics
// as defined above. These are the constants the game actually sees in its
// input loop. The second column is the physical action recieved by the device
// which is to be mapped to the app-defined semantic. For instance, in the
// array below, if the user hits the "Left" key on the keyboard, the app will
// recieve an input code equal to INPUT_TURNLEFT. The last column is a text
// string that DirectInput uses for displaying a configuration dialog box.
#define NUMBER_OF_SEMANTICS 17

DIACTION g_rgGameAction[NUMBER_OF_SEMANTICS] =
{
    // Device input (joystick, etc.) that is pre-defined by DirectInput, 
    // according to genre type. The genre for this app is space simulators.
    { INPUT_LEFTRIGHT_AXIS,  DIAXIS_SPACESIM_LATERAL,         0, _T("Turn"), },
    { INPUT_UPDOWN_AXIS,     DIAXIS_SPACESIM_MOVE,            0, _T("Move"), },
    { INPUT_FIREWEAPONS,     DIBUTTON_SPACESIM_FIRE,          0, _T("Shoot"), },
    { INPUT_ENABLESHIELD,    DIBUTTON_SPACESIM_GEAR,          0, _T("Enable shields"), },
    { INPUT_DISPLAYGAMEMENU, DIBUTTON_SPACESIM_DISPLAY,       0, _T("Display"), },

    // Keyboard input mappings
    { INPUT_TURNLEFT,        DIKEYBOARD_LEFT,    0, _T("Turn left"), },
    { INPUT_TURNRIGHT,       DIKEYBOARD_RIGHT,   0, _T("Turn right"), },
    { INPUT_FORWARDTHRUST,   DIKEYBOARD_UP,      0, _T("Forward thrust"), },
    { INPUT_REVERSETHRUST,   DIKEYBOARD_DOWN,    0, _T("Reverse thrust"), },
    { INPUT_FIREWEAPONS,     DIKEYBOARD_F,       0, _T("Shoot"), },
    { INPUT_ENABLESHIELD,    DIKEYBOARD_S,       0, _T("Enable shields"), },
    { INPUT_DISPLAYGAMEMENU, DIKEYBOARD_D, DIA_APPFIXED, _T("Display"), },
    { INPUT_QUITGAME,        DIKEYBOARD_ESCAPE,  DIA_APPFIXED, _T("Quit Game"), },

    // Mouse input mappings
    { INPUT_LEFTRIGHT_AXIS,  DIMOUSE_XAXIS,      0, _T("Turn"), },
    { INPUT_UPDOWN_AXIS,     DIMOUSE_YAXIS,      0, _T("Move"), },
    { INPUT_FIREWEAPONS,     DIMOUSE_BUTTON0,    0, _T("Shoot"), },
    { INPUT_ENABLESHIELD,    DIMOUSE_BUTTON1,    0, _T("Enable shields"), },
};

//-----------------------------------------------------------------------------
// Globals for the app
//-----------------------------------------------------------------------------
GUID                 g_AppGuid    = { 0x451F8DEF, 0xA7E9, 0x4DF4, 0x9A, 0x6B,
                                      0xF4, 0xb6,0xC7, 0x06, 0xF3, 0x98 };
LPTSTR               g_strAppName = _T("DirectInput8 MultiMapper Sample");
CInputDeviceManager* g_pInputDeviceManager = NULL;


//-----------------------------------------------------------------------------
// Name: CInputDeviceManager()
// Desc: Constructor for the class
//-----------------------------------------------------------------------------
CInputDeviceManager::CInputDeviceManager()
{
    m_pDI                  = NULL;
    m_dwCreateFlags        = 0;
    m_dwTotalDevices       = 0;

    // Uses canned names for players.
    m_szUserNames  = _T("Player 1\0Player 2\0Player 3\0Player 4\0\0");
    ZeroMemory( m_ppdidDevices, sizeof(m_ppdidDevices) );
}


//-----------------------------------------------------------------------------
// Name: Create()
// Desc: Creation method for the class. Creates DirectInput, and enumerates 
//       "suitable" devices for each player. By "suitable", we mean devices
//       that work with the genre specified in the DIACTIONFORMAT structure.
//       This method initializes the class in two ways, as controlled by 
//       the dwFlags field. In the default mode, the method can assign any 
//       devices recently used by each user upon startup. This invites a case
//       where a user has "ownership" of enough devices to limit the number of
//       players who can interact with the app. If this happens, the class is 
//       reinitialized with the APP_DICREATE_FORCEINIT, which causes the class
//       to allocate a single device to each user (with default configs).
//-----------------------------------------------------------------------------
HRESULT CInputDeviceManager::Create( HWND hWnd, DWORD* lpdwNumUsers, 
                                     DIACTIONFORMAT* pdiaf, DWORD dwFlags )
{
    HRESULT hr;

    // Copy passed in arguments for internal use.
    m_hWnd        = hWnd;
    memcpy( &m_diaf, pdiaf, sizeof(DIACTIONFORMAT) );
	m_dwNumUsers = *lpdwNumUsers;
    m_dwCreateFlags = dwFlags;

    // Create the main DirectInput object.
    hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, 
                             IID_IDirectInput8, (VOID**)&m_pDI, NULL );
    if( FAILED(hr) )
    {
        return E_FAIL;
    }

    TCHAR* pszNameScan = m_szUserNames;
    for(DWORD dwPlay = 0; dwPlay < m_dwNumUsers; dwPlay++)
    {        
        ENUMDATA ed;
        ZeroMemory( &ed, sizeof(ed) );
        ed.pDevMan = this;
        ed.dwPlayerNum = dwPlay;
        ed.pszPlayerName = pszNameScan;
        ed.bRet = FALSE; // Will be TRUE if a device was found.
        ed.lpdiaf = &m_diaf;
        ed.hWnd = m_hWnd;
        ed.dwCreateFlags = m_dwCreateFlags;

        if( m_dwCreateFlags == APP_DICREATE_DEFAULT)
        {
            // Enumerate "suitable" devices for this user.
            hr = m_pDI->EnumDevicesBySemantics( pszNameScan, &m_diaf, 
                                                EnumSuitableDevicesCB, 
                                                (LPVOID)&ed, DIEDBSFL_THISUSER | DIEDBSFL_AVAILABLEDEVICES);
        }
        else // m_dwCreateFlags == APP_DICREATE_FORCEINIT
        {
            // Enumerate available devices for any user.
            hr = m_pDI->EnumDevicesBySemantics( NULL, &m_diaf, 
                                                EnumSuitableDevicesCB, 
                                                (LPVOID)&ed, DIEDBSFL_AVAILABLEDEVICES);
        }

        if( FAILED(hr))
        {
            return hr;
        }
        
        if(!ed.bRet)
        {
            if(m_dwTotalDevices < m_dwNumUsers)
                return E_APPERR_TOOMANYUSERS;
            else
                return E_APPERR_DEVICESTAKEN;
        }
    
        // Scan to the next username in the multi-sz list.
        pszNameScan += strlen(pszNameScan) + 1;
    }

    return S_OK;
}

//-----------------------------------------------------------------------------
// Name: GetNumUsers()
// Desc: Returns the number of users currently using the "game".
//-----------------------------------------------------------------------------
DWORD CInputDeviceManager::GetNumUsers()
{
    return m_dwNumUsers;
}

//-----------------------------------------------------------------------------
// Name: GetNumDevices()
// Desc: Returns the number of devices present on the machine.
//-----------------------------------------------------------------------------
DWORD CInputDeviceManager::GetNumDevices()
{
    return m_dwTotalDevices;
}


//-----------------------------------------------------------------------------
// Name: EnumSuitableDevicesCB()
// Desc: Callback function for enumerating suitable devices. The manager class
//       enumerates devices for each player, so this method is called a lot 
//       (#_of_players * installed_devices).
//       
//       This method attempts to assign devices recently used by a player in a 
//       previous instance to that player for this instance. As such, any device 
//       that is enumerated with the DIEDBS_RECENTDEVICE flag is assigned to the 
//       player for which the devices are being enumerated. Devices marked by 
//       DIEDBS_NEWDEVICE and DIEDBS_MAPPEDPRI1 are added to a flat list.
//       If no recent devices are found for the user, we take the topmost device
//       in the flat list (the most suitable) and throw the others away.
//-----------------------------------------------------------------------------
BOOL CALLBACK CInputDeviceManager::EnumSuitableDevicesCB( LPCDIDEVICEINSTANCE  pdidi,
                                                          LPDIRECTINPUTDEVICE8 pdidDevice, 
														  DWORD  dwFlags,
														  DWORD  dwRemainingDevices,
														  LPVOID pContext )
{
    HRESULT hr;
    ENUMDATA ed = *(LPENUMDATA)pContext;
    DWORD dwBuildFlags;    

    // Temp array to hold non-recent/new devices for later allocation.
    // A linked list would be a better choice, but for simplicity, we'll 
    // use a simple array.
    static LPDIRECTINPUTDEVICE8 lprgDevTemp[MAX_DEVICES]; 
    static DIDEVICEINSTANCE     didTemp[MAX_DEVICES];
    static int iIndex = 0;
        

    // Devices of type DI8DEVTYPE_DEVICECTRL are specialized devices not generally
    // considered appropriate to control game actions. We just ignore these.
    if( DI8DEVTYPE_DEVICECTRL == GET_DIDEVICE_TYPE(pdidi->dwDevType) )
    {
        return DIENUM_CONTINUE;     
    }

    if( dwFlags & DIEDBS_RECENTDEVICE )
    {
        // Set the cooperative level.
        hr = pdidDevice->SetCooperativeLevel( ed.hWnd, DISCL_EXCLUSIVE|DISCL_FOREGROUND );
        if( FAILED(hr) )
        {
            return DIENUM_CONTINUE;     
        }

        // Set the action map for this player to remove it from DirecInput's internal
        // list of available devices.
        dwBuildFlags = (ed.dwCreateFlags == APP_DICREATE_DEFAULT) ? DIDBAM_DEFAULT : DIDBAM_HWDEFAULTS;
        pdidDevice->BuildActionMap( ed.lpdiaf, ed.pszPlayerName, dwBuildFlags );
        hr = pdidDevice->SetActionMap( ed.lpdiaf, ed.pszPlayerName, DIDSAM_DEFAULT ); 

        // Add the device to the device manager's internal list
        ed.pDevMan->AddDeviceForPlayer( ed.dwPlayerNum, pdidi, pdidDevice );

        // If BuildActionMap fails, so will SetActionMap. Using a single error check here for simplicity.
        // These may fail if no controls on this device are appropriate. However, other devices may be 
        // just fine, so continue the enumeration.
        if( FAILED(hr) )
        {
            return DIENUM_CONTINUE;
        }
        
#ifdef _DEBUG
        TCHAR str[MAX_PATH];
        OutputDebugString(_T("\n--> !!!RECENT DEVICE ASSIGNED!!! <--\n"));
        sprintf(str, _T("  Player == %s\n"), ed.pszPlayerName );
        OutputDebugString(str);
        sprintf(str, _T("  Device == %s\n"), (*pdidi).tszProductName );
        OutputDebugString(str);
                
        SYSTEMTIME st;
        FILETIME   ft;  
        // Convert the time to local time.
        FileTimeToLocalFileTime(&ed.lpdiaf->ftTimeStamp, &ft);
        // Convert the local file time from UTC to system time.
        FileTimeToSystemTime(&ft, &st);

        // Build a string showing the date and time.
        wsprintf( str, _T("  Timestamp == %02d/%02d/%d  %02d:%02d:%02d \n"), 
                  st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond);
        OutputDebugString(str);

        sprintf(str, _T("  dwFlags == %x\n"), dwFlags);
        OutputDebugString(str);
#endif
        // Set return value in ENUMDATA struct to indicate success and 
        // stop enumeration.
        ((LPENUMDATA)pContext)->bRet = TRUE;
    }
    else if( dwFlags & ( DIEDBS_MAPPEDPRI1 | DIEDBS_NEWDEVICE ) )
    {   // This is neither a RECENT nor a NEW device, add it to an internal list
        // of devices. If we get to the end of the devices without finding a recently
        // used or new device, we'll try one of these. 
        lprgDevTemp[iIndex] = pdidDevice;
        didTemp[iIndex]     = *(LPDIDEVICEINSTANCE)pdidi;
        lprgDevTemp[iIndex++]->AddRef();

#ifdef _DEBUG
        TCHAR str[MAX_PATH];
        if( dwFlags & DIEDBS_NEWDEVICE )
            OutputDebugString(_T("\n--> NEW DEVICE FOUND (not yet assigned) <--\n"));
        else if ( dwFlags & DIEDBS_MAPPEDPRI1 )
            OutputDebugString(_T("\n--> PRI1-CAPABLE DEVICE FOUND (not yet assigned) <--\n"));

        sprintf(str, _T("  Device == %s\n"), (*pdidi).tszProductName );
        OutputDebugString(str);
#endif
    }

    // If no more devices will be enumerated, use one of the other appropriate devices 
    // (according to DIEDBS_MAPPEDPRI1) for this user. For simplicity, we're just taking 
    // the first one in the list. Real applications would probably iterate through action
    // maps for these devices and choose the one with the most game-critical actions assigned. 
    if( 0 == dwRemainingDevices &&  (((LPENUMDATA)pContext)->bRet != TRUE) ) 
    {
        dwBuildFlags = (ed.dwCreateFlags == APP_DICREATE_DEFAULT) ? DIDBAM_DEFAULT : DIDBAM_HWDEFAULTS;

        // Set the action map for this player to remove it from DirectInput's internal
        // list of available devices.
        if( lprgDevTemp[0] )
        {
            // Add the device to the device manager's internal list
            ed.pDevMan->AddDeviceForPlayer( ed.dwPlayerNum, &didTemp[0], lprgDevTemp[0] );

            lprgDevTemp[0]->BuildActionMap( ed.lpdiaf, ed.pszPlayerName, dwBuildFlags );
            hr = lprgDevTemp[0]->SetActionMap( ed.lpdiaf, ed.pszPlayerName, DIDSAM_DEFAULT ); 
            // If BuildActionMap fails, so will SetActionMap. Using a single error check here for simplicity.
            // These may fail if no controls on this device are appropriate. However, other devices may be 
            // just fine, so continue the enumeration.
            if( FAILED(hr) )
                return DIENUM_CONTINUE;

            ((LPENUMDATA)pContext)->bRet = TRUE;

#ifdef _DEBUG
            TCHAR str[MAX_PATH];
            OutputDebugString(_T("\n--> !!! DEVICE ASSIGNED !!! <--\n"));
            sprintf(str, _T("  Player == %s\n"), ed.pszPlayerName );
            OutputDebugString(str);
            sprintf(str, _T("  Device == %s\n"), didTemp[0].tszProductName );
            OutputDebugString(str);
            
            SYSTEMTIME st;
            FILETIME   ft;  
            // Convert the time to local time.
            FileTimeToLocalFileTime(&ed.lpdiaf->ftTimeStamp, &ft);
            // Convert the local file time from UTC to system time.
            FileTimeToSystemTime(&ft, &st);

            // Build a string showing the date and time.
            wsprintf( str, _T("  Timestamp == %02d/%02d/%d  %02d:%02d:%02d \n"), 
                      st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond);
            OutputDebugString(str);

            sprintf(str, _T("  dwFlags == %x\n"), dwFlags);
            OutputDebugString(str);
#endif
        }

        // Release all of the remaining devices in the list.
        for (int i=1 ; i<iIndex ; i++ )
        {
            if( lprgDevTemp[i] )
            {
                lprgDevTemp[i]->Release();
            }
        }

        iIndex = 0; // reset counter for next pass.
    }

    return DIENUM_CONTINUE;
}


//-----------------------------------------------------------------------------
// Name: BuildFlatListCB()
// Desc: Callback function for the enumeration invoked by ReassignDevices(). 
//       This method just adds devices to a flat array that represents all the 
//       devices present on the machine.
//-----------------------------------------------------------------------------
BOOL CALLBACK CInputDeviceManager::BuildFlatListCB( LPCDIDEVICEINSTANCE  pdidi,
                                                    LPDIRECTINPUTDEVICE8 pdidDevice, 
													DWORD  dwFlags,
													DWORD  dwRemainingDevices,
													LPVOID pContext )
{
    static DWORD dwIndex = 0;
    LPDIRECTINPUTDEVICE8* pdidDevArray;
    pdidDevArray = (LPDIRECTINPUTDEVICE8*)pContext;

    pdidDevArray[dwIndex++] = pdidDevice;
    pdidDevice->AddRef(); // Must AddRef any interfaces we keep.

    if(!dwRemainingDevices)
    {
        dwIndex = 0; 
    }

    return DIENUM_CONTINUE;
}



//-----------------------------------------------------------------------------
// Name: AddDeviceForPlayer()
// Desc: Adds a device to the internal array for a player. For gaming devices, 
//       this method sets an axis deadzone. This could be done elsewhere, but 
//       it makes sense to take care of it here since this is a centralized 
//       location.
//-----------------------------------------------------------------------------
VOID CInputDeviceManager::AddDeviceForPlayer( const DWORD dwPlayerNum, 
                                              const DIDEVICEINSTANCE* pdidi, 
									          const LPDIRECTINPUTDEVICE8 pdidDevice )
{
    DWORD                dwDeviceType = pdidi->dwDevType;

    // If this is a gaming device, set an axis deadzone.
    if(GET_DIDEVICE_TYPE(dwDeviceType) & (~(DI8DEVTYPE_MOUSE & DI8DEVTYPE_KEYBOARD)))
        {
        DIPROPDWORD dipdw;
        dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
        dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
        dipdw.diph.dwObj        = 0;
        dipdw.diph.dwHow        = DIPH_DEVICE;
        dipdw.dwData            = 1000;

        // If we can't set the deadzone, don't worry. Setting it is a good idea to 
        // avoid jitter on axes, however.
        pdidDevice->SetProperty( DIPROP_DEADZONE, &dipdw.diph );
    }

    // Add the device to the end of the list for this user.
    DWORD iDev = 0;
    while( NULL != m_ppdidDevices[dwPlayerNum][iDev] ) iDev++;

    m_ppdidDevices[dwPlayerNum][iDev] = pdidDevice;
    m_ppdidDevices[dwPlayerNum][iDev]->AddRef();  // Must increment the ref cout when duplicating interface pointers.

    m_dwTotalDevices++;
}

//-----------------------------------------------------------------------------
// Name: ReassignDevices()
// Desc: Called after the the DirectInput Mapper Default UI returns from the 
//       ConfigureDevices call. This method starts by flushing the manager 
//       class's internal lists, then it creates a flat list of all devices
//       on the machine (by calling EnumDevicesBySemantics with a NULL username).
//       Once the list is built, it rolls through all devices to retrieve the
//       current owner (via DIPROP_USERNAME). If the device has a username, the 
//       method calls AddDeviceForPlayer method to give that device to the 
//       appropriate user.
//-----------------------------------------------------------------------------
VOID CInputDeviceManager::ReassignDevices()
{
    LPDIRECTINPUTDEVICE8 prgAllDev[MAX_DEVICES]; // Flat array for all devices on the machine.
    TCHAR szNameDev[MAX_PATH];
    DWORD dwDev;

    // Clean-out the class-internal array of devices for each user.
    CleanupDevices( APP_CLEANUP_PRESERVEASSIGNMENT );

    // Build a simple flat list of all devices currently attached to the machine. This
    // array will be used to reassign devices to each user.
    // 
    // Using a NULL username and omitting the DIEDBSFL_THISUSER flag enumerates all devices.
    ZeroMemory( prgAllDev, sizeof(prgAllDev) );
    m_pDI->EnumDevicesBySemantics( NULL, &m_diaf, BuildFlatListCB, 
                                   prgAllDev, DIEDBSFL_ATTACHEDONLY); 
   
    DIPROPSTRING dips;
	dips.diph.dwSize       = sizeof(DIPROPSTRING); 
	dips.diph.dwHeaderSize = sizeof(DIPROPHEADER); 
	dips.diph.dwObj        = 0; // device property 
	dips.diph.dwHow        = DIPH_DEVICE; 

    // Now we've got an array with every device attached to the system. 
    // Loop through them all and assign them to a player in the temp array.
    dwDev = 0;
    while( prgAllDev[dwDev] )
    {
        prgAllDev[dwDev]->GetProperty( DIPROP_USERNAME,
							           &dips.diph );
        
        // Convert the string from Unicode to ANSI.
        WideCharToMultiByte( CP_ACP, 0, dips.wsz, -1,
                             szNameDev, MAX_PATH,NULL, NULL);

        // Determine who this device is now assigned to (as a DWORD value)
        // If the device is unassigned (i.e. no username), we skip it.
        if( strlen(szNameDev) )
        {
            DWORD dwAssignedTo;
            dwAssignedTo = (DWORD) ( (byte)szNameDev[strlen(szNameDev)-1] - ((byte)'1') );
            
            DIDEVICEINSTANCE didi;
            ZeroMemory( &didi, sizeof(didi) );
            didi.dwSize = sizeof(didi);
            prgAllDev[dwDev]->GetDeviceInfo( &didi );

            // Get the device ready to go again for their user.
            prgAllDev[dwDev]->BuildActionMap( &m_diaf, szNameDev, DIDBAM_DEFAULT );
            prgAllDev[dwDev]->SetActionMap( &m_diaf, szNameDev, DIDSAM_DEFAULT );
            prgAllDev[dwDev]->SetCooperativeLevel( m_hWnd, DISCL_EXCLUSIVE|DISCL_FOREGROUND );
            
            // Now add it for the player.
            AddDeviceForPlayer( dwAssignedTo, &didi, prgAllDev[dwDev] );

#ifdef _DEBUG
            TCHAR str[MAX_PATH];
            sprintf(str, _T("--> DEVICE ASSIGNED <--\n") );
            OutputDebugString(str);
            sprintf(str, _T("  Player == %s\n"), szNameDev );
            OutputDebugString(str);
            sprintf(str, _T("  Device == %s\n"), didi.tszProductName );
            OutputDebugString(str);
            SYSTEMTIME st;
            FILETIME   ft;  

            // Convert the time to local time.
            FileTimeToLocalFileTime(&m_diaf.ftTimeStamp, &ft);

            // Convert the local file time from UTC to system time.
            FileTimeToSystemTime(&ft, &st);
    
            // Build a string showing the date and time.
            wsprintf( str, _T("  Timestamp == %02d/%02d/%d  %02d:%02d:%02d \n"), 
                      st.wMonth, st.wDay, st.wYear, st.wHour, st.wMinute, st.wSecond);
            OutputDebugString(str);
#endif
        }

        dwDev++;
    }

    // All devices have been assigned a to a user in the new array. 
    // Clean up the local flat array
    dwDev = 0;
    while( prgAllDev[dwDev] )
    {
        prgAllDev[dwDev]->Release();
        prgAllDev[dwDev] = NULL;
        dwDev++;
    }
}


//-----------------------------------------------------------------------------
// Name: GetDevicesForPlayer()
// Desc: Allows access to the private, internal list of devices for a given player.
//-----------------------------------------------------------------------------
HRESULT CInputDeviceManager::GetDevicesForPlayer(DWORD dwPlayer, LPDIRECTINPUTDEVICE8** ppDevices)
{
    *ppDevices = (LPDIRECTINPUTDEVICE8*)&m_ppdidDevices[dwPlayer][0];

    return S_OK;
}


//-----------------------------------------------------------------------------
// Name: ConfigureDevices()
// Desc: Wrapper function for the ConfigureDevices call. Before calling 
//       ConfigureDevices, this function sets up a list of DIACTIONFORMATs and 
//       user names, and creates a custom colorset with which the UI should 
//       be displayed.
//-----------------------------------------------------------------------------
HRESULT CInputDeviceManager::ConfigureDevices( IUnknown* pSurface,
											   VOID* ConfigureDevicesCB,
											   DWORD dwFlags )
{
	HRESULT hr;

	// Initialize all the colors here
	DICOLORSET dics;
	ZeroMemory(&dics, sizeof(DICOLORSET));
	dics.dwSize = sizeof(DICOLORSET);

   	// Fill in all the params
	DICONFIGUREDEVICESPARAMS dicdp;
	ZeroMemory(&dicdp, sizeof(dicdp));
	dicdp.dwSize = sizeof(dicdp);
	dicdp.dwcUsers       = m_dwNumUsers;
	dicdp.lptszUserNames = m_szUserNames;

	dicdp.dwcFormats     = 1;
	dicdp.lprgFormats    = &m_diaf;
	dicdp.hwnd           = m_hWnd;
	dicdp.lpUnkDDSTarget = NULL;

    // Set UI color scheme
    dicdp.dics.dwSize = sizeof(dics);
    dicdp.dics.cTextFore        = D3DCOLOR_RGBA(255,255,255,255);
    dicdp.dics.cTextHighlight   = D3DCOLOR_RGBA(60,191,241,255);
    dicdp.dics.cCalloutLine     = D3DCOLOR_RGBA(255,255,255,128);
    dicdp.dics.cCalloutHighlight= D3DCOLOR_RGBA(60,191,241,255);
    dicdp.dics.cBorder          = D3DCOLOR_RGBA(140,152,140,128);
    dicdp.dics.cControlFill     = D3DCOLOR_RGBA(113,0,0,128);
    dicdp.dics.cHighlightFill   = D3DCOLOR_RGBA(0,0,0,128);
    dicdp.dics.cAreaFill        = D3DCOLOR_RGBA(0,0,0,128);


	// Unacquire all devices so that they don't control the game while 
	// in the default Config UI.
    DWORD dwDev = 0;
    for( DWORD i = 0; i < m_dwNumUsers; i++ )
    {
        while( m_ppdidDevices[i][dwDev] )
        {
		    m_ppdidDevices[i][dwDev]->Unacquire();
            dwDev++;
        }
        dwDev = 0;
    }

	hr = m_pDI->ConfigureDevices( NULL, &dicdp, dwFlags, NULL );

	return hr;
}	
	

//-----------------------------------------------------------------------------
// Name: Cleanup()
// Desc: Releases the DirectInput objects used by the class.
//-----------------------------------------------------------------------------
VOID CInputDeviceManager::Cleanup()
{
    CleanupDevices( APP_CLEANUP_DEFAULT );
    
    if( m_pDI )
    {
        m_pDI->Release();
    }

    m_pDI = NULL;
}

//-----------------------------------------------------------------------------
// Name: CleanupDevices()
// Desc: Unacquires and releases the DirectInput device objects used by the class.
//-----------------------------------------------------------------------------
VOID CInputDeviceManager::CleanupDevices(DWORD dwCleanFlags)
{
    DWORD dwD = 0;

    for( DWORD dwP = 0; dwP < m_dwNumUsers; dwP++ )
    {
        while( m_ppdidDevices[dwP][dwD] )
        {     
            m_ppdidDevices[dwP][dwD]->Unacquire();
            if(APP_CLEANUP_DEFAULT == dwCleanFlags)
            {
                m_ppdidDevices[dwP][dwD]->SetActionMap( &m_diaf, NULL, DIDSAM_NOUSER );
            }
            m_ppdidDevices[dwP][dwD]->Release();
            m_ppdidDevices[dwP][dwD] = NULL;
            dwD++;
        }

        dwD = 0;
    }
}

//-----------------------------------------------------------------------------
// Name: Cleanup()
// Desc: Global cleanup function for the app. This function simply invokes the 
//       manager class's internal cleanup method (to handle all of the DirectInput
//       objects), then deletes the manager class itself.
//-----------------------------------------------------------------------------
VOID Cleanup()
{
    if( g_pInputDeviceManager )
    {
        g_pInputDeviceManager->Cleanup();
        delete g_pInputDeviceManager;
        g_pInputDeviceManager = NULL;
    }
}

//-----------------------------------------------------------------------------
// Name: PaintPlayerStatus()
// Desc: Paints a rudimentary UI to provide visual feedback based on each 
//       player's actions on the devices they own.
//-----------------------------------------------------------------------------
void PaintPlayerStatus(HWND hWnd, DWORD dwPlayer, PLAYERDATA pd)
{    
    // Outout strings.
	TCHAR* strBlank = _T("         ");
    TCHAR* str;

    HDC hDC;
    DWORD  dwY; // Y value for text display.
    DWORD  dwX; // X value for text display.

    // Init text and DC. 
    hDC = GetDC( hWnd );
    SetBkColor( hDC, RGB(0,0,0) );
    SetBkMode( hDC, OPAQUE );

    // Internally to the class, the Player number is zero-based. It's easier to 
    // work with them here as one-based.
    dwPlayer++;

    // Set text foreground color by player.
    switch( dwPlayer )
    {
        case 1: 
            // purple== Player 1
            SetTextColor( hDC, RGB(225, 50, 255) );
            str = _T("Player 1");
            break;

        case 2: 
            // red   == Player 2
            SetTextColor( hDC, RGB(255, 40, 40) );
            str = _T("Player 2");
            break;

        case 3: 
            // green == Player 3
            SetTextColor( hDC, RGB(60, 255, 60) );
            str = _T("Player 3");
            break;

        case 4: 
            // blue  == Player 4
            SetTextColor( hDC, RGB(60, 60, 255) );
            str = _T("Player 4");
            break;
    }

    // Generate the Y value at which to display text for this player. 
    dwY = (dwPlayer * LINE_SPACING);
    dwX = 0;

    TextOut( hDC, dwX, dwY, str, lstrlen(str) );
    dwX += COL_SPACING;

    // The remainder of this function is simply to output the 
    // results of gathering the input.
    if(pd.bTurningLeft)
        str = _T("Left   ");
    else if (pd.bTurningRight)
        str = _T("Right"); 
    else 
        str = strBlank;

    TextOut( hDC, dwX, dwY, str, lstrlen(str) );
    dwX += COL_SPACING;

    if(pd.bForwardThrust)
        str = _T("Fore");
    else if(pd.bReverseThrust)
        str = _T("Aft   ");
    else
        str = strBlank;
    
    TextOut( hDC, dwX, dwY, str, lstrlen(str) );
    dwX += COL_SPACING;

    str =  pd.bFiringWeapons ? _T("Fire") : strBlank;
    TextOut( hDC, dwX, dwY, str, lstrlen(str) );

    dwX += COL_SPACING;
    str =  pd.bEnableShields ? _T("Up     ") : _T("Down");
    TextOut( hDC, dwX, dwY, str, lstrlen(str) );

    ReleaseDC( hWnd, hDC );
}


//-----------------------------------------------------------------------------
// Name: GameLoop()
// Desc: This is the input loop for the app. Input is gathered from the 
//       DirecInput devices, and output is displayed simply in the app's window.
//-----------------------------------------------------------------------------
BOOL GameLoop( HWND hWnd )
{
    BOOL bRet;
	DIDEVICEOBJECTDATA didObjData;
    ZeroMemory( &didObjData, sizeof(didObjData) );
    static PLAYERDATA pd[MAX_USERS]; // State of each player
    LPDIRECTINPUTDEVICE8* pdidDevices;
    
    DWORD dwNumPlayers = g_pInputDeviceManager->GetNumUsers();
            
    // Loop through all devices for all players and check game input.
    for( DWORD i=0; i < dwNumPlayers; i++ )
    {
        // Get access to the list of input devices.
        g_pInputDeviceManager->GetDevicesForPlayer( i, &pdidDevices );

        bRet = ParsePlayerInput(hWnd, pdidDevices, &pd[i]);
        if( !bRet )
        {
            return bRet;
        }

        // Remove any conflicts (in a game, you couldn't go left and right at the same
        // time. Actual conflicts depend on the game logic, and not on the DirectInput
        // semantic mappings.)
        //
        // !!NOTE!!: This imposes some logic that a game MIGHT want (i.e. can't go
        //           forward and back at the same time). Your app might want to do
        //           this differently. 
        if( pd[i].bTurningLeft && pd[i].bTurningRight )
        {
            pd[i].bTurningLeft = pd[i].bTurningRight = FALSE;
        }
        if( pd[i].bForwardThrust && pd[i].bReverseThrust )
        {
            pd[i].bForwardThrust = pd[i].bReverseThrust = FALSE;
        }

        // Paint the status for this device. This could just as easily be a call 
        // to update the frame of your game. 
        PaintPlayerStatus( hWnd, i, pd[i] );
    }

    return TRUE;
}


//-----------------------------------------------------------------------------
// Name: ParsePlayerInput()
// Desc: Parses the actions buffered for the passed-in devices, and places the 
//       results into the provided PLAYERDATA structure. This data represents
//       the in game actions for a player.
//-----------------------------------------------------------------------------
BOOL ParsePlayerInput(HWND hWnd, LPDIRECTINPUTDEVICE8* ppdidDevices, LPPLAYERDATA ppd)
{
    HRESULT hr;
    DWORD   dwItems;
    DIDEVICEOBJECTDATA adod[BUFFER_SIZE];

    DWORD   dwDevice = 0;
    while ( ppdidDevices[dwDevice] )
    {
        
        // Poll the device to read the current state
        if( FAILED(ppdidDevices[dwDevice]->Poll() ) )  
        {
            // DirectInput is telling us that the input stream has been
            // interrupted. We aren't tracking any state between polls, so
            // we don't have any special reset that needs to be done. We
            // just re-acquire and try again.
            hr = ppdidDevices[dwDevice]->Acquire();
            if( DIERR_INPUTLOST  == hr) 
            {
                hr = ppdidDevices[dwDevice]->Acquire();
            }

            // hr may be DIERR_OTHERAPPHASPRIO or other errors.  This
            // may occur when the app is minimized or in the process of 
            // switching, so just try again later 
            return TRUE; 
        }        
        		
        // Retrieve the buffered actions from the device.
        dwItems = BUFFER_SIZE;
        hr = ppdidDevices[dwDevice]->GetDeviceData( sizeof(DIDEVICEOBJECTDATA),
                                                    adod, &dwItems, 0 );


		if( SUCCEEDED(hr) )
		{           
            // Get the actions. The number of input events is stored in
			// dwItems, and all the events are stored in the "adod" array. Each
			// event has a type stored in uAppData, and actual data stored in
			// dwData.
			for( DWORD j=0; j<dwItems; j++ )
			{
				// Non-axis data is recieved as "button pressed" or "button
				// released". Parse input as such.
				BOOL bState = (adod[j].dwData != 0 ) ? TRUE : FALSE;

            	switch (adod[j].uAppData)
				{
				case INPUT_LEFTRIGHT_AXIS: // Parse the left-right axis data
					(*ppd).dwLRAxisData  = adod[j].dwData;
					(*ppd).bTurningRight = (*ppd).bTurningLeft  = FALSE;					
					if( (int)(*ppd).dwLRAxisData > 0 )
						(*ppd).bTurningRight = TRUE;
					else if( (int)(*ppd).dwLRAxisData < 0 )
						(*ppd).bTurningLeft = TRUE;
                    break;

				case INPUT_UPDOWN_AXIS: // Parse the up-down axis data
					(*ppd).dwUDAxisData   = adod[j].dwData;
					(*ppd).bReverseThrust = (*ppd).bForwardThrust = FALSE;

					if( (int)(*ppd).dwUDAxisData > 0 )
						(*ppd).bReverseThrust = TRUE;
					else if( (int)(*ppd).dwUDAxisData < 0 )
						(*ppd).bForwardThrust = TRUE;
					break;
					
				case INPUT_TURNLEFT:        (*ppd).bTurningLeft    = bState; break;
				case INPUT_TURNRIGHT:       (*ppd).bTurningRight   = bState; break;
				case INPUT_FORWARDTHRUST:   (*ppd).bForwardThrust  = bState; break;
				case INPUT_REVERSETHRUST:   (*ppd).bReverseThrust  = bState; break;
				case INPUT_FIREWEAPONS:     (*ppd).bFiringWeapons  = bState; break;
				case INPUT_ENABLESHIELD:    (*ppd).bEnableShields  = bState; break;
				
                case INPUT_QUITGAME:        return FALSE;				
				
				case INPUT_DISPLAYGAMEMENU: 
					(*ppd).bDisplayingMenu = bState; 
                    InvokeDefaultUI(hWnd);
					return TRUE;
                }
			}
        }

        // Go to the next device in the list.
        dwDevice++;
   	}

    return TRUE;
}

//-----------------------------------------------------------------------------
// Name: InvokeDefaultUI()
// Desc: Global function to invoke the Default UI for the DirectInput mapper. 
//       This function uses the ConfigureDevices wrapper method for this task.
//       Note that when the UI is invoked in edit mode (DICD_EDIT), you should
//       assume that devices and the configuration of those devices will probably
//       change due to user interaction. After ConfigureDevices returns, this 
//       function calls the ReassignDevices method on the manager class to 
//       sort out which devices belong to whom, and rebuild action maps to reflect
//       any config changes that may have taken place.
//-----------------------------------------------------------------------------
void InvokeDefaultUI(HWND hWnd)
{
    g_pInputDeviceManager->ConfigureDevices(NULL, NULL, DICD_EDIT);
    g_pInputDeviceManager->ReassignDevices();
}


//-----------------------------------------------------------------------------
// Name: CreateInputStuff()
// Desc: Creates the DirectInput helper class. The DIACTIONFORMAT specifies what
//       type of devices we are looking for (via the genre). Note: genres are
//       defined in the docs and the dinput.h header file. The DIACTIONFORMAT
//       structure also is used to specify the game action array, defined
//       at the beginnning of this file.
//-----------------------------------------------------------------------------
HRESULT CreateInputStuff( HWND hWnd, DWORD dwNumUsers )
{
    HRESULT hr;

    // Setup action format for suitable input devices for this app
    DIACTIONFORMAT diaf;
    ZeroMemory( &diaf, sizeof(DIACTIONFORMAT) );
    diaf.dwSize        = sizeof(DIACTIONFORMAT);
    diaf.dwActionSize  = sizeof(DIACTION);
    diaf.dwDataSize    = NUMBER_OF_SEMANTICS * sizeof(DWORD);
    diaf.dwNumActions  = NUMBER_OF_SEMANTICS;
    diaf.guidActionMap = g_AppGuid;
    diaf.dwGenre       = DIVIRTUAL_SPACESIM;
    diaf.rgoAction     = g_rgGameAction;
    diaf.dwBufferSize  = BUFFER_SIZE;
    diaf.lAxisMin      = -100;
    diaf.lAxisMax      = 100;
    _tcscpy( diaf.tszActionMap, _T("MultiMapper Sample Application") );

    // Create a new input device manager
    g_pInputDeviceManager = new CInputDeviceManager();
    if( !g_pInputDeviceManager )
    {
        return E_OUTOFMEMORY;
    }

    hr = g_pInputDeviceManager->Create( hWnd, &dwNumUsers, &diaf, APP_DICREATE_DEFAULT );

    if( FAILED(hr) )
    {
        TCHAR msg[MAX_PATH];
        int iRet;
        
        switch(hr)
        {
        // It's possible that a single user could "own" too many devices for the other
        // players to get into the game. If so, we reinit the manager class to provide 
        // each user with a device that has a default configuration.
        case E_APPERR_DEVICESTAKEN:
            sprintf( msg, 
                 _T("You have entered more users than there are suitable devices, " \
                 "or some users are claiming too many devices.\n\n" \
                 "Click Yes to give each user a default device, or click No " \
                 "to close the application"));

            iRet = MessageBox( hWnd, msg, _T("Devices Are Taken"), 
                               MB_YESNO | MB_ICONEXCLAMATION );

            if(iRet == IDYES)
            {
                g_pInputDeviceManager->Cleanup();
                g_pInputDeviceManager->Create( hWnd, &dwNumUsers, &diaf, APP_DICREATE_FORCEINIT );
            }
            else
                return E_FAIL;
            
            break;

        // Another common error is if more users are attempting to play than there are devices
        // attached to the machine. In this case, the number of players is automatically 
        // lowered to make playing the game possible. 
        case E_APPERR_TOOMANYUSERS:
            DWORD dwNumDevices = g_pInputDeviceManager->GetNumDevices();
            dwNumUsers = dwNumDevices;
            TCHAR* str = _T( "There are not enough devices attached to the system " \
                             "for the number of users you entered.\n\n" \
                             "The number of users has been automatically changed " \
                             "to %i (the number of devices available on the system).");

            sprintf( msg, str, dwNumDevices);
            MessageBox( hWnd, msg, _T("Too Many Users"), 
                        MB_OK | MB_ICONEXCLAMATION );

            g_pInputDeviceManager->Cleanup();
            g_pInputDeviceManager->Create( hWnd, &dwNumUsers, &diaf, APP_DICREATE_DEFAULT );                           
            break;
        }
    }

    return S_OK;
}


//-----------------------------------------------------------------------------
// Name: MainWndproc()
// Desc: Callback for all Windows messages
//-----------------------------------------------------------------------------
long FAR PASCAL MainWndproc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch( msg )
    {
        case WM_PAINT:
        {
            // Output message to user
            CHAR* str;
            HDC hDC = GetDC( hWnd );
            
            SetTextColor( hDC, RGB(255,255,255) );
            SetBkColor( hDC, RGB(0,0,0) );
            SetBkMode( hDC, OPAQUE );
            
            str = _T("Name");
            TextOut( hDC, 0, 0, str, lstrlen(str) );
            str = _T("Turn");
            TextOut( hDC, COL_SPACING*1, 0, str, lstrlen(str) );
            str = _T("Thrust");
            TextOut( hDC, COL_SPACING*2, 0, str, lstrlen(str) );
            str = _T("Weapon");
            TextOut( hDC, COL_SPACING*3, 0, str, lstrlen(str) );
            str = _T("Shield");
            TextOut( hDC, COL_SPACING*4, 0, str, lstrlen(str) );

            str = _T("Looking for game input... press Escape to exit.");
            TextOut( hDC, 0, 140, str, lstrlen(str) );

			str = _T("Press D to display input device settings.");
            TextOut( hDC, 0, 158, str, lstrlen(str) );

            ReleaseDC( hWnd, hDC );
            break;
        }

        // Handling global keystrokes as WM_CHAR messages. In this case, it's 
        // actually easier to use Windows than a specialized action map.
        case WM_CHAR:
            if( VK_ESCAPE == (TCHAR)wParam )
                SendMessage( hWnd, WM_DESTROY, 0, 0 );
            else if ( 0x64 == (TCHAR)wParam ) // 0x64 == 'D'
                InvokeDefaultUI(hWnd);
            break;

        case WM_DESTROY:
            Cleanup();
            PostQuitMessage( 0 );
            break;
    }

    return DefWindowProc( hWnd, msg, wParam, lParam );
}

INT_PTR CALLBACK NumPlayerFunc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
    case WM_INITDIALOG:
        SetFocus(GetDlgItem(hDlg, IDC_EDIT_NUMPLAYERS));
        SetDlgItemInt( hDlg, IDC_EDIT_NUMPLAYERS, MIN_USERS, FALSE);
        SendDlgItemMessage(hDlg, IDC_EDIT_NUMPLAYERS, EM_SETSEL, 0, -1);
        return FALSE;

    case WM_COMMAND:
        if (wParam == IDOK) 
        { 
            UINT nNumPlayers = 0;
            BOOL bSuccess;
            nNumPlayers= GetDlgItemInt( hDlg, IDC_EDIT_NUMPLAYERS, 
                                        &bSuccess, FALSE);

            if(bSuccess && (nNumPlayers >0 && nNumPlayers <=MAX_USERS))
                EndDialog(hDlg, (INT)nNumPlayers); 
            else
            {
                SetDlgItemInt( hDlg, IDC_EDIT_NUMPLAYERS, MIN_USERS, FALSE);
                SetFocus(GetDlgItem(hDlg, IDC_EDIT_NUMPLAYERS));
                SendDlgItemMessage(hDlg, IDC_EDIT_NUMPLAYERS, EM_SETSEL, 0, -1);
                MessageBeep(MB_OK);
            }
            return TRUE; 
        } 
    }
    
    return FALSE;
}




//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: Application entry point
//-----------------------------------------------------------------------------
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow )
{
    // Register the window class
    WNDCLASS wndClass = { CS_DBLCLKS, MainWndproc, 0, 0, hInstance, NULL,
                          LoadCursor( NULL, IDC_ARROW ), 
                          (HBRUSH)GetStockObject( BLACK_BRUSH ),
                          NULL, _T("IMapClass") };
    RegisterClass( &wndClass );

    // Create our main window
    HWND hWnd = CreateWindowEx( 0, _T("IMapClass"), _T("MultiMapper"),
                                WS_VISIBLE|WS_POPUP|WS_CAPTION|WS_SYSMENU,
                                200, 200, 400, 200, NULL, NULL, hInstance, NULL );
    if( NULL == hWnd )
    {
        return FALSE;
    }

    UpdateWindow( hWnd );

    UINT dwNumUsers;
    // Query the user(s) for the number of players.
    dwNumUsers = DialogBox( hInstance, 
                            MAKEINTRESOURCE(IDD_NUMPLAYERS),
                            hWnd,
                            (DLGPROC)NumPlayerFunc);

    // Create the DirectInput helper class
    if( FAILED( CreateInputStuff( hWnd, dwNumUsers ) ) )
    {
        SendMessage( hWnd, WM_DESTROY, 0, 0 );
        return 0;
    }

    // Traditional message loop to "run" the app
    while( TRUE )
    {
        MSG msg;

        if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )
        {
            if( FALSE == GetMessage( &msg, NULL, 0, 0 ) )
                return msg.wParam;

            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else
        {
            // Most apps use idle time for processing the game loop
            if( FALSE == GameLoop( hWnd ) )
                SendMessage( hWnd, WM_DESTROY, 0, 0 );
        }
    }
}

