Implement FX sound effects (#3394)

## Description:
Adds sound effects for approved events from the [sound asset
pack](https://drive.google.com/drive/folders/1KpGYJkmLxipy8XmTeyHf40XDC4P--Ck8?usp=sharing).
15 new sound effects triggered from `FxLayer`, `EventsDisplay`, and
`RadialMenu`. Sounds play even when visual FX are off, so disabling
explosions doesn't kill audio. Unapproved sounds are included as assets
but not wired up yet.

### SoundManager architecture

Reworked `SoundManager` per [maintainer
feedback](https://github.com/openfrontio/OpenFrontIO/issues/1893#issuecomment-4184649434)
and [follow-up
review](https://github.com/openfrontio/OpenFrontIO/pull/3394):

- No more singleton. `SoundManager` is instantiated in
`createClientGame()` with `EventBus` and `UserSettings`
- Layers emit events (`PlaySoundEffectEvent`,
`SetBackgroundMusicVolumeEvent`, `SetSoundEffectsVolumeEvent`) via
EventBus instead of holding a `SoundManager` reference
- `SoundManager` subscribes to these events in its constructor
- `SoundEffect` is a type union (not an enum), per project convention
- All sound configuration (type, URL mapping, events) lives in
`Sounds.ts`
- Sound effects are lazy-loaded on first play
- Channel limit of 8 concurrent sounds. New sounds always play; when at
the limit, the oldest active sound gets stopped
- `SoundManager` bootstraps volume from `UserSettings` in its
constructor
- All Howler calls are wrapped in try/catch with error logging, so sound
failures never crash the game
- `dispose()` method unsubscribes from EventBus and unloads all Howl
instances on game shutdown
- Sound code stays entirely in `src/client/`, nothing in `core/` touches
it

## Sound approval status (per
[spreadsheet](https://drive.google.com/drive/folders/1KpGYJkmLxipy8XmTeyHf40XDC4P--Ck8?usp=sharing))

### Approved, wired up in this PR

| Event | Sound file | Trigger location |
|-------|-----------|-----------------|
| Message sent/received | `message.mp3` | EventsDisplay |
| Menu open/select | `click.mp3` | RadialMenu |
| Atom bomb launch | `atom-launch.mp3` | FxLayer (unit created) |
| Atom bomb / MIRV hit | `atom-hit.mp3` | FxLayer (reached target) |
| Hydrogen launch | `hydrogen-launch.mp3` | FxLayer (unit created) |
| Hydrogen hit | `hydrogen-hit.mp3` | FxLayer (reached target) |
| MIRV launch | `mirv-launch.mp3` | FxLayer (unit created) |
| Alliance suggested | `alliance-suggested.mp3` | EventsDisplay |
| Alliance broken | `alliance-broken.mp3` | EventsDisplay |
| Port built | `build-port.mp3` | FxLayer (construction complete) |
| City built | `build-city.mp3` | FxLayer (construction complete) |
| Defense post built | `build-defense-post.mp3` | FxLayer (construction
complete) |
| Warship built | `build-warship.mp3` | FxLayer (unit created) |
| SAM built | `sam-built.mp3` | FxLayer (construction complete) |

### Waiting for approval, sound files included but NOT wired up

| Event | Sound file | Notes |
|-------|-----------|-------|
| Missile Silo built | `silo-built.mp3` | Waiting for Approval |
| SAM shoot | `sam-shoot.mp3` | Waiting for Approval |
| SAM hit | - | Waiting for Approval, no sound file assigned |
| Warship sunk | - | Waiting for Approval, no sound file assigned |
| Warship shoot | - | Waiting for Approval, no sound file assigned |

### Not done, no sound files exist yet

| Event | Notes |
|-------|-------|
| Looted player | "Not sure if needed" |
| Invaded | - |
| Ship invasion incoming | - |
| Ship sent | - |
| Menu theme song | - |
| Ambience | "Not sure if needed" |

## Test plan
- [x] Start a private game and launch atom/hydrogen/MIRV nukes, verify
launch and detonation sounds
- [x] Build structures (city, port, defense post, SAM), verify build
completion sounds
- [x] Build a warship, verify warship built sound
- [x] Receive an alliance request, verify alliance suggested sound
- [x] Break an alliance, verify alliance broken sound
- [ ] Receive a chat message, verify message sound
- [x] Open the radial menu and click items, verify click sound
- [x] Disable visual FX in settings, verify sounds still play
- [x] Adjust SFX volume slider, verify it affects all new sounds
- [x] Verify no audio issues with rapid/overlapping events
- [x] Verify SoundManager responds to EventBus events and unsubscribes
cleanly on dispose
- [x] Verify SoundManager swallows Howler errors without crashing the
game
- [x] Verify channel limit of 8, oldest sound stopped when at cap

## Checklist
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

Resolves #1893

## Please put your Discord username so you can be contacted if a bug or
regression is found:
cool_clarky
This commit is contained in:
Cameron Clark
2026-04-06 21:01:23 -07:00
committed by GitHub
parent 341f344ce5
commit 18da7134c8
27 changed files with 639 additions and 132 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.