/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "hsConfig.h" #include "hsWindows.h" // plInputManager.cpp #define DIRECTINPUT_VERSION 0x0800 #include #include "hsTypes.h" #include "plInputManager.h" #include "plPipeline.h" #include "plInputDevice.h" #include "plDInputDevice.h" #include "plMessage/plInputEventMsg.h" #include "plInputInterfaceMgr.h" #include "hsStream.h" #include "pnKeyedObject/plKey.h" #include "pnKeyedObject/plFixedKey.h" #include "hsResMgr.h" #include "hsTimer.h" #include "plgDispatch.h" #include "pnMessage/plTimeMsg.h" #include "pnMessage/plCmdIfaceModMsg.h" #include "pnMessage/plPlayerPageMsg.h" hsBool plInputManager::fUseDInput = false; UInt8 plInputManager::bRecenterMouse = 0; HWND plInputManager::fhWnd = nil; #define NUM_ACTIONS 17 struct plDIDevice { plDIDevice() : fDevice(nil), fCaps(nil) {;} plDIDevice(IDirectInputDevice8* _device) : fCaps(nil) { fDevice = _device;} IDirectInputDevice8* fDevice; DIDEVCAPS* fCaps; }; struct plDInput { plDInput() : fDInput(nil), fActionFormat(nil) {;} IDirectInput8* fDInput; hsTArray fSticks; DIACTIONFORMAT* fActionFormat; }; class plDInputMgr { public: plDInputMgr(); ~plDInputMgr(); void Init(HINSTANCE hInst, HWND hWnd); void Update(); void AddDevice(IDirectInputDevice8* device); void ConfigureDevice(); virtual hsBool MsgReceive(plMessage* msg); // dinput callback functions static int __stdcall EnumGamepadCallback(const DIDEVICEINSTANCE* device, void* pRef); // I should be using these but they don't work... // static int __stdcall SetAxisRange(const DIDEVICEOBJECTINSTANCE* obj, void* pRef); // static int __stdcall EnumSuitableDevices(const struct DIDEVICEINSTANCEA* devInst, struct IDirectInputDevice8* dev, unsigned long why, unsigned long devRemaining, void* pRef); protected: plDInput* fDI; hsTArray fInputDevice; static DIACTION fActionMap[]; HWND fhWnd; }; // function pointers to dinput callbacks typedef int (__stdcall * Pfunc1) (const DIDEVICEINSTANCE* device, void* pRef); // I should need these... //typedef int (__stdcall * Pfunc2) (const DIDEVICEOBJECTINSTANCE* device, void* pRef); //typedef int (__stdcall * Pfunc3) (const struct DIDEVICEINSTANCEA* devInst, struct IDirectInputDevice8* dev, unsigned long why, unsigned long devRemaining, void* pRef); plInputManager* plInputManager::fInstance = nil; plInputManager::plInputManager( HWND hWnd ) : fDInputMgr(nil), fInterfaceMgr(nil) { fhWnd = hWnd; fInstance = this; fActive = false; fFirstActivated = false; fMouseScale = 1.f; } plInputManager::plInputManager() : fDInputMgr(nil), fInterfaceMgr(nil) { fInstance = this; fActive = false; fFirstActivated = false; fMouseScale = 1.f; } plInputManager::~plInputManager() { fInterfaceMgr->Shutdown(); fInterfaceMgr = nil; for (int i = 0; i < fInputDevices.Count(); i++) { fInputDevices[i]->Shutdown(); delete(fInputDevices[i]); } if (fDInputMgr) delete fDInputMgr; } //static void plInputManager::SetRecenterMouse(hsBool b) { if (b) bRecenterMouse++; else if (bRecenterMouse > 0) bRecenterMouse--; } void plInputManager::RecenterCursor() { RECT rect; GetClientRect(fhWnd, &rect); POINT pt; // pt.y = ( (rect.bottom - rect.top) / 2 ) / fInstance->fMouseScale; // pt.x = ( (rect.right - rect.left) / 2 ) / fInstance->fMouseScale; ClientToScreen(fhWnd, &pt); SetCursorPos( pt.x, pt.y ); } void plInputManager::CreateInterfaceMod(plPipeline* p) { fInterfaceMgr = TRACKED_NEW plInputInterfaceMgr(); fInterfaceMgr->Init(); } void plInputManager::InitDInput(HINSTANCE hInst, HWND hWnd) { if (fUseDInput) { fDInputMgr = TRACKED_NEW plDInputMgr; fDInputMgr->Init(hInst, hWnd); } } hsBool plInputManager::MsgReceive(plMessage* msg) { for (int i=0; iMsgReceive(msg)) return true; if (fDInputMgr) return fDInputMgr->MsgReceive(msg); return hsKeyedObject::MsgReceive(msg); } void plInputManager::Update() { if (fDInputMgr) fDInputMgr->Update(); } void plInputManager::SetMouseScale( hsScalar s ) { /* RECT rect; POINT currPos; // Gotta make sure to move the mouse to the correct new position for the scale GetClientRect( fhWnd, &rect ); GetCursorPos( &currPos ); ScreenToClient( fhWnd, &currPos ); float x = (float)currPos.x / rect.right; float y = (float)currPos.y / rect.bottom; x *= fMouseScale; y *= fMouseScale; fMouseScale = s; // Refreshes all of the input devices so that they can reset mouse limits, etc RECT rect2 = rect; rect2.right /= fMouseScale; rect2.bottom /= fMouseScale; ::MapWindowPoints( fhWnd, NULL, (POINT *)&rect2, 2 ); BOOL ret = ::ClipCursor( &rect ); // Now move the cursor to the right spot currPos.x = ( x / fMouseScale ) * rect.right; currPos.y = ( y / fMouseScale ) * rect.bottom; ClientToScreen( fhWnd, &currPos ); SetCursorPos( currPos.x, currPos.y ); */ } // Sometimes the keyboard driver "helps" us translating a key involved in a key // combo. For example pressing shif-numpad8 will actually generate a KEY_UP event, // the same as the up arrow. This function undoes that translation. plKeyDef plInputManager::UntranslateKey(plKeyDef key, hsBool extended) { if (!extended) { if (key == KEY_UP) return KEY_NUMPAD8; if (key == KEY_DOWN) return KEY_NUMPAD2; if (key == KEY_LEFT) return KEY_NUMPAD4; if (key == KEY_RIGHT) return KEY_NUMPAD6; } return key; } void plInputManager::HandleWin32ControlEvent(UINT message, WPARAM Wparam, LPARAM Lparam, HWND hWnd) { if( !fhWnd ) fhWnd = hWnd; hsBool bExtended; switch (message) { case SYSKEYDOWN: case KEYDOWN: { bExtended = Lparam >> 24 & 1; hsBool bRepeat = ((Lparam >> 29) & 0xf) != 0; for (int i=0; iHandleKeyEvent( KEYDOWN, UntranslateKey((plKeyDef)Wparam, bExtended), true, bRepeat ); } break; case SYSKEYUP: case KEYUP: { bExtended = Lparam >> 24 & 1; for (int i=0; iHandleKeyEvent( KEYUP, UntranslateKey((plKeyDef)Wparam, bExtended), false, false ); } break; case CHAR_MSG: { // These are handled by KEYUP/KEYDOWN and should not be sent // We don't like garbage getting in string fields if (Wparam == KEY_BACKSPACE || Wparam == 0x0A || Wparam == KEY_ESCAPE || Wparam == KEY_TAB || Wparam == 0x0D) break; UINT scan = Lparam >> 16; scan = MapVirtualKeyEx(scan, MAPVK_VSC_TO_VK, nil); if (scan == 0) scan = -1; bExtended = Lparam >> 24 & 1; hsBool bRepeat = ((Lparam >> 29) & 0xf) != 0; bool down = !(Lparam >> 31); for (int i=0; iHandleKeyEvent( CHAR_MSG, (plKeyDef)scan, down, bRepeat, (wchar_t)Wparam ); } break; case MOUSEWHEEL: { plMouseEventMsg* pMsg = TRACKED_NEW plMouseEventMsg; int zDelta = GET_WHEEL_DELTA_WPARAM(Wparam); pMsg->SetWheelDelta((float)zDelta); if (zDelta < 0) pMsg->SetButton(kWheelNeg); else pMsg->SetButton(kWheelPos); RECT rect; GetClientRect(hWnd, &rect); pMsg->SetXPos(LOWORD(Lparam) / (float)rect.right); pMsg->SetYPos(HIWORD(Lparam) / (float)rect.bottom); pMsg->Send(); } break; case MOUSEMOVE: case L_BUTTONDN: case L_BUTTONUP: case R_BUTTONDN: case R_BUTTONUP: case L_BUTTONDBLCLK: case R_BUTTONDBLCLK: case M_BUTTONDN: case M_BUTTONUP: { RECT rect; GetClientRect(hWnd, &rect); plIMouseXEventMsg* pXMsg = TRACKED_NEW plIMouseXEventMsg; plIMouseYEventMsg* pYMsg = TRACKED_NEW plIMouseYEventMsg; plIMouseBEventMsg* pBMsg = TRACKED_NEW plIMouseBEventMsg; pXMsg->fWx = LOWORD(Lparam); pXMsg->fX = (float)LOWORD(Lparam) / (float)rect.right; pYMsg->fWy = HIWORD(Lparam); pYMsg->fY = (float)HIWORD(Lparam) / (float)rect.bottom; // Apply mouse scale // pXMsg->fX *= fMouseScale; // pYMsg->fY *= fMouseScale; if (Wparam & MK_LBUTTON && message != L_BUTTONUP) { pBMsg->fButton |= kLeftButtonDown; } else { pBMsg->fButton |= kLeftButtonUp; } if (Wparam & MK_RBUTTON && message != R_BUTTONUP) { pBMsg->fButton |= kRightButtonDown; } else { pBMsg->fButton |= kRightButtonUp; } if (Wparam & MK_MBUTTON && message != M_BUTTONUP) { pBMsg->fButton |= kMiddleButtonDown; } else { pBMsg->fButton |= kMiddleButtonUp; } if( message == L_BUTTONDBLCLK ) pBMsg->fButton |= kLeftButtonDblClk; // We send the double clicks separately if( message == R_BUTTONDBLCLK ) pBMsg->fButton |= kRightButtonDblClk; for (int i=0; iMsgReceive(pXMsg); fInputDevices[i]->MsgReceive(pYMsg); fInputDevices[i]->MsgReceive(pBMsg); } POINT pt; if (RecenterMouse() && (pXMsg->fX <= 0.1 || pXMsg->fX >= 0.9) ) { pt.x = (rect.right - rect.left) / 2; pt.y = HIWORD(Lparam); ClientToScreen(hWnd, &pt); SetCursorPos( pt.x, pt.y ); } else if (RecenterMouse() && (pYMsg->fY <= 0.1 || pYMsg->fY >= 0.9) ) { pt.y = (rect.bottom - rect.top) / 2; pt.x = LOWORD(Lparam); ClientToScreen(hWnd, &pt); SetCursorPos( pt.x, pt.y ); } if (RecenterMouse() && Lparam == 0) { pt.y = (rect.bottom - rect.top) / 2; pt.x = (rect.right - rect.left) / 2; ClientToScreen(hWnd, &pt); SetCursorPos( pt.x, pt.y ); } delete(pXMsg); delete(pYMsg); delete(pBMsg); } break; case WM_ACTIVATE: { bool activated = ( LOWORD( Wparam ) == WA_INACTIVE ) ? false : true; Activate( activated ); } break; } } //// Activate //////////////////////////////////////////////////////////////// // Handles what happens when the app (window) activates/deactivates void plInputManager::Activate( bool activating ) { int i; for( i = 0; i < fInputDevices.GetCount(); i++ ) fInputDevices[ i ]->HandleWindowActivate( activating, fhWnd ); fActive = activating; fFirstActivated = true; } //// AddInputDevice ////////////////////////////////////////////////////////// void plInputManager::AddInputDevice( plInputDevice *pDev ) { fInputDevices.Append( pDev ); if( fFirstActivated ) pDev->HandleWindowActivate( fActive, fhWnd ); } // // // dinput manager // // plDInputMgr::plDInputMgr() : fDI(nil) { fDI = TRACKED_NEW plDInput; } plDInputMgr::~plDInputMgr() { if (fDI) { for (int i = 0; i < fDI->fSticks.Count(); i++) { plDIDevice* pD = fDI->fSticks[i]; pD->fDevice->Release(); delete(pD->fCaps); delete(pD); } fDI->fSticks.SetCountAndZero(0); delete(fDI->fActionFormat); fDI->fDInput->Release(); for(int j = 0; j < fInputDevice.Count(); j++) delete(fInputDevice[j]); fInputDevice.SetCountAndZero(0); delete fDI; } } void plDInputMgr::Init(HINSTANCE hInst, HWND hWnd) { HRESULT hr; hr = DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&fDI->fDInput, NULL); hsAssert(!hr, "failed to initialize directInput!"); // enumerate game controllers Pfunc1 fPtr = &plDInputMgr::EnumGamepadCallback; int i = 0; // set up the action mapping fDI->fActionFormat = TRACKED_NEW DIACTIONFORMAT; fDI->fActionFormat->dwSize = sizeof(DIACTIONFORMAT); fDI->fActionFormat->dwActionSize = sizeof(DIACTION); fDI->fActionFormat->dwDataSize = NUM_ACTIONS * sizeof(DWORD); fDI->fActionFormat->dwNumActions = NUM_ACTIONS; fDI->fActionFormat->guidActionMap = PL_ACTION_GUID; fDI->fActionFormat->dwGenre = DIVIRTUAL_FIGHTING_THIRDPERSON; fDI->fActionFormat->rgoAction = fActionMap; fDI->fActionFormat->dwBufferSize = 16; fDI->fActionFormat->lAxisMin = -1000; fDI->fActionFormat->lAxisMax = 1000; sprintf( fDI->fActionFormat->tszActionMap, "Plasma 2.0 Game Actions" ); // this call should not work: fDI->fDInput->EnumDevices(DI8DEVCLASS_GAMECTRL, fPtr, fDI, DIEDFL_ATTACHEDONLY); // apply the mapping to the game controller // this is the correct way to apply the action map: // Pfunc3 fPtr3 = &plDInputMgr::EnumSuitableDevices; // hr = fDI->fDInput->EnumDevicesBySemantics(NULL, fDI->fActionFormat, EnumSuitableDevices, fDI, NULL); for (i = 0; i < fDI->fSticks.Count(); i++) { fDI->fSticks[i]->fCaps = TRACKED_NEW DIDEVCAPS; fDI->fSticks[i]->fCaps->dwSize = sizeof(DIDEVCAPS); hr = fDI->fSticks[i]->fDevice->GetCapabilities(fDI->fSticks[i]->fCaps); hsAssert(!hr, "Unable to acquire devcaps in DInput Device!"); hr = fDI->fSticks[i]->fDevice->Acquire(); hsAssert(!hr, "Unable to acquire DInput Device!"); } fhWnd = hWnd; for (i = 0; i < fDI->fSticks.Count(); i++) fInputDevice.Append( TRACKED_NEW plDInputDevice ); } void plDInputMgr::Update() { HRESULT hr; if (!fDI->fSticks.Count()) return; // Poll the devices to read the current state for (int i = 0; i < fDI->fSticks.Count(); i++) { hr = fDI->fSticks[i]->fDevice->Poll(); if (FAILED(hr)) { // Attempt to reacquire joystick while(hr == DIERR_INPUTLOST) { hr = fDI->fSticks[i]->fDevice->Acquire(); char str[256]; sprintf(str, "DInput Device # %d connection lost - press Ignore to attempt to reacquire!", i); hsAssert(!hr, str); } } DIDEVICEOBJECTDATA data; ULONG size = 1; hr = fDI->fSticks[i]->fDevice->GetDeviceData(sizeof(DIDEVICEOBJECTDATA),&data,&size,0); fInputDevice[i]->Update(&data); } } void plDInputMgr::AddDevice(IDirectInputDevice8* device) { HRESULT hr = device->BuildActionMap(fDI->fActionFormat, NULL, NULL); if (!FAILED(hr)) device->SetActionMap( fDI->fActionFormat, NULL, NULL ); } void plDInputMgr::ConfigureDevice() { ::ShowCursor( TRUE ); ReleaseCapture(); DICOLORSET dics; ZeroMemory(&dics, sizeof(DICOLORSET)); dics.dwSize = sizeof(DICOLORSET); DICONFIGUREDEVICESPARAMS dicdp; ZeroMemory(&dicdp, sizeof(dicdp)); dicdp.dwSize = sizeof(dicdp); dicdp.dwcUsers = 1; dicdp.lptszUserNames = NULL; dicdp.dwcFormats = 1; dicdp.lprgFormats = fDI->fActionFormat; dicdp.hwnd = fhWnd; dicdp.lpUnkDDSTarget = NULL; fDI->fDInput->ConfigureDevices(NULL, &dicdp, DICD_EDIT, NULL); for (int i = 0; i < fDI->fSticks.Count(); i++) fDI->fSticks[i]->fDevice->SetActionMap( fDI->fActionFormat, NULL, DIDSAM_FORCESAVE ); ::ShowCursor( FALSE ); SetCapture(fhWnd); } hsBool plDInputMgr::MsgReceive(plMessage* msg) { plInputEventMsg* pMsg = plInputEventMsg::ConvertNoRef(msg); if (pMsg && pMsg->fEvent == plInputEventMsg::kConfigure) { ConfigureDevice(); } return false; } // dinput required callback functions: // enumerate the dinput devices int __stdcall plDInputMgr::EnumGamepadCallback(const DIDEVICEINSTANCE* device, void* pRef) { HRESULT hr; plDInput* pDI = (plDInput*)pRef; IDirectInputDevice8* fStick = nil; hr = pDI->fDInput->CreateDevice(device->guidInstance, &fStick, NULL); if(!FAILED(hr)) { pDI->fSticks.Append(TRACKED_NEW plDIDevice(fStick)); // the following code pertaining to the action map shouldn't be here. // in fact this shouldn't work at all according to MS, but this is // currently the only way this works. Whatever - the correct // code is here and commented out in case this ever gets fixed by MS // in a future release of dinput. HRESULT hr = fStick->BuildActionMap(pDI->fActionFormat, NULL, NULL); if (!FAILED(hr)) { hr = fStick->SetActionMap( pDI->fActionFormat, NULL, NULL ); DIPROPDWORD dipW; dipW.diph.dwSize = sizeof(DIPROPDWORD); dipW.diph.dwHeaderSize = sizeof(DIPROPHEADER); dipW.diph.dwHow = DIPH_DEVICE; dipW.diph.dwObj = 0; dipW.dwData = 500; // 5% of axis range for deadzone hr = fStick->SetProperty(DIPROP_DEADZONE , &dipW.diph); } return DIENUM_CONTINUE; } return DIENUM_STOP; } // look for axes on the controller and set the output range to +-100 // apparently not needed with action mapping: /* int __stdcall plDInputMgr::SetAxisRange(const DIDEVICEOBJECTINSTANCE* obj, void* pRef) { HRESULT hr; DIPROPRANGE diprg; diprg.diph.dwSize = sizeof(DIPROPRANGE); diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); diprg.diph.dwHow = DIPH_BYID; diprg.diph.dwObj = obj->dwType; diprg.lMin = -100; diprg.lMax = +100; plDInput* pDI = (plDInput*)pRef; for (int i = 0; i < pDI->fSticks.Count(); i++) hr = pDI->fSticks[i]->fDevice->SetProperty(DIPROP_RANGE, &diprg.diph); if(!FAILED(hr)) return DIENUM_CONTINUE; return DIENUM_STOP; } */ // apply mapping to controller // not used. why? no one really knows. // leave this here in case dinput ever gets fixed... /* int __stdcall plDInputMgr::EnumSuitableDevices(const struct DIDEVICEINSTANCEA* devInst, struct IDirectInputDevice8* dev, unsigned long why, unsigned long devRemaining, void* pRef) { plDInput* pDI = (plDInput*)pRef; HRESULT hr = dev->BuildActionMap(pDI->fActionFormat, NULL, NULL); if (!FAILED(hr)) { hr = dev->SetActionMap( pDI->fActionFormat, NULL, NULL ); } return DIENUM_STOP; } */ DIACTION plDInputMgr::fActionMap[NUM_ACTIONS] = { {A_CONTROL_MOVE, DIAXIS_TPS_MOVE, 0, "Walk Forward-Backward" ,}, {A_CONTROL_TURN, DIAXIS_TPS_TURN, 0, "Turn Left-Right" ,}, {A_CONTROL_MOUSE_X, DIAXIS_ANY_1, 0, "Move Camera Left-Right",}, {A_CONTROL_MOUSE_Y, DIAXIS_ANY_2, 0, "Move Camera Up-Down" ,}, {B_CONTROL_ACTION, DIBUTTON_TPS_ACTION, 0, "Action" ,}, {B_CONTROL_JUMP, DIBUTTON_TPS_JUMP, 0, "Jump" ,}, {B_CONTROL_STRAFE_LEFT, DIBUTTON_TPS_STEPLEFT, 0, "Strafe Left" ,}, {B_CONTROL_STRAFE_RIGHT, DIBUTTON_TPS_STEPRIGHT, 0, "Strafe Right" ,}, {B_CONTROL_MODIFIER_FAST, DIBUTTON_TPS_RUN, 0, "Run" ,}, {B_CONTROL_EQUIP, DIBUTTON_TPS_SELECT, 0, "Equip Item" ,}, {B_CONTROL_DROP, DIBUTTON_TPS_USE, 0, "Drop Item" ,}, {B_CONTROL_MOVE_FORWARD, DIBUTTON_ANY(0), 0, "Walk Forward" ,}, {B_CONTROL_MOVE_BACKWARD, DIBUTTON_ANY(1), 0, "Walk Backward" ,}, {B_CONTROL_ROTATE_LEFT, DIBUTTON_ANY(2), 0, "Turn Left" ,}, {B_CONTROL_ROTATE_RIGHT, DIBUTTON_ANY(3), 0, "Turn Right" ,}, {B_CONTROL_TURN_TO, DIBUTTON_ANY(4), 0, "Pick Item" ,}, {B_CAMERA_RECENTER, DIBUTTON_ANY(5), 0, "Recenter Camera" ,}, };