Skip to content

Lua veto/volume hooks for script sounds, weather effects, thunderbolt claps, and rain - #661

Merged
themrdemonized merged 6 commits into
themrdemonized:prfrom
damiansirbu:feat-script-sound-hooks
Sep 12, 2026
Merged

themrdemonized merged 6 commits into
themrdemonized:prfrom
damiansirbu:feat-script-sound-hooks

Conversation

@damiansirbu

@damiansirbu damiansirbu commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Lua veto/volume hooks at four ambient play sites: every sound_object play (CScriptSound) and the weather-effect recording (WeathersUpdate).
The other two are the lightning-strike clap (CEffect_Thunderbolt) and the looped rain ambient (CEffect_Rain).
Each site calls a _G functor only when Lua defines it.
The return's { volume_mult } scales the play: 0 skips it, <1 attenuates, nil is vanilla.
The shape matches the merged COnBeforePlayHudSound.

Motivation

Soundscape and dynamic-sound mods keep the engine as the scheduler, with its weather selection, placement, and cadence, and still decide at play time what is heard.
Today their only tools replace configs (static, no runtime awareness) or monkey-patch sound_ambient.script, whose one CreateTimeEvent slot is first-writer-wins, so two mods cannot both observe it.
With these hooks a horror-director mod mutes the base channel's copy of a sound it places itself, and the two never double.
A soundscape mod traces exactly what its channels fire in-game for curation.
An audition UI silences everything except the sound under review.
Any mod ducks ambient recordings during its own moments.
All of it still uses the engine's scheduling and costs nothing when no hook is defined.

Safety

All four sites run where Lua already runs: scripts invoke CScriptSound, and the effect block, the clap, and the rain loop run in the main-thread frame path.
The clap and rain sites (xrEngine) reach Lua through a new IGame_Persistent virtual (default 1.0), since xrEngine has no script-engine include.
The effect hook gates the recording only, and particles and wind blast are untouched.
Rain is a loop, so its functor fires once at onset and the returned mult applies at the per-frame volume.
A veto toggled mid-rain takes effect at the next onset.
No subscriber means vanilla behavior at one null-check per play.

Files changed

  • src/xrGame/script_sound.cpp: the three play hooks and the shared return parser.
  • src/xrGame/GamePersistent.cpp, GamePersistent.h: the effect hook and the thunderbolt and rain crossing overrides.
  • src/xrEngine/IGame_Persistent.h: the two crossing virtuals, default 1.0.
  • src/xrEngine/thunderbolt.cpp: the clap gate through the virtual.
  • src/xrEngine/Rain.cpp, Rain.h: the rain-loop gate through the virtual.
  • gamedata/scripts/lua_help_ex.script: four manifest entries.

Note: the small static ambient_hook_volume_mult also appears in #644 (the bed and music hook PR), which writes it into GamePersistent.cpp as well.
If both land, the one merged second drops its copy in a one-line rebase.

Testing

Built locally on all-in-one-vs2022-wpo (DX11, 0 errors) and exercised in-game with a probe defining every global and counting each fire, across a GAMMA session and a vanilla 1.5.3 session.

Script sounds: fired 3740 times on GAMMA and 1013 on vanilla, each carrying the real file, position, and owner.
Returning { volume_mult = 0 } during a stand-still window silenced 6464 plays with no errors, and clearing the return restored vanilla playback.

Weather effects: fired 78 times on GAMMA and 17 on vanilla, where vanilla effects.ltx resolves the trx wind_gust recordings.
Each fire carried the real file and position, and the particle burst and wind blast kept playing.

Thunderbolt: fired 75 times under a forced w_storm2 on GAMMA and 39 on vanilla, each with the real clap file and its distance.

Rain: fired at each rain onset under a forced storm, carrying the loop file, and the cached mult scaled the per-frame volume.

The return parse is one shared helper across all four sites, so the script-sound veto above covers the veto and attenuate branches for every hook.
With no subscriber defined, each site is a single null-check and behavior is exact vanilla.

Lua usage

function COnBeforePlayScriptSound(file, pos, obj)
    if file:find("^ambient\\") then return { volume_mult = 0 } end  -- veto ambient plays
end

…underbolt 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.
…nd 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.
@themrdemonized

Copy link
Copy Markdown
Owner

See #644 (comment)

@damiansirbu
damiansirbu marked this pull request as draft September 7, 2026 14:04
@damiansirbu
damiansirbu marked this pull request as ready for review September 8, 2026 01:24
@damiansirbu
damiansirbu marked this pull request as draft September 8, 2026 08:19
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.
@damiansirbu damiansirbu changed the title Lua veto/volume hooks for script sounds, weather effects, and thunderbolt claps Lua veto/volume hooks for script sounds, weather effects, thunderbolt claps, and rain Sep 8, 2026
@damiansirbu
damiansirbu marked this pull request as ready for review September 8, 2026 10:21
…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.
@damiansirbu

damiansirbu commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Same change as #644 (relay plus AddScriptCallback in callbacks_gameobject.script), for this PR's script sound, weather effect, thunderbolt, and rain.
I can move them to g_patches if you prefer, as noted on #644.

@themrdemonized
themrdemonized changed the base branch from all-in-one-vs2022-wpo to pr September 12, 2026 05:58
@themrdemonized
themrdemonized merged commit a6bd321 into themrdemonized:pr Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants