@adatechnology/game-destruction-three 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3187,7 +3187,7 @@ var Renderer = class {
3187
3187
  this.startFire(ev);
3188
3188
  this.activate(ev.object, ev.part);
3189
3189
  this.sound("ignite", ev.point, 0.7);
3190
- this.sound("burn_loop", ev.point, 0.5);
3190
+ this.sound("burn_loop", ev.point, 0.5, this.burning(ev.object, ev.part));
3191
3191
  break;
3192
3192
  case "primed":
3193
3193
  if (ev.trigger !== "explosion") {
@@ -3195,7 +3195,7 @@ var Renderer = class {
3195
3195
  this.primedBurning.add(`${ev.object}|${ev.part}`);
3196
3196
  this.activate(ev.object, ev.part);
3197
3197
  this.sound("primed", ev.point, 0.7, { seconds: ev.delay });
3198
- this.sound("burn_loop", ev.point, 0.5);
3198
+ this.sound("burn_loop", ev.point, 0.5, { fuel: "gas" });
3199
3199
  }
3200
3200
  break;
3201
3201
  case "spall":
@@ -3221,15 +3221,15 @@ var Renderer = class {
3221
3221
  this.stopFire(`${ev.object}|${ev.part}`);
3222
3222
  this.vfx.burst("steam", f.point, [0, 1, 0], 14, 1.2);
3223
3223
  }
3224
- if (f || primed) this.sound("burn_end", f?.point ?? this.rootPoint(ev.object), 0.5);
3224
+ if (f || primed) this.sound("burn_end", f?.point ?? this.rootPoint(ev.object), 0.5, primed ? { fuel: "gas" } : this.burning(ev.object, ev.part));
3225
3225
  this.activate(ev.object, ev.part);
3226
3226
  this.sound("extinguish", f?.point ?? this.rootPoint(ev.object), 0.6);
3227
3227
  break;
3228
3228
  }
3229
3229
  case "burnout": {
3230
3230
  this.stopFire(`${ev.object}|${ev.part}`);
3231
- this.primedBurning.delete(`${ev.object}|${ev.part}`);
3232
- this.sound("burn_end", ev.point, 0.5);
3231
+ const primed = this.primedBurning.delete(`${ev.object}|${ev.part}`);
3232
+ this.sound("burn_end", ev.point, 0.5, primed ? { fuel: "gas" } : this.burning(ev.object, ev.part));
3233
3233
  this.vfx.burst("ash", ev.point, [0, 1, 0], 16, 1);
3234
3234
  this.vfx.burst("smoke", ev.point, [0, 1, 0], 6, 0.8, { color: SMOKE_TONES.grey });
3235
3235
  const pv = this.partVis(ev.object, ev.part);
@@ -3297,7 +3297,7 @@ var Renderer = class {
3297
3297
  break;
3298
3298
  }
3299
3299
  case "explode":
3300
- this.primedBurning.delete(`${ev.object}|${ev.part}`);
3300
+ if (this.primedBurning.delete(`${ev.object}|${ev.part}`)) this.sound("burn_end", ev.point, 0.5, { fuel: "gas" });
3301
3301
  this.onExplode(ev);
3302
3302
  break;
3303
3303
  case "destroyed":
@@ -3312,6 +3312,11 @@ var Renderer = class {
3312
3312
  if (detail) this.o.onSound?.(key, point, volume, detail);
3313
3313
  else this.o.onSound?.(key, point, volume);
3314
3314
  }
3315
+ /** What a burning part is made of, for the fire's sound. */
3316
+ burning(objectId, partId) {
3317
+ const material = this.partVis(objectId, partId)?.material || this.world.part(objectId, partId)?.material;
3318
+ return material ? { material } : void 0;
3319
+ }
3315
3320
  partVis(objectId, partId) {
3316
3321
  return this.bounds.get(objectId)?.parts.get(partId) ?? null;
3317
3322
  }
@@ -4187,6 +4192,47 @@ var FIRE_LOOP = {
4187
4192
  volume: 0,
4188
4193
  sources: ["sdk: the roar under the crackles of a fire"]
4189
4194
  };
4195
+ var FIRE_FUEL_OF = {
4196
+ wood_soft: "wood",
4197
+ wood_hard: "wood",
4198
+ plywood: "wood",
4199
+ mdf: "wood",
4200
+ green_wood: "wood",
4201
+ foliage: "wood",
4202
+ cactus: "wood",
4203
+ paper: "fabric",
4204
+ fabric: "fabric",
4205
+ leather: "fabric",
4206
+ foam: "plastic",
4207
+ plastic: "plastic",
4208
+ rubber: "plastic",
4209
+ wax: "plastic"
4210
+ };
4211
+ var FIRE_LOOP_OF = {
4212
+ wood: "fire_loop",
4213
+ fabric: "fire_loop",
4214
+ plastic: "fire_loop_plastic",
4215
+ gas: "fire_loop_gas",
4216
+ liquid: "fire_loop_liquid"
4217
+ };
4218
+ var FIRE_CRACKLE_OF = { wood: 1, fabric: 0.5, plastic: 0.35, gas: 0, liquid: 0 };
4219
+ var roar = (id, bank, source) => ({
4220
+ id,
4221
+ bus: "world",
4222
+ order: "chain_loop",
4223
+ source: { kind: "noise", loop: true },
4224
+ filters: { mode: "serial", bank },
4225
+ volume: 0,
4226
+ sources: [source]
4227
+ });
4228
+ var FIRE_FUEL_LOOPS = [
4229
+ // a gas jet: a steady, broad roar, brighter than wood and without crackles
4230
+ roar("fire_loop_gas", [{ type: "highpass", frequency: { set: 220 }, q: 0.7 }, { type: "lowpass", frequency: { set: 2400 }, q: 0.6 }], "sdk: a gas jet burning (a vessel venting into the flame)"),
4231
+ // a fuel pool: a soft, low, rolling roar
4232
+ roar("fire_loop_liquid", [{ type: "highpass", frequency: { set: 90 }, q: 0.7 }, { type: "lowpass", frequency: { set: 650 }, q: 0.9 }], "sdk: a pool of fuel burning"),
4233
+ // plastic and rubber: a thin sizzle over a low burn
4234
+ roar("fire_loop_plastic", [{ type: "bandpass", frequency: { set: 1800 }, q: 0.6 }], "sdk: plastic and rubber melting in the fire")
4235
+ ];
4190
4236
  var BLAST_SOUND_OF = {
4191
4237
  grenade: "explosion_grenade",
4192
4238
  big: "explosion_big",
@@ -4333,7 +4379,7 @@ var IMPACT_SOUNDS = [
4333
4379
  impact("plastic_crack", [noise2({ type: "bandpass", f: 1800, q: 2, d: 0.06, vol: 0.75 }), tone2({ f: [450, 550], f2: 250, d: 0.05, vol: 0.22 })]),
4334
4380
  impact("mdf_crumble", [noise2({ type: "lowpass", f: 700, d: 0.25, vol: 0.75, wet: 0.2 }), ...many2(4, () => noise2({ type: "lowpass", f: 500, d: 0.05, vol: 0.32, delay: [0.05, 0.3] }))])
4335
4381
  ];
4336
- var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, ...LEAF_SOUNDS, ...IMPACT_SOUNDS, EXPLOSION_BOOM, ...BLAST_SOUNDS, ...FIRE_SOUNDS], loops: [BURN_LOOP, FIRE_LOOP] };
4382
+ var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, ...LEAF_SOUNDS, ...IMPACT_SOUNDS, EXPLOSION_BOOM, ...BLAST_SOUNDS, ...FIRE_SOUNDS], loops: [BURN_LOOP, FIRE_LOOP, ...FIRE_FUEL_LOOPS] };
4337
4383
 
4338
4384
  // src/audio/sound.ts
4339
4385
  import { FOLEY_BANK, GAME_SFX_BANK } from "@adatechnology/game-audio";
@@ -4485,31 +4531,52 @@ function createDestructionSound(engine, options = {}) {
4485
4531
  const rules = { ...DESTRUCTION_SOUND_KEYS.art_scene, ...options.style === "game" ? DESTRUCTION_SOUND_KEYS.game : {}, ...options.keys };
4486
4532
  const blastByKind = options.style !== "game" && !(options.keys && "explode" in options.keys);
4487
4533
  const channel = engine.channel({ seed: options.seed ?? 1 });
4488
- let loop;
4489
- let burning = 0;
4534
+ const fuelOf = { ...FIRE_FUEL_OF, ...options.fuels };
4535
+ const loopOf = { ...FIRE_LOOP_OF, ...options.fireLoop ? { wood: fireLoop, fabric: fireLoop } : {}, ...options.fireLoops };
4536
+ const defaultFuel = options.defaultFuel ?? "wood";
4537
+ const loops = /* @__PURE__ */ new Map();
4490
4538
  const fires = [];
4491
4539
  let nextCrackle = 0;
4540
+ const fuelFor = (detail) => detail?.fuel ?? (detail?.material !== void 0 ? fuelOf[detail.material] : void 0) ?? defaultFuel;
4492
4541
  const setFire = () => {
4493
4542
  if (options.burnLoop === false) return;
4494
- if (burning <= 0) {
4495
- loop?.stop();
4496
- loop = void 0;
4497
- return;
4543
+ const count = /* @__PURE__ */ new Map();
4544
+ for (const f of fires) {
4545
+ const id = loopOf[f.fuel];
4546
+ count.set(id, (count.get(id) ?? 0) + 1);
4547
+ }
4548
+ for (const [id, handle] of loops) {
4549
+ if (count.has(id)) continue;
4550
+ handle.stop();
4551
+ loops.delete(id);
4552
+ }
4553
+ for (const [id, n] of count) {
4554
+ let handle = loops.get(id);
4555
+ if (!handle) {
4556
+ handle = engine.loop(id);
4557
+ loops.set(id, handle);
4558
+ }
4559
+ handle.set({ volume: Math.min(fire2.max, fire2.perFire * n) });
4498
4560
  }
4499
- loop ?? (loop = engine.loop(fireLoop));
4500
- loop.set({ volume: Math.min(fire2.max, fire2.perFire * burning) });
4501
4561
  };
4502
- const forget = (point) => {
4562
+ const forget = (point, fuel) => {
4503
4563
  let best = -1;
4504
4564
  let bestD = Infinity;
4505
4565
  fires.forEach((f, i) => {
4506
- const d = (f[0] - point[0]) ** 2 + (f[1] - point[1]) ** 2 + (f[2] - point[2]) ** 2;
4566
+ if (fuel !== void 0 && f.fuel !== fuel) return;
4567
+ const d = (f.point[0] - point[0]) ** 2 + (f.point[1] - point[1]) ** 2 + (f.point[2] - point[2]) ** 2;
4507
4568
  if (d < bestD) {
4508
4569
  bestD = d;
4509
4570
  best = i;
4510
4571
  }
4511
4572
  });
4512
- if (best >= 0) fires.splice(best, 1);
4573
+ if (best < 0 && fuel !== void 0) forget(point, void 0);
4574
+ else if (best >= 0) fires.splice(best, 1);
4575
+ };
4576
+ const reset = () => {
4577
+ for (const handle of loops.values()) handle.stop();
4578
+ loops.clear();
4579
+ fires.length = 0;
4513
4580
  };
4514
4581
  const blastSound = (point, volume, detail) => {
4515
4582
  const named = detail?.effect === void 0 ? void 0 : kinds[detail.effect];
@@ -4521,14 +4588,12 @@ function createDestructionSound(engine, options = {}) {
4521
4588
  return {
4522
4589
  onSound(key, point, volume, detail) {
4523
4590
  if (key === "burn_loop") {
4524
- burning += 1;
4525
- fires.push(point);
4591
+ fires.push({ point, fuel: fuelFor(detail) });
4526
4592
  setFire();
4527
4593
  return;
4528
4594
  }
4529
4595
  if (key === "burn_end") {
4530
- if (burning > 0) burning -= 1;
4531
- forget(point);
4596
+ forget(point, detail?.fuel ?? (detail?.material !== void 0 ? fuelOf[detail.material] : void 0));
4532
4597
  setFire();
4533
4598
  return;
4534
4599
  }
@@ -4558,12 +4623,15 @@ function createDestructionSound(engine, options = {}) {
4558
4623
  },
4559
4624
  update(dt) {
4560
4625
  if (!crackles || fires.length === 0 || options.burnLoop === false) return;
4626
+ const weight = fires.reduce((sum, f) => sum + FIRE_CRACKLE_OF[f.fuel], 0);
4627
+ if (weight <= 0) return;
4561
4628
  nextCrackle -= dt;
4562
4629
  for (let i = 0; i < 4 && nextCrackle <= 0; i++) {
4563
- const rate = Math.min(crackles.max, crackles.base + crackles.perSqrtFire * Math.sqrt(fires.length));
4630
+ const rate = Math.min(crackles.max, crackles.base * Math.min(1, weight) + crackles.perSqrtFire * Math.sqrt(weight));
4564
4631
  nextCrackle += (0.4 + channel.random() * 1.2) / rate;
4565
- const at = fires[Math.floor(channel.random() * fires.length)];
4566
- engine.play(channel.random() < 0.18 ? "fire_pop" : "fire_crackle", { position: at, volume: crackles.volume });
4632
+ let pick = channel.random() * weight;
4633
+ const at = fires.find((f) => (pick -= FIRE_CRACKLE_OF[f.fuel]) <= 0) ?? fires[fires.length - 1];
4634
+ engine.play(channel.random() < 0.18 ? "fire_pop" : "fire_crackle", { position: at.point, volume: crackles.volume });
4567
4635
  }
4568
4636
  if (nextCrackle < 0) nextCrackle = 0;
4569
4637
  },
@@ -4572,13 +4640,14 @@ function createDestructionSound(engine, options = {}) {
4572
4640
  for (const k of detail?.keys ?? []) if (k in rules) return rules[k] ?? void 0;
4573
4641
  return rules[key] ?? void 0;
4574
4642
  },
4575
- burning: () => burning,
4576
- dispose() {
4577
- loop?.stop();
4578
- loop = void 0;
4579
- burning = 0;
4580
- fires.length = 0;
4581
- }
4643
+ burning: () => fires.length,
4644
+ burningBy: () => {
4645
+ const by = {};
4646
+ for (const f of fires) by[f.fuel] = (by[f.fuel] ?? 0) + 1;
4647
+ return by;
4648
+ },
4649
+ reset,
4650
+ dispose: reset
4582
4651
  };
4583
4652
  }
4584
4653
 
@@ -4600,7 +4669,11 @@ var BLAST_FIRE_SAMPLES = {
4600
4669
  fire_extinguish: fire(0.7),
4601
4670
  gas_leak: fire(0.5),
4602
4671
  // the roar: recorded fires chained with a crossfade (the level comes from `createDestructionSound`, by how much burns)
4603
- fire_loop: { bus: "world", gain: 4, loop: { crossfade: 1 } }
4672
+ fire_loop: { bus: "world", gain: 4, loop: { crossfade: 1 } },
4673
+ // each fuel has its own roar: a gas jet (burners and torches), a pool of fuel, plastic and rubber sizzling under the flames
4674
+ fire_loop_gas: { bus: "world", gain: 6, loop: { crossfade: 1 } },
4675
+ fire_loop_liquid: { bus: "world", gain: 3.5, loop: { crossfade: 1 } },
4676
+ fire_loop_plastic: { bus: "world", gain: 1, loop: { crossfade: 0.6 } }
4604
4677
  };
4605
4678
  function blastFireSampleBank(manifest, options = {}) {
4606
4679
  const sounds = options.sounds ?? BLAST_FIRE_SAMPLES;