control rumble intensity to mimic n64 rumble pak & separate rumble from input

This commit is contained in:
thecozies 2024-04-15 10:15:45 -05:00 committed by Mr-Wiseguy
parent fa8092b70e
commit 51759611e1
6 changed files with 139 additions and 5 deletions

View File

@ -118,6 +118,7 @@ namespace recomp {
void get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out); void get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out);
void set_rumble(bool); void set_rumble(bool);
void update_rumble();
void handle_events(); void handle_events();
// Rumble strength ranges from 0 to 100. // Rumble strength ranges from 0 to 100.

View File

@ -13,16 +13,108 @@ void recomp_set_current_frame_poll_id();
void PadMgr_HandleRetrace(void); void PadMgr_HandleRetrace(void);
void PadMgr_LockPadData(void); void PadMgr_LockPadData(void);
void PadMgr_UnlockPadData(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. // @recomp Patched to do the actual input polling.
void PadMgr_GetInput(Input* inputs, s32 gameRequest) { void PadMgr_GetInput(Input* inputs, s32 gameRequest) {
// @recomp Do an actual poll if gameRequest is true. // @recomp Do an actual poll if gameRequest is true.
if (gameRequest) { if (gameRequest) {
PadMgr_HandleRetrace(); poll_inputs();
// @recomp Tag the current frame's input polling id for latency tracking. // @recomp Tag the current frame's input polling id for latency tracking.
recomp_set_current_frame_poll_id(); recomp_set_current_frame_poll_id();
} }

View File

@ -9,6 +9,12 @@
#define osFlashWriteArray osFlashWriteArray_recomp #define osFlashWriteArray osFlashWriteArray_recomp
#define osFlashWriteBuffer osFlashWriteBuffer_recomp #define osFlashWriteBuffer osFlashWriteBuffer_recomp
#define osWritebackDCache osWritebackDCache_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 sinf __sinf_recomp
#define cosf __cosf_recomp #define cosf __cosf_recomp
#define bzero bzero_recomp #define bzero bzero_recomp

View File

@ -42,3 +42,8 @@ osFlashWriteArray_recomp = 0x8F000060;
osFlashWriteBuffer_recomp = 0x8F000064; osFlashWriteBuffer_recomp = 0x8F000064;
osWritebackDCache_recomp = 0x8F000068; osWritebackDCache_recomp = 0x8F000068;
recomp_get_pending_set_time = 0x8F00006C; recomp_get_pending_set_time = 0x8F00006C;
osContStartReadData_recomp = 0x8F000070;
osContGetReadData_recomp = 0x8F000074;
osContStartQuery_recomp = 0x8F000078;
osContGetQuery_recomp = 0x8F00007C;
recomp_update_rumble = 0x8F000080;

View File

@ -31,6 +31,8 @@ static struct {
std::array<float, 2> rotation_delta{}; std::array<float, 2> rotation_delta{};
std::mutex pending_rotation_mutex; std::mutex pending_rotation_mutex;
std::array<float, 2> pending_rotation_delta{}; std::array<float, 2> pending_rotation_delta{};
float cur_rumble;
bool rumble_active;
} InputState; } InputState;
std::atomic<recomp::InputDevice> scanning_device = recomp::InputDevice::COUNT; std::atomic<recomp::InputDevice> scanning_device = recomp::InputDevice::COUNT;
@ -370,10 +372,34 @@ void recomp::poll_inputs() {
} }
void recomp::set_rumble(bool on) { 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. 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) { for (const auto& controller : InputState.cur_controllers) {
SDL_GameControllerRumble(controller, 0, on ? rumble_strength : 0, duration); SDL_GameControllerRumble(controller, 0, rumble_strength, duration);
} }
} }

View File

@ -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) { extern "C" void recomp_time_us(uint8_t* rdram, recomp_context* ctx) {
_return(ctx, static_cast<u32>(std::chrono::duration_cast<std::chrono::microseconds>(ultramodern::time_since_start()).count())); _return(ctx, static_cast<u32>(std::chrono::duration_cast<std::chrono::microseconds>(ultramodern::time_since_start()).count()));
} }
extern "C" void recomp_update_rumble(uint8_t* rdram, recomp_context* ctx) {
recomp::update_rumble();
}