Added rumble from menus

This commit is contained in:
Marcus Minhorst 2025-08-08 14:13:58 -04:00 committed by Rachael Alexanderson
commit 7a21853d3f
10 changed files with 86 additions and 24 deletions

View file

@ -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 --------------------------------------------------------------------

View file

@ -29,6 +29,8 @@
// HEADER FILES ------------------------------------------------------------
#include "c_cvars.h"
#include "i_soundinternal.h"
#ifdef _WIN32
#include <direct.h>
#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<std::string>& 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);

View file

@ -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)

View file

@ -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)

View file

@ -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_t> spechit;
TArray<spechit_t> 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;
}

View file

@ -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"
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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;

View file

@ -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
);
}
// ====================================================================