Enabled by default. Use 'snd_waterfx 0' to disable it.
Adapted code from Ironwail so that it works in this engine.
Cherry-picked from daftmugi/wrath-darkplaces
// used by EntityState5_ReadUpdate
skeleton_t *engineskeletonobjects;
+
+ // used by underwater sound filter (snd_waterfx)
+ qbool view_underwater;
}
client_state_t;
}
};
+void S_SetUnderwaterIntensity(void);
// =======================================================================
// Internal sound data & structures
cvar_t snd_spatialization_occlusion = {CF_CLIENT | CF_ARCHIVE, "snd_spatialization_occlusion", "1", "enable occlusion testing on spatialized sounds, which simply quiets sounds that are blocked by the world; 1 enables PVS method, 2 enables LineOfSight method, 3 enables both"};
// Cvars declared in snd_main.h (shared with other snd_*.c files)
+cvar_t snd_waterfx = {CF_CLIENT | CF_ARCHIVE, "snd_waterfx", "1", "underwater sound filter"};
cvar_t _snd_mixahead = {CF_CLIENT | CF_ARCHIVE, "_snd_mixahead", "0.15", "how much sound to mix ahead of time"};
cvar_t snd_streaming = {CF_CLIENT | CF_ARCHIVE, "snd_streaming", "1", "enables keeping compressed ogg sound files compressed, decompressing them only as needed, otherwise they will be decompressed completely at load (may use a lot of memory); when set to 2, streaming is performed even if this would waste memory"};
cvar_t snd_streaming_length = {CF_CLIENT | CF_ARCHIVE, "snd_streaming_length", "1", "decompress sounds completely if they are less than this play time when snd_streaming is 1"};
Cvar_RegisterVariable(&ambient_fade);
Cvar_RegisterVariable(&snd_noextraupdate);
Cvar_RegisterVariable(&snd_show);
+ Cvar_RegisterVariable(&snd_waterfx);
Cvar_RegisterVariable(&_snd_mixahead);
Cvar_RegisterVariable(&snd_swapstereo); // for people with backwards sound wiring
Cvar_RegisterVariable(&snd_channellayout);
S_PlaySfxOnChannel (sfx, target_chan, CHANNELFLAG_FORCELOOP, origin, fvol, attenuation, true, 0, 0, 0, 1.0f);
}
-
/*
===================
S_UpdateAmbientSounds
if (cl.worldmodel && cl.worldmodel->brush.AmbientSoundLevelsForPoint)
cl.worldmodel->brush.AmbientSoundLevelsForPoint(cl.worldmodel, listener_origin, ambientlevels, sizeof(ambientlevels));
+
// Calc ambient sound levels
+ S_SetUnderwaterIntensity();
+
for (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++)
{
chan = &channels[ambient_channel];
extern qbool snd_threaded; // enables use of snd_usethreadedmixing, provided that no sound hacks are in effect (like timedemo)
extern qbool snd_usethreadedmixing; // if true, the main thread does not mix sound, soundtime does not advance, and neither does snd_renderbuffer->endframe, instead the audio thread will call S_MixToBuffer as needed
+extern struct cvar_s snd_waterfx;
extern struct cvar_s _snd_mixahead;
extern struct cvar_s snd_swapstereo;
extern struct cvar_s snd_streaming;
}
}
+/*
+===============================================================================
+
+UNDERWATER EFFECT
+
+===============================================================================
+*/
+
+static struct {
+ float intensity;
+ float alpha;
+ float accum[SND_LISTENERS];
+} underwater = {0.f, 1.f, {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}};
+
+void S_SetUnderwaterIntensity(void)
+{
+ float host_frametime = cl.realframetime;
+ float target = cl.view_underwater ? 1.f : 0.f;
+
+ if (snd_waterfx.value < 0.f)
+ target *= 0.f;
+ else if (snd_waterfx.value > 2.f)
+ target *= 2.f;
+ else
+ target *= snd_waterfx.value;
+
+ if (underwater.intensity < target)
+ {
+ underwater.intensity += host_frametime * 4.f;
+ underwater.intensity = min(underwater.intensity, target);
+ }
+ else if (underwater.intensity > target)
+ {
+ underwater.intensity -= host_frametime * 4.f;
+ underwater.intensity = max(underwater.intensity, target);
+ }
+
+ underwater.alpha = exp(-underwater.intensity * log(12.f));
+}
+
+static void S_UnderwaterFilter(int endtime)
+{
+ int i;
+ int sl;
+ if (!underwater.intensity) {
+ if (endtime > 0) {
+ for (sl = 0; sl < SND_LISTENERS; sl++) {
+ underwater.accum[sl] = paintbuffer[endtime-1].sample[sl];
+ }
+ }
+ return;
+ }
+ for (i = 0; i < endtime; i++) {
+ for (sl = 0; sl < SND_LISTENERS; sl++) {
+ underwater.accum[sl] += underwater.alpha * (paintbuffer[i].sample[sl] - underwater.accum[sl]);
+ paintbuffer[i].sample[sl] = underwater.accum[sl];
+ }
+ }
+}
/*
===============================================================================
S_SoftClipPaintBuffer(paintbuffer, totalmixframes, snd_renderbuffer->format.width, snd_renderbuffer->format.channels);
+ S_UnderwaterFilter(totalmixframes);
+
+
#ifdef CONFIG_VIDEO_CAPTURE
if (!snd_usethreadedmixing)
S_CaptureAVISound(paintbuffer, totalmixframes);
supercontents = CL_PointSuperContents(vieworigin);
if (supercontents & SUPERCONTENTS_LIQUIDSMASK)
{
+ cl.view_underwater = true;
r_refdef.frustumscale_x *= 1 - (((sin(cl.time * 4.7) + 1) * 0.015) * r_waterwarp.value);
r_refdef.frustumscale_y *= 1 - (((sin(cl.time * 3.0) + 1) * 0.015) * r_waterwarp.value);
if (supercontents & SUPERCONTENTS_LAVA)
}
else
{
+ cl.view_underwater = false;
cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0;
cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 0;
cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0;