orbit/js/combat/CombatSfx.js

131 lines
5.6 KiB
JavaScript

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)
* missile_fire solar_flare_02 the rocket launch whoosh (the heat-seeker's
* distinct voice — the bolts play fire)
* 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)
* bola_snag solar_buzz the Wraith's Boleadoras landing on a craft
* (the energy shimmer of the snare catching)
* blink discovery the Wraith's BLINK (the decoy dropping in —
* the "new object in view" voice)
*
* 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'],
missile_fire: ['solar_flare_02'],
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'],
bola_snag: ['scan'],
blink: ['construct'],
};
// ---------------------------------------------------------------- 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);
}
}
}