diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ca2677..1db183b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -205,7 +205,8 @@ add_executable(devilutionx SourceX/DiabloUI/selgame.cpp SourceX/DiabloUI/selhero.cpp SourceX/DiabloUI/title.cpp - SourceX/main.cpp) + SourceX/main.cpp + touch/touch.cpp) target_include_directories(devilution PUBLIC Source SourceS) target_include_directories(devilutionx PRIVATE diff --git a/README.md b/README.md index d65c5fa..b858244 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,19 @@ - Extract contents of diablo-nx.zip release into SDMC:\switch\diablo-nx - Copy DIABDAT.MPQ from original Diablo game disc or GOG version. - Launch diablo-nx.nro - - *Note:* If using SX OS, hold R on any installed game and launch it. +- *Note:* Hold R on any installed game and launch it. Do not use photo-album to launch. If you use photo-album, the homebrew only has very little memory available, and the touch keyboard doesn't work. This is true for all homebrew, not just Diablo-NX. - Enjoy :) -### Controls +### Joycon Controls - Left Analog : Move Hero - Right Analog : Simulate Mouse -- B : Attack nearby enemies, talk to towns people and merchants. Pickup & Drop items in inventory. +- B : Attack nearby enemies, talk to towns people and merchants. Pickup & Drop items in inventory. OK in Menu - Y : Pickup gold, potions & equipment from ground, open chests and doors that are nearby. Use item when in inventory (read books etc.). -- X : Cast Spell, Previous Menu -- A : Inventory -- R : Character +- X : Cast Spell +- A : Select Spell, Cancel in Menu +- R : Inventory +- L : Character - ZR : Drink Mana Potion - ZL : Drink Heal Potion - Left Analog Click : Quest Log @@ -25,6 +26,14 @@ - Minus : Automap - Plus : Game Menu, Skip Intro +### Touch Controls + +- Single finger drag : move the mouse pointer (pointer jumps to finger) +- Single short tap : left mouse click +- Single short tap while holding a second finger down : right mouse click +- Dual finger drag : drag'n'drop (left mouse button is held down) +- Three finger drag : drag'n'drop (right mouse button is held down) + ### Notes There are lots of bugs. Check issues on the code repo. @@ -53,7 +62,8 @@ There are lots of bugs. Check issues on the code repo. ### Credits - Reverse engineered by GalaXyHaXz in 2018 - Switch Port by MVG in 2019 -- Controller code by Jacob Fliss +- Control Improvements and bug fixes for Switch Port by [rsn8887](https://github.com/rsn8887) in 2019 +- Controller code by [Jacob Fliss](https://github.com/erfg12) - RetroArch team for the Switch mman.h file - [sanctuary](https://github.com/sanctuary) - extensively documenting Diablo's game engine - [BWAPI Team](https://github.com/bwapi) - providing library API to work with Storm diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 99892de..f1e7748 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -114,7 +114,6 @@ BOOL StartGame(BOOL bNewGame, BOOL bSinglePlayer) else uMsg = WM_DIABLOADGAME; - inmainmenu = false; run_game_loop(uMsg); NetClose(); @@ -464,9 +463,10 @@ void diablo_parse_flags(char *args) case 'w': debug_mode_key_w = 1; break; - //case 'x': // JAKE: Removed for spell casting - // fullscreen = FALSE; - // break; + case 'x': + fullscreen = 0; + break; + } #endif } } @@ -1420,12 +1420,8 @@ void PressChar(int vkey) // JAKE: Spacebar used to go back, now Z goes back. if (pcurs >= CURSOR_FIRSTITEM && invflag) DropItemBeforeTrig(); - ticks = GetTickCount(); - if (ticks - castwait < 300 || ticks - talkwait < 600) { // prevent double spell cast - return; - } - castwait = ticks; - if (!invflag && !talkflag && !inmainmenu && stextflag == 0 && !qtextflag) // prevent "spell not rdy" speech + //castwait = ticks; + if (!invflag && !talkflag) RightMouseDown(); PressEscKey(); return; diff --git a/Source/mainmenu.cpp b/Source/mainmenu.cpp index 88e4895..42bd8ad 100644 --- a/Source/mainmenu.cpp +++ b/Source/mainmenu.cpp @@ -98,7 +98,6 @@ void mainmenu_loop() BOOL done; int menu; - inmainmenu = true; // JAKE: Let the controller know we're in the main menu done = FALSE; mainmenu_refresh_music(); diff --git a/Source/plrctrls.cpp b/Source/plrctrls.cpp index fd88ab0..9e7cdba 100644 --- a/Source/plrctrls.cpp +++ b/Source/plrctrls.cpp @@ -39,7 +39,6 @@ int hsr[3] = { 0, 0, 0 }; // hot spell row counts DWORD talkwait; DWORD talktick; DWORD castwait; -bool inmainmenu = false; // 0 = not near, >0 = distance related player 1 coordinates coords checkNearbyObjs(int x, int y, int diff) diff --git a/Source/plrctrls.h b/Source/plrctrls.h index 588d7f2..96e2597 100644 --- a/Source/plrctrls.h +++ b/Source/plrctrls.h @@ -23,8 +23,6 @@ extern DWORD talkwait; extern DWORD talktick; extern DWORD castwait; -extern bool inmainmenu; - #define INV_TOP 240; #define INV_LEFT 350; #define INV_HEIGHT 320; diff --git a/SourceX/DiabloUI/diabloui.cpp b/SourceX/DiabloUI/diabloui.cpp index 4760e99..566ec1d 100644 --- a/SourceX/DiabloUI/diabloui.cpp +++ b/SourceX/DiabloUI/diabloui.cpp @@ -220,26 +220,23 @@ bool UiFocusNavigation(SDL_Event *event) { if (event->type == SDL_QUIT) exit(0); - if (event->type == SDL_JOYBUTTONDOWN) { - if (event->jbutton.which == 0) { - if (event->jbutton.button == 0) { + + if (event->type == SDL_CONTROLLERBUTTONDOWN) { + if (event->cbutton.which == 0) { + switch (event->cbutton.button) { + case SDL_CONTROLLER_BUTTON_A: UiFocusNavigationSelect(); return true; - } - } - - if (event->jbutton.which == 0) { - if (event->jbutton.button == 13){ + case SDL_CONTROLLER_BUTTON_B: + UiFocusNavigationEsc(); + return true; + case SDL_CONTROLLER_BUTTON_DPAD_UP: UiFocus(SelectedItem - 1, UiItemsWraps); return true; - } - } - - if (event->jbutton.which == 0) { - if (event->jbutton.button == 15){ + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: UiFocus(SelectedItem + 1, UiItemsWraps); return true; - } + } } } diff --git a/SourceX/DiabloUI/mainmenu.cpp b/SourceX/DiabloUI/mainmenu.cpp index dc9b8d2..2eb6dc5 100644 --- a/SourceX/DiabloUI/mainmenu.cpp +++ b/SourceX/DiabloUI/mainmenu.cpp @@ -21,7 +21,11 @@ void UiMainMenuSelect(int value) void mainmenu_Esc() { +#ifdef SWITCH + return; +#else UiMainMenuSelect(MAINMENU_EXIT_DIABLO); +#endif } void mainmenu_Load(char *name, void(*fnSound)(char *file)) diff --git a/SourceX/DiabloUI/title.cpp b/SourceX/DiabloUI/title.cpp index 27d5c75..eb3ba4c 100644 --- a/SourceX/DiabloUI/title.cpp +++ b/SourceX/DiabloUI/title.cpp @@ -37,6 +37,7 @@ BOOL UiTitleDialog(int a1) while (SDL_PollEvent(&event)) { switch (event.type) { + case SDL_CONTROLLERBUTTONDOWN: case SDL_KEYDOWN: /* To match the original uncomment this if (event.key.keysym.sym == SDLK_UP || event.key.keysym.sym == SDLK_UP diff --git a/SourceX/miniwin/misc.cpp b/SourceX/miniwin/misc.cpp index 794593c..d67cd72 100644 --- a/SourceX/miniwin/misc.cpp +++ b/SourceX/miniwin/misc.cpp @@ -335,12 +335,13 @@ HWND CreateWindowExA( HINSTANCE hInstance, LPVOID lpParam) { - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) <= -1) { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) <= -1) { SDL_Log(SDL_GetError()); return NULL; } SDL_JoystickOpen(0); + SDL_GameControllerOpen(0); int upscale = 1; DvlIntSetting("upscale", &upscale); diff --git a/SourceX/miniwin/misc_msg.cpp b/SourceX/miniwin/misc_msg.cpp index 8fde71c..3e9a132 100644 --- a/SourceX/miniwin/misc_msg.cpp +++ b/SourceX/miniwin/misc_msg.cpp @@ -3,6 +3,7 @@ #include "devilution.h" #include "stubs.h" #include +#include "../../touch/touch.h" /** @file * * @@ -112,6 +113,41 @@ static int translate_sdl_key(SDL_Keysym key) } } +static int translate_controller_button_to_key(uint8_t sdlControllerButton) +{ + switch (sdlControllerButton) { + case SDL_CONTROLLER_BUTTON_B: + return 'H'; + case SDL_CONTROLLER_BUTTON_A: + return DVL_VK_SPACE; + case SDL_CONTROLLER_BUTTON_Y: + return 'X'; + case SDL_CONTROLLER_BUTTON_X: + return DVL_VK_RETURN; + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + return 'Q'; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + return 'C'; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + return 'I'; + case SDL_CONTROLLER_BUTTON_START: + return DVL_VK_ESCAPE; + case SDL_CONTROLLER_BUTTON_BACK: + return DVL_VK_TAB; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + return DVL_VK_LEFT; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + return DVL_VK_RIGHT; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + return DVL_VK_UP; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + return DVL_VK_DOWN; + default: + return 0; + } +} + + static WPARAM keystate_for_mouse(WPARAM ret) { const Uint8 *keystate = SDL_GetKeyboardState(NULL); @@ -129,9 +165,10 @@ static WINBOOL false_avail() WINBOOL PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) { - // update joystick mouse at maximally 60 fps + // update joystick (and touch mouse on Switch) at maximally 60 fps currentTime = SDL_GetTicks(); if ((currentTime - lastTime) > 15) { + finish_simulated_mouse_clicks(MouseX, MouseY); HandleJoystickAxes(); lastTime = currentTime; } @@ -162,241 +199,184 @@ WINBOOL PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilter if (!SDL_PollEvent(&e)) { return false; } - + lpMsg->hwnd = hWnd; lpMsg->message = 0; lpMsg->lParam = 0; lpMsg->wParam = 0; -#ifdef SWITCH + handle_touch(&e, MouseX, MouseY); if (movie_playing) { // allow plus button or mouse click to skip movie, no other input switch (e.type) { - case SDL_JOYBUTTONDOWN: - switch(e.jbutton.button) - { - case 10: // plus - case 5: // right joystick click - lpMsg->message = DVL_WM_LBUTTONDOWN; - lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF); - lpMsg->wParam = keystate_for_mouse(DVL_MK_LBUTTON); - break; - } - break; - case SDL_JOYBUTTONUP: - switch(e.jbutton.button) - { - case 10: // plus - case 5: // right joystick click - lpMsg->message = DVL_WM_LBUTTONUP; - lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF); - lpMsg->wParam = keystate_for_mouse(0); - break; - } - break; - case SDL_MOUSEBUTTONDOWN: - if (e.button.button == SDL_BUTTON_LEFT) { - lpMsg->message = DVL_WM_LBUTTONDOWN; - lpMsg->lParam = (e.button.y << 16) | (e.button.x & 0xFFFF); + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + switch(e.cbutton.button) { + case SDL_CONTROLLER_BUTTON_START: + case SDL_CONTROLLER_BUTTON_A: // B on Switch + lpMsg->message = e.type == SDL_CONTROLLERBUTTONUP ? DVL_WM_LBUTTONUP : DVL_WM_LBUTTONDOWN; + lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF); + if (lpMsg->message == DVL_WM_LBUTTONUP) + lpMsg->wParam = keystate_for_mouse(0); + else lpMsg->wParam = keystate_for_mouse(DVL_MK_LBUTTON); - } break; - case SDL_MOUSEBUTTONUP: - if (e.button.button == SDL_BUTTON_LEFT) { - lpMsg->message = DVL_WM_LBUTTONUP; - lpMsg->lParam = (e.button.y << 16) | (e.button.x & 0xFFFF); + } + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (e.button.button == SDL_BUTTON_LEFT) { + lpMsg->message = e.type == SDL_MOUSEBUTTONUP ? DVL_WM_LBUTTONUP : DVL_WM_LBUTTONDOWN; + lpMsg->lParam = (e.button.y << 16) | (e.button.x & 0xFFFF); + if (lpMsg->message == DVL_WM_LBUTTONUP) lpMsg->wParam = keystate_for_mouse(0); - } - break; + else + lpMsg->wParam = keystate_for_mouse(DVL_MK_LBUTTON); + } + break; } return true; } -#endif - switch (e.type) { - case SDL_JOYAXISMOTION: - switch (e.jaxis.axis) { - case 0: - leftStickXUnscaled = e.jaxis.value; - break; - case 1: - leftStickYUnscaled = -e.jaxis.value; - break; - case 2: - rightStickXUnscaled = e.jaxis.value; - break; - case 3: - rightStickYUnscaled = -e.jaxis.value; - break; + case SDL_CONTROLLERAXISMOTION: + switch (e.caxis.axis) { + case SDL_CONTROLLER_AXIS_LEFTX: + leftStickXUnscaled = e.caxis.value; + break; + case SDL_CONTROLLER_AXIS_LEFTY: + leftStickYUnscaled = -e.caxis.value; + break; + case SDL_CONTROLLER_AXIS_RIGHTX: + rightStickXUnscaled = e.caxis.value; + break; + case SDL_CONTROLLER_AXIS_RIGHTY: + rightStickYUnscaled = -e.caxis.value; + break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: // ZL on Switch + if (e.caxis.value) + useBeltPotion(false); // health potion + break; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: // ZR on Switch + if (e.caxis.value) + useBeltPotion(true); // mana potion + break; } leftStickX = leftStickXUnscaled; leftStickY = leftStickYUnscaled; - ScaleJoystickAxes(&leftStickX, &leftStickY, leftDeadzone); rightStickX = rightStickXUnscaled; rightStickY = rightStickYUnscaled; + ScaleJoystickAxes(&leftStickX, &leftStickY, leftDeadzone); ScaleJoystickAxes(&rightStickX, &rightStickY, rightDeadzone); break; - case SDL_JOYBUTTONDOWN: - // switch controller - #if defined(SWITCH) - switch(e.jbutton.button) - { - case 0: // A - PressChar('i'); - break; - case 1: // B - if (inmainmenu) { - PressKey(VK_RETURN); - keyboardExpansion(VK_RETURN); - } else { - if (stextflag) - talkwait = GetTickCount(); // JAKE: Wait before we re-initiate talking - PressKey(VK_SPACE); - keyboardExpansion(VK_SPACE); - } - break; - case 2: // X - PressChar('x'); - break; - case 3: // Y - if (invflag) { - lpMsg->message = DVL_WM_RBUTTONDOWN; - lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF); + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + switch(e.cbutton.button) { + case SDL_CONTROLLER_BUTTON_B: // A on Switch + case SDL_CONTROLLER_BUTTON_Y: // X on Switch + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + case SDL_CONTROLLER_BUTTON_START: + case SDL_CONTROLLER_BUTTON_BACK: + lpMsg->message = e.type == SDL_CONTROLLERBUTTONUP ? DVL_WM_KEYUP : DVL_WM_KEYDOWN; + lpMsg->wParam = (DWORD)translate_controller_button_to_key(e.cbutton.button); + break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + lpMsg->message = e.type == SDL_CONTROLLERBUTTONUP ? DVL_WM_KEYUP : DVL_WM_KEYDOWN; + lpMsg->wParam = (DWORD)translate_controller_button_to_key(e.cbutton.button); + if (lpMsg->message == DVL_WM_KEYDOWN) { + if (!stextflag) // prevent walking while in dialog mode + movements(lpMsg->wParam); + } + break; + case SDL_CONTROLLER_BUTTON_A: // B on Switch + lpMsg->message = e.type == SDL_CONTROLLERBUTTONUP ? DVL_WM_KEYUP : DVL_WM_KEYDOWN; + lpMsg->wParam = (DWORD)translate_controller_button_to_key(e.cbutton.button); + if (lpMsg->message == DVL_WM_KEYDOWN) { + if (stextflag) + talkwait = GetTickCount(); // JAKE: Wait before we re-initiate talking + keyboardExpansion(lpMsg->wParam); + } + break; + case SDL_CONTROLLER_BUTTON_X: // Y on Switch + if (invflag) { + lpMsg->message = e.type == SDL_CONTROLLERBUTTONUP ? DVL_WM_RBUTTONUP : DVL_WM_RBUTTONDOWN; + lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF); + if (lpMsg->message == DVL_WM_RBUTTONDOWN) { lpMsg->wParam = keystate_for_mouse(DVL_MK_RBUTTON); } else { - PressKey(VK_RETURN); - keyboardExpansion(VK_RETURN); + lpMsg->wParam = keystate_for_mouse(0); } - break; - case 4: // left joystick click - PressChar('q'); - break; - case 5: // right joystick click + } else { + lpMsg->message = e.type == SDL_CONTROLLERBUTTONUP ? DVL_WM_KEYUP : DVL_WM_KEYDOWN; + lpMsg->wParam = (DWORD)translate_controller_button_to_key(e.cbutton.button); + if (lpMsg->message == DVL_WM_KEYDOWN) + keyboardExpansion(lpMsg->wParam); + } + break; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + lpMsg->message = e.type == SDL_CONTROLLERBUTTONUP ? DVL_WM_LBUTTONUP : DVL_WM_LBUTTONDOWN; + lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF); + if (lpMsg->message == DVL_WM_LBUTTONDOWN) { if (newCurHidden) { // show cursor first, before clicking SetCursor_(CURSOR_HAND); newCurHidden = false; } - lpMsg->message = DVL_WM_LBUTTONDOWN; - lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF); lpMsg->wParam = keystate_for_mouse(DVL_MK_LBUTTON); - break; - case 6: // L - PressChar('h'); - break; - case 7: // R - PressChar('c'); - break; - case 8: // ZL - useBeltPotion(false); // health potion - break; - case 9: // ZR - useBeltPotion(true); // mana potion - break; - case 10: // plus - PressKey(VK_ESCAPE); - break; - case 11: // minus - PressKey(VK_TAB); - break; - case 12: // L_DPAD - PressKey(VK_LEFT); - movements(VK_LEFT); - break; - case 13: // U_DPAD - PressKey(VK_UP); - movements(VK_UP); - break; - case 14: // R_DPAD - PressKey(VK_RIGHT); - movements(VK_RIGHT); - break; - case 15: // D_DPAD - PressKey(VK_DOWN); - movements(VK_DOWN); - break; - case 16: // L_JSTICK - PressKey(VK_LEFT); - break; - case 17: // U_JSTICK - PressKey(VK_UP); - break; - case 18: // R_JSTICK - PressKey(VK_RIGHT); - break; - case 19: // D_JSTICK - PressKey(VK_DOWN); - break; - } - #else // xbox controller (untested) - switch(e.jbutton.button) - { - case 0: // A - if (inmainmenu) { - PressKey(VK_RETURN); - keyboardExpansion(VK_RETURN); - } else { - if (stextflag) - talkwait = GetTickCount(); // JAKE: Wait before we re-initiate talking - PressKey(VK_SPACE); - keyboardExpansion(VK_SPACE); - } - break; - case 1: // B - PressChar('i'); - break; - case 2: // X - PressKey(VK_RETURN); - keyboardExpansion(VK_RETURN); - break; - case 3: // Y - PressChar('x'); - break; - case 4: // Left Shoulder - PressChar('h'); - break; - case 5: // Right Shoulder - PressChar('c'); - break; - case 6: // Back - PressKey(VK_TAB); - break; - case 7: // Start - PressKey(VK_ESCAPE); - break; - case 8: // Left Stick - break; + } else { + lpMsg->wParam = keystate_for_mouse(0); + } + break; } - #endif break; +#ifdef SWITCH + // additional digital buttons that only exist on Switch: + case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: - #if defined(SWITCH) - switch(e.jbutton.button) - { - case 3: // Y - if (invflag) { - lpMsg->message = DVL_WM_RBUTTONUP; - lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF); - lpMsg->wParam = keystate_for_mouse(0); - } - break; - case 5: // right joystick click - lpMsg->message = DVL_WM_LBUTTONUP; - lpMsg->lParam = (MouseY << 16) | (MouseX & 0xFFFF); - lpMsg->wParam = keystate_for_mouse(0); - break; + lpMsg->message = e.type == SDL_JOYBUTTONUP ? DVL_WM_KEYUP : DVL_WM_KEYDOWN; + switch(e.jbutton.button) { + case 16: // L_JSTICK + lpMsg->wParam = DVL_VK_LEFT; + break; + case 17: // U_JSTICK + lpMsg->wParam = DVL_VK_UP; + break; + case 18: // R_JSTICK + lpMsg->wParam = DVL_VK_RIGHT; + break; + case 19: // D_JSTICK + lpMsg->wParam = DVL_VK_DOWN; + break; + default: + lpMsg->message = 0; } - #endif break; +#endif case SDL_QUIT: lpMsg->message = DVL_WM_QUIT; break; - case SDL_FINGERMOTION: + case SDL_KEYDOWN: + case SDL_KEYUP: { + int key = translate_sdl_key(e.key.keysym); + if (key == -1) + return false_avail(); + lpMsg->message = e.type == SDL_KEYDOWN ? DVL_WM_KEYDOWN : DVL_WM_KEYUP; + lpMsg->wParam = (DWORD)key; + // HACK: Encode modifier in lParam for TranslateMessage later + lpMsg->lParam = e.key.keysym.mod << 16; + } break; case SDL_MOUSEMOTION: + if (pcurs == CURSOR_NONE) { + SetCursor_(CURSOR_HAND); + newCurHidden = false; + } lpMsg->message = DVL_WM_MOUSEMOVE; lpMsg->lParam = (e.motion.y << 16) | (e.motion.x & 0xFFFF); lpMsg->wParam = keystate_for_mouse(0); break; - case SDL_FINGERDOWN: case SDL_MOUSEBUTTONDOWN: { int button = e.button.button; if (button == SDL_BUTTON_LEFT) { @@ -411,7 +391,6 @@ WINBOOL PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilter return false_avail(); } } break; - case SDL_FINGERUP: case SDL_MOUSEBUTTONUP: { int button = e.button.button; if (button == SDL_BUTTON_LEFT) { diff --git a/makefile b/makefile index 9c43324..b935a5b 100644 --- a/makefile +++ b/makefile @@ -70,7 +70,7 @@ RADONOBJ = obj/File.o obj/Key.o obj/Named.o obj/Section.o STORMLIBOBJ = obj/FileStream.o obj/SBaseCommon.o obj/SBaseFileTable.o obj/SBaseSubTypes.o obj/SCompression.o obj/SFileExtractFile.o obj/SFileFindFile.o obj/SFileGetFileInfo.o obj/SFileOpenArchive.o obj/SFileOpenFileEx.o obj/SFileReadFile.o PKWAREOBJ = obj/explode.o obj/implode.o DEVILUTIONOBJ = obj/appfat.o obj/automap.o obj/capture.o obj/codec.o obj/control.o obj/cursor.o obj/dead.o obj/debug.o obj/diablo.o obj/doom.o obj/drlg_l1.o obj/drlg_l2.o obj/drlg_l3.o obj/drlg_l4.o obj/dthread.o obj/effects.o obj/encrypt.o obj/engine.o obj/error.o obj/fault.o obj/gamemenu.o obj/gendung.o obj/gmenu.o obj/help.o obj/init.o obj/interfac.o obj/inv.o obj/itemdat.o obj/items.o obj/lighting.o obj/loadsave.o obj/logging.o obj/mmainmenu.o obj/minitext.o obj/misdat.o obj/missiles.o obj/monstdat.o obj/monster.o obj/movie.o obj/mpqapi.o obj/msgcmd.o obj/msg.o obj/multi.o obj/nthread.o obj/objdat.o obj/objects.o obj/pack.o obj/palette.o obj/path.o obj/pfile.o obj/player.o obj/plrctrls.o obj/plrmsg.o obj/portal.o obj/spelldat.o obj/quests.o obj/render.o obj/restrict.o obj/scrollrt.o obj/setmaps.o obj/sha.o obj/spells.o obj/stores.o obj/sync.o obj/textdat.o obj/themes.o obj/tmsg.o obj/town.o obj/towners.o obj/track.o obj/trigs.o obj/wave.o -MAINOBJ = obj/dx.o obj/misc.o obj/misc_io.o obj/misc_msg.o obj/misc_dx.o obj/rand.o obj/thread.o obj/dsound.o obj/ddraw.o obj/sound.o obj/storm.o obj/storm_net.o obj/storm_dx.o obj/abstract_net.o obj/loopback.o obj/packet.o obj/base.o obj/frame_queue.o obj/tcp_client.o obj/tcp_server.o obj/udp_p2p.o obj/credits.o obj/diabloui.o obj/dialogs.o obj/mainmenu.o obj/progress.o obj/selconn.o obj/selgame.o obj/selhero.o obj/title.o obj/main.o obj/switch_keyboard.o +MAINOBJ = obj/dx.o obj/misc.o obj/misc_io.o obj/misc_msg.o obj/misc_dx.o obj/rand.o obj/thread.o obj/dsound.o obj/ddraw.o obj/sound.o obj/storm.o obj/storm_net.o obj/storm_dx.o obj/abstract_net.o obj/loopback.o obj/packet.o obj/base.o obj/frame_queue.o obj/tcp_client.o obj/tcp_server.o obj/udp_p2p.o obj/credits.o obj/diabloui.o obj/dialogs.o obj/mainmenu.o obj/progress.o obj/selconn.o obj/selgame.o obj/selhero.o obj/title.o obj/main.o obj/touch.o obj/switch_keyboard.o LIBS = -specs=$(DEVKITPRO)/libnx/switch.specs -g -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE -L$(DEVKITPRO)/libnx/lib -L$(DEVKITPRO)/portlibs/switch/lib -lSDL2_mixer -lSDL2_ttf -lfreetype -lvorbisfile -lvorbis -logg -lmodplug -lmikmod -lmpg123 -lSDL2 -lopusfile -lopus -lEGL -lglapi -ldrm_nouveau -lpng -lbz2 -lz -lnx INCS = -I$(DEVKITPRO)/portlibs/switch/include/SDL2 -I"Source" -I"SourceS" -I"SourceX" -I"3rdParty/asio/include" -I"3rdParty/Radon/Radon/include" -I"3rdParty/libsmacker" -I$(DEVKITPRO)/libnx/include -I$(DEVKITPRO)/portlibs/switch/include @@ -356,6 +356,8 @@ obj/title.o: $(GLOBALDEPS) SourceX/DiabloUI/title.cpp $(CPP) -c SourceX/DiabloUI/title.cpp -o obj/title.o $(CXXFLAGS) obj/main.o: $(GLOBALDEPS) SourceX/main.cpp $(CPP) -c SourceX/main.cpp -o obj/main.o $(CXXFLAGS) +obj/touch.o: $(GLOBALDEPS) touch/touch.cpp + $(CPP) -c touch/touch.cpp -o obj/touch.o $(CXXFLAGS) obj/switch_keyboard.o: $(GLOBALDEPS) switch/switch_keyboard.cpp $(CPP) -c switch/switch_keyboard.cpp -o obj/switch_keyboard.o $(CXXFLAGS) diff --git a/switch/switch_keyboard.cpp b/switch/switch_keyboard.cpp index 09180e9..8b8b46b 100644 --- a/switch/switch_keyboard.cpp +++ b/switch/switch_keyboard.cpp @@ -52,9 +52,6 @@ void switch_start_text_input(char *guide_text, char *initial_text, int multiline { char text[65] = {'\0'}; switch_keyboard_get(guide_text, initial_text, 64, multiline, text); - if (text == nullptr) { - return; - } for (int i = 0; i < 600; i++) { switch_create_and_push_sdlkey_event(SDL_KEYDOWN, SDL_SCANCODE_BACKSPACE, SDLK_BACKSPACE); switch_create_and_push_sdlkey_event(SDL_KEYUP, SDL_SCANCODE_BACKSPACE, SDLK_BACKSPACE); @@ -63,6 +60,10 @@ void switch_start_text_input(char *guide_text, char *initial_text, int multiline switch_create_and_push_sdlkey_event(SDL_KEYDOWN, SDL_SCANCODE_DELETE, SDLK_DELETE); switch_create_and_push_sdlkey_event(SDL_KEYUP, SDL_SCANCODE_DELETE, SDLK_DELETE); } + if (text[0] == '\0') { + strncpy(text, initial_text, 63); + text[64] = {'\0'}; + } const uint8_t *utf8_text = (uint8_t*) text; for (int i = 0; i < 599 && utf8_text[i];) { int bytes_in_char = get_utf8_character_bytes(&utf8_text[i]); diff --git a/touch/touch.cpp b/touch/touch.cpp new file mode 100644 index 0000000..855fa6f --- /dev/null +++ b/touch/touch.cpp @@ -0,0 +1,437 @@ +#include "touch.h" +#include + +template inline T CLIP(T v, T amin, T amax) + { if (v < amin) return amin; else if (v > amax) return amax; else return v; } + +#ifdef SWITCH +#define DISPLAY_WIDTH 1280 +#define DISPLAY_HEIGHT 720 +#else +// TODO: How to find display size for each platform programmatically? +#define DISPLAY_WIDTH 1920 +#define DISPLAY_HEIGHT 1080 +#endif +#define GAME_WIDTH 640 +#define GAME_HEIGHT 480 +#define TOUCH_PORT_MAX_NUM 1 +#define NO_TOUCH -1 // finger id setting if finger is not touching the screen + +static void init_touch(void); +static void preprocess_events(SDL_Event *event); +static void preprocess_finger_down(SDL_Event *event); +static void preprocess_finger_up(SDL_Event *event); +static void preprocess_finger_motion(SDL_Event *event); +static void set_mouse_button_event(SDL_Event *event, uint32_t type, uint8_t button, int32_t x, int32_t y); +static void set_mouse_motion_event(SDL_Event *event, int32_t x, int32_t y, int32_t xrel, int32_t yrel); +static void convert_touch_xy_to_game_xy(float touch_x, float touch_y, int *game_x, int *game_y); + +static int touch_initialized = 0; +static unsigned int simulated_click_start_time[TOUCH_PORT_MAX_NUM][2]; // initiation time of last simulated left or right click (zero if no click) +static int direct_touch = 1; // pointer jumps to finger +static int mouse_x = 0; // always reflects current mouse position +static int mouse_y = 0; + +enum { + MAX_NUM_FINGERS = 3, // number of fingers to track per panel + MAX_TAP_TIME = 250, // taps longer than this will not result in mouse click events + MAX_TAP_MOTION_DISTANCE = 10, // max distance finger motion in Vita screen pixels to be considered a tap + SIMULATED_CLICK_DURATION = 50, // time in ms how long simulated mouse clicks should be +}; // track three fingers per panel + +typedef struct { + int id; // -1: not touching + uint32_t time_last_down; + int last_x; // last known screen coordinates + int last_y; // last known screen coordinates + float last_down_x; // SDL touch coordinates when last pressed down + float last_down_y; // SDL touch coordinates when last pressed down +} Touch; + +static Touch finger[TOUCH_PORT_MAX_NUM][MAX_NUM_FINGERS]; // keep track of finger status + +typedef enum { + DRAG_NONE = 0, + DRAG_TWO_FINGER, + DRAG_THREE_FINGER, +} DraggingType; + +static DraggingType multi_finger_dragging[TOUCH_PORT_MAX_NUM]; // keep track whether we are currently drag-and-dropping + +static void init_touch(void) +{ + for (int port = 0; port < TOUCH_PORT_MAX_NUM; port++) { + for (int i = 0; i < MAX_NUM_FINGERS; i++) { + finger[port][i].id = NO_TOUCH; + } + multi_finger_dragging[port] = DRAG_NONE; + } + + for (int port = 0; port < TOUCH_PORT_MAX_NUM; port++) { + for (int i = 0; i < 2; i++) { + simulated_click_start_time[port][i] = 0; + } + } +} + +void handle_touch(SDL_Event *event, int current_mouse_x, int current_mouse_y) +{ + mouse_x = current_mouse_x; + mouse_y = current_mouse_y; + + if (!touch_initialized) { + init_touch(); + touch_initialized = 1; + } + preprocess_events(event); + if (event->type == SDL_FINGERDOWN || event->type == SDL_FINGERUP || event->type == SDL_FINGERMOTION) { + event->type = SDL_USEREVENT; + event->user.code = -1; // ensure this event is ignored; + } +} + +static void preprocess_events(SDL_Event *event) +{ + // Supported touch gestures: + // left mouse click: single finger short tap + // right mouse click: second finger short tap while first finger is still down + // pointer motion: single finger drag + // left button drag and drop: dual finger drag + // right button drag and drop: triple finger drag + if (event->type != SDL_FINGERDOWN && event->type != SDL_FINGERUP && event->type != SDL_FINGERMOTION) { + return; + } + + // front (0) or back (1) panel + SDL_TouchID port = event->tfinger.touchId; + if (port != 0) { + return; + } + + switch (event->type) { + case SDL_FINGERDOWN: + preprocess_finger_down(event); + break; + case SDL_FINGERUP: + preprocess_finger_up(event); + break; + case SDL_FINGERMOTION: + preprocess_finger_motion(event); + break; + } +} + +static void preprocess_finger_down(SDL_Event *event) +{ + // front (0) or back (1) panel + SDL_TouchID port = event->tfinger.touchId; + // id (for multitouch) + SDL_FingerID id = event->tfinger.fingerId; + + int x = mouse_x; + int y = mouse_y; + + if (direct_touch) { + convert_touch_xy_to_game_xy(event->tfinger.x, event->tfinger.y, &x, &y); + } + + // make sure each finger is not reported down multiple times + for (int i = 0; i < MAX_NUM_FINGERS; i++) { + if (finger[port][i].id != id) { + continue; + } + finger[port][i].id = NO_TOUCH; + } + + // we need the timestamps to decide later if the user performed a short tap (click) + // or a long tap (drag) + // we also need the last coordinates for each finger to keep track of dragging + for (int i = 0; i < MAX_NUM_FINGERS; i++) { + if (finger[port][i].id != NO_TOUCH) { + continue; + } + finger[port][i].id = id; + finger[port][i].time_last_down = event->tfinger.timestamp; + finger[port][i].last_down_x = event->tfinger.x; + finger[port][i].last_down_y = event->tfinger.y; + finger[port][i].last_x = x; + finger[port][i].last_y = y; + break; + } +} + +static void preprocess_finger_up(SDL_Event *event) +{ + // front (0) or back (1) panel + SDL_TouchID port = event->tfinger.touchId; + // id (for multitouch) + SDL_FingerID id = event->tfinger.fingerId; + + // find out how many fingers were down before this event + int num_fingers_down = 0; + for (int i = 0; i < MAX_NUM_FINGERS; i++) { + if (finger[port][i].id >= 0) { + num_fingers_down++; + } + } + + int x = mouse_x; + int y = mouse_y; + + + for (int i = 0; i < MAX_NUM_FINGERS; i++) { + if (finger[port][i].id != id) { + continue; + } + + finger[port][i].id = NO_TOUCH; + if (!multi_finger_dragging[port]) { + if ((event->tfinger.timestamp - finger[port][i].time_last_down) > MAX_TAP_TIME) { + continue; + } + + // short (tfinger.x * DISPLAY_WIDTH) - (finger[port][i].last_down_x * DISPLAY_WIDTH)); + float yrel = ((event->tfinger.y * DISPLAY_HEIGHT) - (finger[port][i].last_down_y * DISPLAY_HEIGHT)); + float max_r_squared = (float) (MAX_TAP_MOTION_DISTANCE * MAX_TAP_MOTION_DISTANCE); + if ((xrel * xrel + yrel * yrel) >= max_r_squared) { + continue; + } + + if (num_fingers_down != 2 && num_fingers_down != 1) { + continue; + } + + Uint8 simulated_button = 0; + if (num_fingers_down == 2) { + simulated_button = SDL_BUTTON_RIGHT; + // need to raise the button later + simulated_click_start_time[port][1] = event->tfinger.timestamp; + } else if (num_fingers_down == 1) { + simulated_button = SDL_BUTTON_LEFT; + // need to raise the button later + simulated_click_start_time[port][0] = event->tfinger.timestamp; + if (direct_touch) { + convert_touch_xy_to_game_xy(event->tfinger.x, event->tfinger.y, &x, &y); + } + } + set_mouse_button_event(event, SDL_MOUSEBUTTONDOWN, simulated_button, x, y); + } else if (num_fingers_down == 1) { + // when dragging, and the last finger is lifted, the drag is over + Uint8 simulated_button = 0; + if (multi_finger_dragging[port] == DRAG_THREE_FINGER) { + simulated_button = SDL_BUTTON_RIGHT; + } else { + simulated_button = SDL_BUTTON_LEFT; + } + set_mouse_button_event(event, SDL_MOUSEBUTTONUP, simulated_button, x, y); + multi_finger_dragging[port] = DRAG_NONE; + } + } +} + +static void preprocess_finger_motion(SDL_Event *event) +{ + // front (0) or back (1) panel + SDL_TouchID port = event->tfinger.touchId; + // id (for multitouch) + SDL_FingerID id = event->tfinger.fingerId; + + // find out how many fingers were down before this event + int num_fingers_down = 0; + for (int i = 0; i < MAX_NUM_FINGERS; i++) { + if (finger[port][i].id >= 0) { + num_fingers_down++; + } + } + + if (num_fingers_down == 0) { + return; + } + + if (num_fingers_down >= 1) { + int x = mouse_x; + int y = mouse_y; + int xrel = 0; + int yrel = 0; + + if (direct_touch) { + convert_touch_xy_to_game_xy(event->tfinger.x, event->tfinger.y, &x, &y); + } else { + // for relative mode, use the pointer speed setting + float speedFactor = 1.0; + + // convert touch events to relative mouse pointer events + // Whenever an SDL_event involving the mouse is processed, + x = (mouse_x + (event->tfinger.dx * 1.25 * speedFactor * DISPLAY_WIDTH)); + y = (mouse_y + (event->tfinger.dy * 1.25 * speedFactor * DISPLAY_HEIGHT)); + } + + x = CLIP(x, 0, (int)DISPLAY_WIDTH); + y = CLIP(y, 0, (int)DISPLAY_HEIGHT); + xrel = x - mouse_x; + yrel = y - mouse_y; + + // update the current finger's coordinates so we can track it later + for (int i = 0; i < MAX_NUM_FINGERS; i++) { + if (finger[port][i].id != id) + continue; + finger[port][i].last_x = x; + finger[port][i].last_y = y; + } + + // If we are starting a multi-finger drag, start holding down the mouse button + if (num_fingers_down >= 2 && !multi_finger_dragging[port]) { + // only start a multi-finger drag if at least two fingers have been down long enough + int num_fingers_downlong = 0; + for (int i = 0; i < MAX_NUM_FINGERS; i++) { + if (finger[port][i].id == NO_TOUCH) { + continue; + } + if (event->tfinger.timestamp - finger[port][i].time_last_down > MAX_TAP_TIME) { + num_fingers_downlong++; + } + } + if (num_fingers_downlong >= 2) { + int mouse_down_x = mouse_x; + int mouse_down_y = mouse_y; + if (direct_touch) { + for (int i = 0; i < MAX_NUM_FINGERS; i++) { + if (finger[port][i].id == id) { + uint32_t earliest_time = finger[port][i].time_last_down; + for (int j = 0; j < MAX_NUM_FINGERS; j++) { + if (finger[port][j].id >= 0 && (i != j) ) { + if (finger[port][j].time_last_down < earliest_time) { + mouse_down_x = finger[port][j].last_x; + mouse_down_y = finger[port][j].last_y; + earliest_time = finger[port][j].time_last_down; + } + } + } + break; + } + } + } + + Uint8 simulated_button = 0; + if (num_fingers_downlong == 2) { + simulated_button = SDL_BUTTON_LEFT; + multi_finger_dragging[port] = DRAG_TWO_FINGER; + } else { + simulated_button = SDL_BUTTON_RIGHT; + multi_finger_dragging[port] = DRAG_THREE_FINGER; + } + SDL_Event ev; + set_mouse_button_event(&ev, SDL_MOUSEBUTTONDOWN, simulated_button, mouse_down_x, mouse_down_y); + SDL_PushEvent(&ev); + } + } + + if (!xrel && !yrel) { + return; + } + + // check if this is the "oldest" finger down (or the only finger down) + // otherwise it will not affect mouse motion + bool update_pointer = true; + if (num_fingers_down > 1) { + for (int i = 0; i < MAX_NUM_FINGERS; i++) { + if (finger[port][i].id != id) { + continue; + } + for (int j = 0; j < MAX_NUM_FINGERS; j++) { + if (finger[port][j].id == NO_TOUCH || (j == i)) { + continue; + } + if (finger[port][j].time_last_down < finger[port][i].time_last_down) { + update_pointer = false; + } + } + } + } + if (!update_pointer) { + return; + } + set_mouse_motion_event(event, x, y, xrel, yrel); + } +} + +void finish_simulated_mouse_clicks(int current_mouse_x, int current_mouse_y) +{ + mouse_x = current_mouse_x; + mouse_y = current_mouse_y; + + for (int port = 0; port < TOUCH_PORT_MAX_NUM; port++) { + for (int i = 0; i < 2; i++) { + if (simulated_click_start_time[port][i] == 0) { + continue; + } + + Uint32 current_time = SDL_GetTicks(); + if (current_time - simulated_click_start_time[port][i] < SIMULATED_CLICK_DURATION) { + continue; + } + + int simulated_button; + if (i == 0) { + simulated_button = SDL_BUTTON_LEFT; + } else { + simulated_button = SDL_BUTTON_RIGHT; + } + SDL_Event ev; + set_mouse_button_event(&ev, SDL_MOUSEBUTTONUP, simulated_button, mouse_x, mouse_y); + SDL_PushEvent(&ev); + + simulated_click_start_time[port][i] = 0; + } + } +} + +static void set_mouse_button_event(SDL_Event *event, uint32_t type, uint8_t button, int32_t x, int32_t y) +{ + event->type = type; + event->button.button = button; + if (type == SDL_MOUSEBUTTONDOWN) { + event->button.state = SDL_PRESSED; + } else { + event->button.state = SDL_RELEASED; + } + event->button.x = x; + event->button.y = y; +} + +static void set_mouse_motion_event(SDL_Event *event, int32_t x, int32_t y, int32_t xrel, int32_t yrel) +{ + event->type = SDL_MOUSEMOTION; + event->motion.x = x; + event->motion.y = y; + event->motion.xrel = xrel; + event->motion.yrel = yrel; +} + +static void convert_touch_xy_to_game_xy(float touch_x, float touch_y, int *game_x, int *game_y) { + const int screen_h = GAME_HEIGHT; + const int screen_w = GAME_WIDTH; + const int disp_w = DISPLAY_WIDTH; + const int disp_h = DISPLAY_HEIGHT; + + int x, y, w, h; + float sx, sy; + + h = disp_h; + w = h * 16.0/9.0; + + x = (disp_w - w) / 2; + y = (disp_h - h) / 2; + + sy = (float) h / (float) screen_h; + sx = (float) w / (float) screen_w; + + // Find touch coordinates in terms of screen pixels + float disp_touch_x = (touch_x * (float) disp_w); + float disp_touch_y = (touch_y * (float) disp_h); + + *game_x = CLIP((int)((disp_touch_x - x) / sx), 0, (int) GAME_WIDTH); + *game_y = CLIP((int)((disp_touch_y - y) / sy), 0, (int) GAME_HEIGHT); +} diff --git a/touch/touch.h b/touch/touch.h new file mode 100644 index 0000000..d9b6f44 --- /dev/null +++ b/touch/touch.h @@ -0,0 +1,9 @@ +#ifndef TOUCH_H +#define TOUCH_H + +#include +#include + +void handle_touch(SDL_Event *event, int current_mouse_x, int current_mouse_y); +void finish_simulated_mouse_clicks(int current_mouse_x, int current_mouse_y); +#endif