131 lines
5.6 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
}
|