From 21ce906a32bed3fef4323b0e066242231c0d0ae6 Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Sun, 13 Jul 2025 20:26:47 -0700 Subject: [PATCH] Add haptic feedback (rumble) (#596) * Add rumble event for hit actor * Add ini option --- ISLE/isleapp.cpp | 8 ++++++++ ISLE/isleapp.h | 2 ++ LEGO1/lego/legoomni/include/legoinputmanager.h | 1 + LEGO1/lego/legoomni/include/legoutils.h | 1 + LEGO1/lego/legoomni/src/common/legoutils.cpp | 7 +++++++ .../lego/legoomni/src/input/legoinputmanager.cpp | 15 +++++++++++++++ LEGO1/lego/legoomni/src/paths/legoextraactor.cpp | 2 ++ LEGO1/omni/include/mxutilities.h | 1 + LEGO1/omni/src/main/mxomni.cpp | 3 ++- 9 files changed, 39 insertions(+), 1 deletion(-) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index a78a9f71..c3035de6 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -173,6 +173,7 @@ IsleApp::IsleApp() m_transitionType = MxTransitionManager::e_mosaic; m_cursorSensitivity = 4; m_touchScheme = LegoInputManager::e_gamepad; + m_haptic = TRUE; } // FUNCTION: ISLE 0x4011a0 @@ -791,6 +792,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) SDL_Log("Game started"); } } + else if (event->user.type == g_legoSdlEvents.m_hitActor && g_isle->GetHaptic()) { + if (InputManager()) { + InputManager()->HandleRumbleEvent(); + } + } return SDL_APP_CONTINUE; } @@ -1042,6 +1048,7 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:Max Allowed Extras", SDL_itoa(m_maxAllowedExtras, buf, 10)); iniparser_set(dict, "isle:Transition Type", SDL_itoa(m_transitionType, buf, 10)); iniparser_set(dict, "isle:Touch Scheme", SDL_itoa(m_touchScheme, buf, 10)); + iniparser_set(dict, "isle:Haptic", m_haptic ? "true" : "false"); #ifdef EXTENSIONS iniparser_set(dict, "extensions", NULL); @@ -1113,6 +1120,7 @@ bool IsleApp::LoadConfig() m_transitionType = (MxTransitionManager::TransitionType) iniparser_getint(dict, "isle:Transition Type", m_transitionType); m_touchScheme = (LegoInputManager::TouchScheme) iniparser_getint(dict, "isle:Touch Scheme", m_touchScheme); + m_haptic = iniparser_getboolean(dict, "isle:Haptic", m_haptic); const char* deviceId = iniparser_getstring(dict, "isle:3D Device ID", NULL); if (deviceId != NULL) { diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 3322427e..180c2252 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -55,6 +55,7 @@ class IsleApp { MxS32 GetGameStarted() { return m_gameStarted; } MxFloat GetCursorSensitivity() { return m_cursorSensitivity; } LegoInputManager::TouchScheme GetTouchScheme() { return m_touchScheme; } + MxBool GetHaptic() { return m_haptic; } void SetWindowActive(MxS32 p_windowActive) { m_windowActive = p_windowActive; } void SetGameStarted(MxS32 p_gameStarted) { m_gameStarted = p_gameStarted; } @@ -105,6 +106,7 @@ class IsleApp { MxU32 m_maxAllowedExtras; MxTransitionManager::TransitionType m_transitionType; LegoInputManager::TouchScheme m_touchScheme; + MxBool m_haptic; }; extern IsleApp* g_isle; diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index 3d17df56..ee87b50a 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -153,6 +153,7 @@ class LegoInputManager : public MxPresenter { MxResult GetNavigationKeyStates(MxU32& p_keyFlags); MxResult GetNavigationTouchStates(MxU32& p_keyFlags); LEGO1_EXPORT MxBool HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme); + LEGO1_EXPORT MxBool HandleRumbleEvent(); // SYNTHETIC: LEGO1 0x1005b8d0 // LegoInputManager::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/include/legoutils.h b/LEGO1/lego/legoomni/include/legoutils.h index 2df6ad4a..b29a336c 100644 --- a/LEGO1/lego/legoomni/include/legoutils.h +++ b/LEGO1/lego/legoomni/include/legoutils.h @@ -71,6 +71,7 @@ LegoNamedTexture* ReadNamedTexture(LegoStorage* p_storage); void WriteDefaultTexture(LegoStorage* p_storage, const char* p_name); void WriteNamedTexture(LegoStorage* p_storage, LegoNamedTexture* p_namedTexture); void LoadFromNamedTexture(LegoNamedTexture* p_namedTexture); +void HitActorEvent(); // FUNCTION: BETA10 0x100260a0 inline void StartIsleAction(IsleScript::Script p_objectId) diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index ff45676b..0a07a7c0 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -782,3 +782,10 @@ void LoadFromNamedTexture(LegoNamedTexture* p_namedTexture) textureInfo->LoadBits(p_namedTexture->GetTexture()->GetImage()->GetBits()); } } + +void HitActorEvent() +{ + SDL_Event event; + event.user.type = g_legoSdlEvents.m_hitActor; + SDL_PushEvent(&event); +} diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index b0eae128..7c80e78e 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -626,3 +626,18 @@ MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touc return TRUE; } + +MxBool LegoInputManager::HandleRumbleEvent() +{ + if (m_joystick != NULL && SDL_GamepadConnected(m_joystick) == TRUE) { + const Uint16 frequency = 65535 / 2; + const Uint32 durationMs = 700; + SDL_RumbleGamepad(m_joystick, frequency, frequency, durationMs); + } + else { + return FALSE; + } + + // Add support for SDL Haptic API + return TRUE; +} diff --git a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp index 0a888b46..7f640326 100644 --- a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp @@ -235,6 +235,7 @@ MxResult LegoExtraActor::HitActor(LegoPathActor* p_actor, MxBool p_bool) assert(m_roi); assert(SoundManager()->GetCacheSoundManager()); SoundManager()->GetCacheSoundManager()->Play("crash5", m_roi->GetName(), FALSE); + HitActorEvent(); m_scheduledTime = Timer()->GetTime() + m_disAnim->GetDuration(); m_prevWorldSpeed = GetWorldSpeed(); VTable0xc4(); @@ -248,6 +249,7 @@ MxResult LegoExtraActor::HitActor(LegoPathActor* p_actor, MxBool p_bool) LegoROI* roi = GetROI(); assert(roi); SoundManager()->GetCacheSoundManager()->Play("crash5", m_roi->GetName(), FALSE); + HitActorEvent(); VTable0xc4(); SetActorState(c_two | c_noCollide); Mx3DPointFloat dir = p_actor->GetWorldDirection(); diff --git a/LEGO1/omni/include/mxutilities.h b/LEGO1/omni/include/mxutilities.h index c5237bb0..33754f3c 100644 --- a/LEGO1/omni/include/mxutilities.h +++ b/LEGO1/omni/include/mxutilities.h @@ -10,6 +10,7 @@ struct LegoSdlEvents { Uint32 m_windowsMessage; Uint32 m_presenterProgress; + Uint32 m_hitActor; }; LEGO1_EXPORT extern LegoSdlEvents g_legoSdlEvents; diff --git a/LEGO1/omni/src/main/mxomni.cpp b/LEGO1/omni/src/main/mxomni.cpp index d7b76847..b6081b16 100644 --- a/LEGO1/omni/src/main/mxomni.cpp +++ b/LEGO1/omni/src/main/mxomni.cpp @@ -163,9 +163,10 @@ MxResult MxOmni::Create(MxOmniCreateParam& p_param) } { - Uint32 event = SDL_RegisterEvents(2); + Uint32 event = SDL_RegisterEvents(3); g_legoSdlEvents.m_windowsMessage = event + 0; g_legoSdlEvents.m_presenterProgress = event + 1; + g_legoSdlEvents.m_hitActor = event + 2; } result = SUCCESS;