diff --git a/files.cmake b/files.cmake index d810568896..7507fb6742 100644 --- a/files.cmake +++ b/files.cmake @@ -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 diff --git a/include/d/actor/d_a_alink.h b/include/d/actor/d_a_alink.h index f74aef4f0a..b68a4de5a9 100644 --- a/include/d/actor/d_a_alink.h +++ b/include/d/actor/d_a_alink.h @@ -4550,6 +4550,7 @@ public: #if TARGET_PC void handleQuickTransform(); + bool checkGyroAimItemContext(); #endif }; // Size: 0x385C diff --git a/include/dusk/gyro_aim.h b/include/dusk/gyro_aim.h new file mode 100644 index 0000000000..aafc926811 --- /dev/null +++ b/include/dusk/gyro_aim.h @@ -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 diff --git a/include/dusk/settings.h b/include/dusk/settings.h index 2bdfe02bb1..2ab587d3ce 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -62,6 +62,12 @@ struct UserSettings { ConfigVar noLowHpSound; ConfigVar midnasLamentNonStop; + // Input + ConfigVar enableGyroAim; + ConfigVar gyroAimSensitivity; + ConfigVar gyroAimInvertPitch; + ConfigVar gyroAimInvertYaw; + // Cheats ConfigVar enableFastIronBoots; ConfigVar canTransformAnywhere; diff --git a/src/d/actor/d_a_alink_dusk.cpp b/src/d/actor/d_a_alink_dusk.cpp index ae4cb2e96c..dbff923a2e 100644 --- a/src/d/actor/d_a_alink_dusk.cpp +++ b/src/d/actor/d_a_alink_dusk.cpp @@ -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; + } +} diff --git a/src/d/actor/d_a_alink_link.inc b/src/d/actor/d_a_alink_link.inc index 3b61cdfd96..4f8f5fa7b6 100644 --- a/src/d/actor/d_a_alink_link.inc +++ b/src/d/actor/d_a_alink_link.inc @@ -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); } diff --git a/src/dusk/gyro_aim.cpp b/src/dusk/gyro_aim.cpp new file mode 100644 index 0000000000..3558b804d1 --- /dev/null +++ b/src/dusk/gyro_aim.cpp @@ -0,0 +1,88 @@ +#include "dusk/gyro_aim.h" + +#include +#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(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(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 diff --git a/src/dusk/imgui/ImGuiControllerOverlay.cpp b/src/dusk/imgui/ImGuiControllerOverlay.cpp index bd53e6f91d..8c24063375 100644 --- a/src/dusk/imgui/ImGuiControllerOverlay.cpp +++ b/src/dusk/imgui/ImGuiControllerOverlay.cpp @@ -5,6 +5,8 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuGame.hpp" +#include + 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 \ No newline at end of file +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiMenuEnhancements.cpp b/src/dusk/imgui/ImGuiMenuEnhancements.cpp index 87df76ea11..50e44f5c60 100644 --- a/src/dusk/imgui/ImGuiMenuEnhancements.cpp +++ b/src/dusk/imgui/ImGuiMenuEnhancements.cpp @@ -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); diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index 67b7800490..0e39e55854 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -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); diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 4dd1a658e5..7aaec4b005 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -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(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(frame_seconds), dusk::gyro_aim::queryGyroAimItemContext()); + } // EXECUTE GAME LOGIC & RENDER // This calls mDoGph_Painter -> JFWDisplay -> GX Functions