/* Starshatter: The Open Source Project Copyright (c) 2021-2024, Starshatter: The Open Source Project Contributors Copyright (c) 2011-2012, Starshatter OpenSource Distribution Contributors Copyright (c) 1997-2006, Destroyer Studios LLC. AUTHOR: John DiCamillo OVERVIEW ======== Joystick Input class */ #include "Joystick.h" #include #include "GameWinDX9.h" #include "Types.h" #define DIRECTINPUT_VERSION 0x0800 #include #define JOY_POVUPRIGHT 4500 #define JOY_POVDNRIGHT 13500 #define JOY_POVDNLEFT 22500 #define JOY_POVUPLEFT 31500 // +--------------------------------------------------------------------+ // DIRECT INPUT SUPPORT const int MAX_DEVICES = 8; static LPDIRECTINPUT7 pdi = 0; static LPDIRECTINPUTDEVICE7 pdev = 0; static DIDEVICEINSTANCE devices[MAX_DEVICES]; static int ndev = 0; static int idev = -1; static int strikes = 3; static Joystick* joystick = 0; void DirectInputError(const char* msg, HRESULT err); const char* DIErrStr(HRESULT hr); void ReleaseDirectInput(); // +--------------------------------------------------------------------+ Joystick::Joystick() : x(0), y(0), z(0), p(0), r(0), w(0), t(0) { if (!joystick) joystick = this; select = 1; rudder = 0; throttle = 1; sensitivity = 25; dead_zone = 100; for (int i = 0; i < MotionController::MaxActions; i++) action[i] = false; for (int i = 0; i < KEY_MAP_SIZE; i++) map[i] = 0; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { hat[i][j] = false; } } map_axis[0] = KEY_JOY_AXIS_X; map_axis[1] = KEY_JOY_AXIS_Y; map_axis[2] = KEY_JOY_AXIS_RZ; map_axis[3] = KEY_JOY_AXIS_S0; inv_axis[0] = false; inv_axis[1] = false; inv_axis[2] = false; inv_axis[3] = false; if (InfoEx::DirectX() < InfoEx::DXVersion::Dx7) { Print("Joystick: DI7 not found, using multimedia library\n"); pdi = 0; pdev = 0; } else if (!pdi) { HRESULT hr = DirectInput8Create(GameWinDX9::GetInstance()->GetHINST(), DIRECTINPUT_VERSION, IID_IDirectInput7, (void**)&pdi, NULL); if FAILED(hr) { DirectInputError("Failed to initialize DI7", hr); pdi = 0; pdev = 0; } else { Print("Joystick: initialized DI7 pdi = %08x\n", (DWORD) pdi); } } } Joystick::~Joystick() { ReleaseDirectInput(); joystick = 0; } void ReleaseDirectInput() { if (pdev) { pdev->Unacquire(); pdev->Release(); pdev = 0; } if (pdi) { pdi->Release(); pdi = 0; } } Joystick* Joystick::GetInstance() { return joystick; } // +--------------------------------------------------------------------+ void Joystick::MapKeys(KeyMapEntry* mapping, int nkeys) { ZeroMemory(map, sizeof(map)); for (int i = 0; i < nkeys; i++) { KeyMapEntry k = mapping[i]; if (k.act >= KEY_MAP_FIRST && k.act < KEY_MAP_LAST) { if (k.act == KEY_JOY_SENSE) sensitivity = k.key; else if (k.act == KEY_JOY_DEAD_ZONE) dead_zone = k.key; else if (k.act == KEY_JOY_SWAP) swapped = k.key; else if (k.act == KEY_JOY_RUDDER) rudder = k.key; else if (k.act == KEY_JOY_THROTTLE) throttle = k.key; else if (k.act == KEY_JOY_SELECT) select = k.key; else if (k.act == KEY_AXIS_YAW) map_axis[0] = k.key; else if (k.act == KEY_AXIS_PITCH) map_axis[1] = k.key; else if (k.act == KEY_AXIS_ROLL) map_axis[2] = k.key; else if (k.act == KEY_AXIS_THROTTLE) map_axis[3] = k.key; else if (k.act == KEY_AXIS_YAW_INVERT) inv_axis[0] = k.key ? true : false; else if (k.act == KEY_AXIS_PITCH_INVERT) inv_axis[1] = k.key ? true : false; else if (k.act == KEY_AXIS_ROLL_INVERT) inv_axis[2] = k.key ? true : false; else if (k.act == KEY_AXIS_THROTTLE_INVERT) inv_axis[3] = k.key ? true : false; else if (k.key >= KEY_JOY_1 && k.key <= KEY_JOY_32) map[k.act] = k.key; else if (k.alt >= KEY_JOY_1 && k.alt <= KEY_JOY_32) map[k.act] = k.alt; else if (k.joy >= KEY_JOY_1 && k.joy <= KEY_JOY_32) map[k.act] = k.joy; else if (k.key >= KEY_POV_0_UP && k.key <= KEY_POV_3_RIGHT) map[k.act] = k.key; else if (k.alt >= KEY_POV_0_UP && k.alt <= KEY_POV_3_RIGHT) map[k.act] = k.alt; else if (k.joy >= KEY_POV_0_UP && k.joy <= KEY_POV_3_RIGHT) map[k.act] = k.joy; } } } // +--------------------------------------------------------------------+ static inline double sqr(double a) { return a*a; } BOOL FAR PASCAL EnumJoystick(LPCDIDEVICEINSTANCE pdinst, LPVOID pvSelect) { CopyMemory(&devices[ndev++], pdinst, pdinst->dwSize); ::Print("EnumJoystick %d: '%s'\n", ndev, pdinst->tszInstanceName); ::Print(" guid: {%08x-%04x-%04x-%02x%02x%02x%02x%02x%02x} \n", (DWORD) pdinst->guidInstance.Data1, (WORD) pdinst->guidInstance.Data2, (WORD) pdinst->guidInstance.Data3, (BYTE) pdinst->guidInstance.Data4[0], (BYTE) pdinst->guidInstance.Data4[1], (BYTE) pdinst->guidInstance.Data4[2], (BYTE) pdinst->guidInstance.Data4[3], (BYTE) pdinst->guidInstance.Data4[4], (BYTE) pdinst->guidInstance.Data4[5]); if (ndev < MAX_DEVICES) return DIENUM_CONTINUE; return DIENUM_STOP; } bool CreateDevice(int select) { if (!pdi || ndev < select) return false; LPCDIDEVICEINSTANCE pdinst = &devices[select-1]; ::Print("Joystick CreateDevice(%d)\n", select); ::Print(" name: %s\n\n", pdinst->tszInstanceName); // release current device before trying to get another: if (pdev) { pdev->Unacquire(); pdev->Release(); pdev = 0; } HRESULT hr = DI_OK; // Create the DirectInput joystick device: hr = pdi->CreateDeviceEx(pdinst->guidInstance, IID_IDirectInputDevice7, (void**)&pdev, NULL); if (hr != DI_OK || pdev == 0) { DirectInputError("Create Device Ex failed", hr); return false; } // Set the data format: hr = pdev->SetDataFormat(&c_dfDIJoystick); if (hr != DI_OK) { DirectInputError("Set Data Format failed", hr); pdev->Release(); pdev = 0; return false; } // Set the coop level: hr = pdev->SetCooperativeLevel(GameWinDX9::GetInstance()->GetHWND(), DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); if (hr != DI_OK) { DirectInputError("Set Cooperative Level failed", hr); pdev->Release(); return false; } // Set data ranges DIPROPRANGE diprg; diprg.lMin = -32768; diprg.lMax = +32768; diprg.diph.dwSize = sizeof(diprg); diprg.diph.dwHeaderSize = sizeof(diprg.diph); diprg.diph.dwObj = DIJOFS_X; diprg.diph.dwHow = DIPH_BYOFFSET; pdev->SetProperty(DIPROP_RANGE, &diprg.diph); diprg.diph.dwObj = DIJOFS_Y; pdev->SetProperty(DIPROP_RANGE, &diprg.diph); diprg.diph.dwObj = DIJOFS_Z; pdev->SetProperty(DIPROP_RANGE, &diprg.diph); diprg.diph.dwObj = DIJOFS_RX; pdev->SetProperty(DIPROP_RANGE, &diprg.diph); diprg.diph.dwObj = DIJOFS_RY; pdev->SetProperty(DIPROP_RANGE, &diprg.diph); diprg.diph.dwObj = DIJOFS_RZ; pdev->SetProperty(DIPROP_RANGE, &diprg.diph); diprg.diph.dwObj = DIJOFS_SLIDER(0); pdev->SetProperty(DIPROP_RANGE, &diprg.diph); diprg.diph.dwObj = DIJOFS_SLIDER(1); pdev->SetProperty(DIPROP_RANGE, &diprg.diph); ::Print("Created joystick %d (pdev = %08x)\n", select, (DWORD) pdev); idev = select; return true; } void Joystick::EnumerateDevices() { if (!pdi) { Print("Joystick: no DI7, unable to enumerate devices\n"); ndev = 0; } else if (ndev < 1) { Print("Joystick: preparing to enumerate devices\n"); ndev = 0; HRESULT hr = pdi->EnumDevices(DI8DEVTYPE_JOYSTICK, EnumJoystick, (LPVOID) 0, DIEDFL_ATTACHEDONLY); if (FAILED(hr)) { DirectInputError("Failed to enumerate devices", hr); ReleaseDirectInput(); } else if (ndev < 1) { Print("Joystick: no devices found\n"); ReleaseDirectInput(); } } } int Joystick::NumDevices() { return ndev; } const char* Joystick::GetDeviceName(int i) { if (i >= 0 && i < ndev) return devices[i].tszInstanceName; return 0; } // +--------------------------------------------------------------------+ static DIJOYSTATE joystate; static JOYINFOEX joyinfo; int Joystick::ReadRawAxis(int a) { if (!joystick) return 0; int result = 0; if (pdev) { switch (a) { case KEY_JOY_AXIS_X: result = joystate.lX; break; case KEY_JOY_AXIS_Y: result = joystate.lY; break; case KEY_JOY_AXIS_Z: result = joystate.lZ; break; case KEY_JOY_AXIS_RX: result = joystate.lRx; break; case KEY_JOY_AXIS_RY: result = joystate.lRy; break; case KEY_JOY_AXIS_RZ: result = joystate.lRz; break; case KEY_JOY_AXIS_S0: result = joystate.rglSlider[0]; break; case KEY_JOY_AXIS_S1: result = joystate.rglSlider[1]; break; } } else { switch (a) { case KEY_JOY_AXIS_X: if (joyinfo.dwFlags & JOY_RETURNX) result = joyinfo.dwXpos; break; case KEY_JOY_AXIS_Y: if (joyinfo.dwFlags & JOY_RETURNY) result = joyinfo.dwYpos; break; case KEY_JOY_AXIS_Z: if (joyinfo.dwFlags & JOY_RETURNZ) result = joyinfo.dwZpos; break; case KEY_JOY_AXIS_RZ: if (joyinfo.dwFlags & JOY_RETURNR) result = joyinfo.dwRpos; break; } } return result; } double Joystick::ReadAxisDI(int a) { if (a < 0 || a > 3) return 0; double result = 0; switch (map_axis[a]) { case KEY_JOY_AXIS_X: result = joystate.lX; break; case KEY_JOY_AXIS_Y: result = joystate.lY; break; case KEY_JOY_AXIS_Z: result = joystate.lZ; break; case KEY_JOY_AXIS_RX: result = joystate.lRx; break; case KEY_JOY_AXIS_RY: result = joystate.lRy; break; case KEY_JOY_AXIS_RZ: result = joystate.lRz; break; case KEY_JOY_AXIS_S0: result = joystate.rglSlider[0]; break; case KEY_JOY_AXIS_S1: result = joystate.rglSlider[1]; break; } if (a < 3) { // ignore small movements: if (result > dead_zone) result -= dead_zone; else if (result < -dead_zone) result += dead_zone; else result = 0; double scale = 1.0 / (32768.0-dead_zone); if (result >= 0) result = sqr(result * scale); else result = sqr(result * scale) * -1.0; if (inv_axis[a]) result = -result; } else { result = (result+32768.0) / 65536.0; if (inv_axis[a]) result = 1 - result; } return result; } double Joystick::ReadAxisMM(int a) { if (a < 0 || a > 3) return 0; double result = 0; switch (map_axis[a]) { case KEY_JOY_AXIS_X: if (joyinfo.dwFlags & JOY_RETURNX) result = static_cast(joyinfo.dwXpos) - 32768.0; break; case KEY_JOY_AXIS_Y: if (joyinfo.dwFlags & JOY_RETURNY) result = static_cast(joyinfo.dwYpos) - 32768.0; break; case KEY_JOY_AXIS_Z: if (joyinfo.dwFlags & JOY_RETURNZ) result = static_cast(joyinfo.dwZpos) - 32768.0; break; case KEY_JOY_AXIS_RZ: if (joyinfo.dwFlags & JOY_RETURNR) result = static_cast(joyinfo.dwRpos) - 32768.0; break; } if (a < 3) { // ignore small movements: if (result > dead_zone) result -= dead_zone; else if (result < -dead_zone) result += dead_zone; else result = 0; double scale = 1.0 / (32768.0-dead_zone); if (result >= 0) result = sqr(result * scale); else result = sqr(result * scale) * -1.0; if (inv_axis[a]) result = -result; } else { result = (result+32768.0) / 65536.0; if (inv_axis[a]) result = 1 - result; } return result; } // +--------------------------------------------------------------------+ void Joystick::Acquire() { t = x = y = z = p = r = w = 0; for (int i = 0; i < MotionController::MaxActions; i++) action[i] = false; for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) hat[i][j] = false; if (select == 0) return; //============================================================ // // FIRST TRY DIRECT INPUT bool acquired = false; if (pdi) { if (idev != select) { if (ndev < 1) EnumerateDevices(); if (CreateDevice(select) && pdev) pdev->Acquire(); } if (pdev) { HRESULT hr = 0; hr = pdev->Poll(); hr = pdev->GetDeviceState(sizeof(joystate), &joystate); if (hr == DIERR_INPUTLOST) { pdev->Acquire(); hr = pdev->Poll(); hr = pdev->GetDeviceState(sizeof(joystate), &joystate); if (FAILED(hr)) { strikes--; ::Print("Joystick could not re-acquire joystick (%08x)\n", hr); // give up before you hurt yourself: if (strikes <= 0) { ReleaseDirectInput(); ndev = 0; select = 0; } return; } } for (int i = 0; i < 32; i++) action[i] = (joystate.rgbButtons[i] & 0x80) != 0; double joy_x = ReadAxisDI(0); double joy_y = ReadAxisDI(1); double joy_r = rudder ? ReadAxisDI(2) : 0; double joy_t = throttle ? ReadAxisDI(3) : 0; int joy_p = joystate.rgdwPOV[0]; ProcessAxes(joy_x, joy_y, joy_r, joy_t); for (int i = 0; i < 4; i++) ProcessHat(i, joystate.rgdwPOV[i]); acquired = true; } } //============================================================ // // THEN TRY WINDOWS MULTIMEDIA LIBRARY if (!acquired) { memset(&joyinfo, 0, sizeof(JOYINFOEX)); joyinfo.dwSize = sizeof(JOYINFOEX); joyinfo.dwFlags = JOY_RETURNALL; HRESULT hr = 0; if (select == 1) hr = joyGetPosEx(JOYSTICKID1, &joyinfo); else if (select == 2) hr = joyGetPosEx(JOYSTICKID2, &joyinfo); if (hr != 0) { Print("\nJoystick::Acquire() joyGetPosEx %d failed (err=%08x)\n\n", select, hr); select = 0; } action[0] = (joyinfo.dwButtons & JOY_BUTTON1) ? true : false; action[1] = (joyinfo.dwButtons & JOY_BUTTON2) ? true : false; action[2] = (joyinfo.dwButtons & JOY_BUTTON3) ? true : false; action[3] = (joyinfo.dwButtons & JOY_BUTTON4) ? true : false; double joy_x = ReadAxisMM(0); double joy_y = ReadAxisMM(1); double joy_r = rudder ? ReadAxisMM(2) : 0; double joy_t = throttle ? ReadAxisMM(3) : 0; ProcessAxes(joy_x, joy_y, joy_r, joy_t); ProcessHat(0, joyinfo.dwPOV); } // lateral translations: if (KeyDownMap(KEY_PLUS_Y)) y = 1; else if (KeyDownMap(KEY_MINUS_Y)) y = -1; if (KeyDownMap(KEY_PLUS_Z)) z = 1; else if (KeyDownMap(KEY_MINUS_Z)) z = -1; if (KeyDownMap(KEY_MINUS_X)) x = -1; else if (KeyDownMap(KEY_PLUS_X)) x = 1; // button-based turns: const double steps=10; static double p1=0, r1=0, w1=0; // if roll and yaw are swapped -------------------------- if (swapped) { // yaw: if (KeyDownMap(KEY_ROLL_LEFT)) { if (w1 3) return; if (LOWORD(joy_pov) == 0xFFFF) return; switch (joy_pov) { case JOY_POVFORWARD: hat[i][0] = true; break; case JOY_POVBACKWARD: hat[i][1] = true; break; case JOY_POVLEFT: hat[i][2] = true; break; case JOY_POVRIGHT: hat[i][3] = true; break; case JOY_POVUPRIGHT: hat[i][0] = true; hat[i][3] = true; break; case JOY_POVDNRIGHT: hat[i][1] = true; hat[i][3] = true; break; case JOY_POVDNLEFT: hat[i][1] = true; hat[i][2] = true; break; case JOY_POVUPLEFT: hat[i][0] = true; hat[i][2] = true; break; default: break; } } // +--------------------------------------------------------------------+ bool Joystick::KeyDown(int key) { if (!joystick) return false; if (key >= KEY_JOY_1 && key <= KEY_JOY_32) return joystick->action[key - KEY_JOY_1]; else if (key >= KEY_POV_0_UP && key <= KEY_POV_0_RIGHT) return joystick->hat[0][key - KEY_POV_0_UP]; else if (key >= KEY_POV_1_UP && key <= KEY_POV_1_RIGHT) return joystick->hat[1][key - KEY_POV_1_UP]; else if (key >= KEY_POV_2_UP && key <= KEY_POV_2_RIGHT) return joystick->hat[2][key - KEY_POV_2_UP]; else if (key >= KEY_POV_3_UP && key <= KEY_POV_3_RIGHT) return joystick->hat[3][key - KEY_POV_3_UP]; return false; } // +--------------------------------------------------------------------+ bool Joystick::KeyDownMap(int key) { if (!joystick) return false; if (key >= KEY_MAP_FIRST && key <= KEY_MAP_LAST && joystick->map[key]) return KeyDown(joystick->map[key]); return false; } // +--------------------------------------------------------------------+ int Joystick::GetAxisMap(int n) { if (!joystick || n < 0 || n > 3) return -1; return joystick->map_axis[n]; } int Joystick::GetAxisInv(int n) { if (!joystick || n < 0 || n > 3) return -1; return joystick->inv_axis[n]; } // +--------------------------------------------------------------------+ void DirectInputError(const char* msg, HRESULT err) { static int report = 50; if (report > 0) report--; else return; Print(" DirectInput7: %s. [%s]\n", msg, DIErrStr(err)); } static char errstrbuf[128]; const char* DIErrStr(HRESULT hr) { auto casted = static_cast(hr); switch (casted) { default: sprintf_s(errstrbuf, "Unrecognized error value = %08x.", casted); return errstrbuf; case DI_OK: return "No error."; case DI_BUFFEROVERFLOW: return "The device buffer overflowed and some input was lost. This value is equal to the S_FALSE standard COM return value."; case DI_DOWNLOADSKIPPED: return "The parameters of the effect were successfully updated, but the effect could not be downloaded because the associated device was not acquired in exclusive mode."; case DI_EFFECTRESTARTED: return "The effect was stopped, the parameters were updated, and the effect was restarted."; case DI_POLLEDDEVICE: return "The device is a polled device. As a result, device buffering does not collect any data and event notifications is not signaled until the IDirectInputDevice7::Poll method is called."; case DI_TRUNCATED: return "The parameters of the effect were successfully updated, but some of them were beyond the capabilities of the device and were truncated to the nearest supported value."; case DI_TRUNCATEDANDRESTARTED: return "Equal to DI_EFFECTRESTARTED | DI_TRUNCATED"; case DIERR_ACQUIRED: return "The operation cannot be performed while the device is acquired."; case DIERR_ALREADYINITIALIZED: return "This object is already initialized"; case DIERR_BADDRIVERVER: return "The object could not be created due to an incompatible driver version or mismatched or incomplete driver components."; case DIERR_BETADIRECTINPUTVERSION: return "The application was written for an unsupported prerelease version of DirectInput."; case DIERR_DEVICEFULL: return "The device is full."; case DIERR_DEVICENOTREG: return "The device or device instance is not registered with DirectInput. This value is equal to the REGDB_E_CLASSNOTREG standard COM return value."; case DIERR_EFFECTPLAYING: return "The parameters were updated in memory but were not downloaded to the device because the device does not support updating an effect while it is still playing."; case DIERR_HASEFFECTS: return "The device cannot be reinitialized because there are still effects attached to it."; case DIERR_GENERIC: return "An undetermined error occurred inside the DirectInput subsystem. This value is equal to the E_FAIL standard COM return value."; case DIERR_HANDLEEXISTS: return "The device already has an event notification associated with it. This value is equal to the E_ACCESSDENIED standard COM return value."; case DIERR_INCOMPLETEEFFECT: return "The effect could not be downloaded because essential information is missing. For example, no axes have been associated with the effect, or no type-specific information has been supplied."; case DIERR_INPUTLOST: return "Access to the input device has been lost. It must be reacquired."; case DIERR_INVALIDPARAM: return "An invalid parameter was passed to the returning function, or the object was not in a state that permitted the function to be called. This value is equal to the E_INVALIDARG standard COM return value."; case DIERR_MOREDATA: return "Not all the requested information fit into the buffer."; case DIERR_NOAGGREGATION: return "This object does not support aggregation."; case DIERR_NOINTERFACE: return "The specified interface is not supported by the object. This value is equal to the E_NOINTERFACE standard COM return value."; case DIERR_NOTACQUIRED: return "The operation cannot be performed unless the device is acquired."; case DIERR_NOTBUFFERED: return "The device is not buffered. Set the DIPROP_BUFFERSIZE property to enable buffering."; case DIERR_NOTDOWNLOADED: return "The effect is not downloaded."; case DIERR_NOTEXCLUSIVEACQUIRED: return "The operation cannot be performed unless the device is acquired in DISCL_EXCLUSIVE mode."; case DIERR_NOTFOUND: return "The requested object does not exist."; case DIERR_NOTINITIALIZED: return "This object has not been initialized."; case DIERR_OLDDIRECTINPUTVERSION: return "The application requires a newer version of DirectInput."; case DIERR_OUTOFMEMORY: return "The DirectInput subsystem could not allocate sufficient memory to complete the call. This value is equal to the E_OUTOFMEMORY standard COM return value."; case DIERR_REPORTFULL: return "More information was requested to be sent than can be sent to the device."; case DIERR_UNPLUGGED: return "The operation could not be completed because the device is not plugged in."; case DIERR_UNSUPPORTED: return "The function called is not supported at this time. This value is equal to the E_NOTIMPL standard COM return value."; } }