Implemented mouse aiming

This commit is contained in:
Mr-Wiseguy 2024-04-26 01:48:26 -04:00
parent 539497f84d
commit b28614b128
9 changed files with 126 additions and 30 deletions

View File

@ -74,31 +74,50 @@
</div>
</div>
<!-- mouse sensitivity -->
<div class="config-option" data-event-mouseover="set_cur_config_index(3)">
<label class="config-option__title">Mouse Sensitivity</label>
<div class="config-option__range-wrapper config-option__list">
<label class="config-option__range-label">{{mouse_sensitivity}}%</label>
<input
class="nav-vert"
data-event-blur="set_cur_config_index(-1)"
data-event-focus="set_cur_config_index(3)"
id="mouse_sensitivity_input"
type="range"
min="0"
max="100"
style="flex: 1; margin: 0dp;"
data-value="mouse_sensitivity"
/>
</div>
</div>
<!-- targeting mode -->
<div class="config-option" data-event-mouseover="set_cur_config_index(3)" id="conf-general__Background-Input">
<div class="config-option" data-event-mouseover="set_cur_config_index(4)" id="conf-general__Background-Input">
<label class="config-option__title">Background Input</label>
<div class="config-option__list">
<input
type="radio"
data-event-blur="set_cur_config_index(-1)"
data-event-focus="set_cur_config_index(3)"
data-event-focus="set_cur_config_index(4)"
name="background_input_mode"
data-checked="background_input_mode"
value="On"
id="bg_input_enabled"
style="nav-up: #gyro_sensitivity_input"
style="nav-up: #mouse_sensitivity_input"
/>
<label class="config-option__tab-label" for="bg_input_enabled">On</label>
<input
type="radio"
data-event-blur="set_cur_config_index(-1)"
data-event-focus="set_cur_config_index(3)"
data-event-focus="set_cur_config_index(4)"
name="background_input_mode"
data-checked="background_input_mode"
value="Off"
id="bg_input_disabled"
style="nav-up: #gyro_sensitivity_input"
style="nav-up: #mouse_sensitivity_input"
/>
<label class="config-option__tab-label" for="bg_input_disabled">Off</label>
</div>
@ -119,6 +138,12 @@
<b>Note: To recalibrate controller gyro, set the controller down on a still, flat surface for 5 seconds.</b>
</p>
<p data-if="cur_config_index == 3">
Controls the sensitivity of mouse aiming when using items in first person for controllers that support it. <b>Setting this to zero will disable mouse aiming.</b>
<br />
<br />
<b>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.</b>
</p>
<p data-if="cur_config_index == 4">
Allows the game to read controller input when out of focus.
<br/>
<b>This setting does not affect keyboard input.</b>

View File

@ -66,6 +66,7 @@ namespace recomp {
bool get_input_digital(const InputField& field);
bool get_input_digital(const std::span<const recomp::InputField> 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,

View File

@ -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;
}

View File

@ -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

View File

@ -46,3 +46,4 @@ osContStartReadData_recomp = 0x8F000070;
osContGetReadData_recomp = 0x8F000074;
osContStartQuery_recomp = 0x8F000078;
osContGetQuery_recomp = 0x8F00007C;
recomp_get_mouse_deltas = 0x8F000080;

View File

@ -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));
}

View File

@ -30,9 +30,13 @@ static struct {
std::mutex cur_controllers_mutex;
std::vector<SDL_GameController*> cur_controllers{};
std::unordered_map<SDL_JoystickID, ControllerState> controller_states;
std::array<float, 2> rotation_delta{};
std::mutex pending_rotation_mutex;
std::array<float, 2> mouse_delta{};
std::mutex pending_input_mutex;
std::array<float, 2> pending_rotation_delta{};
std::array<float, 2> 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<const recomp::InputField> fields)
void recomp::get_gyro_deltas(float* x, float* y) {
std::array<float, 2> 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<float, 2> 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;

View File

@ -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);

View File

@ -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);