Background Input
On
Off
@@ -119,6 +138,12 @@
Note: To recalibrate controller gyro, set the controller down on a still, flat surface for 5 seconds.
+ Controls the sensitivity of mouse aiming when using items in first person for controllers that support it. Setting this to zero will disable mouse aiming.
+
+
+ Note: This option does not allow mouse buttons to activate items. Mouse aiming is meant for using inputs that are mapped to mouse movement, such as gyro on Steam Deck.
+
+
Allows the game to read controller input when out of focus.
This setting does not affect keyboard input.
diff --git a/include/recomp_input.h b/include/recomp_input.h
index 6315f48..dff7ba6 100644
--- a/include/recomp_input.h
+++ b/include/recomp_input.h
@@ -66,6 +66,7 @@ namespace recomp {
bool get_input_digital(const InputField& field);
bool get_input_digital(const std::span fields);
void get_gyro_deltas(float* x, float* y);
+ void get_mouse_deltas(float* x, float* y);
enum class InputDevice {
Controller,
@@ -125,9 +126,11 @@ namespace recomp {
int get_rumble_strength();
void set_rumble_strength(int strength);
- // Gyro sensitivity ranges from 0 to 100 (gets doubled).
+ // Gyro and mouse sensitivities range from 0 to 100.
int get_gyro_sensitivity();
+ int get_mouse_sensitivity();
void set_gyro_sensitivity(int strength);
+ void set_mouse_sensitivity(int strength);
enum class TargetingMode {
Switch,
diff --git a/patches/input.c b/patches/input.c
index 2710340..e76621e 100644
--- a/patches/input.c
+++ b/patches/input.c
@@ -9,7 +9,7 @@ s32 func_80847190(PlayState* play, Player* this, s32 arg2);
s16 func_80832754(Player* this, s32 arg1);
s32 func_8082EF20(Player* this);
-// Patched to add gyro aiming
+// @recomp Patched to add gyro and mouse aiming.
s32 func_80847190(PlayState* play, Player* this, s32 arg2) {
s32 pad;
s16 var_s0;
@@ -24,18 +24,19 @@ s32 func_80847190(PlayState* play, Player* this, s32 arg2) {
}
else {
static float total_gyro_x, total_gyro_y;
+ static float total_mouse_x, total_mouse_y;
static float filtered_gyro_x, filtered_gyro_y;
- static int applied_gyro_x, applied_gyro_y;
+ static int applied_aim_x, applied_aim_y;
- const float filter_factor = 0.00f;
+ const float gyro_filter_factor = 0.00f;
- // TODO remappable gyro reset button
- if (play->state.input[0].press.button & BTN_L) {
- total_gyro_x = 0;
- total_gyro_y = 0;
- filtered_gyro_x = 0;
- filtered_gyro_y = 0;
- }
+ // // TODO remappable gyro reset button
+ // if (play->state.input[0].press.button & BTN_L) {
+ // total_gyro_x = 0;
+ // total_gyro_y = 0;
+ // filtered_gyro_x = 0;
+ // filtered_gyro_y = 0;
+ // }
float delta_gyro_x, delta_gyro_y;
recomp_get_gyro_deltas(&delta_gyro_x, &delta_gyro_y);
@@ -43,18 +44,28 @@ s32 func_80847190(PlayState* play, Player* this, s32 arg2) {
total_gyro_x += delta_gyro_x;
total_gyro_y += delta_gyro_y;
- filtered_gyro_x = filtered_gyro_x * filter_factor + total_gyro_x * (1.0f - filter_factor);
- filtered_gyro_y = filtered_gyro_y * filter_factor + total_gyro_y * (1.0f - filter_factor);
+ filtered_gyro_x = filtered_gyro_x * gyro_filter_factor + total_gyro_x * (1.0f - gyro_filter_factor);
+ filtered_gyro_y = filtered_gyro_y * gyro_filter_factor + total_gyro_y * (1.0f - gyro_filter_factor);
- int target_gyro_x = (int)filtered_gyro_x;
- int target_gyro_y = (int)filtered_gyro_y;
+ float delta_mouse_x, delta_mouse_y;
+ recomp_get_mouse_deltas(&delta_mouse_x, &delta_mouse_y);
+
+ total_mouse_x += delta_mouse_x;
+ total_mouse_y += delta_mouse_y;
+
+ // The gyro X-axis (tilt) corresponds to the camera X-axis (tilt).
+ // The gyro Y-axis (left/right rotation) corresponds to the camera Y-axis (left/right rotation).
+ // The mouse Y-axis (up/down movement) corresponds to the camera X-axis (tilt).
+ // The mouse X-axis (left/right movement) corresponds to the camera Y-axis (left/right rotation).
+ int target_aim_x = (int)(filtered_gyro_x * -3.0f + total_mouse_y * 20.0f);
+ int target_aim_y = (int)(filtered_gyro_y * 3.0f + total_mouse_x * -20.0f);
s16 temp3;
temp3 = ((play->state.input[0].rel.stick_y >= 0) ? 1 : -1) *
(s32)((1.0f - Math_CosS(play->state.input[0].rel.stick_y * 0xC8)) * 1500.0f);
- this->actor.focus.rot.x += temp3 + (s32)((target_gyro_x - applied_gyro_x) * -1.5f);
- applied_gyro_x = target_gyro_x;
+ this->actor.focus.rot.x += temp3 + (s32)(target_aim_x - applied_aim_x);
+ applied_aim_x = target_aim_x;
if (this->stateFlags1 & PLAYER_STATE1_800000) {
this->actor.focus.rot.x = CLAMP(this->actor.focus.rot.x, -0x1F40, 0xFA0);
@@ -66,8 +77,8 @@ s32 func_80847190(PlayState* play, Player* this, s32 arg2) {
var_s0 = this->actor.focus.rot.y - this->actor.shape.rot.y;
temp3 = ((play->state.input[0].rel.stick_x >= 0) ? 1 : -1) *
(s32)((1.0f - Math_CosS(play->state.input[0].rel.stick_x * 0xC8)) * -1500.0f);
- var_s0 += temp3 + (s32)((target_gyro_y - applied_gyro_y) * 1.5f);
- applied_gyro_y = target_gyro_y;
+ var_s0 += temp3 + (s32)(target_aim_y - applied_aim_y);
+ applied_aim_y = target_aim_y;
this->actor.focus.rot.y = CLAMP(var_s0, -0x4AAA, 0x4AAA) + this->actor.shape.rot.y;
}
diff --git a/patches/input.h b/patches/input.h
index b0d5154..d1399b7 100644
--- a/patches/input.h
+++ b/patches/input.h
@@ -11,6 +11,7 @@ typedef enum {
extern RecompCameraMode recomp_camera_mode;
DECLARE_FUNC(void, recomp_get_gyro_deltas, float* x, float* y);
+DECLARE_FUNC(void, recomp_get_mouse_deltas, float* x, float* y);
DECLARE_FUNC(int, recomp_get_targeting_mode);
#endif
diff --git a/patches/syms.ld b/patches/syms.ld
index 5640bd2..4332214 100644
--- a/patches/syms.ld
+++ b/patches/syms.ld
@@ -46,3 +46,4 @@ osContStartReadData_recomp = 0x8F000070;
osContGetReadData_recomp = 0x8F000074;
osContStartQuery_recomp = 0x8F000078;
osContGetQuery_recomp = 0x8F00007C;
+recomp_get_mouse_deltas = 0x8F000080;
diff --git a/src/game/config.cpp b/src/game/config.cpp
index bbbd2de..698a3c2 100644
--- a/src/game/config.cpp
+++ b/src/game/config.cpp
@@ -130,6 +130,7 @@ void save_general_config(const std::filesystem::path& path) {
recomp::to_json(config_json["background_input_mode"], recomp::get_background_input_mode());
config_json["rumble_strength"] = recomp::get_rumble_strength();
config_json["gyro_sensitivity"] = recomp::get_gyro_sensitivity();
+ config_json["mouse_sensitivity"] = recomp::get_mouse_sensitivity();
config_json["debug_mode"] = recomp::get_debug_mode_enabled();
config_file << std::setw(4) << config_json;
}
@@ -144,6 +145,7 @@ void load_general_config(const std::filesystem::path& path) {
recomp::set_background_input_mode(from_or_default(config_json, "background_input_mode", recomp::BackgroundInputMode::On));
recomp::set_rumble_strength(from_or_default(config_json, "rumble_strength", 25));
recomp::set_gyro_sensitivity(from_or_default(config_json, "gyro_sensitivity", 50));
+ recomp::set_mouse_sensitivity(from_or_default(config_json, "mouse_sensitivity", 0));
recomp::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
}
diff --git a/src/game/input.cpp b/src/game/input.cpp
index 73433d2..f1fe816 100644
--- a/src/game/input.cpp
+++ b/src/game/input.cpp
@@ -30,9 +30,13 @@ static struct {
std::mutex cur_controllers_mutex;
std::vector cur_controllers{};
std::unordered_map controller_states;
+
std::array rotation_delta{};
- std::mutex pending_rotation_mutex;
+ std::array mouse_delta{};
+ std::mutex pending_input_mutex;
std::array pending_rotation_delta{};
+ std::array pending_mouse_delta{};
+
float cur_rumble;
bool rumble_active;
} InputState;
@@ -189,12 +193,19 @@ bool sdl_event_filter(void* userdata, SDL_Event* event) {
state.motion.GetPlayerSpaceGyro(rot_x, rot_y);
{
- std::lock_guard lock{ InputState.pending_rotation_mutex };
+ std::lock_guard lock{ InputState.pending_input_mutex };
InputState.pending_rotation_delta[0] += rot_x;
InputState.pending_rotation_delta[1] += rot_y;
}
}
break;
+ case SDL_EventType::SDL_MOUSEMOTION:
+ if (!recomp::game_input_disabled()) {
+ SDL_MouseMotionEvent* motion_event = &event->motion;
+ std::lock_guard lock{ InputState.pending_input_mutex };
+ InputState.pending_mouse_delta[0] += motion_event->xrel;
+ InputState.pending_mouse_delta[1] += motion_event->yrel;
+ }
default:
queue_if_enabled(event);
break;
@@ -207,7 +218,18 @@ void recomp::handle_events() {
static bool exited = false;
while (SDL_PollEvent(&cur_event) && !exited) {
exited = sdl_event_filter(nullptr, &cur_event);
- SDL_ShowCursor(cursor_enabled ? SDL_ENABLE : SDL_DISABLE);
+
+ // Lock the cursor if all three conditions are true: mouse aiming is enabled, game input is not disabled, and the game has been started.
+ bool cursor_locked = (recomp::get_mouse_sensitivity() != 0) && !recomp::game_input_disabled() && ultramodern::is_game_started();
+
+ // Hide the cursor based on its enable state, but override visibility to false if the cursor is locked.
+ bool cursor_visible = cursor_enabled;
+ if (cursor_locked) {
+ cursor_visible = false;
+ }
+
+ SDL_ShowCursor(cursor_visible ? SDL_ENABLE : SDL_DISABLE);
+ SDL_SetRelativeMouseMode(cursor_locked ? SDL_TRUE : SDL_FALSE);
}
}
@@ -352,9 +374,13 @@ void recomp::poll_inputs() {
// Read the deltas while resetting them to zero.
{
- std::lock_guard lock{ InputState.pending_rotation_mutex };
+ std::lock_guard lock{ InputState.pending_input_mutex };
+
InputState.rotation_delta = InputState.pending_rotation_delta;
InputState.pending_rotation_delta = { 0.0f, 0.0f };
+
+ InputState.mouse_delta = InputState.pending_mouse_delta;
+ InputState.pending_mouse_delta = { 0.0f, 0.0f };
}
// Quicksaving is disabled for now and will likely have more limited functionality
@@ -503,11 +529,18 @@ bool recomp::get_input_digital(const std::span fields)
void recomp::get_gyro_deltas(float* x, float* y) {
std::array cur_rotation_delta = InputState.rotation_delta;
- float sensitivity = (float)recomp::get_gyro_sensitivity() / 50.0f;
+ float sensitivity = (float)recomp::get_gyro_sensitivity() / 100.0f;
*x = cur_rotation_delta[0] * sensitivity;
*y = cur_rotation_delta[1] * sensitivity;
}
+void recomp::get_mouse_deltas(float* x, float* y) {
+ std::array cur_mouse_delta = InputState.mouse_delta;
+ float sensitivity = (float)recomp::get_mouse_sensitivity() / 100.0f;
+ *x = cur_mouse_delta[0] * sensitivity;
+ *y = cur_mouse_delta[1] * sensitivity;
+}
+
bool recomp::game_input_disabled() {
// Disable input if any menu is open.
return recomp::get_current_menu() != recomp::Menu::None;
diff --git a/src/game/recomp_api.cpp b/src/game/recomp_api.cpp
index 7d7b8bd..8a82278 100644
--- a/src/game/recomp_api.cpp
+++ b/src/game/recomp_api.cpp
@@ -35,6 +35,13 @@ extern "C" void recomp_get_gyro_deltas(uint8_t* rdram, recomp_context* ctx) {
recomp::get_gyro_deltas(x_out, y_out);
}
+extern "C" void recomp_get_mouse_deltas(uint8_t* rdram, recomp_context* ctx) {
+ float* x_out = _arg<0, float*>(rdram, ctx);
+ float* y_out = _arg<1, float*>(rdram, ctx);
+
+ recomp::get_mouse_deltas(x_out, y_out);
+}
+
extern "C" void recomp_powf(uint8_t* rdram, recomp_context* ctx) {
float a = _arg<0, float>(rdram, ctx);
float b = ctx->f14.fl; //_arg<1, float>(rdram, ctx);
diff --git a/src/ui/ui_config.cpp b/src/ui/ui_config.cpp
index 94e909b..a74aacc 100644
--- a/src/ui/ui_config.cpp
+++ b/src/ui/ui_config.cpp
@@ -264,7 +264,8 @@ void open_quit_game_prompt() {
struct ControlOptionsContext {
int rumble_strength = 50; // 0 to 100
- int gyro_sensitivity = 50; // 0 to 200
+ int gyro_sensitivity = 50; // 0 to 100
+ int mouse_sensitivity = 50; // 0 to 100
recomp::TargetingMode targeting_mode = recomp::TargetingMode::Switch;
recomp::BackgroundInputMode background_input_mode = recomp::BackgroundInputMode::On;
};
@@ -286,6 +287,10 @@ int recomp::get_gyro_sensitivity() {
return control_options_context.gyro_sensitivity;
}
+int recomp::get_mouse_sensitivity() {
+ return control_options_context.mouse_sensitivity;
+}
+
void recomp::set_gyro_sensitivity(int sensitivity) {
control_options_context.gyro_sensitivity = sensitivity;
if (general_model_handle) {
@@ -293,6 +298,13 @@ void recomp::set_gyro_sensitivity(int sensitivity) {
}
}
+void recomp::set_mouse_sensitivity(int sensitivity) {
+ control_options_context.mouse_sensitivity = sensitivity;
+ if (general_model_handle) {
+ general_model_handle.DirtyVariable("mouse_sensitivity");
+ }
+}
+
recomp::TargetingMode recomp::get_targeting_mode() {
return control_options_context.targeting_mode;
}
@@ -787,6 +799,7 @@ public:
constructor.Bind("rumble_strength", &control_options_context.rumble_strength);
constructor.Bind("gyro_sensitivity", &control_options_context.gyro_sensitivity);
+ constructor.Bind("mouse_sensitivity", &control_options_context.mouse_sensitivity);
bind_option(constructor, "targeting_mode", &control_options_context.targeting_mode);
bind_option(constructor, "background_input_mode", &control_options_context.background_input_mode);