Added haptic bindings

This commit is contained in:
Marcus Minhorst 2025-07-21 22:30:45 -04:00 committed by Rachael Alexanderson
commit 848df46ee0
8 changed files with 224 additions and 12 deletions

View file

@ -32,6 +32,8 @@ enum EChanFlag
CHANF_TRANSIENT = 32768, // Do not record in savegames - used for sounds that get restarted outside the sound system (e.g. ambients in SW and Blood)
CHANF_FORCE = 65536, // Start, even if sound is paused.
CHANF_SINGULAR = 0x20000, // Only start if no sound of this name is already playing.
CHANF_RUMBLE = 0x40000, // Hint to rumble trigger rumble from sound
CHANF_NORUMBLE = 0x80000, // Disable rumble even if it would normally happen
};
typedef TFlags<EChanFlag> EChanFlags;

View file

@ -40,6 +40,7 @@
#include "c_cvars.h"
#include "gamestate.h"
#include "i_soundinternal.h"
#include "m_haptics.h"
#include "m_random.h"
#include "m_swap.h"
#include "printf.h"
@ -475,6 +476,11 @@ FSoundChan *SoundEngine::StartSound(int type, const void *source,
// Attenuate the attenuation based on the sound.
attenuation *= sfx->Attenuation;
#if 0
// sound debug printout
Printf("sound: '%s' -> '%s' %g\n", soundEngine->GetSoundName(org_id), soundEngine->GetSoundName(sound_id), attenuation);
#endif
// The passed rolloff overrides any sound-specific rolloff.
if (forcedrolloff != NULL && forcedrolloff->MinDistance != 0)
{
@ -608,6 +614,14 @@ FSoundChan *SoundEngine::StartSound(int type, const void *source,
{
chanflags |= CHANF_LISTENERZ | CHANF_JUSTSTARTED;
}
if (chanflags & CHANF_RUMBLE && !(chanflags & CHANF_NORUMBLE))
{
// I would love to use attenuation here, but it seems weird.
// Pistol's attenuation is always 1, but it's not silent.
// I'm not sure why that is.
// Joy_Rumble(soundEngine->GetSoundName(org_id), (chanflags&CHANF_IS3D)? attenuation: 0);
Joy_Rumble(soundEngine->GetSoundName(org_id));
}
if (chan != NULL)
{
chan->SoundID = sound_id;

View file

@ -30,20 +30,19 @@
#include <math.h>
#include "c_cvars.h"
#include "c_dispatch.h"
#include "doomdef.h"
#include "doomstat.h"
#include "m_haptics.h"
#include "name.h"
#include "printf.h"
#include "s_soundinternal.h"
#include "tarray.h"
#include "vm.h"
#include "zstring.h"
// MACROS ------------------------------------------------------------------
#ifndef MAX_TRY_DEPTH
#define MAX_TRY_DEPTH 8
#endif
// TYPES -------------------------------------------------------------------
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
@ -379,3 +378,172 @@ void Joy_Rumble(const FName identifier, double attenuation)
Joy_Rumble(identifier, * rumble, attenuation);
}
//==========================================================================
//
// rumble
//
// test command
//
//==========================================================================
CCMD (rumble)
{
int count = argv.argc()-1;
int ticks;
double high_freq, low_freq, left_trig, right_trig;
switch (count) {
case 0: {
TArray<FString> unused, used;
{
TMapIterator<FName, struct Haptics> it(RumbleDefinition);
TMap<FName, struct Haptics>::Pair* pair;
while (it.NextPair(pair)) unused.AddUnique(pair->Key.GetChars());
}
{
TMapIterator<FName, FName> it(RumbleAlias);
TMap<FName, FName>::Pair* pair;
while (it.NextPair(pair)) unused.AddUnique(pair->Key.GetChars());
}
{
TMapIterator<FName, FName> it(RumbleAlias);
TMap<FName, FName>::Pair* pair;
while (it.NextPair(pair))
{
if (unused.Contains(pair->Value.GetChars()))
unused.Delete(unused.Find(pair->Value.GetChars()));
}
}
{
TMapIterator<FName, FName> it(RumbleMapping);
TMap<FName, FName>::Pair* pair;
Printf("Mappings:\n");
while (it.NextPair(pair)) {
used.AddUnique(pair->Value.GetChars());
if (unused.Contains(pair->Value.GetChars()))
unused.Delete(unused.Find(pair->Value.GetChars()));
auto mapping = Joy_GetRumble(pair->Value);
FString key = pair->Key.GetChars();
FString val = pair->Value.GetChars();
key.ToLower();
val.ToUpper();
FString a = FStringf("'%s'\t->\t'%s'", key.GetChars(), val.GetChars());
FString b = mapping
? FStringf(
"{ %d %g %g %g %g }",
mapping->ticks,
mapping->high_frequency,
mapping->low_frequency,
mapping->left_trigger,
mapping->right_trigger
) : "[undefined]";
Printf("\t%s\t->\t%s\n", a.GetChars(), b.GetChars());
}
}
if (unused.Size() > 0)
{
Printf("Unused:\n");
for (auto i:unused)
{
FString s = i.GetChars();
s.ToUpper();
Printf("\t'%s'\n", s.GetChars());
}
}
Printf("Testing rumble for 5s\n");
Joy_Rumble("", {5 * TICRATE, 1.0, 1.0, 1.0, 1.0});
}
break;
case 1:
Printf("Testing rumble for action '%s'\n", argv[1]);
Joy_Rumble(argv[1]);
break;
case 5:
try {
ticks = std::stoi(argv[1], nullptr, 10);
high_freq = static_cast <double> (std::stof(argv[2], nullptr));
low_freq = static_cast <double> (std::stof(argv[3], nullptr));
left_trig = static_cast <double> (std::stof(argv[4], nullptr));
right_trig = static_cast <double> (std::stof(argv[5], nullptr));
} catch (...) {
Printf("Failed to parse args\n");
return;
}
Printf("testing rumble with params (%d, %f, %f, %f, %f)\n", ticks, high_freq, low_freq, left_trig, right_trig);
Joy_Rumble("", {ticks, high_freq, low_freq, left_trig, right_trig});
break;
default:
Printf(
"usage:\n %s\n %s\n %s\n",
"rumble",
"rumble string_id",
"rumble int_duration float_high_freq float_low_freq float_left_trig float_right_trigger"
);
break;
}
}
//==========================================================================
//
// _Rumble
//
// VM wrapper for Joy_Rumble
//
//==========================================================================
void _Rumble(const int identifier) {
Joy_Rumble(ENamedName(identifier));
}
//==========================================================================
//
// Rumble
//
// VM function for named Joy_Rumble
//
//==========================================================================
DEFINE_ACTION_FUNCTION_NATIVE(DHaptics, Rumble, _Rumble)
{
PARAM_PROLOGUE;
PARAM_INT(identifier);
_Rumble(ENamedName(identifier));
return 0;
}
//==========================================================================
//
// _RumbleDirect
//
// VM wrapper for Joy_Rumble
//
//==========================================================================
void _RumbleDirect(int source, int tic_count, double high_frequency, double low_frequency, double left_trigger, double right_trigger) {
Joy_Rumble(ENamedName(source), {tic_count, high_frequency, low_frequency, left_trigger, right_trigger});
}
//==========================================================================
//
// Rumble
//
// VM function for direct Joy_Rumble
//
//==========================================================================
DEFINE_ACTION_FUNCTION_NATIVE(DHaptics, RumbleDirect, _RumbleDirect)
{
PARAM_PROLOGUE;
PARAM_INT(source);
PARAM_INT(tic_count);
PARAM_FLOAT(high_frequency);
PARAM_FLOAT(low_frequency);
PARAM_FLOAT(left_trigger);
PARAM_FLOAT(right_trigger);
_RumbleDirect(source, tic_count, high_frequency, low_frequency, left_trigger, right_trigger);
return 0;
}

View file

@ -4,6 +4,7 @@
** Routines for managing SNDINFO lumps and ambient sounds
**
**---------------------------------------------------------------------------
**
** Copyright 1998-2008 Randy Heit
** Copyright 2017-2025 GZDoom Maintainers and Contributors
** All rights reserved.
@ -30,6 +31,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.
**
**---------------------------------------------------------------------------
**
*/
@ -44,6 +46,7 @@
#include "gi.h"
#include "i_music.h"
#include "i_sound.h"
#include "m_haptics.h"
#include "r_data/sprites.h"
#include "s_music.h"
#include "serializer.h"
@ -350,7 +353,7 @@ void S_CheckIntegrity()
}
}
// Joy_ReadyRumbleMapping();
Joy_ReadyRumbleMapping();
}
//==========================================================================
@ -578,7 +581,7 @@ void S_ClearSoundData()
HexenMusic.Clear();
ModPlayers.Clear();
// Joy_ResetRumbleMapping();
Joy_ResetRumbleMapping();
}
//==========================================================================
@ -1156,7 +1159,7 @@ static void S_AddSNDINFO (int lump)
if (isAlias)
{
sc.MustGetString();
// Joy_AddRumbleAlias(identifier, FName(sc.String));
Joy_AddRumbleAlias(identifier, FName(sc.String));
}
else
{
@ -1171,10 +1174,10 @@ static void S_AddSNDINFO (int lump)
sc.MustGetFloat();
double right_trig = sc.Float;
// Joy_AddRumbleType(
// identifier,
// { duration, low_freq, high_freq, left_trig, right_trig, }
// );
Joy_AddRumbleType(
identifier,
{ duration, low_freq, high_freq, left_trig, right_trig, }
);
}
// if (sc.CheckToken(TK_IntConst))
@ -1195,7 +1198,7 @@ static void S_AddSNDINFO (int lump)
sc.MustGetString();
FString mapping (sc.String);
// Joy_MapRumbleType(sound, mapping);
Joy_MapRumbleType(sound, mapping);
}
break;
}

View file

@ -31,6 +31,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.
**
**---------------------------------------------------------------------------
**
*/
@ -511,6 +512,20 @@ void DoomSoundEngine::StopChannel(FSoundChan* chan)
void S_SoundPitchActor(AActor *ent, int channel, EChanFlags flags, FSoundID sound_id, float volume, float attenuation, float pitch, float startTime)
{
#if 0
// sound source debug printout
Printf("sound '%s' from '%s'\n", soundEngine->GetSoundName(sound_id), ent->GetClass()->TypeName.GetChars());
#endif
if (flags & CHANF_NORUMBLE)
{
// remove conflicting flag
if (flags & CHANF_RUMBLE)
{
flags = (EChanFlag)(flags - CHANF_RUMBLE);
}
}
if (VerifyActorSound(ent, sound_id, channel, flags))
soundEngine->StartSound (SOURCE_Actor, ent, nullptr, channel, flags, sound_id, volume, attenuation, 0, pitch, startTime);
}

View file

@ -96,6 +96,7 @@ version "4.15.1"
#include "zscript/actors/shared/randomspawner.zs"
#include "zscript/actors/shared/dynlights.zs"
#include "zscript/actors/shared/corona.zs"
#include "zscript/actors/shared/haptics.zs"
#include "zscript/actors/doom/doomplayer.zs"
#include "zscript/actors/doom/possessed.zs"

View file

@ -0,0 +1,5 @@
class Haptics
{
native static void RumbleDirect(int tic_count, float high_frequency, float low_frequency, float left_trigger, float right_trigger);
native static void Rumble(Name identifier);
}

View file

@ -2,6 +2,7 @@
** base.zs
**
**---------------------------------------------------------------------------
**
** Copyright 2016-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.
**
**---------------------------------------------------------------------------
**
*/
@ -67,6 +69,8 @@ enum ESoundFlags
CHANF_TRANSIENT = 32768, // Do not record in savegames - used for sounds that get restarted outside the sound system (e.g. ambients in SW and Blood)
CHANF_FORCE = 65536, // Start, even if sound is paused.
CHANF_SINGULAR = 0x20000, // Only start if no sound of this name is already playing.
CHANF_RUMBLE = 0x40000, // Hint to rumble trigger rumble from sound
CHANF_NORUMBLE = 0x80000, // Disable rumble even if it would normally happen
CHANF_LOOPING = CHANF_LOOP | CHANF_NOSTOP, // convenience value for replicating the old 'looping' boolean.
};