diff --git a/src/common/audio/sound/i_soundinternal.h b/src/common/audio/sound/i_soundinternal.h index d1db403d2..408344ebd 100644 --- a/src/common/audio/sound/i_soundinternal.h +++ b/src/common/audio/sound/i_soundinternal.h @@ -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 EChanFlags; diff --git a/src/common/audio/sound/s_sound.cpp b/src/common/audio/sound/s_sound.cpp index 341e0a519..c8d71da2b 100644 --- a/src/common/audio/sound/s_sound.cpp +++ b/src/common/audio/sound/s_sound.cpp @@ -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; diff --git a/src/common/engine/m_haptics.cpp b/src/common/engine/m_haptics.cpp index 8db6c99e1..2d1305536 100644 --- a/src/common/engine/m_haptics.cpp +++ b/src/common/engine/m_haptics.cpp @@ -30,20 +30,19 @@ #include #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 unused, used; + + { + TMapIterator it(RumbleDefinition); + TMap::Pair* pair; + while (it.NextPair(pair)) unused.AddUnique(pair->Key.GetChars()); + } + { + TMapIterator it(RumbleAlias); + TMap::Pair* pair; + while (it.NextPair(pair)) unused.AddUnique(pair->Key.GetChars()); + } + { + TMapIterator it(RumbleAlias); + TMap::Pair* pair; + while (it.NextPair(pair)) + { + if (unused.Contains(pair->Value.GetChars())) + unused.Delete(unused.Find(pair->Value.GetChars())); + } + } + { + TMapIterator it(RumbleMapping); + TMap::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 (std::stof(argv[2], nullptr)); + low_freq = static_cast (std::stof(argv[3], nullptr)); + left_trig = static_cast (std::stof(argv[4], nullptr)); + right_trig = static_cast (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; +} diff --git a/src/sound/s_advsound.cpp b/src/sound/s_advsound.cpp index 4678f5cf1..985dfd3a8 100644 --- a/src/sound/s_advsound.cpp +++ b/src/sound/s_advsound.cpp @@ -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; } diff --git a/src/sound/s_doomsound.cpp b/src/sound/s_doomsound.cpp index 1a47aa619..dcb0ff05e 100644 --- a/src/sound/s_doomsound.cpp +++ b/src/sound/s_doomsound.cpp @@ -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); } diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 55469c4e4..a793fd879 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -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" diff --git a/wadsrc/static/zscript/actors/shared/haptics.zs b/wadsrc/static/zscript/actors/shared/haptics.zs new file mode 100644 index 000000000..a1c56ebea --- /dev/null +++ b/wadsrc/static/zscript/actors/shared/haptics.zs @@ -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); +} diff --git a/wadsrc/static/zscript/engine/base.zs b/wadsrc/static/zscript/engine/base.zs index 938125342..5abd62422 100644 --- a/wadsrc/static/zscript/engine/base.zs +++ b/wadsrc/static/zscript/engine/base.zs @@ -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. };