From 7a21853d3f35454647d461bbf097f58f217adb0f Mon Sep 17 00:00:00 2001 From: Marcus Minhorst Date: Fri, 8 Aug 2025 14:13:58 -0400 Subject: [PATCH] Added rumble from menus --- src/common/engine/m_haptics.cpp | 1 + src/d_main.cpp | 8 +++-- src/menu/doommenu.cpp | 30 ++++++++++------- src/playsim/p_lnspec.cpp | 6 ++-- src/playsim/p_map.cpp | 14 ++++++-- wadsrc/static/menudef.txt | 1 + wadsrc/static/zscript/actors/actor.zs | 2 +- wadsrc/static/zscript/engine/ui/menu/menu.zs | 6 +++- .../zscript/ui/menu/conversationmenu.zs | 32 +++++++++++++++++-- .../zscript/ui/statscreen/statscreen.zs | 10 +++++- 10 files changed, 86 insertions(+), 24 deletions(-) diff --git a/src/common/engine/m_haptics.cpp b/src/common/engine/m_haptics.cpp index 351eb4d2d..02be43b9e 100644 --- a/src/common/engine/m_haptics.cpp +++ b/src/common/engine/m_haptics.cpp @@ -141,6 +141,7 @@ CUSTOM_CVARD(Int, haptics_compat, HAPTCOMPAT_MATCH, CVAR_ARCHIVE | CVAR_GLOBALCO if (self >= NUM_HAPTCOMPAT) self = NUM_HAPTCOMPAT-1; } +CVARD(Bool, haptics_do_menus, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "allow haptic feedback for menus"); CVARD(Bool, haptics_do_action, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG, "allow haptic feedback for player doing things"); // CODE -------------------------------------------------------------------- diff --git a/src/d_main.cpp b/src/d_main.cpp index 30253cd5c..aaa393f06 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -29,6 +29,8 @@ // HEADER FILES ------------------------------------------------------------ +#include "c_cvars.h" +#include "i_soundinternal.h" #ifdef _WIN32 #include #endif @@ -200,6 +202,7 @@ EXTERN_CVAR (Bool, r_drawplayersprites) EXTERN_CVAR (Bool, show_messages) EXTERN_CVAR(Bool, ticker) EXTERN_CVAR(Bool, vid_fps) +EXTERN_CVAR(Bool, haptics_do_menus) extern bool setmodeneeded; extern bool demorecording; @@ -2823,14 +2826,13 @@ static bool System_CaptureModeInGame() static void System_PlayStartupSound(const char* sndname) { - S_Sound(CHAN_BODY, 0, sndname, 1, ATTN_NONE); + S_Sound(CHAN_BODY, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), sndname, 1, ATTN_NONE); } static bool System_IsSpecialUI() { return (generic_ui || !!log_vgafont || !!dlg_vgafont || ConsoleState != c_up || multiplayer || (menuactive == MENU_On && CurrentMenu && !CurrentMenu->IsKindOf("ConversationMenu"))); - } static bool System_DisableTextureFilter() @@ -3577,7 +3579,7 @@ static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allw autostart = true; } - S_Sound (CHAN_BODY, 0, "misc/startupdone", 1, ATTN_NONE); + S_Sound(CHAN_BODY, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "misc/startupdone", 1, ATTN_NONE); if (!batchrun) Printf ("Init complete.\n"); StartWindow->Progress(max_progress); diff --git a/src/menu/doommenu.cpp b/src/menu/doommenu.cpp index 5ae9c33cd..d803e0251 100644 --- a/src/menu/doommenu.cpp +++ b/src/menu/doommenu.cpp @@ -3,6 +3,7 @@ ** Menu base class and global interface ** **--------------------------------------------------------------------------- +** ** Copyright 2010 Christoph Oelckers ** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. @@ -29,6 +30,7 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ @@ -47,6 +49,8 @@ #include "gstrings.h" #include "hwrenderer/scene/hw_drawinfo.h" #include "i_interface.h" +#include "i_net.h" +#include "i_soundinternal.h" #include "i_time.h" #include "menu.h" #include "p_tick.h" @@ -64,6 +68,7 @@ EXTERN_CVAR(Bool, m_quickexit) EXTERN_CVAR(Bool, saveloadconfirmation) // [mxd] EXTERN_CVAR(Bool, quicksaverotation) EXTERN_CVAR(Bool, show_messages) +EXTERN_CVAR(Bool, haptics_do_menus) EXTERN_CVAR(Float, hud_scalefactor) CVAR(Bool, m_simpleoptions, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG); @@ -312,7 +317,7 @@ void OnMenuOpen(bool makeSound) if (makeSound) { - S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/activate", snd_menuvolume, ATTN_NONE); } } @@ -414,7 +419,7 @@ CCMD (menu_quit) { if (gameinfo.quitSound.IsNotEmpty()) { - S_Sound(CHAN_VOICE, CHANF_UI, gameinfo.quitSound, snd_menuvolume, ATTN_NONE); + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), gameinfo.quitSound, snd_menuvolume, ATTN_NONE); I_WaitVBL(105); } } @@ -451,12 +456,12 @@ CCMD (menu_endgame) { // F7 if (!usergame) { - S_Sound (CHAN_VOICE, CHANF_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); + S_Sound (CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/invalid", snd_menuvolume, ATTN_NONE); return; } //M_StartControlPanel (true); - S_Sound (CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + S_Sound (CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/activate", snd_menuvolume, ATTN_NONE); ActivateEndGameMenu(); } @@ -471,7 +476,7 @@ CCMD (quicksave) { // F6 if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer)) { - S_Sound (CHAN_VOICE, CHANF_UI, "menu/invalid", snd_menuvolume, ATTN_NONE); + S_Sound (CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/invalid", snd_menuvolume, ATTN_NONE); return; } @@ -487,7 +492,7 @@ CCMD (quicksave) if (savegameManager.quickSaveSlot == NULL || savegameManager.quickSaveSlot == (FSaveGameNode*)1) { - S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/activate", snd_menuvolume, ATTN_NONE); M_StartControlPanel(false); M_SetMenu(NAME_SavegameMenu); return; @@ -500,7 +505,7 @@ CCMD (quicksave) return; } - S_Sound(CHAN_VOICE, CHANF_UI, "menu/activate", snd_menuvolume, ATTN_NONE); + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/activate", snd_menuvolume, ATTN_NONE); FString tempstring = GStrings.GetString("QSPROMPT"); tempstring.Substitute("%s", savegameManager.quickSaveSlot->SaveTitle.GetChars()); @@ -508,7 +513,8 @@ CCMD (quicksave) DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring.GetChars(), 0, false, NAME_None, []() { G_SaveGame(savegameManager.quickSaveSlot->Filename.GetChars(), savegameManager.quickSaveSlot->SaveTitle.GetChars()); - S_Sound(CHAN_VOICE, CHANF_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); + + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/dismiss", snd_menuvolume, ATTN_NONE); M_ClearMenus(); }); @@ -553,7 +559,7 @@ CCMD (quickload) DMenu *newmenu = CreateMessageBoxMenu(CurrentMenu, tempstring.GetChars(), 0, false, NAME_None, []() { G_LoadGame(savegameManager.quickSaveSlot->Filename.GetChars()); - S_Sound(CHAN_VOICE, CHANF_UI, "menu/dismiss", snd_menuvolume, ATTN_NONE); + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/dismiss", snd_menuvolume, ATTN_NONE); M_ClearMenus(); }); M_ActivateMenu(newmenu); @@ -588,7 +594,8 @@ CCMD (sizedown) { screenblocks = screenblocks - 1; } - S_Sound (CHAN_VOICE, CHANF_UI, "menu/change", snd_menuvolume, ATTN_NONE); + + S_Sound (CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/change", snd_menuvolume, ATTN_NONE); } CCMD (sizeup) @@ -601,7 +608,8 @@ CCMD (sizeup) { screenblocks = screenblocks + 1; } - S_Sound(CHAN_VOICE, CHANF_UI, "menu/change", snd_menuvolume, ATTN_NONE); + + S_Sound(CHAN_VOICE, CHANF_UI|(haptics_do_menus?CHANF_RUMBLE:CHANF_NORUMBLE), "menu/change", snd_menuvolume, ATTN_NONE); } CCMD(reset2defaults) diff --git a/src/playsim/p_lnspec.cpp b/src/playsim/p_lnspec.cpp index ae187f9ca..62bb31456 100644 --- a/src/playsim/p_lnspec.cpp +++ b/src/playsim/p_lnspec.cpp @@ -3,6 +3,7 @@ ** Handles line specials ** **--------------------------------------------------------------------------- +** ** Copyright 1998-2007 Randy Heit ** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. @@ -29,6 +30,7 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** ** Each function returns true if it caused something to happen @@ -133,7 +135,7 @@ FName MODtoDamageType (int mod) } } -int NativeStartConversation(AActor* self, AActor* player, bool faceTalker, bool saveAngle); +int NativeStartConversation(AActor* self, AActor* player, bool faceTalker, bool saveAngle, bool rumble); FUNC(LS_NOP) { @@ -3390,7 +3392,7 @@ FUNC(LS_StartConversation) return false; } - return NativeStartConversation(target, it, !!arg1, true); + return NativeStartConversation(target, it, !!arg1, true, false); // TODO: expose rumble? } FUNC(LS_Thing_SetConversation) diff --git a/src/playsim/p_map.cpp b/src/playsim/p_map.cpp index fd507f12e..fcd748e53 100644 --- a/src/playsim/p_map.cpp +++ b/src/playsim/p_map.cpp @@ -74,6 +74,7 @@ #include "g_levellocals.h" #include "gi.h" #include "m_bbox.h" +#include "m_haptics.h" #include "m_random.h" #include "p_3dmidtex.h" #include "p_blockmap.h" @@ -113,6 +114,7 @@ TArray spechit; TArray portalhit; EXTERN_CVAR(Bool, net_limitconversations) +EXTERN_CVAR(Bool, haptics_do_menus) //========================================================================== // @@ -5712,7 +5714,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, HasConversation, HasConversation) ACTION_RETURN_BOOL(HasConversation(self)); } -int NativeStartConversation(AActor *self, AActor *player, bool faceTalker, bool saveAngle) +int NativeStartConversation(AActor *self, AActor *player, bool faceTalker, bool saveAngle, bool rumble) { if (!CanTalk(self)) return false; @@ -5724,6 +5726,9 @@ int NativeStartConversation(AActor *self, AActor *player, bool faceTalker, bool return false; } + if (rumble && haptics_do_menus) + Joy_Rumble("menu/activate"); + self->ConversationAnimation(0); P_StartConversation(self, player, faceTalker, saveAngle); return true; @@ -5735,7 +5740,8 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, StartConversation, NativeStartConversation PARAM_OBJECT(player, AActor); PARAM_BOOL(faceTalker); PARAM_BOOL(saveAngle); - ACTION_RETURN_BOOL(NativeStartConversation(self, player, faceTalker, saveAngle)); + PARAM_BOOL(rumble); + ACTION_RETURN_BOOL(NativeStartConversation(self, player, faceTalker, saveAngle, rumble)); } bool P_TalkFacing(AActor *player) @@ -5747,7 +5753,9 @@ bool P_TalkFacing(AActor *player) { P_AimLineAttack(player, player->Angles.Yaw + DAngle::fromDeg(angle), TALKRANGE, &t, DAngle::fromDeg(35.), ALF_FORCENOSMART | ALF_CHECKCONVERSATION | ALF_PORTALRESTRICT); if (t.linetarget != nullptr) - return NativeStartConversation(t.linetarget, player, true, true); + { + return NativeStartConversation(t.linetarget, player, true, true, true); + } } return false; } diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index a83e30f09..9e4bbca99 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -1262,6 +1262,7 @@ OptionMenu "HapticsOptions" protected Option "$HAPMNU_COMPAT", "haptics_compat", "HapticsCompatTypes" StaticText "" StaticText "$HAPMNU_OPTIONS" + Option "$HAPMNU_OPT_MENU", "haptics_do_menus", "OnOff" Option "$HAPMNU_OPT_ACTION", "haptics_do_action", "OnOff" } diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index 22ccfa7c6..3efe514b0 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -775,7 +775,7 @@ class Actor : Thinker native protected native void CheckPortalTransition(bool linked = true); native clearscope bool HasConversation() const; native clearscope bool CanTalk() const; - native bool StartConversation(Actor player, bool faceTalker = true, bool saveAngle = true); + native bool StartConversation(Actor player, bool faceTalker = true, bool saveAngle = true, bool rumble = true); native clearscope string GetTag(string defstr = "") const; native clearscope string GetCharacterName() const; diff --git a/wadsrc/static/zscript/engine/ui/menu/menu.zs b/wadsrc/static/zscript/engine/ui/menu/menu.zs index 17ad810b0..c08000e20 100644 --- a/wadsrc/static/zscript/engine/ui/menu/menu.zs +++ b/wadsrc/static/zscript/engine/ui/menu/menu.zs @@ -367,8 +367,12 @@ class Menu : Object native ui version("2.4") // //============================================================================= - static void MenuSound(Name snd) + static void MenuSound(Name snd, bool rumble = true) { + if (rumble && CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble(snd); + } menuDelegate.PlaySound(snd); } diff --git a/wadsrc/static/zscript/ui/menu/conversationmenu.zs b/wadsrc/static/zscript/ui/menu/conversationmenu.zs index 40645b487..d4e9dd0d7 100644 --- a/wadsrc/static/zscript/ui/menu/conversationmenu.zs +++ b/wadsrc/static/zscript/ui/menu/conversationmenu.zs @@ -3,6 +3,7 @@ ** The Strife dialogue display ** **--------------------------------------------------------------------------- +** ** Copyright 2010-2017 Christoph Oelckers ** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. @@ -29,6 +30,7 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ @@ -312,24 +314,46 @@ class ConversationMenu : Menu if (mkey == MKEY_Back) { Close(); + if (CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble("menu/dismiss"); + } return true; } return false; } if (mkey == MKEY_Up) { - if (--mSelection < 0) mSelection = mResponses.Size() - 1; + if (--mSelection < 0) + { + mSelection = mResponses.Size() - 1; + } + if (CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble("menu/cursor"); + } return true; } else if (mkey == MKEY_Down) { - if (++mSelection >= mResponses.Size()) mSelection = 0; + if (++mSelection >= mResponses.Size()) + { + mSelection = 0; + } + if (CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble("menu/cursor"); + } return true; } else if (mkey == MKEY_Back) { SendConversationReply(-1, GetReplyNum()); Close(); + if (CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble("menu/dismiss"); + } return true; } else if (mkey == MKEY_Enter) @@ -345,6 +369,10 @@ class ConversationMenu : Menu SendConversationReply(mCurNode.ThisNodeNum, replynum); } Close(); + if (CVar.GetCVar('haptics_do_menus').GetBool()) + { + Haptics.Rumble("menu/choose"); + } return true; } return false; diff --git a/wadsrc/static/zscript/ui/statscreen/statscreen.zs b/wadsrc/static/zscript/ui/statscreen/statscreen.zs index e36750d0b..0b9fe66aa 100644 --- a/wadsrc/static/zscript/ui/statscreen/statscreen.zs +++ b/wadsrc/static/zscript/ui/statscreen/statscreen.zs @@ -2,6 +2,7 @@ ** statscreen.zs ** **--------------------------------------------------------------------------- +** ** Copyright 2010-2017 Christoph Oelckers ** Copyright 2017-2025 GZDoom Maintainers and Contributors ** All rights reserved. @@ -28,6 +29,7 @@ ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** **--------------------------------------------------------------------------- ** */ @@ -812,7 +814,13 @@ class StatusScreen : ScreenJob abstract version("2.5") static void PlaySound(Sound snd) { - S_StartSound(snd, CHAN_VOICE, CHANF_MAYBE_LOCAL|CHANF_UI, 1, ATTN_NONE); + S_StartSound( + snd, + CHAN_VOICE, + CHANF_MAYBE_LOCAL|CHANF_UI|(CVar.GetCVar('haptics_do_menus').GetBool()? CHANF_RUMBLE: CHANF_NORUMBLE), + 1, + ATTN_NONE + ); } // ====================================================================