You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
352 lines
12 KiB
352 lines
12 KiB
#include "controls/game_controls.h" |
|
|
|
#include <cstdint> |
|
|
|
#include "controls/controller.h" |
|
#include "controls/controller_motion.h" |
|
#include "controls/devices/game_controller.h" |
|
#include "controls/devices/joystick.h" |
|
#include "controls/menu_controls.h" |
|
#include "controls/modifier_hints.h" |
|
#include "controls/plrctrls.h" |
|
#include "options.h" |
|
|
|
namespace devilution { |
|
|
|
bool start_modifier_active = false; |
|
bool select_modifier_active = false; |
|
|
|
namespace { |
|
|
|
DWORD translate_controller_button_to_key(ControllerButton controller_button) |
|
{ |
|
switch (controller_button) { |
|
case ControllerButton_BUTTON_A: // Bottom button |
|
return questlog ? DVL_VK_SPACE : DVL_VK_ESCAPE; |
|
case ControllerButton_BUTTON_B: // Right button |
|
return sgpCurrentMenu || stextflag || questlog ? DVL_VK_RETURN : DVL_VK_SPACE; |
|
case ControllerButton_BUTTON_Y: // Top button |
|
return DVL_VK_RETURN; |
|
case ControllerButton_BUTTON_LEFTSTICK: |
|
return DVL_VK_TAB; // Map |
|
case ControllerButton_BUTTON_BACK: |
|
case ControllerButton_BUTTON_START: |
|
return DVL_VK_ESCAPE; |
|
case ControllerButton_BUTTON_DPAD_LEFT: |
|
return DVL_VK_LEFT; |
|
case ControllerButton_BUTTON_DPAD_RIGHT: |
|
return DVL_VK_RIGHT; |
|
case ControllerButton_BUTTON_DPAD_UP: |
|
return DVL_VK_UP; |
|
case ControllerButton_BUTTON_DPAD_DOWN: |
|
return DVL_VK_DOWN; |
|
default: |
|
return 0; |
|
} |
|
} |
|
|
|
bool HandleStartAndSelect(const ControllerButtonEvent &ctrl_event, GameAction *action) |
|
{ |
|
const bool in_game_menu = InGameMenu(); |
|
|
|
const bool start_is_down = IsControllerButtonPressed(ControllerButton_BUTTON_START); |
|
const bool select_is_down = IsControllerButtonPressed(ControllerButton_BUTTON_BACK); |
|
start_modifier_active = !in_game_menu && start_is_down; |
|
select_modifier_active = !in_game_menu && select_is_down && !start_modifier_active; |
|
|
|
// Tracks whether we've received both START and SELECT down events. |
|
// |
|
// Using `IsControllerButtonPressed()` for this would be incorrect. |
|
// If both buttons are pressed simultaneously, SDL sends 2 events for which both buttons are in the pressed state. |
|
// This allows us to avoid triggering START+SELECT action twice in this case. |
|
static bool start_down_received = false; |
|
static bool select_down_received = false; |
|
switch (ctrl_event.button) { |
|
case ControllerButton_BUTTON_BACK: |
|
select_down_received = !ctrl_event.up; |
|
break; |
|
case ControllerButton_BUTTON_START: |
|
start_down_received = !ctrl_event.up; |
|
break; |
|
default: |
|
return false; |
|
} |
|
|
|
if (start_down_received && select_down_received) { |
|
*action = GameActionSendKey { DVL_VK_ESCAPE, ctrl_event.up }; |
|
return true; |
|
} |
|
|
|
if (in_game_menu && (start_is_down || select_is_down) && !ctrl_event.up) { |
|
// If both are down, do nothing because `both_received` will trigger soon. |
|
if (start_is_down && select_is_down) |
|
return true; |
|
*action = GameActionSendKey { DVL_VK_ESCAPE, ctrl_event.up }; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
} // namespace |
|
|
|
bool GetGameAction(const SDL_Event &event, ControllerButtonEvent ctrl_event, GameAction *action) |
|
{ |
|
const bool in_game_menu = InGameMenu(); |
|
|
|
if (HandleStartAndSelect(ctrl_event, action)) |
|
return true; |
|
|
|
// Stick clicks simulate the mouse both in menus and in-game. |
|
switch (ctrl_event.button) { |
|
case ControllerButton_BUTTON_LEFTSTICK: |
|
if (select_modifier_active) { |
|
if (!IsAutomapActive()) |
|
*action = GameActionSendMouseClick { GameActionSendMouseClick::LEFT, ctrl_event.up }; |
|
return true; |
|
} |
|
break; |
|
case ControllerButton_BUTTON_RIGHTSTICK: |
|
if (!IsAutomapActive()) { |
|
if (IsControllerButtonPressed(ControllerButton_BUTTON_BACK)) |
|
*action = GameActionSendMouseClick { GameActionSendMouseClick::RIGHT, ctrl_event.up }; |
|
else |
|
*action = GameActionSendMouseClick { GameActionSendMouseClick::LEFT, ctrl_event.up }; |
|
} |
|
return true; |
|
default: |
|
break; |
|
} |
|
|
|
if (!in_game_menu) { |
|
switch (ctrl_event.button) { |
|
case ControllerButton_BUTTON_LEFTSHOULDER: |
|
if ((select_modifier_active && !sgOptions.Controller.bSwapShoulderButtonMode) || (sgOptions.Controller.bSwapShoulderButtonMode && !select_modifier_active)) { |
|
if (!IsAutomapActive()) |
|
*action = GameActionSendMouseClick { GameActionSendMouseClick::LEFT, ctrl_event.up }; |
|
return true; |
|
} |
|
break; |
|
case ControllerButton_BUTTON_RIGHTSHOULDER: |
|
if ((select_modifier_active && !sgOptions.Controller.bSwapShoulderButtonMode) || (sgOptions.Controller.bSwapShoulderButtonMode && !select_modifier_active)) { |
|
if (!IsAutomapActive()) |
|
*action = GameActionSendMouseClick { GameActionSendMouseClick::RIGHT, ctrl_event.up }; |
|
return true; |
|
} |
|
break; |
|
case ControllerButton_AXIS_TRIGGERLEFT: // ZL (aka L2) |
|
if (!ctrl_event.up) { |
|
if (select_modifier_active) |
|
*action = GameAction(GameActionType_TOGGLE_QUEST_LOG); |
|
else |
|
*action = GameAction(GameActionType_TOGGLE_CHARACTER_INFO); |
|
} |
|
return true; |
|
case ControllerButton_AXIS_TRIGGERRIGHT: // ZR (aka R2) |
|
if (!ctrl_event.up) { |
|
if (select_modifier_active) |
|
*action = GameAction(GameActionType_TOGGLE_SPELL_BOOK); |
|
else |
|
*action = GameAction(GameActionType_TOGGLE_INVENTORY); |
|
} |
|
return true; |
|
case ControllerButton_IGNORE: |
|
case ControllerButton_BUTTON_START: |
|
case ControllerButton_BUTTON_BACK: |
|
return true; |
|
break; |
|
default: |
|
break; |
|
} |
|
if (sgOptions.Controller.bDpadHotkeys) { |
|
switch (ctrl_event.button) { |
|
case ControllerButton_BUTTON_DPAD_UP: |
|
if (IsControllerButtonPressed(ControllerButton_BUTTON_BACK)) |
|
*action = GameActionSendKey { DVL_VK_F6, ctrl_event.up }; |
|
else |
|
*action = GameActionSendKey { DVL_VK_ESCAPE, ctrl_event.up }; |
|
return true; |
|
case ControllerButton_BUTTON_DPAD_RIGHT: |
|
if (IsControllerButtonPressed(ControllerButton_BUTTON_BACK)) |
|
*action = GameActionSendKey { DVL_VK_F8, ctrl_event.up }; |
|
else if (!ctrl_event.up) |
|
*action = GameAction(GameActionType_TOGGLE_INVENTORY); |
|
return true; |
|
case ControllerButton_BUTTON_DPAD_DOWN: |
|
if (IsControllerButtonPressed(ControllerButton_BUTTON_BACK)) |
|
*action = GameActionSendKey { DVL_VK_F7, ctrl_event.up }; |
|
else |
|
*action = GameActionSendKey { DVL_VK_TAB, ctrl_event.up }; |
|
return true; |
|
case ControllerButton_BUTTON_DPAD_LEFT: |
|
if (IsControllerButtonPressed(ControllerButton_BUTTON_BACK)) |
|
*action = GameActionSendKey { DVL_VK_F5, ctrl_event.up }; |
|
else if (!ctrl_event.up) |
|
*action = GameAction(GameActionType_TOGGLE_CHARACTER_INFO); |
|
return true; |
|
default: |
|
break; |
|
} |
|
} |
|
if (start_modifier_active) { |
|
switch (ctrl_event.button) { |
|
case ControllerButton_BUTTON_DPAD_UP: |
|
*action = GameActionSendKey { DVL_VK_ESCAPE, ctrl_event.up }; |
|
return true; |
|
case ControllerButton_BUTTON_DPAD_RIGHT: |
|
if (!ctrl_event.up) |
|
*action = GameAction(GameActionType_TOGGLE_INVENTORY); |
|
return true; |
|
case ControllerButton_BUTTON_DPAD_DOWN: |
|
*action = GameActionSendKey { DVL_VK_TAB, ctrl_event.up }; |
|
return true; |
|
case ControllerButton_BUTTON_DPAD_LEFT: |
|
if (!ctrl_event.up) |
|
*action = GameAction(GameActionType_TOGGLE_CHARACTER_INFO); |
|
return true; |
|
case ControllerButton_BUTTON_Y: // Top button |
|
#ifdef __3DS__ |
|
if (!ctrl_event.up) { |
|
zoomflag = !zoomflag; |
|
CalcViewportGeometry(); |
|
} |
|
#else |
|
// Not mapped. Reserved for future use. |
|
#endif |
|
return true; |
|
case ControllerButton_BUTTON_B: // Right button |
|
// Not mapped. TODO: map to attack in place. |
|
return true; |
|
case ControllerButton_BUTTON_A: // Bottom button |
|
if (!ctrl_event.up) |
|
*action = GameAction(GameActionType_TOGGLE_SPELL_BOOK); |
|
return true; |
|
case ControllerButton_BUTTON_X: // Left button |
|
if (!ctrl_event.up) |
|
*action = GameAction(GameActionType_TOGGLE_QUEST_LOG); |
|
return true; |
|
case ControllerButton_BUTTON_LEFTSHOULDER: |
|
if (!ctrl_event.up) |
|
*action = GameAction(GameActionType_TOGGLE_CHARACTER_INFO); |
|
return true; |
|
case ControllerButton_BUTTON_RIGHTSHOULDER: |
|
if (!ctrl_event.up) |
|
*action = GameAction(GameActionType_TOGGLE_INVENTORY); |
|
return true; |
|
default: |
|
return true; |
|
} |
|
} |
|
|
|
// Bottom button: Closes menus or opens quick spell book if nothing is open. |
|
if (ctrl_event.button == ControllerButton_BUTTON_A) { // Bottom button |
|
if (ctrl_event.up) |
|
return true; |
|
if (IsControllerButtonPressed(ControllerButton_BUTTON_BACK)) |
|
*action = GameActionSendKey { DVL_VK_F7, ctrl_event.up }; |
|
else if (invflag) |
|
*action = GameAction(GameActionType_TOGGLE_INVENTORY); |
|
else if (sbookflag) |
|
*action = GameAction(GameActionType_TOGGLE_SPELL_BOOK); |
|
else if (questlog) |
|
*action = GameAction(GameActionType_TOGGLE_QUEST_LOG); |
|
else if (chrflag) |
|
*action = GameAction(GameActionType_TOGGLE_CHARACTER_INFO); |
|
else |
|
*action = GameAction(GameActionType_TOGGLE_QUICK_SPELL_MENU); |
|
return true; |
|
} |
|
|
|
if (!questlog && !sbookflag) { |
|
switch (ctrl_event.button) { |
|
case ControllerButton_IGNORE: |
|
return true; |
|
case ControllerButton_BUTTON_B: // Right button |
|
if (!ctrl_event.up) { |
|
if (IsControllerButtonPressed(ControllerButton_BUTTON_BACK)) |
|
*action = GameActionSendKey { DVL_VK_F8, ctrl_event.up }; |
|
else |
|
*action = GameAction(GameActionType_PRIMARY_ACTION); |
|
} |
|
return true; |
|
case ControllerButton_BUTTON_Y: // Top button |
|
if (!ctrl_event.up) { |
|
if (IsControllerButtonPressed(ControllerButton_BUTTON_BACK)) |
|
*action = GameActionSendKey { DVL_VK_F6, ctrl_event.up }; |
|
else |
|
*action = GameAction(GameActionType_SECONDARY_ACTION); |
|
} |
|
return true; |
|
case ControllerButton_BUTTON_X: // Left button |
|
if (!ctrl_event.up) { |
|
if (IsControllerButtonPressed(ControllerButton_BUTTON_BACK)) |
|
*action = GameActionSendKey { DVL_VK_F5, ctrl_event.up }; |
|
else |
|
*action = GameAction(GameActionType_CAST_SPELL); |
|
} |
|
return true; |
|
case ControllerButton_BUTTON_LEFTSHOULDER: |
|
if (!stextflag && !ctrl_event.up) |
|
*action = GameAction(GameActionType_USE_HEALTH_POTION); |
|
return true; |
|
case ControllerButton_BUTTON_RIGHTSHOULDER: |
|
if (!stextflag && !ctrl_event.up) |
|
*action = GameAction(GameActionType_USE_MANA_POTION); |
|
return true; |
|
case ControllerButton_BUTTON_DPAD_UP: |
|
case ControllerButton_BUTTON_DPAD_DOWN: |
|
case ControllerButton_BUTTON_DPAD_LEFT: |
|
case ControllerButton_BUTTON_DPAD_RIGHT: |
|
// The rest of D-Pad actions are handled in charMovement() on every game_logic() call. |
|
return true; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
if (ctrl_event.button == ControllerButton_BUTTON_BACK) { |
|
return true; // Ignore mod button |
|
} |
|
} |
|
|
|
// DPad navigation is handled separately for these. |
|
if (gmenu_is_active() || questlog || stextflag != STORE_NONE) { |
|
switch (ctrl_event.button) { |
|
case ControllerButton_BUTTON_DPAD_UP: |
|
case ControllerButton_BUTTON_DPAD_DOWN: |
|
case ControllerButton_BUTTON_DPAD_LEFT: |
|
case ControllerButton_BUTTON_DPAD_RIGHT: |
|
return true; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
// By default, map to a keyboard key. |
|
if (ctrl_event.button != ControllerButton_NONE) { |
|
*action = GameActionSendKey { translate_controller_button_to_key(ctrl_event.button), |
|
ctrl_event.up }; |
|
return true; |
|
} |
|
|
|
#ifndef USE_SDL1 |
|
// Ignore unhandled joystick events where a GameController is open for this joystick. |
|
// This is because SDL sends both game controller and joystick events in this case. |
|
const Joystick *const joystick = Joystick::Get(event); |
|
if (joystick != NULL && GameController::Get(joystick->instance_id()) != NULL) { |
|
return true; |
|
} |
|
if (event.type == SDL_CONTROLLERAXISMOTION) { |
|
return true; // Ignore releasing the trigger buttons |
|
} |
|
#endif |
|
|
|
return false; |
|
} |
|
|
|
AxisDirection GetMoveDirection() |
|
{ |
|
return GetLeftStickOrDpadDirection(/*allow_dpad=*/!sgOptions.Controller.bDpadHotkeys); |
|
} |
|
|
|
} // namespace devilution
|
|
|