import { config } from '../config/Config.js'; import { playSfxOn, playSfxOneOfOn } from '../utils/Sfx.js'; /** * Combat SFX — ONE shared event→voice map for both combat contexts (the * range: js/combat/CombatScene.js, and the main game: js/combat/CombatSystem.js). * The contexts call `combatSfx(scene, event)` at their event sites; the * map + the playback path live HERE, so the two contexts can never drift. * * Every event gets a DISTINCT voice from the data/sfx.json set — the two * cannon voices ride the new assets/fx/combat-mass-cannon*.mp3 files, the * rest the existing set: * * cannon_fire mass_cannon the player's cannon going off (the new * combat-mass-cannon.mp3) * cannon_hit mass_cannon_hit the player's tracer striking a hostile * (the new combat-mass-cannon-hit.mp3) * fire laser the ENEMY's laser going off (the new * combat-laser.mp3 — the player's cannon * plays cannon_fire; the laser plays at * the same volume) * impact ui_click the light tick of a tracer striking (the * player being hit, wall ticks — a hostile * taking the player's shot plays cannon_hit) * shield solar_buzz the energy shimmer of a field absorbing * hull mining_split the heavy crack of armor taking the hit * kill thunder_01..04 one at random — the boom of a ship dying * down deconstruct the player's death (the collapse voice) * respawn discovery the player reforming (the "new object in * view" voice) * levelup solar_flare_01 the combat level landing (the charge-rise * voice — js/combat/XpManager.js's 'levelup' * event, subscribed by CombatSystem) * * Playback goes through the shared Sfx.js guards (master switch, audio * manager, cache) — a headless scene with no manager simply doesn't * sound, but the dev log below still records the call. */ export const COMBAT_SFX = { cannon_fire: ['mass_cannon'], cannon_hit: ['mass_cannon_hit'], fire: ['laser'], impact: ['ui_click'], shield: ['solar_buzz'], hull: ['mining_split'], kill: ['thunder_01', 'thunder_02', 'thunder_03', 'thunder_04'], down: ['deconstruct'], respawn: ['discovery'], levelup: ['solar_flare_01'], }; // ---------------------------------------------------------------- dev log // // Dev hook (documented in the README's combat section): every combatSfx // call is recorded here — capped, mirrored to window.__COMBAT_SFX__ — // so the wiring is verifiable in a headless browser (no audio manager) // in BOTH contexts: dev/combat.sfxcheck.mjs drives real game events and // reads the log back. It observes the real call path; it never plays // sound itself. const LOG_CAP = 64; const log = []; function remember(entry) { log.push(entry); if (log.length > LOG_CAP) log.splice(0, log.length - LOG_CAP); if (typeof window !== 'undefined') { window.__COMBAT_SFX__ = { log: log.slice(), last: entry.event }; } } /** * Play the combat voice for `event` on `scene`. * * @param {object} scene a Phaser scene (any of the combat contexts') * @param {string} event a COMBAT_SFX key * @param {object} [o] `vol` = 0..1 volume multiplier (default 1 — the * enemy laser and the player's cannon both play at full) * @returns {string|null} the event name (null for an unknown event) */ export function combatSfx(scene, event, o = {}) { const names = COMBAT_SFX[event]; if (!names) return null; const vol = Math.min(1, Math.max(0, Number(o.vol) || 1)); const pass = { ...o, volume: Number(config.get('sfx.volume', 0.55)) * vol }; delete pass.vol; if (names.length > 1) playSfxOneOfOn(scene, names, pass); else playSfxOn(scene, names[0], pass); remember({ event, names, t: Date.now() }); return event; } /** * A hostile took a `dmg`-point hit with `preShields` shield points in * front of it (read BEFORE the damage landed): the shield/hull voices — * a hit that BREAKS through to the hull plays both, which reads as the * shield popping. The `impact` tick is the caller's (it also plays on * walls and rams). */ export function combatSfxEnemyHit(scene, preShields, dmg) { if (preShields > 0) combatSfx(scene, 'shield'); if (preShields < dmg) combatSfx(scene, 'hull'); } /** * Queue the combat voices onto a scene's loader (the range's preload — * the main game already loads ALL of data/sfx.json in GameScene). * Skipped when the master switch is off (same rule as GameScene). */ export function loadCombatSfx(scene) { if (!scene || !scene.load || !config.get('sfx.enabled', true)) return; const seen = new Set(); for (const names of Object.values(COMBAT_SFX)) { for (const n of names) { if (seen.has(n)) continue; seen.add(n); const path = config.get(`sfx.${n}`, ''); if (typeof path === 'string' && path) scene.load.audio(`sfx_${n}`, path); } } }