diff --git a/Source/controls/controller_motion.cpp b/Source/controls/controller_motion.cpp index bb2be1654..5811828e0 100644 --- a/Source/controls/controller_motion.cpp +++ b/Source/controls/controller_motion.cpp @@ -20,7 +20,12 @@ namespace devilution { -bool SimulatingMouseWithPadmapper; +static bool GamepadAimActive = false; + +bool IsGamepadAimActive() +{ + return GamepadAimActive; +} namespace { @@ -98,15 +103,19 @@ bool IsPressedForMovement(ControllerButton button) && !(SpellSelectFlag && TriggersQuickSpellAction(button)); } -void SetSimulatingMouseWithPadmapper(bool value) + +void SetGamepadAimActive(bool value) { - if (SimulatingMouseWithPadmapper == value) + if (GamepadAimActive == value) return; - SimulatingMouseWithPadmapper = value; + GamepadAimActive = value; if (value) { - LogVerbose("Control: begin simulating mouse with D-Pad"); + LogVerbose("GamepadAim: begin aiming"); + // Show cursor for aiming (if needed) } else { - LogVerbose("Control: end simulating mouse with D-Pad"); + LogVerbose("GamepadAim: end aiming"); + // Hide/reset cursor when movement resumes + ResetCursor(); } } @@ -188,14 +197,41 @@ void ProcessControllerMotion(const SDL_Event &event) GameController *const controller = GameController::Get(event); if (controller != nullptr && devilution::GameController::ProcessAxisMotion(event)) { ScaleJoysticks(); - SetSimulatingMouseWithPadmapper(false); + // End aiming and hide cursor only when left stick axis moved + if (event.type == SDL_CONTROLLERAXISMOTION) { + if (event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX || event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY) { + SetGamepadAimActive(false); + } + } return; } #endif Joystick *const joystick = Joystick::Get(event); if (joystick != nullptr && devilution::Joystick::ProcessAxisMotion(event)) { ScaleJoysticks(); - SetSimulatingMouseWithPadmapper(false); + if (event.type == SDL_JOYAXISMOTION) { +#ifdef JOY_AXIS_LEFTX + if (event.jaxis.axis == JOY_AXIS_LEFTX) + SetGamepadAimActive(false); +#endif +#ifdef JOY_AXIS_LEFTY + if (event.jaxis.axis == JOY_AXIS_LEFTY) + SetGamepadAimActive(false); +#endif + } + } + + // D-pad movement detection (button events, not padmapper mouse simulation) + StaticVector buttonEvents = ToControllerButtonEvents(event); + for (const auto &ctrlEvent : buttonEvents) { + if (!ctrlEvent.up && IsDPadButton(ctrlEvent.button)) { + // Only clear aim if this is NOT a padmapper mouse simulation combo + // (i.e., no modifier or not SELECT + D-pad) + ControllerButtonCombo combo(ctrlEvent.button); + if (combo.modifier == ControllerButton_NONE) { + SetGamepadAimActive(false); + } + } } } @@ -216,7 +252,7 @@ AxisDirection GetLeftStickOrDpadDirection(bool usePadmapper) isDownPressed |= PadmapperIsActionActive("MoveDown"); isLeftPressed |= PadmapperIsActionActive("MoveLeft"); isRightPressed |= PadmapperIsActionActive("MoveRight"); - } else if (!SimulatingMouseWithPadmapper) { + } else if (!IsGamepadAimActive()) { isUpPressed |= IsPressedForMovement(ControllerButton_BUTTON_DPAD_UP); isDownPressed |= IsPressedForMovement(ControllerButton_BUTTON_DPAD_DOWN); isLeftPressed |= IsPressedForMovement(ControllerButton_BUTTON_DPAD_LEFT); @@ -259,11 +295,9 @@ void SimulateRightStickWithPadmapper(ControllerButtonEvent ctrlEvent) const bool downTriggered = actionName == "MouseDown"; const bool leftTriggered = actionName == "MouseLeft"; const bool rightTriggered = actionName == "MouseRight"; - if (!upTriggered && !downTriggered && !leftTriggered && !rightTriggered) { - if (rightStickX == 0 && rightStickY == 0) - SetSimulatingMouseWithPadmapper(false); - return; - } + if (!upTriggered && !downTriggered && !leftTriggered && !rightTriggered) { + return; + } const bool upActive = (upTriggered && !ctrlEvent.up) || (!upTriggered && PadmapperIsActionActive("MouseUp")); const bool downActive = (downTriggered && !ctrlEvent.up) || (!downTriggered && PadmapperIsActionActive("MouseDown")); @@ -280,7 +314,8 @@ void SimulateRightStickWithPadmapper(ControllerButtonEvent ctrlEvent) rightStickX -= 1.F; if (rightActive) rightStickX += 1.F; - SetSimulatingMouseWithPadmapper(true); + // Begin aiming when any direction is active + SetGamepadAimActive(true); } } // namespace devilution diff --git a/Source/controls/controller_motion.h b/Source/controls/controller_motion.h index f7c372fbc..f640f0ca2 100644 --- a/Source/controls/controller_motion.h +++ b/Source/controls/controller_motion.h @@ -8,9 +8,9 @@ #include "./controller.h" namespace devilution { +// Returns true if gamepad aiming is currently active (right stick or padmapped mouse). +bool IsGamepadAimActive(); -// Whether we're currently simulating the mouse with SELECT + D-Pad. -extern bool SimulatingMouseWithPadmapper; // Raw axis values. extern float leftStickXUnscaled, leftStickYUnscaled, rightStickXUnscaled, rightStickYUnscaled; diff --git a/Source/controls/modifier_hints.cpp b/Source/controls/modifier_hints.cpp index 881b3adbc..06f6ce681 100644 --- a/Source/controls/modifier_hints.cpp +++ b/Source/controls/modifier_hints.cpp @@ -141,7 +141,7 @@ void DrawSpellsCircleMenuHint(const Surface &out, const Point &origin) void DrawGamepadMenuNavigator(const Surface &out) { - if (!PadMenuNavigatorActive || SimulatingMouseWithPadmapper) + if (!PadMenuNavigatorActive || IsGamepadAimActive()) return; static const CircleMenuHint DPad(/*top=*/HintIcon::IconMenu, /*right=*/HintIcon::IconInv, /*bottom=*/HintIcon::IconMap, /*left=*/HintIcon::IconChar); static const CircleMenuHint Buttons(/*top=*/HintIcon::IconNull, /*right=*/HintIcon::IconNull, /*bottom=*/HintIcon::IconSpells, /*left=*/HintIcon::IconQuests); @@ -152,7 +152,7 @@ void DrawGamepadMenuNavigator(const Surface &out) void DrawGamepadHotspellMenu(const Surface &out) { - if (!PadHotspellMenuActive || SimulatingMouseWithPadmapper) + if (!PadHotspellMenuActive || IsGamepadAimActive()) return; const Rectangle &mainPanel = GetMainPanel(); diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 00725426b..8dc4a46bf 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -495,6 +495,13 @@ bool IsStandingGround() void Interact() { + if (IsGamepadAimActive()) { + const Player &myPlayer = *MyPlayer; + NetSendCmdLoc(MyPlayerId, true, myPlayer.UsesRangedWeapon() ? CMD_RATTACKXY : CMD_SATTACKXY, cursPosition); + LastPlayerAction = PlayerActionType::Attack; + return; + } + if (leveltype == DTYPE_TOWN && pcursmonst != -1) { NetSendCmdLocParam1(true, CMD_TALKXY, Towners[pcursmonst].position, pcursmonst); return; @@ -1642,7 +1649,7 @@ bool ContinueSimulatedMouseEvent(const SDL_Event &event, const ControllerButtonE return true; } - return SimulatingMouseWithPadmapper || IsSimulatedMouseClickBinding(gamepadEvent); + return IsGamepadAimActive() || IsSimulatedMouseClickBinding(gamepadEvent); } std::string_view ControlTypeToString(ControlTypes controlType) @@ -1735,12 +1742,15 @@ void DetectInputMethod(const SDL_Event &event, const ControllerButtonEvent &game if (newControlDevice != ControlDevice) { ControlDevice = newControlDevice; -#ifndef USE_SDL1 - if (ControlDevice != ControlTypes::KeyboardAndMouse) { - if (IsHardwareCursor()) - SetHardwareCursor(CursorInfo::UnknownCursor()); - } else { - ResetCursor(); + #ifndef USE_SDL1 + // Prevent cursor hiding and device/mode swap while gamepad aiming is active + if (!IsGamepadAimActive()) { + if (ControlDevice != ControlTypes::KeyboardAndMouse) { + if (IsHardwareCursor()) + SetHardwareCursor(CursorInfo::UnknownCursor()); + } else { + ResetCursor(); + } } if (ControlDevice == ControlTypes::Gamepad) { const GamepadLayout newGamepadLayout = GameController::getLayout(event); @@ -1749,7 +1759,7 @@ void DetectInputMethod(const SDL_Event &event, const ControllerButtonEvent &game GamepadType = newGamepadLayout; } } -#endif + #endif } if (newControlMode != ControlMode) { @@ -1974,6 +1984,11 @@ void UseBeltItem(BeltItemType type) void PerformPrimaryAction() { + if (IsGamepadAimActive()) { + Interact(); + return; + } + if (SpellSelectFlag) { SetSpell(); return; @@ -2099,8 +2114,21 @@ void PerformSpellAction() if (pcurs > CURSOR_HAND) NewCursor(CURSOR_HAND); - const Player &myPlayer = *MyPlayer; + Player &myPlayer = *MyPlayer; const SpellID spl = myPlayer._pRSpell; + + // Controller aiming: always cast at cursor position and turn player + if (IsGamepadAimActive()) { + // Turn player to face cursor + Direction newDir = GetDirection(myPlayer.position.tile, cursPosition); + myPlayer._pdir = newDir; + // Set spell target + cursPosition = cursPosition; + CheckPlrSpell(false); + LastPlayerAction = PlayerActionType::Spell; + return; + } + if ((PlayerUnderCursor == nullptr && (spl == SpellID::Resurrect || spl == SpellID::HealOther)) || (ObjectUnderCursor == nullptr && spl == SpellID::TrapDisarm)) { myPlayer.Say(HeroSpeech::ICantCastThatHere); @@ -2190,6 +2218,20 @@ void PerformSecondaryAction() if (!MyPlayer->HoldItem.isEmpty() && !TryDropItem()) return; + + if (pcurs == CURSOR_TELEKINESIS + || pcurs == CURSOR_IDENTIFY + || pcurs == CURSOR_REPAIR + || pcurs == CURSOR_RECHARGE + || pcurs == CURSOR_DISARM + || pcurs == CURSOR_OIL + || pcurs == CURSOR_RESURRECT + || pcurs == CURSOR_TELEPORT + || pcurs == CURSOR_HEALOTHER) { + TryIconCurs(); + return; + } + if (pcurs > CURSOR_HAND) NewCursor(CURSOR_HAND);