Browse Source

Game controller support

Initial game controller support.

Actions are based on the Switch branch but the controller code itself is
implemented differently, allowing for easy remapping and minimizing
changes to the Source/ directory.

Many subtle and not so subtle controller bugs have been fixed
in this implementation, including:

1. Smoother & more responsive movement with the joysticks.
2. Consistent controls for all the menus in the game (stores, quest log,
etc).
3. Cursor appearance / disappearance at appropriate times.

Low-level controls are abstracted and 3 SDL interfaces are supported:
game controller, joystick, and keyboard. See SourceX/controls/ for more
details on this.

Wishlist for the future:

1. Primary button and use button should attack continously.
   This is hard as it requires checking the cooldowns / attack speed.
2. Quick spell menu navigation is very buggy. It is also buggy in the
   switch branch. I haven't had a change to investigate.
pull/420/head
Gleb Mazovetskiy 6 years ago committed by Anders Jenbo
parent
commit
a2e821d241
  1. 8
      CMakeLists.txt
  2. 4
      Source/diablo.cpp
  3. 1
      Source/inv.h
  4. 2
      Source/scrollrt.cpp
  5. 1
      Source/scrollrt.h
  6. 12
      SourceS/sdl2_to_1_2_backports.h
  7. 28
      SourceS/sdl_compat.h
  8. 10
      SourceX/DiabloUI/credits.cpp
  9. 68
      SourceX/DiabloUI/diabloui.cpp
  10. 23
      SourceX/DiabloUI/dialogs.cpp
  11. 5
      SourceX/DiabloUI/title.cpp
  12. 20
      SourceX/controls/README.md
  13. 65
      SourceX/controls/controller.cpp
  14. 18
      SourceX/controls/controller.h
  15. 30
      SourceX/controls/controller_buttons.h
  16. 91
      SourceX/controls/controller_motion.cpp
  17. 23
      SourceX/controls/controller_motion.h
  18. 159
      SourceX/controls/devices/game_controller.cpp
  19. 22
      SourceX/controls/devices/game_controller.h
  20. 193
      SourceX/controls/devices/joystick.cpp
  21. 19
      SourceX/controls/devices/joystick.h
  22. 202
      SourceX/controls/devices/kbcontroller.cpp
  23. 23
      SourceX/controls/devices/kbcontroller.h
  24. 156
      SourceX/controls/game_controls.cpp
  25. 79
      SourceX/controls/game_controls.h
  26. 74
      SourceX/controls/menu_controls.cpp
  27. 24
      SourceX/controls/menu_controls.h
  28. 416
      SourceX/controls/plrctrls.cpp
  29. 44
      SourceX/controls/plrctrls.h
  30. 3
      SourceX/miniwin/misc.cpp
  31. 2
      SourceX/miniwin/misc_dx.cpp
  32. 218
      SourceX/miniwin/misc_msg.cpp

8
CMakeLists.txt

@ -208,6 +208,14 @@ add_library(devilution STATIC
set(devilutionx_SRCS
SourceX/dx.cpp
SourceX/controls/devices/game_controller.cpp
SourceX/controls/devices/joystick.cpp
SourceX/controls/devices/kbcontroller.cpp
SourceX/controls/controller.cpp
SourceX/controls/controller_motion.cpp
SourceX/controls/game_controls.cpp
SourceX/controls/menu_controls.cpp
SourceX/controls/plrctrls.cpp
SourceX/miniwin/ddraw.cpp
SourceX/miniwin/misc.cpp
SourceX/miniwin/misc_io.cpp

4
Source/diablo.cpp

@ -1675,6 +1675,8 @@ void game_loop(BOOL bStartup)
}
}
extern void plrctrls_game_logic();
void game_logic()
{
if (PauseMode == 2) {
@ -1720,6 +1722,8 @@ void game_logic()
CheckQuests();
force_redraw |= 1;
pfile_update(FALSE);
plrctrls_game_logic();
}
void timeout_cursor(BOOL bTimeout)

1
Source/inv.h

@ -4,6 +4,7 @@
extern BOOL invflag;
extern BOOL drawsbarflag;
extern const InvXY InvRect[73];
void FreeInvGFX();
void InitInv();

2
Source/scrollrt.cpp

@ -102,7 +102,7 @@ static void scrollrt_draw_cursor_item()
assert(! sgdwCursWdt);
if (pcurs <= 0 || cursW == 0 || cursH == 0) {
if (pcurs <= 0 || cursW == 0 || cursH == 0 || sgbControllerActive) {
return;
}

1
Source/scrollrt.h

@ -2,6 +2,7 @@
#ifndef __SCROLLRT_H__
#define __SCROLLRT_H__
extern bool sgbControllerActive;
extern int light_table_index;
extern BYTE *gpBufStart;
extern BYTE *gpBufEnd;

12
SourceS/sdl2_to_1_2_backports.h

@ -17,7 +17,6 @@
#define SDL_zero(x) SDL_memset(&(x), 0, sizeof((x)))
#define SDL_InvalidParamError(param) SDL_SetError("Parameter '%s' is invalid", (param))
#define SDL_Log puts
#define SDL_floor floor
//== Events handling
@ -47,6 +46,17 @@
// For now we only process ASCII input when using SDL1.
#define SDL_TEXTINPUTEVENT_TEXT_SIZE 2
#define SDL_JoystickNameForIndex SDL_JoystickName
inline void SDL_Log(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
puts("");
}
static SDL_bool SDLBackport_IsTextInputActive = SDL_FALSE;
inline SDL_bool SDL_IsTextInputActive()

28
SourceS/sdl_compat.h

@ -1,6 +1,34 @@
// Compatibility wrappers for SDL 1 & 2.
#pragma once
#include <SDL.h>
#ifndef USE_SDL1
#define SDLC_KEYSTATE_LEFTSHIFT SDL_SCANCODE_LSHIFT
#define SDLC_KEYSTATE_RIGHTSHIFT SDL_SCANCODE_RSHIFT
#define SDLC_KEYSTATE_MENU SDL_SCANCODE_MENU
#define SDLC_KEYSTATE_UP SDL_SCANCODE_UP
#define SDLC_KEYSTATE_DOWN SDL_SCANCODE_DOWN
#define SDLC_KEYSTATE_LEFT SDL_SCANCODE_LEFT
#define SDLC_KEYSTATE_RIGHT SDL_SCANCODE_RIGHT
#else
#define SDLC_KEYSTATE_LEFTSHIFT SDLK_LSHIFT
#define SDLC_KEYSTATE_RIGHTSHIFT SDLK_LSHIFT
#define SDLC_KEYSTATE_MENU SDLK_MENU
#define SDLC_KEYSTATE_UP SDLK_UP
#define SDLC_KEYSTATE_DOWN SDLK_DOWN
#define SDLC_KEYSTATE_LEFT SDLK_LEFT
#define SDLC_KEYSTATE_RIGHT SDLK_RIGHT
#endif
inline const Uint8 *SDLC_GetKeyState()
{
#ifndef USE_SDL1
return SDL_GetKeyboardState(nullptr);
#else
return SDL_GetKeyState(nullptr);
#endif
}
inline int SDLC_SetColorKey(SDL_Surface *surface, Uint32 key)
{
#ifdef USE_SDL1

10
SourceX/DiabloUI/credits.cpp

@ -2,6 +2,7 @@
#include <memory>
#include <vector>
#include "controls/menu_controls.h"
#include "devilution.h"
#include "miniwin/ddraw.h"
@ -259,6 +260,15 @@ BOOL UiCreditsDialog(int a1)
break;
case SDL_QUIT:
exit(0);
default:
switch (GetMenuAction(event)) {
case MenuAction::BACK:
case MenuAction::SELECT:
endMenu = true;
break;
default:
break;
}
}
}
} while (!endMenu && !credits_renderer.Finished());

68
SourceX/DiabloUI/diabloui.cpp

@ -5,6 +5,8 @@
#include <string>
#include <algorithm>
#include "controls/menu_controls.h"
#include "DiabloUI/scrollbar.h"
#include "DiabloUI/diabloui.h"
@ -181,6 +183,34 @@ bool UiFocusNavigation(SDL_Event *event)
if (event->type == SDL_QUIT)
exit(0);
switch (GetMenuAction(*event)) {
case MenuAction::SELECT:
UiFocusNavigationSelect();
return true;
case MenuAction::UP:
UiFocus(SelectedItem - 1, UiItemsWraps);
return true;
case MenuAction::DOWN:
UiFocus(SelectedItem + 1, UiItemsWraps);
return true;
case MenuAction::PAGE_UP:
UiFocusPageUp();
return true;
case MenuAction::PAGE_DOWN:
UiFocusPageDown();
return true;
case MenuAction::DELETE:
UiFocusNavigationYesNo();
return true;
case MenuAction::BACK:
if (!gfnListEsc)
break;
UiFocusNavigationEsc();
return true;
default:
break;
}
switch (event->type) {
case SDL_KEYUP:
case SDL_MOUSEBUTTONUP:
@ -203,39 +233,6 @@ bool UiFocusNavigation(SDL_Event *event)
mainmenu_restart_repintro();
}
if (event->type == SDL_KEYDOWN) {
switch (event->key.keysym.sym) {
case SDLK_UP:
UiFocus(SelectedItem - 1, UiItemsWraps);
return true;
case SDLK_DOWN:
UiFocus(SelectedItem + 1, UiItemsWraps);
return true;
case SDLK_TAB:
if (SDL_GetModState() & KMOD_SHIFT)
UiFocus(SelectedItem - 1, UiItemsWraps);
else
UiFocus(SelectedItem + 1, UiItemsWraps);
return true;
case SDLK_PAGEUP:
UiFocusPageUp();
return true;
case SDLK_PAGEDOWN:
UiFocusPageDown();
return true;
case SDLK_RETURN:
case SDLK_KP_ENTER:
case SDLK_SPACE:
UiFocusNavigationSelect();
return true;
case SDLK_DELETE:
UiFocusNavigationYesNo();
return true;
default:
break;
}
}
if (SDL_IsTextInputActive()) {
switch (event->type) {
case SDL_KEYDOWN: {
@ -289,11 +286,6 @@ bool UiFocusNavigation(SDL_Event *event)
if (UiItemMouseEvents(event, gUiItems, gUiItemCnt))
return true;
if (gfnListEsc && event->type == SDL_KEYDOWN && event->key.keysym.sym == SDLK_ESCAPE) {
UiFocusNavigationEsc();
return true;
}
return false;
}

23
SourceX/DiabloUI/dialogs.cpp

@ -1,5 +1,6 @@
#include "DiabloUI/dialogs.h"
#include "controls/menu_controls.h"
#include "devilution.h"
#include "dx.h"
#include "DiabloUI/diabloui.h"
@ -257,24 +258,22 @@ void DialogLoop(UiItem *items, std::size_t num_items, UiItem *render_behind, std
do {
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_KEYDOWN:
switch (event.key.keysym.sym) {
case SDLK_ESCAPE:
case SDLK_RETURN:
case SDLK_KP_ENTER:
case SDLK_SPACE:
state = State::OK;
break;
default:
break;
}
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
UiItemMouseEvents(&event, items, num_items);
break;
case SDL_QUIT:
exit(0);
default:
switch (GetMenuAction(event)) {
case MenuAction::BACK:
case MenuAction::SELECT:
state = State::OK;
break;
default:
break;
}
break;
}
}

5
SourceX/DiabloUI/title.cpp

@ -1,4 +1,5 @@
#include "devilution.h"
#include "controls/menu_controls.h"
#include "DiabloUI/diabloui.h"
namespace dvl {
@ -34,6 +35,10 @@ void UiTitleDialog()
UiFadeIn();
while (SDL_PollEvent(&event)) {
if (GetMenuAction(event) != MenuAction::NONE) {
endMenu = true;
break;
}
switch (event.type) {
case SDL_KEYDOWN: /* To match the original uncomment this
if (event.key.keysym.sym == SDLK_UP

20
SourceX/controls/README.md

@ -0,0 +1,20 @@
# Controls handling
DevilutionX supports mouse & keyboard and gamepad input.
This directory currently mostly handles gamepad input.
Low-level gamepad handling is abstracted and 3 implementations are provided:
1. SDL2 controller API.
2. SDL 1&2 joystick API.
This can be used in SDL1 joystick platforms and for mapping additional
buttons not defined by SDL2 controller mappings (e.g. additional Nintendo
Switch arrows).
3. Keyboard keys acting as controller buttons.
This can be used for testing, or on devices where this is the
only or the easiest API to use (e.g. RetroFW).

65
SourceX/controls/controller.cpp

@ -0,0 +1,65 @@
#include "controls/controller.h"
#include "controls/devices/kbcontroller.h"
#include "controls/devices/joystick.h"
#include "controls/devices/game_controller.h"
namespace dvl {
ControllerButtonEvent ToControllerButtonEvent(const SDL_Event &event)
{
ControllerButtonEvent result{ ControllerButton::NONE, false };
switch (event.type) {
#ifndef USE_SDL1
case SDL_CONTROLLERAXISMOTION:
result.up = event.caxis.value == 0;
break;
case SDL_CONTROLLERBUTTONUP:
#endif
case SDL_JOYBUTTONUP:
case SDL_KEYUP:
result.up = true;
break;
default:
break;
}
#if HAS_KBCTRL == 1
result.button = KbCtrlToControllerButton(event);
if (result.button != ControllerButton::NONE)
return result;
#endif
#ifndef USE_SDL1
result.button = GameControllerToControllerButton(event);
if (result.button != ControllerButton::NONE)
return result;
#endif
result.button = JoyButtonToControllerButton(event);
return result;
}
bool IsControllerButtonPressed(ControllerButton button)
{
bool result = false;
#ifndef USE_SDL1
result = result || IsGameControllerButtonPressed(button);
#endif
#if HAS_KBCTRL == 1
result = result || IsKbCtrlButtonPressed(button);
#endif
result = result || IsJoystickButtonPressed(button);
return result;
}
void InitController()
{
InitJoystick();
#ifndef USE_SDL1
InitGameController();
#endif
}
} // namespace dvl

18
SourceX/controls/controller.h

@ -0,0 +1,18 @@
#pragma once
#include "controls/controller_buttons.h"
namespace dvl {
struct ControllerButtonEvent {
ControllerButton button;
bool up;
};
ControllerButtonEvent ToControllerButtonEvent(const SDL_Event &event);
bool IsControllerButtonPressed(ControllerButton button);
void InitController();
} // namespace dvl

30
SourceX/controls/controller_buttons.h

@ -0,0 +1,30 @@
#pragma once
// Unifies joystick, gamepad, and keyboard controller APIs.
#include "devilution.h"
namespace dvl {
// NOTE: A, B, X, Y refer to physical positions on an XBox 360 controller.
// A<->B and X<->Y are reversed on a Nintendo controller.
enum class ControllerButton {
NONE = 0,
AXIS_TRIGGERLEFT, // ZL (aka L2)
AXIS_TRIGGERRIGHT, // ZR (aka R2)
BUTTON_A, // Bottom button
BUTTON_B, // Right button
BUTTON_X, // Left button
BUTTON_Y, // TOP button
BUTTON_LEFTSTICK,
BUTTON_RIGHTSTICK,
BUTTON_LEFTSHOULDER,
BUTTON_RIGHTSHOULDER,
BUTTON_START,
BUTTON_BACK,
BUTTON_DPAD_UP,
BUTTON_DPAD_DOWN,
BUTTON_DPAD_LEFT,
BUTTON_DPAD_RIGHT
};
} // namespace dvl

91
SourceX/controls/controller_motion.cpp

@ -0,0 +1,91 @@
#include "controls/controller_motion.h"
#include "controls/devices/game_controller.h"
namespace dvl {
namespace {
void ScaleJoystickAxes(float *x, float *y, float deadzone)
{
//radial and scaled dead_zone
//http://www.third-helix.com/2013/04/12/doing-thumbstick-dead-zones-right.html
//input values go from -32767.0...+32767.0, output values are from -1.0 to 1.0;
if (deadzone == 0) {
return;
}
if (deadzone >= 1.0) {
*x = 0;
*y = 0;
return;
}
const float maximum = 32767.0f;
float analog_x = *x;
float analog_y = *y;
float dead_zone = deadzone * maximum;
float magnitude = sqrtf(analog_x * analog_x + analog_y * analog_y);
if (magnitude >= dead_zone) {
// find scaled axis values with magnitudes between zero and maximum
float scalingFactor = 1.0 / magnitude * (magnitude - dead_zone) / (maximum - dead_zone);
analog_x = (analog_x * scalingFactor);
analog_y = (analog_y * scalingFactor);
// clamp to ensure results will never exceed the max_axis value
float clamping_factor = 1.0f;
float abs_analog_x = fabs(analog_x);
float abs_analog_y = fabs(analog_y);
if (abs_analog_x > 1.0 || abs_analog_y > 1.0) {
if (abs_analog_x > abs_analog_y) {
clamping_factor = 1 / abs_analog_x;
} else {
clamping_factor = 1 / abs_analog_y;
}
}
*x = (clamping_factor * analog_x);
*y = (clamping_factor * analog_y);
} else {
*x = 0;
*y = 0;
}
}
} // namespace
float leftStickX, leftStickY, rightStickX, rightStickY;
float leftStickXUnscaled, leftStickYUnscaled, rightStickXUnscaled, rightStickYUnscaled;
bool leftStickNeedsScaling, rightStickNeedsScaling;
void ScaleJoysticks()
{
constexpr float rightDeadzone = 0.07;
constexpr float leftDeadzone = 0.07;
if (leftStickNeedsScaling) {
leftStickX = leftStickXUnscaled;
leftStickY = leftStickYUnscaled;
ScaleJoystickAxes(&leftStickX, &leftStickY, leftDeadzone);
leftStickNeedsScaling = false;
}
if (rightStickNeedsScaling) {
rightStickX = rightStickXUnscaled;
rightStickY = rightStickYUnscaled;
ScaleJoystickAxes(&rightStickX, &rightStickY, rightDeadzone);
rightStickNeedsScaling = false;
}
}
// Updates motion state for mouse and joystick sticks.
bool ProcessControllerMotion(const SDL_Event &event)
{
#ifndef USE_SDL1
if (ProcessGameControllerAxisMotion(event))
return true;
#endif
return false;
}
} // namespace dvl

23
SourceX/controls/controller_motion.h

@ -0,0 +1,23 @@
#pragma once
// Processes and stores mouse and joystick motion.
#include "devilution.h"
namespace dvl {
// Raw axis values.
extern float leftStickXUnscaled, leftStickYUnscaled, rightStickXUnscaled, rightStickYUnscaled;
// Axis values scaled to [-1, 1] range and clamped to a deadzone.
extern float leftStickX, leftStickY, rightStickX, rightStickY;
// Whether stick positions have been updated and need rescaling.
extern bool leftStickNeedsScaling, rightStickNeedsScaling;
void ScaleJoysticks();
// Updates motion state for mouse and joystick sticks.
bool ProcessControllerMotion(const SDL_Event &event);
} // namespace dvl

159
SourceX/controls/devices/game_controller.cpp

@ -0,0 +1,159 @@
#include "controls/devices/game_controller.h"
#ifndef USE_SDL1
#include "controls/controller_motion.h"
#include "controls/devices/joystick.h"
#include "stubs.h"
namespace dvl {
static SDL_GameController *current_game_controller = nullptr;
ControllerButton GameControllerToControllerButton(const SDL_Event &event)
{
switch (event.type) {
case SDL_CONTROLLERAXISMOTION:
switch (event.caxis.axis) {
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
return ControllerButton::AXIS_TRIGGERLEFT;
break;
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
return ControllerButton::AXIS_TRIGGERRIGHT;
break;
}
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
switch (event.cbutton.button) {
case SDL_CONTROLLER_BUTTON_A:
return ControllerButton::BUTTON_A;
case SDL_CONTROLLER_BUTTON_B:
return ControllerButton::BUTTON_B;
case SDL_CONTROLLER_BUTTON_X:
return ControllerButton::BUTTON_X;
case SDL_CONTROLLER_BUTTON_Y:
return ControllerButton::BUTTON_Y;
case SDL_CONTROLLER_BUTTON_LEFTSTICK:
return ControllerButton::BUTTON_LEFTSTICK;
case SDL_CONTROLLER_BUTTON_RIGHTSTICK:
return ControllerButton::BUTTON_RIGHTSTICK;
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER:
return ControllerButton::BUTTON_LEFTSHOULDER;
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER:
return ControllerButton::BUTTON_RIGHTSHOULDER;
case SDL_CONTROLLER_BUTTON_START:
return ControllerButton::BUTTON_START;
case SDL_CONTROLLER_BUTTON_BACK:
return ControllerButton::BUTTON_BACK;
case SDL_CONTROLLER_BUTTON_DPAD_UP:
return ControllerButton::BUTTON_DPAD_UP;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
return ControllerButton::BUTTON_DPAD_DOWN;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
return ControllerButton::BUTTON_DPAD_LEFT;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
return ControllerButton::BUTTON_DPAD_RIGHT;
default:
break;
}
default:
break;
}
return ControllerButton::NONE;
}
namespace {
SDL_GameControllerButton ControllerButtonToGameControllerButton(ControllerButton button)
{
if (button == ControllerButton::AXIS_TRIGGERLEFT || button == ControllerButton::AXIS_TRIGGERRIGHT)
UNIMPLEMENTED();
switch (button) {
case ControllerButton::BUTTON_A:
return SDL_CONTROLLER_BUTTON_A;
case ControllerButton::BUTTON_B:
return SDL_CONTROLLER_BUTTON_B;
case ControllerButton::BUTTON_X:
return SDL_CONTROLLER_BUTTON_X;
case ControllerButton::BUTTON_Y:
return SDL_CONTROLLER_BUTTON_Y;
case ControllerButton::BUTTON_BACK:
return SDL_CONTROLLER_BUTTON_BACK;
case ControllerButton::BUTTON_START:
return SDL_CONTROLLER_BUTTON_START;
case ControllerButton::BUTTON_LEFTSTICK:
return SDL_CONTROLLER_BUTTON_LEFTSTICK;
case ControllerButton::BUTTON_RIGHTSTICK:
return SDL_CONTROLLER_BUTTON_RIGHTSTICK;
case ControllerButton::BUTTON_LEFTSHOULDER:
return SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
case ControllerButton::BUTTON_RIGHTSHOULDER:
return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
case ControllerButton::BUTTON_DPAD_UP:
return SDL_CONTROLLER_BUTTON_DPAD_UP;
case ControllerButton::BUTTON_DPAD_DOWN:
return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
case ControllerButton::BUTTON_DPAD_LEFT:
return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
case ControllerButton::BUTTON_DPAD_RIGHT:
return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
default:
return SDL_CONTROLLER_BUTTON_INVALID;
}
}
} // namespace
bool IsGameControllerButtonPressed(ControllerButton button)
{
if (current_game_controller == nullptr)
return false;
const SDL_GameControllerButton gc_button = ControllerButtonToGameControllerButton(button);
return gc_button != SDL_CONTROLLER_BUTTON_INVALID && SDL_GameControllerGetButton(current_game_controller, gc_button);
}
bool ProcessGameControllerAxisMotion(const SDL_Event &event)
{
if (event.type != SDL_CONTROLLERAXISMOTION)
return false;
switch (event.caxis.axis) {
case SDL_CONTROLLER_AXIS_LEFTX:
leftStickXUnscaled = event.caxis.value;
leftStickNeedsScaling = true;
break;
case SDL_CONTROLLER_AXIS_LEFTY:
leftStickYUnscaled = -event.caxis.value;
leftStickNeedsScaling = true;
break;
case SDL_CONTROLLER_AXIS_RIGHTX:
rightStickXUnscaled = event.caxis.value;
rightStickNeedsScaling = true;
break;
case SDL_CONTROLLER_AXIS_RIGHTY:
rightStickYUnscaled = -event.caxis.value;
rightStickNeedsScaling = true;
break;
default:
return false;
}
return true;
}
SDL_GameController *CurrentGameController()
{
return current_game_controller;
}
void InitGameController()
{
if (CurrentJoystickIndex() == -1)
return;
const SDL_JoystickGUID guid = SDL_JoystickGetGUID(CurrentJoystick());
SDL_Log("Opening gamepad %d: %s", CurrentJoystickIndex(), SDL_GameControllerMappingForGUID(guid));
current_game_controller = SDL_GameControllerOpen(CurrentJoystickIndex());
if (current_game_controller == nullptr)
SDL_Log(SDL_GetError());
}
} // namespace dvl
#endif

22
SourceX/controls/devices/game_controller.h

@ -0,0 +1,22 @@
#pragma once
#include <SDL.h>
#include "controls/controller_buttons.h"
#ifndef USE_SDL1
namespace dvl {
ControllerButton GameControllerToControllerButton(const SDL_Event &event);
bool IsGameControllerButtonPressed(ControllerButton button);
bool ProcessGameControllerAxisMotion(const SDL_Event &event);
SDL_GameController *CurrentGameController();
// Must be called after InitJoystick().
void InitGameController();
} // namespace dvl
#endif

193
SourceX/controls/devices/joystick.cpp

@ -0,0 +1,193 @@
#include "controls/devices/joystick.h"
#include "stubs.h"
#ifdef SWITCH
#define JOY_BUTTON_DPAD_LEFT 16
#define JOY_BUTTON_DPAD_UP 17
#define JOY_BUTTON_DPAD_RIGHT 18
#define JOY_BUTTON_DPAD_DOWN 19
#endif
namespace dvl {
ControllerButton JoyButtonToControllerButton(const SDL_Event &event)
{
switch (event.type) {
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
switch (event.jbutton.button) {
#ifdef JOY_BUTTON_A
case JOY_BUTTON_A:
return ControllerButton::BUTTON_A;
#endif
#ifdef JOY_BUTTON_B
case JOY_BUTTON_B:
return ControllerButton::BUTTON_B;
#endif
#ifdef JOY_BUTTON_X
case JOY_BUTTON_X:
return ControllerButton::BUTTON_X;
#endif
#ifdef JOY_BUTTON_Y
case JOY_BUTTON_Y:
return ControllerButton::BUTTON_Y;
#endif
#ifdef JOY_BUTTON_LEFTSTICK
case JOY_BUTTON_LEFTSTICK:
return ControllerButton::BUTTON_LEFTSTICK;
#endif
#ifdef JOY_BUTTON_RIGHTSTICK
case JOY_BUTTON_RIGHTSTICK:
return ControllerButton::BUTTON_RIGHTSTICK;
#endif
#ifdef JOY_BUTTON_LEFTSHOULDER
case JOY_BUTTON_LEFTSHOULDER:
return ControllerButton::BUTTON_LEFTSHOULDER;
#endif
#ifdef JOY_BUTTON_RIGHTSHOULDER
case JOY_BUTTON_RIGHTSHOULDER:
return ControllerButton::BUTTON_RIGHTSHOULDER;
#endif
#ifdef JOY_BUTTON_START
case JOY_BUTTON_START:
return ControllerButton::BUTTON_START;
#endif
#ifdef JOY_BUTTON_BACK
case JOY_BUTTON_BACK:
return ControllerButton::BUTTON_BACK;
#endif
#ifdef JOY_BUTTON_DPAD_LEFT
case JOY_BUTTON_DPAD_LEFT:
return ControllerButton::BUTTON_DPAD_LEFT;
#endif
#ifdef JOY_BUTTON_DPAD_UP
case JOY_BUTTON_DPAD_UP:
return ControllerButton::BUTTON_DPAD_UP;
#endif
#ifdef JOY_BUTTON_DPAD_RIGHT
case JOY_BUTTON_DPAD_RIGHT:
return ControllerButton::BUTTON_DPAD_RIGHT;
#endif
#ifdef JOY_BUTTON_DPAD_DOWN
case JOY_BUTTON_DPAD_DOWN:
return ControllerButton::BUTTON_DPAD_DOWN;
#endif
default:
break;
}
break;
}
return ControllerButton::NONE;
}
int JoyButtonToControllerButton(ControllerButton button) {
if (button == ControllerButton::AXIS_TRIGGERLEFT || button == ControllerButton::AXIS_TRIGGERRIGHT)
UNIMPLEMENTED();
switch (button) {
#ifdef JOY_BUTTON_A
case ControllerButton::BUTTON_A:
return JOY_BUTTON_A;
#endif
#ifdef JOY_BUTTON_B
case ControllerButton::BUTTON_B:
return JOY_BUTTON_B;
#endif
#ifdef JOY_BUTTON_X
case ControllerButton::BUTTON_X:
return JOY_BUTTON_X;
#endif
#ifdef JOY_BUTTON_Y
case ControllerButton::BUTTON_Y:
return JOY_BUTTON_Y;
#endif
#ifdef JOY_BUTTON_BACK
case ControllerButton::BUTTON_BACK:
return JOY_BUTTON_BACK;
#endif
#ifdef JOY_BUTTON_START
case ControllerButton::BUTTON_START:
return JOY_BUTTON_START;
#endif
#ifdef JOY_BUTTON_LEFTSTICK
case ControllerButton::BUTTON_LEFTSTICK:
return JOY_BUTTON_LEFTSTICK;
#endif
#ifdef JOY_BUTTON_RIGHTSTICK
case ControllerButton::BUTTON_RIGHTSTICK:
return JOY_BUTTON_RIGHTSTICK;
#endif
#ifdef JOY_BUTTON_LEFTSHOULDER
case ControllerButton::BUTTON_LEFTSHOULDER:
return JOY_BUTTON_LEFTSHOULDER;
#endif
#ifdef JOY_BUTTON_RIGHTSHOULDER
case ControllerButton::BUTTON_RIGHTSHOULDER:
return JOY_BUTTON_RIGHTSHOULDER;
#endif
#ifdef JOY_BUTTON_DPAD_UP
case ControllerButton::BUTTON_DPAD_UP:
return JOY_BUTTON_DPAD_UP;
#endif
#ifdef JOY_BUTTON_DPAD_DOWN
case ControllerButton::BUTTON_DPAD_DOWN:
return JOY_BUTTON_DPAD_DOWN;
#endif
#ifdef JOY_BUTTON_DPAD_LEFT
case ControllerButton::BUTTON_DPAD_LEFT:
return JOY_BUTTON_DPAD_LEFT;
#endif
#ifdef JOY_BUTTON_DPAD_RIGHT
case ControllerButton::BUTTON_DPAD_RIGHT:
return JOY_BUTTON_DPAD_RIGHT;
#endif
default:
return -1;
}
}
bool IsJoystickButtonPressed(ControllerButton button) {
if (CurrentJoystick() == nullptr)
return false;
const int joy_button = JoyButtonToControllerButton(button);
return joy_button != -1 && SDL_JoystickGetButton(CurrentJoystick(), joy_button);
}
static SDL_Joystick *current_joystick = nullptr;
SDL_Joystick *CurrentJoystick()
{
return current_joystick;
}
static int current_joystick_index = -1;
int CurrentJoystickIndex()
{
return current_joystick_index;
}
void InitJoystick()
{
if (SDL_NumJoysticks() == 0)
return;
// Get the first available controller.
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
#ifndef USE_SDL1
if (!SDL_IsGameController(i))
continue;
#endif
SDL_Log("Initializing joystick %d: %s", i, SDL_JoystickNameForIndex(i));
current_joystick = SDL_JoystickOpen(i);
if (current_joystick == nullptr) {
SDL_Log(SDL_GetError());
continue;
}
current_joystick_index = i;
break;
}
}
} // namespace dvl

19
SourceX/controls/devices/joystick.h

@ -0,0 +1,19 @@
#pragma once
// Joystick mappings for SDL1 and additional buttons on SDL2.
#include <SDL.h>
#include "controls/controller_buttons.h"
namespace dvl {
ControllerButton JoyButtonToControllerButton(const SDL_Event &event);
bool IsJoystickButtonPressed(ControllerButton button);
SDL_Joystick *CurrentJoystick();
int CurrentJoystickIndex();
void InitJoystick();
} // namespace dvl

202
SourceX/controls/devices/kbcontroller.cpp

@ -0,0 +1,202 @@
#include "controls/devices/kbcontroller.h"
#if defined(RETROFW)
#define HAS_KBCTRL 1
#define KBCTRL_BUTTON_DPAD_LEFT SDLK_LEFT
#define KBCTRL_BUTTON_DPAD_RIGHT SDLK_RIGHT
#define KBCTRL_BUTTON_DPAD_UP SDLK_UP
#define KBCTRL_BUTTON_DPAD_DOWN SDLK_DOWN
#define KBCTRL_BUTTON_B SDLK_LCTRL
#define KBCTRL_BUTTON_A SDLK_LALT
#define KBCTRL_BUTTON_Y SDLK_SPACE
#define KBCTRL_BUTTON_X SDLK_LSHIFT
#define KBCTRL_BUTTON_RIGHTSHOULDER SDLK_BACKSPACE
#define KBCTRL_BUTTON_LEFTSHOULDER SDLK_TAB
#define KBCTRL_BUTTON_START SDLK_RETURN
#define KBCTRL_BUTTON_BACK SDLK_ESCAPE
#define KBCTRL_MODIFIER_KEY SDLK_END // The suspend key on RG300
#endif
#if HAS_KBCTRL == 1
#include "sdl2_to_1_2_backports.h"
#include "sdl_compat.h"
#include "stubs.h"
namespace dvl {
namespace {
bool IsModifierKey()
{
#ifdef KBCTRL_MODIFIER_KEY
return SDLC_GetKeyboardState(nullptr)[KBCTRL_MODIFIER_KEY];
#else
return false;
#endif
}
} // namespace
ControllerButton KbCtrlToControllerButton(const SDL_Event &event)
{
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP:
switch (event.key.keysym.sym) {
#ifdef KBCTRL_BUTTON_A
case KBCTRL_BUTTON_A:
return ControllerButton::BUTTON_A;
#endif
#ifdef KBCTRL_BUTTON_B
case KBCTRL_BUTTON_B:
return ControllerButton::BUTTON_B;
#endif
#ifdef KBCTRL_BUTTON_X
case KBCTRL_BUTTON_X:
if (IsModifierKey())
return ControllerButton::BUTTON_LEFTSTICK;
return ControllerButton::BUTTON_X;
#endif
#ifdef KBCTRL_BUTTON_Y
case KBCTRL_BUTTON_Y:
if (IsModifierKey())
return ControllerButton::BUTTON_RIGHTSTICK;
return ControllerButton::BUTTON_Y;
#endif
#ifdef KBCTRL_BUTTON_LEFTSTICK
case KBCTRL_BUTTON_LEFTSTICK:
return ControllerButton::BUTTON_LEFTSTICK;
#endif
#ifdef KBCTRL_BUTTON_RIGHTSTICK
case KBCTRL_BUTTON_RIGHTSTICK:
return ControllerButton::BUTTON_RIGHTSTICK;
#endif
#ifdef KBCTRL_BUTTON_LEFTSHOULDER
case KBCTRL_BUTTON_LEFTSHOULDER:
if (IsModifierKey())
return ControllerButton::AXIS_TRIGGERLEFT;
return ControllerButton::BUTTON_LEFTSHOULDER;
#endif
#ifdef KBCTRL_BUTTON_RIGHTSHOULDER
case KBCTRL_BUTTON_RIGHTSHOULDER:
if (IsModifierKey())
return ControllerButton::AXIS_TRIGGERRIGHT;
return ControllerButton::BUTTON_RIGHTSHOULDER;
#endif
#ifdef KBCTRL_BUTTON_START
case KBCTRL_BUTTON_START:
return ControllerButton::BUTTON_START;
#endif
#ifdef KBCTRL_BUTTON_BACK
case KBCTRL_BUTTON_BACK:
return ControllerButton::BUTTON_BACK;
#endif
#ifdef KBCTRL_BUTTON_DPAD_UP
case KBCTRL_BUTTON_DPAD_UP:
return ControllerButton::BUTTON_DPAD_UP;
#endif
#ifdef KBCTRL_BUTTON_DPAD_DOWN
case KBCTRL_BUTTON_DPAD_DOWN:
return ControllerButton::BUTTON_DPAD_DOWN;
#endif
#ifdef KBCTRL_BUTTON_DPAD_LEFT
case KBCTRL_BUTTON_DPAD_LEFT:
return ControllerButton::BUTTON_DPAD_LEFT;
#endif
#ifdef KBCTRL_BUTTON_DPAD_RIGHT
case KBCTRL_BUTTON_DPAD_RIGHT:
return ControllerButton::BUTTON_DPAD_RIGHT;
#endif
default:
return ControllerButton::NONE;
}
}
}
namespace {
int ControllerButtonToKbCtrlKeyCode(ControllerButton button)
{
if (button == ControllerButton::AXIS_TRIGGERLEFT || button == ControllerButton::AXIS_TRIGGERRIGHT)
UNIMPLEMENTED();
switch (button) {
#ifdef KBCTRL_BUTTON_A
case ControllerButton::BUTTON_A:
return KBCTRL_BUTTON_A;
#endif
#ifdef KBCTRL_BUTTON_B
case ControllerButton::BUTTON_B:
return KBCTRL_BUTTON_B;
#endif
#ifdef KBCTRL_BUTTON_X
case ControllerButton::BUTTON_X:
return KBCTRL_BUTTON_X;
#endif
#ifdef KBCTRL_BUTTON_Y
case ControllerButton::BUTTON_Y:
return KBCTRL_BUTTON_Y;
#endif
#ifdef KBCTRL_BUTTON_BACK
case ControllerButton::BUTTON_BACK:
return KBCTRL_BUTTON_BACK;
#endif
#ifdef KBCTRL_BUTTON_START
case ControllerButton::BUTTON_START:
return KBCTRL_BUTTON_START;
#endif
#ifdef KBCTRL_BUTTON_LEFTSTICK
case ControllerButton::BUTTON_LEFTSTICK:
return KBCTRL_BUTTON_LEFTSTICK;
#endif
#ifdef KBCTRL_BUTTON_RIGHTSTICK
case ControllerButton::BUTTON_RIGHTSTICK:
return KBCTRL_BUTTON_RIGHTSTICK;
#endif
#ifdef KBCTRL_BUTTON_LEFTSHOULDER
case ControllerButton::BUTTON_LEFTSHOULDER:
return KBCTRL_BUTTON_LEFTSHOULDER;
#endif
#ifdef KBCTRL_BUTTON_RIGHTSHOULDER
case ControllerButton::BUTTON_RIGHTSHOULDER:
return KBCTRL_BUTTON_RIGHTSHOULDER;
#endif
#ifdef KBCTRL_BUTTON_DPAD_UP
case ControllerButton::BUTTON_DPAD_UP:
return KBCTRL_BUTTON_DPAD_UP;
#endif
#ifdef KBCTRL_BUTTON_DPAD_DOWN
case ControllerButton::BUTTON_DPAD_DOWN:
return KBCTRL_BUTTON_DPAD_DOWN;
#endif
#ifdef KBCTRL_BUTTON_DPAD_LEFT
case ControllerButton::BUTTON_DPAD_LEFT:
return KBCTRL_BUTTON_DPAD_LEFT;
#endif
#ifdef KBCTRL_BUTTON_DPAD_RIGHT
case ControllerButton::BUTTON_DPAD_RIGHT:
return KBCTRL_BUTTON_DPAD_RIGHT;
#endif
default:
return -1;
}
}
} // namespace
bool IsKbCtrlButtonPressed(ControllerButton button)
{
int key_code = ControllerButtonToKbCtrlKeyCode(button);
if (key_code == -1)
return false;
#ifndef USE_SDL1
return SDL_GetKeyboardState(nullptr)[SDL_GetScancodeFromKey(key_code)];
#else
return SDL_GetKeyState(nullptr)[key_code];
#endif
}
} // namespace dvl
#endif

23
SourceX/controls/devices/kbcontroller.h

@ -0,0 +1,23 @@
#pragma once
// Keyboard keys acting like gamepad buttons
#ifndef HAS_KBCTRL
#define HAS_KBCTRL 0
#endif
#if defined(RETROFW)
#define HAS_KBCTRL 1
#endif
#if HAS_KBCTRL == 1
#include <SDL.h>
#include "controls/controller_buttons.h"
namespace dvl {
ControllerButton KbCtrlToControllerButton(const SDL_Event &event);
bool IsKbCtrlButtonPressed(ControllerButton button);
} // namespace dvl
#endif

156
SourceX/controls/game_controls.cpp

@ -0,0 +1,156 @@
#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/plrctrls.h"
namespace dvl {
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_X: // Left button
return 'X';
case ControllerButton::BUTTON_Y: // Top button
return DVL_VK_RETURN;
case ControllerButton::BUTTON_LEFTSTICK:
return 'Q'; // Quest log
case ControllerButton::BUTTON_START:
return DVL_VK_ESCAPE;
case ControllerButton::BUTTON_BACK:
return DVL_VK_TAB; // Map
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;
}
}
} // namespace
bool GetGameAction(const SDL_Event &event, GameAction *action)
{
const ControllerButtonEvent ctrl_event = ToControllerButtonEvent(event);
switch (ctrl_event.button) {
case ControllerButton::AXIS_TRIGGERLEFT: // ZL (aka L2)
if (!ctrl_event.up)
*action = GameAction(GameActionType::USE_HEALTH_POTION);
return true;
case ControllerButton::AXIS_TRIGGERRIGHT: // ZR (aka R2)
if (!ctrl_event.up)
*action = GameAction(GameActionType::USE_MANA_POTION);
return true;
case ControllerButton::BUTTON_B: // Right button
if (InGameMenu())
break; // Map to keyboard key
if (!ctrl_event.up)
*action = GameAction(GameActionType::PRIMARY_ACTION);
return true;
case ControllerButton::BUTTON_Y: // Top button
if (InGameMenu())
break; // Map to keyboard key
if (!ctrl_event.up)
*action = GameAction(GameActionType::SECONDARY_ACTION);
return true;
case ControllerButton::BUTTON_A: // Bottom button
if (InGameMenu())
break; // Map to keyboard key
if (!ctrl_event.up)
*action = GameAction(GameActionType::TOGGLE_QUICK_SPELL_MENU);
return true;
case ControllerButton::BUTTON_LEFTSHOULDER:
if (!stextflag && !ctrl_event.up)
*action = GameAction(GameActionType::TOGGLE_CHARACTER_INFO);
return true;
case ControllerButton::BUTTON_RIGHTSHOULDER:
if (!stextflag && !ctrl_event.up)
*action = GameAction(GameActionType::TOGGLE_INVENTORY);
return true;
case ControllerButton::BUTTON_DPAD_UP:
case ControllerButton::BUTTON_DPAD_DOWN:
case ControllerButton::BUTTON_DPAD_LEFT:
case ControllerButton::BUTTON_DPAD_RIGHT:
if (InGameMenu())
break;
// The rest is handled in charMovement() on every game_logic() call.
return true;
case ControllerButton::BUTTON_RIGHTSTICK:
*action = GameActionSendMouseLeftClick { ctrl_event.up };
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 if gamepad is active.
// We receive the same events as gamepad events.
if (CurrentGameController() != nullptr && event.type >= SDL_JOYAXISMOTION && event.type <= SDL_JOYBUTTONUP) {
return true;
}
#endif
return false;
}
bool ShouldSkipMovie(const SDL_Event &event)
{
if (GetMenuAction(event) != MenuAction::NONE)
return true;
switch (event.type) {
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
return event.button.button == SDL_BUTTON_LEFT;
case SDL_KEYDOWN:
case SDL_KEYUP:
return true;
default:
return false;
}
}
MoveDirection GetMoveDirection()
{
const float stickX = leftStickX;
const float stickY = leftStickY;
MoveDirection result{ MoveDirectionX::NONE, MoveDirectionY::NONE };
if (stickY >= 0.5 || IsControllerButtonPressed(ControllerButton::BUTTON_DPAD_UP)) {
result.y = MoveDirectionY::UP;
} else if (stickY <= -0.5 || IsControllerButtonPressed(ControllerButton::BUTTON_DPAD_DOWN)) {
result.y = MoveDirectionY::DOWN;
}
if (stickX <= -0.5 || IsControllerButtonPressed(ControllerButton::BUTTON_DPAD_LEFT)) {
result.x = MoveDirectionX::LEFT;
} else if (stickX >= 0.5 || IsControllerButtonPressed(ControllerButton::BUTTON_DPAD_RIGHT)) {
result.x = MoveDirectionX::RIGHT;
}
return result;
}
} // namespace dvl

79
SourceX/controls/game_controls.h

@ -0,0 +1,79 @@
#pragma once
#include "devilution.h"
namespace dvl {
enum class GameActionType {
NONE = 0,
USE_HEALTH_POTION,
USE_MANA_POTION,
PRIMARY_ACTION, // Talk to towners, click on inv items, attack, etc.
SECONDARY_ACTION, // Open chests, doors, pickup items.
TOGGLE_INVENTORY,
TOGGLE_CHARACTER_INFO,
TOGGLE_QUICK_SPELL_MENU,
SEND_KEY,
SEND_MOUSE_LEFT_CLICK
};
struct GameActionSendKey {
DWORD vk_code;
bool up;
};
struct GameActionSendMouseLeftClick {
bool up;
};
struct GameAction {
GameActionType type;
GameAction()
: type(GameActionType::NONE)
{
}
explicit GameAction(GameActionType type)
: type(type)
{
}
GameAction(GameActionSendKey send_key)
: type(GameActionType::SEND_KEY)
, send_key(send_key)
{
}
GameAction(GameActionSendMouseLeftClick send_mouse_left_click)
: type(GameActionType::SEND_MOUSE_LEFT_CLICK)
, send_mouse_left_click(send_mouse_left_click)
{
}
union {
GameActionSendKey send_key;
GameActionSendMouseLeftClick send_mouse_left_click;
};
};
bool GetGameAction(const SDL_Event &event, GameAction *action);
bool ShouldSkipMovie(const SDL_Event &event);
enum class MoveDirectionX {
NONE = 0,
LEFT,
RIGHT
};
enum class MoveDirectionY {
NONE = 0,
UP,
DOWN
};
struct MoveDirection {
MoveDirectionX x;
MoveDirectionY y;
};
MoveDirection GetMoveDirection();
} // namespace dvl

74
SourceX/controls/menu_controls.cpp

@ -0,0 +1,74 @@
#include "controls/menu_controls.h"
#include "controls/controller.h"
namespace dvl {
MenuAction GetMenuAction(const SDL_Event &event)
{
const ControllerButtonEvent ctrl_event = ToControllerButtonEvent(event);
if (!ctrl_event.up) {
switch (ctrl_event.button) {
case ControllerButton::BUTTON_B: // Right button
case ControllerButton::BUTTON_START:
return MenuAction::SELECT;
case ControllerButton::BUTTON_BACK:
case ControllerButton::BUTTON_A: // Bottom button
return MenuAction::BACK;
case ControllerButton::BUTTON_X: // Left button
return MenuAction::DELETE;
case ControllerButton::BUTTON_DPAD_UP:
return MenuAction::UP;
case ControllerButton::BUTTON_DPAD_DOWN:
return MenuAction::DOWN;
case ControllerButton::BUTTON_DPAD_LEFT:
return MenuAction::LEFT;
case ControllerButton::BUTTON_DPAD_RIGHT:
return MenuAction::RIGHT;
case ControllerButton::BUTTON_LEFTSHOULDER:
return MenuAction::PAGE_UP;
case ControllerButton::BUTTON_RIGHTSHOULDER:
return MenuAction::PAGE_DOWN;
default:
break;
}
}
#if HAS_KBCTRL == 0
if (event.type == SDL_KEYDOWN) {
switch (event.key.keysym.sym) {
case SDLK_UP:
return MenuAction::UP;
case SDLK_DOWN:
return MenuAction::DOWN;
case SDLK_TAB:
if (SDL_GetModState() & KMOD_SHIFT)
return MenuAction::UP;
else
return MenuAction::DOWN;
case SDLK_PAGEUP:
return MenuAction::PAGE_UP;
case SDLK_PAGEDOWN:
return MenuAction::PAGE_DOWN;
case SDLK_RETURN:
case SDLK_KP_ENTER:
case SDLK_SPACE:
return MenuAction::SELECT;
case SDLK_DELETE:
return MenuAction::DELETE;
case SDLK_LEFT:
return MenuAction::LEFT;
case SDLK_RIGHT:
return MenuAction::RIGHT;
case SDLK_ESCAPE:
return MenuAction::BACK;
default:
break;
}
}
#endif
return MenuAction::NONE;
} // namespace dvl
} // namespace dvl

24
SourceX/controls/menu_controls.h

@ -0,0 +1,24 @@
#pragma once
#include "devilution.h"
namespace dvl {
enum class MenuAction {
NONE = 0,
SELECT,
BACK,
DELETE,
UP,
DOWN,
LEFT,
RIGHT,
PAGE_UP,
PAGE_DOWN,
};
MenuAction GetMenuAction(const SDL_Event &event);
} // namespace dvl

416
SourceX/controls/plrctrls.cpp

@ -1,23 +1,36 @@
#include "diablo.h"
#include "../3rdParty/Storm/Source/storm.h"
#include "controls/plrctrls.h"
DEVILUTION_BEGIN_NAMESPACE
#include <cstdint>
extern float leftStickX;
extern float leftStickY;
#include "controls/controller_motion.h"
#include "controls/game_controls.h"
// JAKE: My functions for movement and interaction via keyboard/controller
bool newCurHidden = false;
static DWORD attacktick;
static DWORD invmove;
int slot = SLOTXY_INV_FIRST;
int spbslot = 0;
// Based on the Nintendo Switch port by @lantus, @erfg12, @rsn8887.
namespace dvl {
bool sgbControllerActive = false;
coords speedspellscoords[50];
int speedspellcount = 0;
// Native game menu, controlled by simulating a keyboard.
bool InGameMenu()
{
return stextflag > 0 || questlog || helpflag || talkflag || qtextflag || sgpCurrentMenu;
}
namespace {
DWORD invmove = 0;
int hsr[3] = { 0, 0, 0 }; // hot spell row counts
DWORD talkwait;
DWORD talktick;
DWORD castwait;
int slot = SLOTXY_INV_FIRST;
int spbslot = 0;
// Menu controlled by simulating a mouse.
bool InControlledMenu()
{
return invflag || spselflag || chrflag;
}
// 0 = not near, >0 = distance related player 1 coordinates
coords checkNearbyObjs(int x, int y, int diff)
@ -49,7 +62,7 @@ void checkItemsNearby(bool interact)
return; // item nearby, don't find objects
}
}
if (newCurHidden)
if (sgbControllerActive)
pcursitem = -1;
//sprintf(tempstr, "SCANNING FOR OBJECTS");
//NetSendCmdString(1 << myplr, tempstr);
@ -62,12 +75,16 @@ void checkItemsNearby(bool interact)
return;
}
}
if (newCurHidden)
if (sgbControllerActive)
pcursobj = -1;
}
void checkTownersNearby(bool interact)
{
if (pcursitem != -1)
// Items take priority over towners because the player can move
// items but not towners.
return;
for (int i = 0; i < 16; i++) {
if (checkNearbyObjs(towner[i]._tx, towner[i]._ty, 2).x != -1) {
if (towner[i]._ttype == -1)
@ -85,7 +102,8 @@ bool checkMonstersNearby(bool attack)
{
int closest = 0; // monster ID who is closest
coords objDistLast = { 99, 99 }; // previous obj distance
for (int i = 0; i < MAXMONSTERS; i++) {
// The first MAX_PLRS monsters are reserved for players' golems.
for (int i = MAX_PLRS; i < MAXMONSTERS; i++) {
int d_monster = dMonster[monster[i]._mx][monster[i]._my];
if (monster[i]._mFlags & MFLAG_HIDDEN || monster[i]._mhitpoints <= 0) // monster is hiding or dead, skip
continue;
@ -110,6 +128,7 @@ bool checkMonstersNearby(bool attack)
return false;
}
if (attack) {
static DWORD attacktick = 0;
DWORD ticks = GetTickCount();
if (ticks - attacktick > 100) { // prevent accidental double attacks
attacktick = ticks;
@ -131,8 +150,7 @@ void HideCursor()
SetCursorPos(320, 180);
MouseX = 320;
MouseY = 180;
SetCursor_(CURSOR_NONE);
newCurHidden = true;
sgbControllerActive = true;
}
void attrIncBtnSnap(int key)
@ -152,28 +170,27 @@ void attrIncBtnSnap(int key)
// first, find our cursor location
int slot = 0;
for (int i = 0; i < 4; i++) {
// 0 = x, 1 = y, 2 = width, 3 = height
if (MouseX >= attribute_inc_rects2[i][0]
&& MouseX <= attribute_inc_rects2[i][0] + attribute_inc_rects2[i][2]
&& MouseY >= attribute_inc_rects2[i][1]
&& MouseY <= attribute_inc_rects2[i][3] + attribute_inc_rects2[i][1]) {
if (MouseX >= ChrBtnsRect[i].x
&& MouseX <= ChrBtnsRect[i].x + ChrBtnsRect[i].w
&& MouseY >= ChrBtnsRect[i].y
&& MouseY <= ChrBtnsRect[i].h + ChrBtnsRect[i].y) {
slot = i;
break;
}
}
// set future location up or down
if (key == VK_UP) {
if (key == DVL_VK_UP) {
if (slot > 0)
slot--;
} else if (key == VK_DOWN) {
--slot;
} else if (key == DVL_VK_DOWN) {
if (slot < 3)
slot++;
++slot;
}
// move cursor to our new location
int x = attribute_inc_rects2[slot][0] + (attribute_inc_rects2[slot][2] / 2);
int y = attribute_inc_rects2[slot][1] + (attribute_inc_rects2[slot][3] / 2);
int x = ChrBtnsRect[slot].x + (ChrBtnsRect[slot].w / 2);
int y = ChrBtnsRect[slot].y + (ChrBtnsRect[slot].h / 2);
SetCursorPos(x, y);
MouseX = x;
MouseY = y;
@ -328,17 +345,14 @@ void invMove(int key)
if (pcurs > 1) { // [3] Keep item in the same slot, don't jump it up
if (x != MouseX) // without this, the cursor keeps moving -10
{
SetCursorPos((x - 10), (y - 10));
MouseX = x - 10;
MouseY = y - 10;
}
{
SetCursorPos(x - 10, y - 10);
}
} else {
SetCursorPos(x, y);
MouseX = x;
MouseY = y;
}
}
// check if hot spell at X Y exists
bool HSExists(int x, int y)
{
@ -352,12 +366,12 @@ bool HSExists(int x, int y)
void hotSpellMove(int key)
{
int x = 0;
int y = 0;
if (!spselflag)
return;
int x = 0;
int y = 0;
if (pcurs > 0)
if (!sgbControllerActive)
HideCursor();
DWORD ticks = GetTickCount();
@ -381,7 +395,7 @@ void hotSpellMove(int key)
}
}
if (key == VK_UP) {
if (key == DVL_VK_UP) {
if (speedspellscoords[spbslot].y == 307 && hsr[1] > 0) { // we're in row 1, check if row 2 has spells
if (HSExists(MouseX, 251)) {
x = MouseX;
@ -393,7 +407,7 @@ void hotSpellMove(int key)
y = 195;
}
}
} else if (key == VK_DOWN) {
} else if (key == DVL_VK_DOWN) {
if (speedspellscoords[spbslot].y == 251) { // we're in row 2
if (HSExists(MouseX, 307)) {
x = MouseX;
@ -405,13 +419,13 @@ void hotSpellMove(int key)
y = 251;
}
}
} else if (key == VK_LEFT) {
} else if (key == DVL_VK_LEFT) {
if (spbslot >= speedspellcount - 1)
return;
spbslot++;
x = speedspellscoords[spbslot].x;
y = speedspellscoords[spbslot].y;
} else if (key == VK_RIGHT) {
} else if (key == DVL_VK_RIGHT) {
if (spbslot <= 0)
return;
spbslot--;
@ -425,11 +439,10 @@ void hotSpellMove(int key)
MouseY = y;
}
}
// walk in the direction specified
void walkInDir(int dir)
void walkInDir(MoveDirection dir)
{
if (invflag || spselflag || chrflag || questlog) // don't walk if inventory, speedbook or char info windows are open
if (dir.x == MoveDirectionX::NONE && dir.y == MoveDirectionY::NONE)
return;
DWORD ticks = GetTickCount();
if (ticks - invmove < 370) {
@ -439,148 +452,235 @@ void walkInDir(int dir)
ClrPlrPath(myplr); // clear nodes
plr[myplr].destAction = ACTION_NONE; // stop attacking, etc.
HideCursor();
plr[myplr].walkpath[0] = dir;
static const _walk_path kMoveToWalkDir[3][3] = {
// NONE UP DOWN
{ WALK_NONE, WALK_N, WALK_S }, // NONE
{ WALK_W, WALK_NW, WALK_SW }, // LEFT
{ WALK_E, WALK_NE, WALK_SE }, // RIGHT
};
plr[myplr].walkpath[0] = kMoveToWalkDir[static_cast<std::size_t>(dir.x)][static_cast<std::size_t>(dir.y)];
}
static DWORD menuopenslow;
void useBeltPotion(bool mana)
void menuMoveX(MoveDirectionX dir)
{
DWORD ticks = GetTickCount();
int invNum = 0;
if (ticks - menuopenslow < 300) {
if (dir == MoveDirectionX::NONE)
return;
invMove(dir == MoveDirectionX::LEFT ? WALK_W : WALK_E);
hotSpellMove(dir == MoveDirectionX::LEFT ? DVL_VK_LEFT : DVL_VK_RIGHT);
}
void menuMoveY(MoveDirectionY dir)
{
if (dir == MoveDirectionY::NONE)
return;
invMove(dir == MoveDirectionY::UP ? WALK_N : WALK_S);
const auto key = dir == MoveDirectionY::UP ? DVL_VK_UP : DVL_VK_DOWN;
hotSpellMove(key);
attrIncBtnSnap(key);
}
void movement()
{
if (InGameMenu())
return;
MoveDirection move_dir = GetMoveDirection();
if (InControlledMenu()) {
menuMoveX(move_dir.x);
menuMoveY(move_dir.y);
} else {
walkInDir(move_dir);
}
menuopenslow = ticks;
for (int i = 0; i < MAXBELTITEMS; i++) {
if ((AllItemsList[plr[myplr].SpdList[i].IDidx].iMiscId == IMISC_HEAL && mana == false) || (AllItemsList[plr[myplr].SpdList[i].IDidx].iMiscId == IMISC_FULLHEAL && mana == false) || (AllItemsList[plr[myplr].SpdList[i].IDidx].iMiscId == IMISC_MANA && mana == true) || (AllItemsList[plr[myplr].SpdList[i].IDidx].iMiscId == IMISC_FULLMANA && mana == true) || (AllItemsList[plr[myplr].SpdList[i].IDidx].iMiscId == IMISC_REJUV && AllItemsList[plr[myplr].SpdList[i].IDidx].iMiscId == IMISC_FULLREJUV)) {
if (plr[myplr].SpdList[i]._itype > -1) {
invNum = i + INVITEM_BELT_FIRST;
UseInvItem(myplr, invNum);
break;
}
if (GetAsyncKeyState(DVL_MK_LBUTTON) & 0x8000) {
if (sgbControllerActive) { // show cursor first, before clicking
sgbControllerActive = false;
} else if (spselflag) {
SetSpell();
} else {
LeftMouseCmd(false);
}
}
}
void movements(int key) {
if (key == VK_UP) {
invMove(WALK_N);
hotSpellMove(VK_UP);
attrIncBtnSnap(VK_UP);
walkInDir(WALK_N);
} else if (key == VK_RIGHT) {
invMove(WALK_E);
hotSpellMove(VK_RIGHT);
walkInDir(WALK_E);
} else if (key == VK_DOWN) {
invMove(WALK_S);
hotSpellMove(VK_DOWN);
attrIncBtnSnap(VK_DOWN);
walkInDir(WALK_S);
} else if (key == VK_LEFT) {
invMove(WALK_W);
hotSpellMove(VK_LEFT);
walkInDir(WALK_W);
// Move map or mouse no more than 60 times per second.
// This is called from both the game loop and the event loop for responsiveness.
void HandleRightStickMotionAt60Fps()
{
static std::int64_t currentTime = 0;
static std::int64_t lastTime = 0;
currentTime = SDL_GetTicks();
if (currentTime - lastTime > 15) {
HandleRightStickMotion();
lastTime = currentTime;
}
}
void charMovement() {
if (stextflag > 0 || questlog || helpflag || talkflag || qtextflag)
return;
struct RightStickAccumulator {
void start(int *x, int *y)
{
hiresDX += rightStickX * kGranularity;
hiresDY += rightStickY * kGranularity;
*x += hiresDX / slowdown;
*y += -hiresDY / slowdown;
}
if (!invflag && !spselflag && !chrflag) {
if (GetAsyncKeyState(VK_RIGHT) & 0x8000 && GetAsyncKeyState(VK_DOWN) & 0x8000 || GetAsyncKeyState(0x44) & 0x8000 && GetAsyncKeyState(0x53) & 0x8000 || leftStickY <= -0.5 && leftStickX >= 0.5) {
walkInDir(WALK_SE);
} else if (GetAsyncKeyState(VK_RIGHT) & 0x8000 && GetAsyncKeyState(VK_UP) & 0x8000 || GetAsyncKeyState(0x57) & 0x8000 && GetAsyncKeyState(0x44) & 0x8000 || leftStickY >= 0.5 && leftStickX >= 0.5) {
walkInDir(WALK_NE);
} else if (GetAsyncKeyState(VK_LEFT) & 0x8000 && GetAsyncKeyState(VK_DOWN) & 0x8000 || GetAsyncKeyState(0x41) & 0x8000 && GetAsyncKeyState(0x53) & 0x8000 || leftStickY <= -0.5 && leftStickX <= -0.5) {
walkInDir(WALK_SW);
} else if (GetAsyncKeyState(VK_LEFT) & 0x8000 && GetAsyncKeyState(VK_UP) & 0x8000 || GetAsyncKeyState(0x57) & 0x8000 && GetAsyncKeyState(0x41) & 0x8000 || leftStickY >= 0.40 && leftStickX <= -0.5) {
walkInDir(WALK_NW);
}
void finish()
{
// keep track of remainder for sub-pixel motion
hiresDX %= slowdown;
hiresDY %= slowdown;
}
if (GetAsyncKeyState(VK_UP) & 0x8000 || GetAsyncKeyState(0x57) & 0x8000 || leftStickY >= 0.5) {
movements(VK_UP);
} else if (GetAsyncKeyState(VK_RIGHT) & 0x8000 || GetAsyncKeyState(0x44) & 0x8000 || leftStickX >= 0.5) {
movements(VK_RIGHT);
} else if (GetAsyncKeyState(VK_DOWN) & 0x8000 || GetAsyncKeyState(0x53) & 0x8000 || leftStickY <= -0.5) {
movements(VK_DOWN);
} else if (GetAsyncKeyState(VK_LEFT) & 0x8000 || GetAsyncKeyState(0x41) & 0x8000 || leftStickX <= -0.5) {
movements(VK_LEFT);
static const int kGranularity = (1 << 15) - 1;
int slowdown; // < kGranularity
int hiresDX;
int hiresDY;
};
} // namespace
void HandleRightStickMotion()
{
// deadzone is handled in ScaleJoystickAxes() already
if (rightStickX == 0 && rightStickY == 0)
return;
if (automapflag) { // move map
static RightStickAccumulator acc = { /*slowdown=*/(1 << 14) + (1 << 13), 0, 0 };
int dx = 0, dy = 0;
acc.start(&dx, &dy);
if (dy > 1)
AutomapUp();
else if (dy < -1)
AutomapDown();
else if (dx < -1)
AutomapRight();
else if (dx > 1)
AutomapLeft();
acc.finish();
} else { // move cursor
if (sgbControllerActive) {
sgbControllerActive = false;
}
static RightStickAccumulator acc = { /*slowdown=*/(1 << 13) + (1 << 12), 0, 0 };
int x = MouseX;
int y = MouseY;
acc.start(&x, &y);
if (x < 0)
x = 0;
if (y < 0)
y = 0;
SetCursorPos(x, y);
acc.finish();
}
}
if (GetAsyncKeyState(MK_LBUTTON) & 0x8000) {
if (newCurHidden) { // show cursor first, before clicking
SetCursor_(CURSOR_HAND);
newCurHidden = false;
} else if (spselflag) {
SetSpell();
void plrctrls_game_logic()
{
// check for monsters first, then items, then towners.
if (sgbControllerActive) { // cursor should be missing
if (!checkMonstersNearby(false)) {
pcursmonst = -1;
checkItemsNearby(false);
checkTownersNearby(false);
} else {
LeftMouseCmd(false);
pcursitem = -1;
}
}
movement();
HandleRightStickMotionAt60Fps();
}
void keyboardExpansion(int vikey)
void plrctrls_event_loop()
{
static DWORD opentimer;
static DWORD clickinvtimer;
static DWORD statuptimer;
DWORD ticks = GetTickCount();
HandleRightStickMotionAt60Fps();
}
if (stextflag > 0 || questlog || helpflag || talkflag || qtextflag)
void useBeltPotion(bool mana)
{
static DWORD menuopenslow = 0;
DWORD ticks = GetTickCount();
int invNum = 0;
if (ticks - menuopenslow < 300) {
return;
if (vikey == VK_SPACE) { // similar to X button on PS1 ccontroller. Talk to towners, click on inv items, attack.
if (invflag) { // inventory is open
if (ticks - clickinvtimer >= 300) {
clickinvtimer = ticks;
if (pcurs == CURSOR_IDENTIFY)
CheckIdentify(myplr, pcursinvitem);
else if (pcurs == CURSOR_REPAIR)
DoRepair(myplr, pcursinvitem);
else
CheckInvItem();
}
menuopenslow = ticks;
for (int i = 0; i < MAXBELTITEMS; i++) {
const auto id = AllItemsList[plr[myplr].SpdList[i].IDidx].iMiscId;
if ((!mana && (id == IMISC_HEAL || id == IMISC_FULLHEAL)) || (mana && (id == IMISC_MANA || id == IMISC_FULLMANA)) || id == IMISC_REJUV || id == IMISC_FULLREJUV) {
if (plr[myplr].SpdList[i]._itype > -1) {
invNum = i + INVITEM_BELT_FIRST;
UseInvItem(myplr, invNum);
break;
}
} else if (spselflag) {
SetSpell();
} else if (chrflag) {
if (ticks - statuptimer >= 400) {
statuptimer = ticks;
if (!chrbtnactive && plr[myplr]._pStatPts) {
CheckChrBtns();
for (int i = 0; i < 4; i++) {
if (MouseX >= attribute_inc_rects2[i][0]
&& MouseX <= attribute_inc_rects2[i][0] + attribute_inc_rects2[i][2]
&& MouseY >= attribute_inc_rects2[i][1]
&& MouseY <= attribute_inc_rects2[i][3] + attribute_inc_rects2[i][1]) {
chrbtn[i] = 1;
chrbtnactive = TRUE;
ReleaseChrBtns();
}
}
}
}
void performPrimaryAction()
{
const DWORD ticks = GetTickCount();
if (invflag) { // inventory is open
static DWORD clickinvtimer;
if (ticks - clickinvtimer >= 300) {
clickinvtimer = ticks;
if (pcurs == CURSOR_IDENTIFY)
CheckIdentify(myplr, pcursinvitem);
else if (pcurs == CURSOR_REPAIR)
DoRepair(myplr, pcursinvitem);
else
CheckInvItem();
}
} else if (spselflag) {
SetSpell();
} else if (chrflag) {
static DWORD statuptimer;
if (ticks - statuptimer >= 400) {
statuptimer = ticks;
if (!chrbtnactive && plr[myplr]._pStatPts) {
CheckChrBtns();
for (int i = 0; i < 4; i++) {
if (MouseX >= ChrBtnsRect[i].x
&& MouseX <= ChrBtnsRect[i].x + ChrBtnsRect[i].w
&& MouseY >= ChrBtnsRect[i].y
&& MouseY <= ChrBtnsRect[i].h + ChrBtnsRect[i].y) {
chrbtn[i] = 1;
chrbtnactive = true;
ReleaseChrBtns();
}
}
if (plr[myplr]._pStatPts == 0)
HideCursor();
}
} else {
HideCursor();
talktick = GetTickCount(); // this is shared with STextESC, do NOT duplicate or use anywhere else
if (!checkMonstersNearby(true)) {
if (talktick - talkwait > 600) { // prevent re-entering talk after finished
talkwait = talktick;
checkTownersNearby(true);
}
}
if (plr[myplr]._pStatPts == 0)
HideCursor();
}
} else if (vikey == VK_RETURN) { // similar to [] button on PS1 controller. Open chests, doors, pickup items
if (!invflag) {
HideCursor();
if (ticks - opentimer > 500) {
opentimer = ticks;
checkItemsNearby(true);
} else {
static DWORD talkwait;
if (stextflag)
talkwait = GetTickCount(); // Wait before we re-initiate talking
HideCursor();
DWORD talktick = GetTickCount();
if (!checkMonstersNearby(true)) {
if (talktick - talkwait > 600) { // prevent re-entering talk after finished
talkwait = talktick;
checkTownersNearby(true);
}
}
}
}
DEVILUTION_END_NAMESPACE
void performSecondaryAction()
{
if (invflag)
return;
static DWORD opentimer = 0;
HideCursor();
const DWORD ticks = GetTickCount();
if (ticks - opentimer > 500) {
opentimer = ticks;
checkItemsNearby(true);
}
}
} // namespace dvl

44
SourceX/controls/plrctrls.h

@ -1,29 +1,35 @@
#pragma once
// Controller actions implementation
#include "devilution.h"
namespace dvl {
// Run on every game logic iteration.
void plrctrls_game_logic();
// Runs before the start of event loop drain, even if there are no events.
void plrctrls_event_loop();
// Moves the map if active, the cursor otherwise.
void HandleRightStickMotion();
// Whether we're in a dialog menu that the game handles natively with keyboard controls.
bool InGameMenu();
void checkTownersNearby(bool interact);
void checkItemsNearby(bool interact);
void keyboardExpansion(int vikey);
void charMovement();
void movements(int key);
bool checkMonstersNearby(bool attack);
extern bool newCurHidden;
void invMove(int key);
void HideCursor();
void useBeltPotion(bool mana);
// Talk to towners, click on inv items, attack, etc.
void performPrimaryAction();
// Open chests, doors, pickup items.
void performSecondaryAction();
typedef struct coords {
int x;
int y;
} coords;
extern coords speedspellscoords[50];
extern int speedspellcount;
extern const InvXY InvRect[73]; // wasn't made public, so I'll add this here from inv.cpp
extern DWORD talkwait;
extern DWORD talktick;
extern DWORD castwait;
#define INV_TOP 240;
#define INV_LEFT 350;
#define INV_HEIGHT 320;
#define DISPLAY_HEIGHT 360;
#define DISPLAY_WIDTH 640;
} // namespace dvl

3
SourceX/miniwin/misc.cpp

@ -4,6 +4,7 @@
#include <SDL.h>
#include <string>
#include "controls/controller.h"
#include "DiabloUI/diabloui.h"
#include "DiabloUI/dialogs.h"
@ -128,6 +129,8 @@ bool SpawnWindow(LPCSTR lpWindowName, int nWidth, int nHeight)
SDL_EnableUNICODE(1);
#endif
InitController();
int upscale = 1;
DvlIntSetting("upscale", &upscale);
DvlIntSetting("fullscreen", (int *)&fullscreen);

2
SourceX/miniwin/misc_dx.cpp

@ -24,6 +24,8 @@ WINBOOL SetCursorPos(int X, int Y)
#endif
SDL_WarpMouseInWindow(window, X, Y);
MouseX = X;
MouseY = Y;
return true;
}

218
SourceX/miniwin/misc_msg.cpp

@ -1,8 +1,12 @@
#include <cstdint>
#include <deque>
#include <SDL.h>
#include "devilution.h"
#include "stubs.h"
#include "controls/controller_motion.h"
#include "controls/game_controls.h"
#include "controls/plrctrls.h"
/** @file
* *
@ -178,19 +182,115 @@ static int translate_sdl_key(SDL_Keysym key)
}
}
static WPARAM keystate_for_mouse(WPARAM ret)
namespace {
WPARAM keystate_for_mouse(WPARAM ret)
{
ret |= (SDL_GetModState() & KMOD_SHIFT) ? DVL_MK_SHIFT : 0;
// XXX: other DVL_MK_* codes not implemented
return ret;
}
static WINBOOL false_avail()
WINBOOL false_avail()
{
DUMMY_PRINT("return %s although event available", "false");
return false;
}
void SetMouseLMBMessage(const SDL_Event &event, LPMSG lpMsg)
{
switch (event.type) {
#ifndef USE_SDL1
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
lpMsg->message = event.type == SDL_CONTROLLERBUTTONUP ? DVL_WM_LBUTTONUP : DVL_WM_LBUTTONDOWN;
lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF);
break;
#endif
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
lpMsg->message = event.type == SDL_MOUSEBUTTONUP ? DVL_WM_LBUTTONUP : DVL_WM_LBUTTONDOWN;
lpMsg->lParam = (event.button.y << 16) | (event.button.x & 0xFFFF);
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
lpMsg->message = event.type == SDL_KEYUP ? DVL_WM_LBUTTONUP : DVL_WM_LBUTTONDOWN;
lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF);
break;
default:
lpMsg->message = event.type == DVL_WM_LBUTTONUP;
lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF);
break;
}
if (lpMsg->message == DVL_WM_LBUTTONUP || lpMsg->message == DVL_WM_LBUTTONDOWN)
lpMsg->wParam = keystate_for_mouse(lpMsg->message == DVL_WM_LBUTTONUP ? 0 : DVL_MK_LBUTTON);
}
void ShowCursor()
{
if (sgbControllerActive) {
sgbControllerActive = false;
}
}
void HideCursorIfNotNeeded(bool newspselflag = spselflag) {
if (!chrflag && !invflag && !newspselflag) {
sgbControllerActive = true;
}
}
// Moves the mouse to the first inventory slot.
bool FocusOnInventory()
{
if (!invflag)
return false;
ShowCursor();
SetCursorPos(InvRect[25].X + (INV_SLOT_SIZE_PX / 2), InvRect[25].Y - (INV_SLOT_SIZE_PX / 2));
return true;
}
// Moves the mouse to the first attribute "+" button.
bool FocusOnCharInfo()
{
if (!chrflag || plr[myplr]._pStatPts == 0)
return false;
// Find the first incrementable stat.
int pc = plr[myplr]._pClass;
int stat = -1;
for (int i = 4; i >= 0; --i) {
switch (i) {
case ATTRIB_STR:
if (plr[myplr]._pBaseStr >= MaxStats[pc][ATTRIB_STR])
continue;
break;
case ATTRIB_MAG:
if (plr[myplr]._pBaseMag >= MaxStats[pc][ATTRIB_MAG])
continue;
break;
case ATTRIB_DEX:
if (plr[myplr]._pBaseDex >= MaxStats[pc][ATTRIB_DEX])
continue;
break;
case ATTRIB_VIT:
if (plr[myplr]._pBaseVit >= MaxStats[pc][ATTRIB_VIT])
continue;
break;
default:
continue;
}
stat = i;
}
if (stat == -1)
return false;
ShowCursor();
const auto &rect = ChrBtnsRect[stat];
SetCursorPos(rect.x + (rect.w / 2), rect.y + (rect.h / 2));
return true;
}
} // namespace
WINBOOL PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg)
{
if (wMsgFilterMin != 0)
@ -201,6 +301,8 @@ WINBOOL PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilter
UNIMPLEMENTED();
if (wRemoveMsg == DVL_PM_NOREMOVE) {
plrctrls_event_loop();
// This does not actually fill out lpMsg, but this is ok
// since the engine never uses it in this case
return !message_queue.empty() || SDL_PollEvent(NULL);
@ -221,9 +323,81 @@ WINBOOL PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilter
}
lpMsg->hwnd = hWnd;
lpMsg->message = 0;
lpMsg->lParam = 0;
lpMsg->wParam = 0;
if (e.type == SDL_QUIT) {
lpMsg->message = DVL_WM_QUIT;
return true;
}
if (movie_playing) {
if (ShouldSkipMovie(e))
SetMouseLMBMessage(e, lpMsg);
return true;
}
if (ProcessControllerMotion(e)) {
ScaleJoysticks();
HandleRightStickMotion();
return true;
}
GameAction action;
if (GetGameAction(e, &action)) {
switch (action.type) {
case GameActionType::NONE:
break;
case GameActionType::USE_HEALTH_POTION:
useBeltPotion(/*mana=*/false);
break;
case GameActionType::USE_MANA_POTION:
useBeltPotion(/*mana=*/true);
break;
case GameActionType::PRIMARY_ACTION:
performPrimaryAction();
break;
case GameActionType::SECONDARY_ACTION:
performSecondaryAction();
break;
case GameActionType::TOGGLE_QUICK_SPELL_MENU:
ShowCursor();
lpMsg->message = DVL_WM_KEYDOWN;
lpMsg->wParam = 'S';
// We expect the flag value to change after this.
HideCursorIfNotNeeded(!spselflag);
return true;
case GameActionType::TOGGLE_CHARACTER_INFO:
questlog = false;
chrflag = !chrflag;
if (chrflag)
FocusOnCharInfo();
else
FocusOnInventory();
HideCursorIfNotNeeded();
break;
case GameActionType::TOGGLE_INVENTORY:
sbookflag = false;
invflag = !invflag;
if (invflag)
FocusOnInventory();
else
FocusOnCharInfo();
HideCursorIfNotNeeded();
break;
case GameActionType::SEND_KEY:
lpMsg->message = action.send_key.up ? DVL_WM_KEYUP : DVL_WM_KEYDOWN;
lpMsg->wParam = action.send_key.vk_code;
return true;
case GameActionType::SEND_MOUSE_LEFT_CLICK:
ShowCursor();
SetMouseLMBMessage(e, lpMsg);
return true;
}
return true;
}
switch (e.type) {
case SDL_QUIT:
lpMsg->message = DVL_WM_QUIT;
@ -374,43 +548,27 @@ WINBOOL TranslateMessage(const MSG *lpMsg)
SHORT GetAsyncKeyState(int vKey)
{
#ifndef USE_SDL1
const Uint8 *state = SDL_GetKeyboardState(nullptr);
if (vKey == DVL_MK_LBUTTON)
return SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_LEFT);
if (vKey == DVL_MK_RBUTTON)
return SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_RIGHT);
const Uint8 *state = SDLC_GetKeyboardState();
switch (vKey) {
case DVL_VK_SHIFT:
return state[SDL_SCANCODE_LSHIFT] || state[SDL_SCANCODE_RSHIFT] ? 0x8000 : 0;
return state[SDLC_KEYSTATE_LEFTSHIFT] || state[SDLC_KEYSTATE_RIGHTSHIFT] ? 0x8000 : 0;
case DVL_VK_MENU:
return state[SDL_SCANCODE_MENU] ? 0x8000 : 0;
return state[SDLC_KEYSTATE_MENU] ? 0x8000 : 0;
case DVL_VK_LEFT:
return state[SDL_SCANCODE_LEFT] ? 0x8000 : 0;
return state[SDLC_KEYSTATE_LEFT] ? 0x8000 : 0;
case DVL_VK_UP:
return state[SDL_SCANCODE_UP] ? 0x8000 : 0;
return state[SDLC_KEYSTATE_UP] ? 0x8000 : 0;
case DVL_VK_RIGHT:
return state[SDL_SCANCODE_RIGHT] ? 0x8000 : 0;
return state[SDLC_KEYSTATE_RIGHT] ? 0x8000 : 0;
case DVL_VK_DOWN:
return state[SDL_SCANCODE_DOWN] ? 0x8000 : 0;
return state[SDLC_KEYSTATE_DOWN] ? 0x8000 : 0;
default:
return 0;
}
#else
const Uint8 *state = SDL_GetKeyState(nullptr);
switch (vKey) {
case DVL_VK_SHIFT:
return state[SDLK_LSHIFT] || state[SDLK_RSHIFT] ? 0x8000 : 0;
case DVL_VK_MENU:
return state[SDLK_MENU] ? 0x8000 : 0;
case DVL_VK_LEFT:
return state[SDLK_LEFT] ? 0x8000 : 0;
case DVL_VK_UP:
return state[SDLK_UP] ? 0x8000 : 0;
case DVL_VK_RIGHT:
return state[SDLK_RIGHT] ? 0x8000 : 0;
case DVL_VK_DOWN:
return state[SDLK_DOWN] ? 0x8000 : 0;
default:
return 0;
}
#endif
}
LRESULT DispatchMessageA(const MSG *lpMsg)
@ -438,4 +596,4 @@ WINBOOL PostMessageA(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
return true;
}
}
} // namespace dvl

Loading…
Cancel
Save