Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions gamedata/scripts/callbacks_gameobject.script
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,25 @@ _G.COnBeforePlayHudSound = function(alias, parent)
end
end

--[[
on_before_play_script_sound callback, scale or veto any script sound_object play (CScriptSound).
Return via result.volume_mult: 0 vetoes, <1 attenuates, nil leaves vanilla. A later engine
.volume write can override the attenuation.

Arguments
file: resolved sound file name
pos: Fvector world position, nil when the play is 2D / positionless
obj: game_object that owns the sound, nil when absent
--]]
AddScriptCallback("on_before_play_script_sound")
_G.COnBeforePlayScriptSound = function(file, pos, obj)
local result = { volume_mult = nil }
SendScriptCallback("on_before_play_script_sound", file, pos, obj, result)
if result.volume_mult then
return result
end
end

--[[
on_before_play_ambient_bed callback, scale or veto the per-weather ambient background bed.
Fired from CGamePersistent::WeathersUpdate (GamePersistent.cpp) at each bed play site.
Expand All @@ -670,6 +689,58 @@ _G.COnAmbientBed = function(section, file, pos)
end
end

--[[
on_before_play_ambient_effect callback, scale or veto the weather-effect recording sound
(CGamePersistent::WeathersUpdate). The particle burst and wind blast still play regardless.
Return via result.volume_mult: 0 vetoes the sound, <1 attenuates, nil leaves vanilla.

Arguments
file: resolved effect sound file name
pos: Fvector world position the effect plays at
--]]
AddScriptCallback("on_before_play_ambient_effect")
_G.COnAmbientEffectSound = function(file, pos)
local result = { volume_mult = nil }
SendScriptCallback("on_before_play_ambient_effect", file, pos, result)
if result.volume_mult then
return result
end
end

--[[
on_before_play_thunderbolt callback, scale or veto the lightning-strike clap. Fires on the
last bolt of a burst only.
Return via result.volume_mult: 0 vetoes the clap, <1 attenuates, nil leaves vanilla.

Arguments
file: resolved clap sound file name
distance: distance to the strike in metres
--]]
AddScriptCallback("on_before_play_thunderbolt")
_G.COnThunderboltSound = function(file, distance)
local result = { volume_mult = nil }
SendScriptCallback("on_before_play_thunderbolt", file, distance, result)
if result.volume_mult then
return result
end
end

--[[
on_before_play_rain callback, scale or veto the looped rain ambient (CEffect_Rain). Sampled
at rain onset, so a veto applies from the next time rain starts.
Return via result.volume_mult: 0 vetoes the loop, <1 attenuates, nil leaves vanilla.

Arguments
file: resolved rain ambient file name
--]]
AddScriptCallback("on_before_play_rain")
_G.COnRainSound = function(file)
local result = { volume_mult = nil }
SendScriptCallback("on_before_play_rain", file, result)
if result.volume_mult then
return result
end
end
--[[
on_before_play_level_music callback, scale or silence the level music track.
Fired from SMusicTrack::Play (level_sounds.cpp). Volume-only: a skip would make the
Expand Down
6 changes: 6 additions & 0 deletions src/xrEngine/IGame_Persistent.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ class ENGINE_API IGame_Persistent :

virtual bool CanBePaused() { return true; }

// thunderbolt clap volume gate, overridden by the game layer to ask Lua. Returns 1.0 for vanilla.
// Appended at the end of the virtual block so it adds no vtable slot ahead of the existing ones.
virtual float OnThunderboltSound(LPCSTR file, float distance) { return 1.0f; }
// looped rain ambient volume gate, overridden by the game layer to ask Lua. Returns 1.0 for vanilla.
virtual float OnRainSound(LPCSTR file) { return 1.0f; }

struct pda_data
{
float pda_display_factor;
Expand Down
15 changes: 11 additions & 4 deletions src/xrEngine/Rain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,16 @@ void CEffect_Rain::OnFrame()
case stIdle:
if (factor < EPS_L) return;
state = stWorking;
snd_Ambient.play(0, sm_Looped);
snd_Ambient.set_position(Fvector().set(0, 0, 0));
snd_Ambient.set_range(source_offset, source_offset * 2.f);
// Rain-loop hook, mirrors COnBeforePlayHudSound: veto (0) or attenuate, sampled once at onset.
rain_volume_mult = g_pGamePersistent
? g_pGamePersistent->OnRainSound(snd_Ambient._handle() ? snd_Ambient._handle()->file_name() : "")
: 1.0f;
if (rain_volume_mult > EPS_S)
{
snd_Ambient.play(0, sm_Looped);
snd_Ambient.set_position(Fvector().set(0, 0, 0));
snd_Ambient.set_range(source_offset, source_offset * 2.f);
}
break;
case stWorking:
if (factor < EPS_L)
Expand All @@ -242,7 +249,7 @@ void CEffect_Rain::OnFrame()
// snd_Ambient.set_position(sndP);
rain_volume = factor * hemi_factor;
clamp(rain_volume, .1f, 1.f);
snd_Ambient.set_volume(rain_volume);
snd_Ambient.set_volume(rain_volume * rain_volume_mult);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/xrEngine/Rain.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class ENGINE_API CEffect_Rain
ref_sound snd_Ambient;
float rain_volume;
float rain_hemi = 0.0f;
float rain_volume_mult = 1.0f; // set once at rain onset by COnRainSound; 1.0 = vanilla

// Utilities
void p_create();
Expand Down
9 changes: 8 additions & 1 deletion src/xrEngine/thunderbolt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,14 @@ void CEffect_Thunderbolt::Bolt(shared_str id, float period, float lt)
else
{
next_lightning_time = Device.fTimeGlobal + period + Random.randF(-period * 0.3f, period * 0.3f);
current->snd.play_no_feedback(0, 0, dist / 300.f, &pos, 0, 0, &Fvector2().set(dist / 2, dist * 2.f));

// clap volume gate through the game layer (no script-engine reach from xrEngine). 0 skips the clap.
float clap_volume_mult = g_pGamePersistent
? g_pGamePersistent->OnThunderboltSound(current->snd._handle() ? current->snd._handle()->file_name() : "", dist)
: 1.0f;
if (clap_volume_mult > EPS_S)
current->snd.play_no_feedback(0, 0, dist / 300.f, &pos, (clap_volume_mult < 1.0f) ? &clap_volume_mult : 0, 0,
&Fvector2().set(dist / 2, dist * 2.f));
}


Expand Down
38 changes: 36 additions & 2 deletions src/xrGame/GamePersistent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ void CGamePersistent::OnGameEnd()
xr_delete(g_stalker_velocity_holder);
}

// { volume_mult = number } from a sound hook's return; 1.0 (vanilla) when nil, not a table, or not a number
// { volume_mult = number } from a sound hook's return. 1.0 (vanilla) when nil, not a table, or not a number.
static float ambient_hook_volume_mult(const ::luabind::object& output)
{
if (output && output.type() == LUA_TTABLE)
Expand All @@ -288,6 +288,24 @@ static float ambient_hook_volume_mult(const ::luabind::object& output)
return 1.0f;
}

// Thunderbolt clap crossing. CEffect_Thunderbolt (xrEngine) has no script-engine reach, so it calls this
// IGame_Persistent virtual and the functor fires here, the COnBeforePlayHudSound shape. An unset global leaves vanilla.
float CGamePersistent::OnThunderboltSound(LPCSTR file, float distance)
{
::luabind::functor<::luabind::object> funct;
if (ai().script_engine().functor("_G.COnThunderboltSound", funct))
return ambient_hook_volume_mult(funct(file, distance));
return 1.0f;
}

float CGamePersistent::OnRainSound(LPCSTR file)
{
::luabind::functor<::luabind::object> funct;
if (ai().script_engine().functor("_G.COnRainSound", funct))
return ambient_hook_volume_mult(funct(file));
return 1.0f;
}

void CGamePersistent::WeathersUpdate()
{
if (g_pGameLevel && !g_dedicated_server)
Expand Down Expand Up @@ -396,7 +414,23 @@ void CGamePersistent::WeathersUpdate()
offset.z += Random.randF(0.5f, 5.f) * (Random.randF(0.f, 1.f) < 0.5f ? -1.f : 1.f);
pos.add(Device.vCameraPosition, offset);
ambient_particles->play_at_pos(pos);
if (eff->sound._handle()) eff->sound.play_at_pos(0, pos);

// Ambient-effect sound hook. Lua may veto (0) or attenuate the recording. The particle burst and wind blast play either way.
// An unset global leaves exact vanilla.
if (eff->sound._handle())
{
float effect_volume_mult = 1.0f;
::luabind::functor<::luabind::object> effect_funct;
if (ai().script_engine().functor("_G.COnAmbientEffectSound", effect_funct))
effect_volume_mult = ambient_hook_volume_mult(effect_funct(eff->sound._handle()->file_name(), pos));

if (effect_volume_mult > EPS_S)
{
eff->sound.play_at_pos(0, pos);
if (effect_volume_mult < 1.0f)
eff->sound.set_volume(effect_volume_mult);
}
}


Environment().wind_blast_strength_start_value = Environment().wind_strength_factor;
Expand Down
6 changes: 6 additions & 0 deletions src/xrGame/GamePersistent.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ class CGamePersistent :
void WeathersUpdate();
void UpdateDof();

public:
virtual float OnThunderboltSound(LPCSTR file, float distance);
virtual float OnRainSound(LPCSTR file);

private:

public:
ui_core* m_pUI_core;
IReader* pDemoFile;
Expand Down
46 changes: 46 additions & 0 deletions src/xrGame/script_sound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,38 @@
#include "ai_space.h"
#include "script_engine.h"

// { volume_mult = number } from a sound hook's return. 1.0 (vanilla) when nil, not a table, or not a number.
static float ambient_hook_volume_mult(const ::luabind::object& output)
{
if (output && output.type() == LUA_TTABLE)
{
auto volume_mult_obj = output["volume_mult"];
if (volume_mult_obj.type() == LUA_TNUMBER)
return ::luabind::object_cast<float>(volume_mult_obj);
}
return 1.0f;
}

// COnBeforePlayScriptSound(file, pos, obj) -> { volume_mult }, the COnBeforePlayHudSound shape.
// 0 vetoes the play, <1 attenuates, nil = vanilla. Absent args reach Lua as nil. An unset global costs one lookup.
static float script_sound_hook_volume_mult(LPCSTR file, const Fvector* pos, CScriptGameObject* object)
{
::luabind::functor<::luabind::object> funct;
if (!ai().script_engine().functor("_G.COnBeforePlayScriptSound", funct))
return 1.0f;
Fvector obj_pos; // Play() carries no position: read the object's, but only past the gate
if (!pos && object)
{
obj_pos = object->object().Position();
pos = &obj_pos;
}
if (pos && object)
return ambient_hook_volume_mult(funct(file, *pos, object));
if (pos)
return ambient_hook_volume_mult(funct(file, *pos));
return ambient_hook_volume_mult(funct(file));
}

CScriptSound::CScriptSound(LPCSTR caSoundName, ESoundTypes sound_type)
{
m_caSoundToPlay = caSoundName;
Expand Down Expand Up @@ -54,19 +86,33 @@ void CScriptSound::Play(CScriptGameObject* object, float delay, int flags)
{
THROW3(m_sound._handle(), "There is no sound", *m_caSoundToPlay);
// Msg ("%6d : CScriptSound::Play (%s), delay %f, flags %d",Device.dwTimeGlobal,m_sound._handle()->file_name(),delay,flags);
float volume_mult = script_sound_hook_volume_mult(*m_caSoundToPlay, NULL, object);
if (volume_mult <= EPS_S)
return;
m_sound.play((object) ? &object->object() : NULL, flags, delay);
if (volume_mult < 1.0f)
m_sound.set_volume(volume_mult);
}

void CScriptSound::PlayAtPos(CScriptGameObject* object, const Fvector& position, float delay, int flags)
{
THROW3(m_sound._handle(), "There is no sound", *m_caSoundToPlay);
// Msg ("%6d : CScriptSound::Play (%s), delay %f, flags %d",m_sound._handle()->file_name(),delay,flags);
float volume_mult = script_sound_hook_volume_mult(*m_caSoundToPlay, &position, object);
if (volume_mult <= EPS_S)
return;
m_sound.play_at_pos((object) ? &object->object() : NULL, position, flags, delay);
if (volume_mult < 1.0f)
m_sound.set_volume(volume_mult);
}

void CScriptSound::PlayNoFeedback(CScriptGameObject* object, u32 flags/*!< Looping */, float delay/*!< Delay */,
Fvector pos, float vol, float freq)
{
THROW3(m_sound._handle(), "There is no sound", *m_caSoundToPlay);
float volume_mult = script_sound_hook_volume_mult(*m_caSoundToPlay, &pos, object);
if (volume_mult <= EPS_S)
return;
vol *= volume_mult;
m_sound.play_no_feedback((object) ? &object->object() : NULL, flags, delay, &pos, &vol, &freq);
}