From 2fcf0a9217901b09040f5301d5209a2aa1b02926 Mon Sep 17 00:00:00 2001 From: wkdgmr Date: Sat, 23 Aug 2025 14:59:55 -0400 Subject: [PATCH 1/3] GamepadAim --- Source/controls/controller_motion.cpp | 65 ++++++++++++++++++++------- Source/controls/controller_motion.h | 4 +- Source/controls/modifier_hints.cpp | 4 +- Source/controls/plrctrls.cpp | 60 +++++++++++++++++++++---- 4 files changed, 105 insertions(+), 28 deletions(-) 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); From 85dde8999719d3970e07714ea97017d1d0a910e8 Mon Sep 17 00:00:00 2001 From: wkdgmr Date: Sat, 23 Aug 2025 18:49:16 -0400 Subject: [PATCH 2/3] QuickCast fixes --- Source/controls/plrctrls.cpp | 51 +++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 8dc4a46bf..66ef37b60 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -1,5 +1,3 @@ -#include "controls/plrctrls.h" - #include #include #include @@ -17,6 +15,7 @@ #endif #include "controls/control_mode.hpp" #include "controls/game_controls.h" +#include "controls/plrctrls.h" #include "controls/touch/gamepad.h" #include "cursor.h" #include "doom.h" @@ -411,10 +410,14 @@ void CheckPlayerNearby() void FindActor() { - if (leveltype != DTYPE_TOWN) + if (leveltype != DTYPE_TOWN) { CheckMonstersNearby(); - else + // Always prefer monsters for spell casting if a monster is targeted + if (pcursmonst != -1) + cursPosition = Monsters[pcursmonst].position.tile; + } else { CheckTownersNearby(); + } if (gbIsMultiplayer) CheckPlayerNearby(); @@ -479,6 +482,13 @@ void FindTrigger() CheckRportal(); } +void PreferMonsterTarget() +{ + if (ControlMode == ControlTypes::Gamepad && pcursmonst != -1 && (ObjectUnderCursor != nullptr || pcursitem != -1)) { + cursPosition = Monsters[pcursmonst].position.tile; + } +} + bool IsStandingGround() { if (ControlMode == ControlTypes::Gamepad) { @@ -1948,6 +1958,7 @@ void plrctrls_after_check_curs_move() FindActor(); FindItemOrObject(); FindTrigger(); + PreferMonsterTarget(); } } @@ -2018,31 +2029,30 @@ void PerformPrimaryAction() Interact(); } -bool SpellHasActorTarget() +bool SpellHasActorTarget(SpellID spell) { - const SpellID spl = MyPlayer->_pRSpell; - if (spl == SpellID::TownPortal || spl == SpellID::Teleport) + if (spell == SpellID::TownPortal || spell == SpellID::Teleport) return false; - if (IsWallSpell(spl) && pcursmonst != -1) { - cursPosition = Monsters[pcursmonst].position.tile; - } - return PlayerUnderCursor != nullptr || pcursmonst != -1; } void UpdateSpellTarget(SpellID spell) { - if (SpellHasActorTarget()) + // For wall spells, if a monster is targeted, always set cursPosition to the monster's tile + if (IsWallSpell(spell) && pcursmonst != -1) { + cursPosition = Monsters[pcursmonst].position.tile; + return; + } + + if (SpellHasActorTarget(spell)) return; PlayerUnderCursor = nullptr; pcursmonst = -1; const Player &myPlayer = *MyPlayer; - const int range = spell == SpellID::Teleport ? 4 : 1; - cursPosition = myPlayer.position.future + Displacement(myPlayer._pdir) * range; } @@ -2236,10 +2246,15 @@ void PerformSecondaryAction() NewCursor(CURSOR_HAND); if (pcursitem != -1) { - NetSendCmdLocParam1(true, CMD_GOTOAGETITEM, cursPosition, pcursitem); + // Use item's actual position for interaction + const Item &item = Items[pcursitem]; + NetSendCmdLocParam1(true, CMD_GOTOAGETITEM, item.position, pcursitem); + return; } else if (ObjectUnderCursor != nullptr) { - NetSendCmdLoc(MyPlayerId, true, CMD_OPOBJXY, cursPosition); + // Use object's actual position for interaction + NetSendCmdLoc(MyPlayerId, true, CMD_OPOBJXY, ObjectUnderCursor->position); LastPlayerAction = PlayerActionType::OperateObject; + return; } else { if (pcursmissile != nullptr) { MakePlrPath(myPlayer, pcursmissile->position.tile, true); @@ -2261,8 +2276,8 @@ void QuickCast(size_t slot) const SpellID spell = myPlayer._pSplHotKey[slot]; const SpellType spellType = myPlayer._pSplTHotKey[slot]; - if (ControlMode != ControlTypes::KeyboardAndMouse) { - UpdateSpellTarget(spell); + if (!IsGamepadAimActive) { + UpdateSpellTarget(spell); } CheckPlrSpell(false, spell, spellType); From a82bed289cc72e999d83398de143a2245163801a Mon Sep 17 00:00:00 2001 From: wkdgmr Date: Sat, 23 Aug 2025 18:59:35 -0400 Subject: [PATCH 3/3] clang --- Source/controls/controller_motion.cpp | 7 +++---- Source/controls/controller_motion.h | 1 - Source/controls/plrctrls.cpp | 9 +++++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Source/controls/controller_motion.cpp b/Source/controls/controller_motion.cpp index 5811828e0..291cf2a2e 100644 --- a/Source/controls/controller_motion.cpp +++ b/Source/controls/controller_motion.cpp @@ -103,7 +103,6 @@ bool IsPressedForMovement(ControllerButton button) && !(SpellSelectFlag && TriggersQuickSpellAction(button)); } - void SetGamepadAimActive(bool value) { if (GamepadAimActive == value) @@ -295,9 +294,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) { - 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")); diff --git a/Source/controls/controller_motion.h b/Source/controls/controller_motion.h index f640f0ca2..befe2e4fb 100644 --- a/Source/controls/controller_motion.h +++ b/Source/controls/controller_motion.h @@ -11,7 +11,6 @@ namespace devilution { // Returns true if gamepad aiming is currently active (right stick or padmapped mouse). bool IsGamepadAimActive(); - // Raw axis values. extern float leftStickXUnscaled, leftStickYUnscaled, rightStickXUnscaled, rightStickYUnscaled; diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index 66ef37b60..70aed34a6 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -128,7 +128,8 @@ int GetDistance(Point destination, int maxDistance) int8_t walkpath[MaxPathLengthPlayer]; Player &myPlayer = *MyPlayer; - const int steps = FindPath(CanStep, [&myPlayer](Point position) { return PosOkPlayer(myPlayer, position); }, myPlayer.position.future, destination, walkpath, std::min(maxDistance, MaxPathLengthPlayer)); + const int steps = FindPath( + CanStep, [&myPlayer](Point position) { return PosOkPlayer(myPlayer, position); }, myPlayer.position.future, destination, walkpath, std::min(maxDistance, MaxPathLengthPlayer)); if (steps > maxDistance) return 0; @@ -1752,7 +1753,7 @@ void DetectInputMethod(const SDL_Event &event, const ControllerButtonEvent &game if (newControlDevice != ControlDevice) { ControlDevice = newControlDevice; - #ifndef USE_SDL1 +#ifndef USE_SDL1 // Prevent cursor hiding and device/mode swap while gamepad aiming is active if (!IsGamepadAimActive()) { if (ControlDevice != ControlTypes::KeyboardAndMouse) { @@ -1769,7 +1770,7 @@ void DetectInputMethod(const SDL_Event &event, const ControllerButtonEvent &game GamepadType = newGamepadLayout; } } - #endif +#endif } if (newControlMode != ControlMode) { @@ -2277,7 +2278,7 @@ void QuickCast(size_t slot) const SpellType spellType = myPlayer._pSplTHotKey[slot]; if (!IsGamepadAimActive) { - UpdateSpellTarget(spell); + UpdateSpellTarget(spell); } CheckPlrSpell(false, spell, spellType);