From 51759611e1a225584cef7e2bf28d7ff6f47aab47 Mon Sep 17 00:00:00 2001 From: thecozies <79979276+thecozies@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:15:45 -0500 Subject: [PATCH] control rumble intensity to mimic n64 rumble pak & separate rumble from input --- include/recomp_input.h | 1 + patches/input_latency.c | 98 +++++++++++++++++++++++++++++++++++++++-- patches/patches.h | 6 +++ patches/syms.ld | 5 +++ src/game/input.cpp | 30 ++++++++++++- src/game/recomp_api.cpp | 4 ++ 6 files changed, 139 insertions(+), 5 deletions(-) diff --git a/include/recomp_input.h b/include/recomp_input.h index ad9aced..1d4c7b0 100644 --- a/include/recomp_input.h +++ b/include/recomp_input.h @@ -118,6 +118,7 @@ namespace recomp { void get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out); void set_rumble(bool); + void update_rumble(); void handle_events(); // Rumble strength ranges from 0 to 100. diff --git a/patches/input_latency.c b/patches/input_latency.c index bec156c..a93cb7c 100644 --- a/patches/input_latency.c +++ b/patches/input_latency.c @@ -13,16 +13,108 @@ void recomp_set_current_frame_poll_id(); void PadMgr_HandleRetrace(void); void PadMgr_LockPadData(void); void PadMgr_UnlockPadData(void); +void PadMgr_UpdateRumble(void); +void PadMgr_UpdateConnections(void); +void PadMgr_UpdateInputs(void); +void PadMgr_InitVoice(void); +OSMesgQueue* PadMgr_AcquireSerialEventQueue(void); +void PadMgr_ReleaseSerialEventQueue(OSMesgQueue* serialEventQueue); -void PadMgr_ThreadEntry() { - // @recomp Controller polling was moved to the main thread, so there's nothing to do here. + +extern PadMgr* sPadMgrInstance; +extern s32 sPadMgrRetraceCount; +extern FaultMgr gFaultMgr; +extern s32 sVoiceInitStatus; + + +typedef enum { + /* 0 */ VOICE_INIT_FAILED, // voice initialization failed + /* 1 */ VOICE_INIT_TRY, // try to initialize voice + /* 2 */ VOICE_INIT_SUCCESS // voice initialized +} VoiceInitStatus; + +void recomp_update_rumble(); + +void PadMgr_HandleRetrace(void) { + // Execute rumble callback + if (sPadMgrInstance->rumbleRetraceCallback != NULL) { + sPadMgrInstance->rumbleRetraceCallback(sPadMgrInstance->rumbleRetraceArg); + } + + // Try and initialize a Voice Recognition Unit if not already attempted + if (sVoiceInitStatus != VOICE_INIT_FAILED) { + PadMgr_InitVoice(); + } + + // Rumble Pak + if (gFaultMgr.msgId != 0) { + // If fault is active, no rumble + PadMgr_RumbleStop(); + } else if (sPadMgrInstance->rumbleOffTimer > 0) { + // If the rumble off timer is active, no rumble + --sPadMgrInstance->rumbleOffTimer; + PadMgr_RumbleStop(); + } else if (sPadMgrInstance->rumbleOnTimer == 0) { + // If the rumble on timer is inactive, no rumble + PadMgr_RumbleStop(); + } else if (!sPadMgrInstance->isResetting) { + // If not resetting, update rumble + PadMgr_UpdateRumble(); + --sPadMgrInstance->rumbleOnTimer; + } + + recomp_update_rumble(); +} + +void poll_inputs(void) { + OSMesgQueue* serialEventQueue = PadMgr_AcquireSerialEventQueue(); + // Begin reading controller data + osContStartReadData(serialEventQueue); + + // Wait for controller data + osRecvMesg(serialEventQueue, NULL, OS_MESG_BLOCK); + osContGetReadData(sPadMgrInstance->pads); + + // Clear all but controller 1 + bzero(&sPadMgrInstance->pads[1], sizeof(*sPadMgrInstance->pads) * (MAXCONTROLLERS - 1)); + + // If in PreNMI, clear all controllers + if (sPadMgrInstance->isResetting) { + bzero(sPadMgrInstance->pads, sizeof(sPadMgrInstance->pads)); + } + + // Query controller statuses + osContStartQuery(serialEventQueue); + osRecvMesg(serialEventQueue, NULL, OS_MESG_BLOCK); + osContGetQuery(sPadMgrInstance->padStatus); + + // Lock serial message queue + PadMgr_ReleaseSerialEventQueue(serialEventQueue); + + // Update connections + PadMgr_UpdateConnections(); + + // Lock input data + PadMgr_LockPadData(); + + // Update input data + PadMgr_UpdateInputs(); + + // Execute input callback + if (sPadMgrInstance->inputRetraceCallback != NULL) { + sPadMgrInstance->inputRetraceCallback(sPadMgrInstance->inputRetraceArg); + } + + // Unlock input data + PadMgr_UnlockPadData(); + sPadMgrRetraceCount++; } // @recomp Patched to do the actual input polling. void PadMgr_GetInput(Input* inputs, s32 gameRequest) { // @recomp Do an actual poll if gameRequest is true. if (gameRequest) { - PadMgr_HandleRetrace(); + poll_inputs(); // @recomp Tag the current frame's input polling id for latency tracking. recomp_set_current_frame_poll_id(); } diff --git a/patches/patches.h b/patches/patches.h index e1934b3..d84d554 100644 --- a/patches/patches.h +++ b/patches/patches.h @@ -9,6 +9,12 @@ #define osFlashWriteArray osFlashWriteArray_recomp #define osFlashWriteBuffer osFlashWriteBuffer_recomp #define osWritebackDCache osWritebackDCache_recomp + +#define osContStartReadData osContStartReadData_recomp +#define osContGetReadData osContGetReadData_recomp +#define osContStartQuery osContStartQuery_recomp +#define osContGetQuery osContGetQuery_recomp + #define sinf __sinf_recomp #define cosf __cosf_recomp #define bzero bzero_recomp diff --git a/patches/syms.ld b/patches/syms.ld index 5a629fd..7252a30 100644 --- a/patches/syms.ld +++ b/patches/syms.ld @@ -42,3 +42,8 @@ osFlashWriteArray_recomp = 0x8F000060; osFlashWriteBuffer_recomp = 0x8F000064; osWritebackDCache_recomp = 0x8F000068; recomp_get_pending_set_time = 0x8F00006C; +osContStartReadData_recomp = 0x8F000070; +osContGetReadData_recomp = 0x8F000074; +osContStartQuery_recomp = 0x8F000078; +osContGetQuery_recomp = 0x8F00007C; +recomp_update_rumble = 0x8F000080; diff --git a/src/game/input.cpp b/src/game/input.cpp index f1dd6e0..a11e8da 100644 --- a/src/game/input.cpp +++ b/src/game/input.cpp @@ -31,6 +31,8 @@ static struct { std::array rotation_delta{}; std::mutex pending_rotation_mutex; std::array pending_rotation_delta{}; + float cur_rumble; + bool rumble_active; } InputState; std::atomic scanning_device = recomp::InputDevice::COUNT; @@ -370,10 +372,34 @@ void recomp::poll_inputs() { } void recomp::set_rumble(bool on) { - uint16_t rumble_strength = recomp::get_rumble_strength() * 0xFFFF / 100; + InputState.rumble_active = on; +} + +static float lerp(float from, float to, float amount) { + return (from + (to - from) * amount); +} +static float smoothstep(float from, float to, float amount) { + amount = (amount * amount) * (3.0f - 2.0f * amount); + return lerp(from, to, amount); +} + +// Update rumble to attempt to mimic the way n64 rumble ramps up and falls off +void recomp::update_rumble() { + // Note: values are not accurate! just approximations based on feel + if (InputState.rumble_active) { + InputState.cur_rumble += 0.17f; + if (InputState.cur_rumble > 1) InputState.cur_rumble = 1; + } else { + InputState.cur_rumble *= 0.92f; + InputState.cur_rumble -= 0.01f; + if (InputState.cur_rumble < 0) InputState.cur_rumble = 0; + } + float smooth_rumble = smoothstep(0, 1, InputState.cur_rumble); + + uint16_t rumble_strength = smooth_rumble * (recomp::get_rumble_strength() * 0xFFFF / 100); uint32_t duration = 1000000; // Dummy duration value that lasts long enough to matter as the game will reset rumble on its own. for (const auto& controller : InputState.cur_controllers) { - SDL_GameControllerRumble(controller, 0, on ? rumble_strength : 0, duration); + SDL_GameControllerRumble(controller, 0, rumble_strength, duration); } } diff --git a/src/game/recomp_api.cpp b/src/game/recomp_api.cpp index f5c7f7c..30ec251 100644 --- a/src/game/recomp_api.cpp +++ b/src/game/recomp_api.cpp @@ -81,3 +81,7 @@ extern "C" void recomp_get_low_health_beeps_enabled(uint8_t* rdram, recomp_conte extern "C" void recomp_time_us(uint8_t* rdram, recomp_context* ctx) { _return(ctx, static_cast(std::chrono::duration_cast(ultramodern::time_since_start()).count())); } + +extern "C" void recomp_update_rumble(uint8_t* rdram, recomp_context* ctx) { + recomp::update_rumble(); +}