diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a457968..a3bdb8e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -555,6 +555,7 @@ if (ISLE_BUILD_APP) ISLE/emscripten/config.cpp ISLE/emscripten/events.cpp ISLE/emscripten/filesystem.cpp + ISLE/emscripten/haptic.cpp ISLE/emscripten/messagebox.cpp ISLE/emscripten/window.cpp ) diff --git a/ISLE/emscripten/haptic.cpp b/ISLE/emscripten/haptic.cpp new file mode 100644 index 00000000..364701d2 --- /dev/null +++ b/ISLE/emscripten/haptic.cpp @@ -0,0 +1,52 @@ +#include "haptic.h" + +#include "compat.h" +#include "lego/sources/misc/legoutil.h" +#include "legoinputmanager.h" +#include "misc.h" + +#include + +void Emscripten_HandleRumbleEvent(float p_lowFrequencyRumble, float p_highFrequencyRumble, MxU32 p_milliseconds) +{ + std::visit( + overloaded{ + [](LegoInputManager::SDL_KeyboardID_v p_id) {}, + [](LegoInputManager::SDL_MouseID_v p_id) {}, + [p_lowFrequencyRumble, p_highFrequencyRumble, p_milliseconds](LegoInputManager::SDL_JoystickID_v p_id) { + const char* name = SDL_GetJoystickNameForID((SDL_JoystickID) p_id); + if (name) { + MAIN_THREAD_EM_ASM( + { + const name = UTF8ToString($0); + const gamepads = navigator.getGamepads(); + for (const gamepad of gamepads) { + if (gamepad && gamepad.connected && gamepad.id == name && gamepad.vibrationActuator) { + gamepad.vibrationActuator.playEffect("dual-rumble", { + startDelay : 0, + weakMagnitude : $1, + strongMagnitude : $2, + duration : $3, + }); + break; + } + } + }, + name, + SDL_clamp(p_lowFrequencyRumble, 0, 1), + SDL_clamp(p_highFrequencyRumble, 0, 1), + p_milliseconds + ); + } + }, + [](LegoInputManager::SDL_TouchID_v p_id) { + MAIN_THREAD_EM_ASM({ + if (navigator.vibrate) { + navigator.vibrate(700); + } + }); + } + }, + InputManager()->GetLastInputMethod() + ); +} diff --git a/ISLE/emscripten/haptic.h b/ISLE/emscripten/haptic.h new file mode 100644 index 00000000..51605128 --- /dev/null +++ b/ISLE/emscripten/haptic.h @@ -0,0 +1,8 @@ +#ifndef EMSCRIPTEN_HAPTIC_H +#define EMSCRIPTEN_HAPTIC_H + +#include "mxtypes.h" + +void Emscripten_HandleRumbleEvent(float p_lowFrequencyRumble, float p_highFrequencyRumble, MxU32 p_milliseconds); + +#endif // EMSCRIPTEN_HAPTIC_H diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 5a071664..a3ddb0d2 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -54,6 +54,7 @@ #include "emscripten/config.h" #include "emscripten/events.h" #include "emscripten/filesystem.h" +#include "emscripten/haptic.h" #include "emscripten/messagebox.h" #include "emscripten/window.h" #endif @@ -501,6 +502,16 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; } + case SDL_EVENT_KEYBOARD_ADDED: + if (InputManager()) { + InputManager()->AddKeyboard(event->kdevice.which); + } + break; + case SDL_EVENT_KEYBOARD_REMOVED: + if (InputManager()) { + InputManager()->RemoveKeyboard(event->kdevice.which); + } + break; case SDL_EVENT_MOUSE_ADDED: if (InputManager()) { InputManager()->AddMouse(event->mdevice.which); @@ -789,8 +800,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } } else if (event->user.type == g_legoSdlEvents.m_hitActor && g_isle->GetHaptic()) { - if (InputManager()) { - InputManager()->HandleRumbleEvent(); + if (!InputManager()->HandleRumbleEvent(0.5f, 0.5f, 0.5f, 700)) { +// Platform-specific handling +#ifdef __EMSCRIPTEN__ + Emscripten_HandleRumbleEvent(0.5f, 0.5f, 700); +#endif } } diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index bbe0a2b5..04ac7294 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -154,24 +154,29 @@ public: void GetKeyboardState(); MxResult GetNavigationKeyStates(MxU32& p_keyFlags); MxResult GetNavigationTouchStates(MxU32& p_keyFlags); + LEGO1_EXPORT void AddKeyboard(SDL_KeyboardID p_keyboardID); + LEGO1_EXPORT void RemoveKeyboard(SDL_KeyboardID p_keyboardID); LEGO1_EXPORT void AddMouse(SDL_MouseID p_mouseID); LEGO1_EXPORT void RemoveMouse(SDL_MouseID p_mouseID); LEGO1_EXPORT void AddJoystick(SDL_JoystickID p_joystickID); LEGO1_EXPORT void RemoveJoystick(SDL_JoystickID p_joystickID); LEGO1_EXPORT MxBool HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme); - LEGO1_EXPORT MxBool HandleRumbleEvent(); + LEGO1_EXPORT MxBool + HandleRumbleEvent(float p_strength, float p_lowFrequencyRumble, float p_highFrequencyRumble, MxU32 p_milliseconds); LEGO1_EXPORT void UpdateLastInputMethod(SDL_Event* p_event); + const auto& GetLastInputMethod() { return m_lastInputMethod; } - // SYNTHETIC: LEGO1 0x1005b8d0 - // LegoInputManager::`scalar deleting destructor' - -private: // clang-format off + enum class SDL_KeyboardID_v : SDL_KeyboardID {}; enum class SDL_MouseID_v : SDL_MouseID {}; enum class SDL_JoystickID_v : SDL_JoystickID {}; enum class SDL_TouchID_v : SDL_TouchID {}; // clang-format on + // SYNTHETIC: LEGO1 0x1005b8d0 + // LegoInputManager::`scalar deleting destructor' + +private: void InitializeHaptics(); MxCriticalSection m_criticalSection; // 0x58 @@ -197,10 +202,11 @@ private: SDL_Point m_touchVirtualThumb = {0, 0}; SDL_FPoint m_touchVirtualThumbOrigin; std::map m_touchFlags; + std::map> m_keyboards; std::map> m_mice; std::map> m_joysticks; std::map m_otherHaptics; - std::variant m_lastInputMethod; + std::variant m_lastInputMethod; }; // TEMPLATE: LEGO1 0x10028850 diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index 1bb0295d..f7fef27e 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -160,7 +160,8 @@ MxResult LegoInputManager::GetNavigationKeyStates(MxU32& p_keyFlags) // FUNCTION: LEGO1 0x1005c320 MxResult LegoInputManager::GetJoystickState(MxU32* p_joystickX, MxU32* p_joystickY, MxU32* p_povPosition) { - if (m_joysticks.empty() && m_touchScheme != e_gamepad) { + if (!std::holds_alternative(m_lastInputMethod) && + !(std::holds_alternative(m_lastInputMethod) && m_touchScheme == e_gamepad)) { return FAILURE; } @@ -513,6 +514,24 @@ MxResult LegoInputManager::GetNavigationTouchStates(MxU32& p_keyStates) return SUCCESS; } +void LegoInputManager::AddKeyboard(SDL_KeyboardID p_keyboardID) +{ + if (m_keyboards.count(p_keyboardID)) { + return; + } + + m_keyboards[p_keyboardID] = {nullptr, nullptr}; +} + +void LegoInputManager::RemoveKeyboard(SDL_KeyboardID p_keyboardID) +{ + if (!m_keyboards.count(p_keyboardID)) { + return; + } + + m_keyboards.erase(p_keyboardID); +} + void LegoInputManager::AddMouse(SDL_MouseID p_mouseID) { if (m_mice.count(p_mouseID)) { @@ -656,7 +675,12 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc return TRUE; } -MxBool LegoInputManager::HandleRumbleEvent() +MxBool LegoInputManager::HandleRumbleEvent( + float p_strength, + float p_lowFrequencyRumble, + float p_highFrequencyRumble, + MxU32 p_milliseconds +) { static bool g_hapticsInitialized = false; @@ -668,6 +692,7 @@ MxBool LegoInputManager::HandleRumbleEvent() SDL_Haptic* haptic = nullptr; std::visit( overloaded{ + [](SDL_KeyboardID_v p_id) {}, [&haptic, this](SDL_MouseID_v p_id) { if (m_mice.count((SDL_MouseID) p_id)) { haptic = m_mice[(SDL_MouseID) p_id].second; @@ -688,11 +713,8 @@ MxBool LegoInputManager::HandleRumbleEvent() m_lastInputMethod ); - const float strength = 0.5f; - const Uint32 durationMs = 700; - if (haptic) { - return SDL_PlayHapticRumble(haptic, strength, durationMs); + return SDL_PlayHapticRumble(haptic, p_strength, p_milliseconds); } // A joystick isn't necessarily a haptic device; try basic rumble instead @@ -700,9 +722,9 @@ MxBool LegoInputManager::HandleRumbleEvent() if (m_joysticks.count((SDL_JoystickID) *joystick)) { return SDL_RumbleGamepad( m_joysticks[(SDL_JoystickID) *joystick].first, - strength * 65535, - strength * 65535, - durationMs + SDL_clamp(p_lowFrequencyRumble, 0, 1) * USHRT_MAX, + SDL_clamp(p_highFrequencyRumble, 0, 1) * USHRT_MAX, + p_milliseconds ); } } @@ -752,6 +774,10 @@ void LegoInputManager::InitializeHaptics() void LegoInputManager::UpdateLastInputMethod(SDL_Event* p_event) { switch (p_event->type) { + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + m_lastInputMethod = SDL_KeyboardID_v{p_event->key.which}; + break; case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: m_lastInputMethod = SDL_MouseID_v{p_event->button.which}; diff --git a/tools/ncc/skip.yml b/tools/ncc/skip.yml index 2bae0ab4..322cded0 100644 --- a/tools/ncc/skip.yml +++ b/tools/ncc/skip.yml @@ -74,6 +74,7 @@ cksize: "Re-defined Windows name" fccType: "Re-defined Windows name" dwDataOffset: "Re-defined Windows name" fccType: "Re-defined Windows name" +SDL_KeyboardID_v: "SDL-based name" SDL_MouseID_v: "SDL-based name" SDL_JoystickID_v: "SDL-based name" SDL_TouchID_v: "SDL-based name" \ No newline at end of file