Files
Battle-for-Tismo/client/js/game_screen/bombing.js
T

119 lines
3.9 KiB
JavaScript

// Bombardment sound.
//
// Two effects share the same clip. A standing ground or naval barrage plays
// spaced impacts at random intervals while its target is in view. An aircraft
// bomb run is different: the plane flies to its target and only then drops its
// bombs, so it gets one large burst when the run actually resolves, not an
// ambience during the flight. Both rise in loudness as the camera zooms in, and
// all the timing is frame-driven, so closing the map stops them without leaving
// a timer behind.
import { ZOOM_MIN, ZOOM_MAX } from "../map_view/constants.js";
export const BOMB_BURST = { min: 1, max: 3 };
// Time between the impacts inside one burst.
export const BOMB_GAP_MS = { min: 160, max: 380 };
// Time between bursts.
export const BOMB_IDLE_MS = { min: 900, max: 2400 };
// How much of the clip each hit plays.
export const BOMB_CLIP_S = { min: 0.3, max: 0.7 };
export const BOMB_VOLUME = { min: 0.06, max: 0.6 };
// An aircraft's bomb run arrives all at once: a large, fast burst.
export const AIR_BOMB_BURST = { min: 7, max: 10 };
export const AIR_BOMB_GAP_MS = { min: 55, max: 150 };
export const AIR_BOMB_CLIP_S = { min: 0.25, max: 0.5 };
// Ceiling on a burst so several squadrons bombing together cannot swamp the mix.
const AIR_BOMB_LIMIT = AIR_BOMB_BURST.max * 3;
function between(range, random) {
return range.min + random() * (range.max - range.min);
}
// The impact volume for a camera zoom: quiet when zoomed far out, louder the
// closer the camera is to the ground.
export function zoomVolume(zoom) {
const t = Math.min(1, Math.max(0, (zoom - ZOOM_MIN) / (ZOOM_MAX - ZOOM_MIN)));
return BOMB_VOLUME.min + t * (BOMB_VOLUME.max - BOMB_VOLUME.min);
}
export class BombardmentSfx {
// `play(volume, durationSeconds)` is called once per impact. `random` is
// injectable so a test can pin the intervals.
constructor(play, random = Math.random) {
this.play = play;
this.random = random;
this.reset();
}
reset() {
this._idle = 0;
this._left = 0;
this._gap = 0;
}
// Advances one frame. `dtMs` is the elapsed time, `active` whether a barrage
// is falling in view, and `volume` the zoom-scaled loudness of each hit.
update(dtMs, active, volume) {
if (!active) {
this.reset();
return;
}
if (this._left > 0) {
this._gap -= dtMs;
while (this._left > 0 && this._gap <= 0) {
this.play(volume, between(BOMB_CLIP_S, this.random));
this._left -= 1;
// Reset rather than carry the leftover, so a large frame step can never
// bunch the impacts together.
this._gap = between(BOMB_GAP_MS, this.random);
}
if (this._left <= 0) this._idle = between(BOMB_IDLE_MS, this.random);
return;
}
this._idle -= dtMs;
if (this._idle > 0) return;
this._left = Math.min(
BOMB_BURST.max,
BOMB_BURST.min + Math.floor(this.random() * (BOMB_BURST.max - BOMB_BURST.min + 1))
);
this._gap = 0;
}
}
export class AirStrikeSfx {
constructor(play, random = Math.random) {
this.play = play;
this.random = random;
this.reset();
}
reset() {
this._left = 0;
this._gap = 0;
this._volume = 0;
}
// Starts (or extends) a bomb-run burst. Each triggered strike adds at least
// AIR_BOMB_BURST.min explosions, so planes bombing together sound heavier.
trigger(volume) {
const count =
AIR_BOMB_BURST.min +
Math.floor(this.random() * (AIR_BOMB_BURST.max - AIR_BOMB_BURST.min + 1));
this._left = Math.min(AIR_BOMB_LIMIT, this._left + count);
this._gap = 0;
this._volume = volume;
}
// Advances the burst. It runs to completion even if the camera moves away.
update(dtMs) {
if (this._left <= 0) return;
this._gap -= dtMs;
while (this._left > 0 && this._gap <= 0) {
this.play(this._volume, between(AIR_BOMB_CLIP_S, this.random));
this._left -= 1;
this._gap = between(AIR_BOMB_GAP_MS, this.random);
}
}
}