From 9655422d3f0e0121910d52760926ea1cf7cdc5b0 Mon Sep 17 00:00:00 2001 From: "dami.sirbu" Date: Mon, 7 Sep 2026 11:58:33 +0300 Subject: [PATCH 1/5] feat(sound): Lua veto/volume hooks for script, weather-effect, and thunderbolt plays Three _G callbacks at the ambient play sites, each fired only when Lua defines the global. The return's { volume_mult } scales the play: 0 skips it, <1 attenuates, nil is vanilla. The shape matches the merged COnBeforePlayHudSound. - COnBeforePlayScriptSound(file, pos, obj): every sound_object play (CScriptSound Play, PlayAtPos, PlayNoFeedback). - COnAmbientEffectSound(file, pos): the weather-effect recording in WeathersUpdate. The particle burst and wind blast play regardless. - COnThunderboltSound(file, distance): the strike clap. CEffect_Thunderbolt has no script-engine reach, so it crosses through a new IGame_Persistent OnThunderboltSound virtual (default 1.0). Manifest entries in lua_help_ex.script. No subscriber means vanilla at one functor lookup per play. --- gamedata/scripts/lua_help_ex.script | 7 +++++ src/xrEngine/IGame_Persistent.h | 3 ++ src/xrEngine/thunderbolt.cpp | 9 +++++- src/xrGame/GamePersistent.cpp | 40 ++++++++++++++++++++++++++- src/xrGame/GamePersistent.h | 5 ++++ src/xrGame/script_sound.cpp | 43 +++++++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 2 deletions(-) diff --git a/gamedata/scripts/lua_help_ex.script b/gamedata/scripts/lua_help_ex.script index 97d0205257..5b86b8f915 100644 --- a/gamedata/scripts/lua_help_ex.script +++ b/gamedata/scripts/lua_help_ex.script @@ -155,6 +155,13 @@ ClrSetB(number argb, number b) } + engine callbacks (Lua defines the _G function, the engine calls it only if present) { + -- Each returns { volume_mult = number } to scale the play (0 vetoes, <1 attenuates), or nil for vanilla. An undefined global costs nothing. The shape mirrors _G.COnBeforePlayHudSound. + table COnBeforePlayScriptSound(string file, Fvector pos, game_object obj) -- every sound_object play (CScriptSound). Absent pos and obj reach Lua as nil. Veto holds, later .volume writes override attenuation + table COnAmbientEffectSound(string file, Fvector pos) -- the weather-effect recording (WeathersUpdate). The particle burst and wind blast play regardless + table COnThunderboltSound(string file, number distance) -- the lightning-strike clap. Fires on the last bolt of a burst only, distance in metres + } + get_console() { table get_variable_bounds(cmd) } diff --git a/src/xrEngine/IGame_Persistent.h b/src/xrEngine/IGame_Persistent.h index c40b35ba2f..fe1374fdc1 100644 --- a/src/xrEngine/IGame_Persistent.h +++ b/src/xrEngine/IGame_Persistent.h @@ -159,6 +159,9 @@ class ENGINE_API IGame_Persistent : }; virtual void OnAssetsChanged(); + // thunderbolt clap volume gate, overridden by the game layer to ask Lua. Returns 1.0 for vanilla. + virtual float OnThunderboltSound(LPCSTR file, float distance) { return 1.0f; } + virtual void RegisterModel(IRenderVisual* V) #ifndef _EDITOR = 0; diff --git a/src/xrEngine/thunderbolt.cpp b/src/xrEngine/thunderbolt.cpp index 651ed52f41..0fd944b9df 100644 --- a/src/xrEngine/thunderbolt.cpp +++ b/src/xrEngine/thunderbolt.cpp @@ -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)); } diff --git a/src/xrGame/GamePersistent.cpp b/src/xrGame/GamePersistent.cpp index 19a8afb755..5cd4205bcc 100644 --- a/src/xrGame/GamePersistent.cpp +++ b/src/xrGame/GamePersistent.cpp @@ -276,6 +276,28 @@ 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. +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(volume_mult_obj); + } + 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; +} + void CGamePersistent::WeathersUpdate() { if (g_pGameLevel && !g_dedicated_server) @@ -369,7 +391,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; diff --git a/src/xrGame/GamePersistent.h b/src/xrGame/GamePersistent.h index c6d6b09351..6cb46c0def 100644 --- a/src/xrGame/GamePersistent.h +++ b/src/xrGame/GamePersistent.h @@ -50,6 +50,11 @@ class CGamePersistent : void WeathersUpdate(); void UpdateDof(); +public: + virtual float OnThunderboltSound(LPCSTR file, float distance); + +private: + public: ui_core* m_pUI_core; IReader* pDemoFile; diff --git a/src/xrGame/script_sound.cpp b/src/xrGame/script_sound.cpp index c44ad35ad1..38ace23723 100644 --- a/src/xrGame/script_sound.cpp +++ b/src/xrGame/script_sound.cpp @@ -13,6 +13,32 @@ #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(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; + 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; @@ -54,19 +80,36 @@ 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); + Fvector object_pos; + if (object) + object_pos = object->object().Position(); + float volume_mult = script_sound_hook_volume_mult(*m_caSoundToPlay, (object) ? &object_pos : 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); } From a34f1ad20c081a03eb472c8a8c8414c0b3d82499 Mon Sep 17 00:00:00 2001 From: "dami.sirbu" Date: Mon, 7 Sep 2026 17:01:06 +0300 Subject: [PATCH 2/5] refactor(sound): gate the Play position read behind the functor, append the virtual Review follow-ups, no behavior change with or without a subscriber: - CScriptSound::Play read the object position on every call; move it behind the functor presence check so the no-subscriber path costs only the lookup. - Move IGame_Persistent::OnThunderboltSound to the end of the virtual block so it adds no vtable slot ahead of the existing methods. --- src/xrEngine/IGame_Persistent.h | 7 ++++--- src/xrGame/script_sound.cpp | 11 +++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/xrEngine/IGame_Persistent.h b/src/xrEngine/IGame_Persistent.h index fe1374fdc1..b355e1237f 100644 --- a/src/xrEngine/IGame_Persistent.h +++ b/src/xrEngine/IGame_Persistent.h @@ -159,9 +159,6 @@ class ENGINE_API IGame_Persistent : }; virtual void OnAssetsChanged(); - // thunderbolt clap volume gate, overridden by the game layer to ask Lua. Returns 1.0 for vanilla. - virtual float OnThunderboltSound(LPCSTR file, float distance) { return 1.0f; } - virtual void RegisterModel(IRenderVisual* V) #ifndef _EDITOR = 0; @@ -199,6 +196,10 @@ 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; } + struct pda_data { float pda_display_factor; diff --git a/src/xrGame/script_sound.cpp b/src/xrGame/script_sound.cpp index 38ace23723..4315374914 100644 --- a/src/xrGame/script_sound.cpp +++ b/src/xrGame/script_sound.cpp @@ -32,6 +32,12 @@ static float script_sound_hook_volume_mult(LPCSTR file, const Fvector* pos, CScr ::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) @@ -80,10 +86,7 @@ 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); - Fvector object_pos; - if (object) - object_pos = object->object().Position(); - float volume_mult = script_sound_hook_volume_mult(*m_caSoundToPlay, (object) ? &object_pos : NULL, object); + 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); From 73b9044c68b888d0294a962cfab81c700ab34f2f Mon Sep 17 00:00:00 2001 From: "dami.sirbu" Date: Tue, 8 Sep 2026 11:25:05 +0300 Subject: [PATCH 3/5] feat(sound): COnRainSound hook for the looped rain ambient CEffect_Rain lives in xrEngine, which cannot reach the script engine, so it crosses through a new IGame_Persistent::OnRainSound virtual next to OnThunderboltSound. The functor fires once at the rain loop onset (stIdle to stWorking in CEffect_Rain::OnFrame), never per frame. The returned volume_mult caches on the effect and multiplies the per-frame set_volume. 0 skips the loop start, under 1 attenuates, an absent global is vanilla. Because rain is a loop sampled at onset, a veto toggled mid-rain takes effect at the next onset. Manifest entry added. --- gamedata/scripts/lua_help_ex.script | 1 + src/xrEngine/IGame_Persistent.h | 2 ++ src/xrEngine/Rain.cpp | 16 ++++++++++++---- src/xrEngine/Rain.h | 1 + src/xrGame/GamePersistent.cpp | 8 ++++++++ src/xrGame/GamePersistent.h | 1 + 6 files changed, 25 insertions(+), 4 deletions(-) diff --git a/gamedata/scripts/lua_help_ex.script b/gamedata/scripts/lua_help_ex.script index 5b86b8f915..72c4d57c17 100644 --- a/gamedata/scripts/lua_help_ex.script +++ b/gamedata/scripts/lua_help_ex.script @@ -160,6 +160,7 @@ table COnBeforePlayScriptSound(string file, Fvector pos, game_object obj) -- every sound_object play (CScriptSound). Absent pos and obj reach Lua as nil. Veto holds, later .volume writes override attenuation table COnAmbientEffectSound(string file, Fvector pos) -- the weather-effect recording (WeathersUpdate). The particle burst and wind blast play regardless table COnThunderboltSound(string file, number distance) -- the lightning-strike clap. Fires on the last bolt of a burst only, distance in metres + table COnRainSound(string file) -- the looped rain ambient (CEffect_Rain). Sampled at rain onset, so a veto applies from the next time rain starts } get_console() { diff --git a/src/xrEngine/IGame_Persistent.h b/src/xrEngine/IGame_Persistent.h index b355e1237f..569f933bb0 100644 --- a/src/xrEngine/IGame_Persistent.h +++ b/src/xrEngine/IGame_Persistent.h @@ -199,6 +199,8 @@ class ENGINE_API IGame_Persistent : // 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 { diff --git a/src/xrEngine/Rain.cpp b/src/xrEngine/Rain.cpp index fbe22a9300..ba5f64cdf7 100644 --- a/src/xrEngine/Rain.cpp +++ b/src/xrEngine/Rain.cpp @@ -219,9 +219,17 @@ 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: Lua may veto (0) or attenuate the loop at onset. + // Sampled once here, applied at set_volume below; a solo toggled mid-rain takes effect next 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) @@ -242,7 +250,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); } } diff --git a/src/xrEngine/Rain.h b/src/xrEngine/Rain.h index 69402fb3cc..6c826cb54e 100644 --- a/src/xrEngine/Rain.h +++ b/src/xrEngine/Rain.h @@ -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(); diff --git a/src/xrGame/GamePersistent.cpp b/src/xrGame/GamePersistent.cpp index 5cd4205bcc..888e34850b 100644 --- a/src/xrGame/GamePersistent.cpp +++ b/src/xrGame/GamePersistent.cpp @@ -298,6 +298,14 @@ float CGamePersistent::OnThunderboltSound(LPCSTR file, float 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) diff --git a/src/xrGame/GamePersistent.h b/src/xrGame/GamePersistent.h index 6cb46c0def..936d80bfab 100644 --- a/src/xrGame/GamePersistent.h +++ b/src/xrGame/GamePersistent.h @@ -52,6 +52,7 @@ class CGamePersistent : public: virtual float OnThunderboltSound(LPCSTR file, float distance); + virtual float OnRainSound(LPCSTR file); private: From 0bdc6206efdfaa93a0ef89ac0b2839e500621688 Mon Sep 17 00:00:00 2001 From: "dami.sirbu" Date: Tue, 8 Sep 2026 13:20:13 +0300 Subject: [PATCH 4/5] style(sound): tighten the rain-loop comment to one row (xrEngine convention) --- src/xrEngine/Rain.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/xrEngine/Rain.cpp b/src/xrEngine/Rain.cpp index ba5f64cdf7..82a5745077 100644 --- a/src/xrEngine/Rain.cpp +++ b/src/xrEngine/Rain.cpp @@ -219,8 +219,7 @@ void CEffect_Rain::OnFrame() case stIdle: if (factor < EPS_L) return; state = stWorking; - // Rain-loop hook, mirrors COnBeforePlayHudSound: Lua may veto (0) or attenuate the loop at onset. - // Sampled once here, applied at set_volume below; a solo toggled mid-rain takes effect next onset. + // 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; From d994e444423edb886ab91ee9badf6125878c9fef Mon Sep 17 00:00:00 2001 From: "dami.sirbu" Date: Wed, 9 Sep 2026 04:02:16 +0300 Subject: [PATCH 5/5] feat(callbacks): register script sound, weather effect, thunderbolt, rain hooks Add the _G.C* relay plus AddScriptCallback for on_before_play_script_sound, on_before_play_ambient_effect, on_before_play_thunderbolt, and on_before_play_rain, so a third party subscribes via RegisterScriptCallback and scales or vetoes through the result table. Mirrors COnBeforePlayHudSound. Drop the lua_help_ex engine-callbacks block: a script callback is documented at its registration, not in the bind manifest. --- gamedata/scripts/callbacks_gameobject.script | 72 ++++++++++++++++++++ gamedata/scripts/lua_help_ex.script | 8 --- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/gamedata/scripts/callbacks_gameobject.script b/gamedata/scripts/callbacks_gameobject.script index fafd64c23d..714b1f5c42 100644 --- a/gamedata/scripts/callbacks_gameobject.script +++ b/gamedata/scripts/callbacks_gameobject.script @@ -651,6 +651,78 @@ _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_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 + -- NLTP_ASHES & demonized : Actor on death callback AddScriptCallback("actor_on_death") function actor_on_first_update() diff --git a/gamedata/scripts/lua_help_ex.script b/gamedata/scripts/lua_help_ex.script index 72c4d57c17..97d0205257 100644 --- a/gamedata/scripts/lua_help_ex.script +++ b/gamedata/scripts/lua_help_ex.script @@ -155,14 +155,6 @@ ClrSetB(number argb, number b) } - engine callbacks (Lua defines the _G function, the engine calls it only if present) { - -- Each returns { volume_mult = number } to scale the play (0 vetoes, <1 attenuates), or nil for vanilla. An undefined global costs nothing. The shape mirrors _G.COnBeforePlayHudSound. - table COnBeforePlayScriptSound(string file, Fvector pos, game_object obj) -- every sound_object play (CScriptSound). Absent pos and obj reach Lua as nil. Veto holds, later .volume writes override attenuation - table COnAmbientEffectSound(string file, Fvector pos) -- the weather-effect recording (WeathersUpdate). The particle burst and wind blast play regardless - table COnThunderboltSound(string file, number distance) -- the lightning-strike clap. Fires on the last bolt of a burst only, distance in metres - table COnRainSound(string file) -- the looped rain ambient (CEffect_Rain). Sampled at rain onset, so a veto applies from the next time rain starts - } - get_console() { table get_variable_bounds(cmd) }