Merge pull request #343 from TwilitRealm/ira/gyro-aim

Initial implementation of gyro aim
This commit is contained in:
TakaRikka
2026-04-12 16:54:25 -07:00
committed by GitHub
11 changed files with 241 additions and 8 deletions
+6 -6
View File
@@ -1338,17 +1338,17 @@ set(DUSK_FILES
src/d/actor/d_a_alink_dusk.cpp
src/dusk/asserts.cpp
src/dusk/config.cpp
src/dusk/settings.cpp
src/dusk/logging.cpp
src/dusk/frame_interpolation.cpp
src/dusk/layout.cpp
src/dusk/stubs.cpp
src/dusk/endian.cpp
src/dusk/extras.c
src/dusk/extras.cpp
src/dusk/io.cpp
src/dusk/frame_interpolation.cpp
src/dusk/globals.cpp
src/dusk/gyro_aim.cpp
src/dusk/io.cpp
src/dusk/layout.cpp
src/dusk/logging.cpp
src/dusk/settings.cpp
src/dusk/stubs.cpp
#src/dusk/m_Do_ext_dusk.cpp
src/dusk/imgui/ImGuiConfig.hpp
src/dusk/imgui/ImGuiConsole.hpp
+1
View File
@@ -4550,6 +4550,7 @@ public:
#if TARGET_PC
void handleQuickTransform();
bool checkGyroAimItemContext();
#endif
}; // Size: 0x385C
+10
View File
@@ -0,0 +1,10 @@
#ifndef DUSK_GYRO_AIM_H
#define DUSK_GYRO_AIM_H
namespace dusk::gyro_aim {
void read(float dt, bool context_active);
void consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad);
bool queryGyroAimItemContext();
} // namespace dusk::gyro_aim
#endif
+6
View File
@@ -62,6 +62,12 @@ struct UserSettings {
ConfigVar<bool> noLowHpSound;
ConfigVar<bool> midnasLamentNonStop;
// Input
ConfigVar<bool> enableGyroAim;
ConfigVar<float> gyroAimSensitivity;
ConfigVar<bool> gyroAimInvertPitch;
ConfigVar<bool> gyroAimInvertYaw;
// Cheats
ConfigVar<bool> enableFastIronBoots;
ConfigVar<bool> canTransformAnywhere;
+29
View File
@@ -83,3 +83,32 @@ void daAlink_c::handleQuickTransform() {
OSReport("Running quick transform!");
procCoMetamorphoseInit();
}
bool daAlink_c::checkGyroAimItemContext() {
if (checkWolf()) {
return false;
}
switch (mProcID) {
case PROC_BOW_SUBJECT:
case PROC_BOOMERANG_SUBJECT:
case PROC_COPY_ROD_SUBJECT:
case PROC_HOOKSHOT_SUBJECT:
case PROC_SWIM_HOOKSHOT_SUBJECT:
case PROC_HORSE_BOW_SUBJECT:
case PROC_HORSE_BOOMERANG_SUBJECT:
case PROC_HORSE_HOOKSHOT_SUBJECT:
case PROC_CANOE_BOW_SUBJECT:
case PROC_CANOE_BOOMERANG_SUBJECT:
case PROC_CANOE_HOOKSHOT_SUBJECT:
case PROC_HOOKSHOT_ROOF_WAIT:
case PROC_HOOKSHOT_ROOF_SHOOT:
case PROC_HOOKSHOT_WALL_WAIT:
case PROC_HOOKSHOT_WALL_SHOOT:
return true;
case PROC_IRON_BALL_SUBJECT:
return itemButton() && mItemVar0.field_0x3018 == 2;
default:
return false;
}
}
+38
View File
@@ -10,6 +10,10 @@
#include "d/actor/d_a_tag_mstop.h"
#include "d/actor/d_a_tag_mhint.h"
#if TARGET_PC
#include "dusk/gyro_aim.h"
#endif
bool daAlink_c::checkNoSubjectModeCamera() {
return dCam_getBody()->Type() == dCam_getBody()->GetCameraTypeFromCameraName("Rotary") ||
dCam_getBody()->Type() == dCam_getBody()->GetCameraTypeFromCameraName("Rampart2") ||
@@ -125,6 +129,40 @@ BOOL daAlink_c::setBodyAngleToCamera() {
sp8 = mBodyAngle.x;
}
#if TARGET_PC
if (dusk::getSettings().game.enableGyroAim && checkGyroAimItemContext()) {
f32 gyro_scale = 1.0f;
if (checkWolfEyeUp()) {
gyro_scale *= 0.6f;
}
if (dComIfGp_checkPlayerStatus0(0, 0x200000)) {
gyro_scale /= dComIfGp_getCameraZoomScale(field_0x317c);
}
f32 gy_yaw = 0.f;
f32 gy_pitch = 0.f;
dusk::gyro_aim::consumeAimDeltas(gy_yaw, gy_pitch);
if (dusk::getSettings().game.gyroAimInvertPitch) {
gy_pitch = -gy_pitch;
}
if (dusk::getSettings().game.gyroAimInvertYaw) {
gy_yaw = -gy_yaw;
}
if (dusk::getSettings().game.enableMirrorMode) {
gy_yaw = -gy_yaw;
}
shape_angle.y = shape_angle.y + cM_rad2s(gy_yaw * gyro_scale);
sp8 = sp8 + cM_rad2s(gy_pitch * gyro_scale);
if (checkNotItemSinkLimit() && sp8 > 0 && sp8 > mBodyAngle.x) {
sp8 = mBodyAngle.x;
}
}
#endif
if (checkNotItemSinkLimit() && sp8 > 0) {
cLib_addCalcAngleS(&sp8, 0, 5, 0x1000, 0x400);
}
+88
View File
@@ -0,0 +1,88 @@
#include "dusk/gyro_aim.h"
#include <SDL3/SDL.h>
#include "d/actor/d_a_alink.h"
namespace dusk::gyro_aim {
namespace {
// TODO: Make deadband and smoothing configurable
constexpr float kDeadbandRadS = 0.04f;
constexpr float kSmoothAlpha = 0.35f;
bool s_sensor_enabled = false;
float s_smooth_gx = 0.0f;
float s_smooth_gy = 0.0f;
float s_pending_yaw_rad = 0.0f;
float s_pending_pitch_rad = 0.0f;
void reset_filter_state() {
s_smooth_gx = s_smooth_gy = 0.0f;
s_pending_yaw_rad = s_pending_pitch_rad = 0.0f;
}
float apply_deadband(float v) {
if (v > -kDeadbandRadS && v < kDeadbandRadS) {
return 0.0f;
}
return v;
}
} // namespace
void read(float dt, bool context_active) {
if (!context_active || !static_cast<bool>(dusk::getSettings().game.enableGyroAim)) {
SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0);
if (pad != nullptr && s_sensor_enabled) {
SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, false);
s_sensor_enabled = false;
}
reset_filter_state();
return;
}
SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0);
if (pad == nullptr || !SDL_GamepadHasSensor(pad, SDL_SENSOR_GYRO)) {
return;
}
if (!s_sensor_enabled) {
if (!SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, true)) {
return;
}
s_sensor_enabled = true;
reset_filter_state();
}
float gyro[3];
if (!SDL_GetGamepadSensorData(pad, SDL_SENSOR_GYRO, gyro, 3)) {
return;
}
s_smooth_gx += kSmoothAlpha * (gyro[0] - s_smooth_gx);
s_smooth_gy += kSmoothAlpha * (gyro[1] - s_smooth_gy);
float yaw_rate = apply_deadband(s_smooth_gy);
float pitch_rate = apply_deadband(s_smooth_gx);
const float sens = dusk::getSettings().game.gyroAimSensitivity;
s_pending_yaw_rad += yaw_rate * dt * sens;
s_pending_pitch_rad += pitch_rate * dt * sens;
}
void consumeAimDeltas(float& out_yaw_rad, float& out_pitch_rad) {
out_yaw_rad = s_pending_yaw_rad;
out_pitch_rad = s_pending_pitch_rad;
s_pending_yaw_rad = 0.0f;
s_pending_pitch_rad = 0.0f;
}
bool queryGyroAimItemContext() {
if (!static_cast<bool>(dusk::getSettings().game.enableGyroAim)) {
return false;
}
daAlink_c* link = daAlink_getAlinkActorClass();
if (link == nullptr) {
return false;
}
return link->checkGyroAimItemContext() && dComIfGp_checkCameraAttentionStatus(link->field_0x317c, 0x10);
}
} // namespace dusk::gyro_aim
+31 -1
View File
@@ -5,6 +5,8 @@
#include "ImGuiConsole.hpp"
#include "ImGuiMenuGame.hpp"
#include <SDL3/SDL.h>
namespace dusk {
void ImGuiMenuGame::windowInputViewer() {
if (!m_showInputViewer) {
@@ -258,9 +260,37 @@ namespace dusk {
size.y = 130 * scale;
ImGui::Dummy(size);
SDL_Gamepad* pad = SDL_GetGamepadFromPlayerIndex(0);
if (pad != nullptr && SDL_GamepadHasSensor(pad, SDL_SENSOR_GYRO)) {
ImGui::Separator();
ImGui::TextUnformatted("Gyro");
constexpr float kBarScale = 4.0f;
auto bar = [kBarScale](const char* label, float v) {
const float a = std::fabs(v);
const float t = std::min(1.f, a / kBarScale);
char overlay[32];
snprintf(overlay, sizeof(overlay), "%s %+.3f", label, v);
ImGui::ProgressBar(t, ImVec2(-1, 0), overlay);
};
if (SDL_SetGamepadSensorEnabled(pad, SDL_SENSOR_GYRO, true)) {
float gyro[3];
if (SDL_GetGamepadSensorData(pad, SDL_SENSOR_GYRO, gyro, 3)) {
bar("X", gyro[0]);
bar("Y", gyro[1]);
bar("Z", gyro[2]);
}
} else {
bar("X", 0.f);
bar("Y", 0.f);
bar("Z", 0.f);
}
}
ShowCornerContextMenu(m_inputOverlayCorner, 0);
}
ImGui::End();
}
} // namespace dusk
} // namespace dusk
+14
View File
@@ -112,6 +112,20 @@ namespace dusk {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Input")) {
config::ImGuiCheckbox("Gyro Aim", getSettings().game.enableGyroAim);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Enables the gyroscope on supported controllers while aiming the\n"
"Slingshot, Gale Boomerang, Hero's Bow, Clawshot(s), Ball and Chain, and Dominion Rod.");
}
config::ImGuiSliderFloat("Gyro Sensitivity", getSettings().game.gyroAimSensitivity, 0.25f, 4.0f, "%.2f");
config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroAimInvertPitch);
config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroAimInvertYaw);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Cheats")) {
config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots);
+11 -1
View File
@@ -50,6 +50,12 @@ UserSettings g_userSettings = {
.noLowHpSound {"game.noLowHpSound", false},
.midnasLamentNonStop {"game.midnasLamentNonStop", false},
// Input
.enableGyroAim {"game.enableGyroAim", false},
.gyroAimSensitivity {"game.gyroAimSensitivity", 1.0f},
.gyroAimInvertPitch {"game.gyroAimInvertPitch", false},
.gyroAimInvertYaw {"game.gyroAimInvertYaw", false},
// Cheats
.enableFastIronBoots {"game.enableFastIronBoots", false},
.canTransformAnywhere {"game.canTransformAnywhere", false},
@@ -60,7 +66,7 @@ UserSettings g_userSettings = {
.restoreWiiGlitches {"game.restoreWiiGlitches", false},
// Controls
.enableTurboKeybind {"game.enableTurboKeybind", false},
.enableTurboKeybind {"game.enableTurboKeybind", false}
},
.backend = {
@@ -119,6 +125,10 @@ void registerSettings() {
Register(g_userSettings.game.enableTurboKeybind);
Register(g_userSettings.game.fastSpinner);
Register(g_userSettings.game.enableFrameInterpolation);
Register(g_userSettings.game.enableGyroAim);
Register(g_userSettings.game.gyroAimSensitivity);
Register(g_userSettings.game.gyroAimInvertPitch);
Register(g_userSettings.game.gyroAimInvertYaw);
Register(g_userSettings.backend.isoPath);
Register(g_userSettings.backend.graphicsBackend);
+7
View File
@@ -48,6 +48,7 @@
#include "dusk/app_info.hpp"
#include "dusk/dusk.h"
#include "dusk/frame_interpolation.h"
#include "dusk/gyro_aim.h"
#include "dusk/imgui/ImGuiEngine.hpp"
#include "dusk/logging.h"
#include "dusk/main.h"
@@ -241,6 +242,9 @@ void main01(void) {
if (dusk::getSettings().game.enableFrameInterpolation) {
while (accumulator >= kSimStepSeconds) {
mDoCPd_c::read();
if (dusk::getSettings().game.enableGyroAim) {
dusk::gyro_aim::read(static_cast<float>(kSimStepSeconds), dusk::gyro_aim::queryGyroAimItemContext());
}
fapGm_Execute();
mDoAud_Execute();
accumulator -= kSimStepSeconds;
@@ -254,6 +258,9 @@ void main01(void) {
// Game Inputs
mDoCPd_c::read();
if (dusk::getSettings().game.enableGyroAim) {
dusk::gyro_aim::read(static_cast<float>(frame_seconds), dusk::gyro_aim::queryGyroAimItemContext());
}
// EXECUTE GAME LOGIC & RENDER
// This calls mDoGph_Painter -> JFWDisplay -> GX Functions