@adatechnology/game-destruction-three 0.13.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/README.md +1 -1
- package/dist/index.d.ts +171 -12
- package/dist/index.global.js +1831 -205
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +758 -118
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -2,6 +2,43 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
|
+
// src/audio/impact-keys.ts
|
|
6
|
+
var IMPACT_FAMILY_TAGS = [
|
|
7
|
+
["metal", "metal"],
|
|
8
|
+
["glass", "glass"],
|
|
9
|
+
["ceramic", "ceramic"],
|
|
10
|
+
["soil", "soil"],
|
|
11
|
+
["masonry", "masonry"],
|
|
12
|
+
["wood", "wood"],
|
|
13
|
+
["plant", "plant"],
|
|
14
|
+
["plastic", "plastic"],
|
|
15
|
+
["rubber", "rubber"],
|
|
16
|
+
["wax", "wax"],
|
|
17
|
+
["fabric", "soft"]
|
|
18
|
+
];
|
|
19
|
+
function impactFamily(def) {
|
|
20
|
+
const tags = def?.tags ?? [];
|
|
21
|
+
for (const [tag, family] of IMPACT_FAMILY_TAGS) if (tags.includes(tag)) return family;
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
var ordered = (prefix, material, family, blow) => {
|
|
25
|
+
const keys = [];
|
|
26
|
+
if (material && blow) keys.push(`${prefix}_${material}_${blow}`);
|
|
27
|
+
if (material) keys.push(`${prefix}_${material}`);
|
|
28
|
+
if (family && blow) keys.push(`${prefix}_${family}_${blow}`);
|
|
29
|
+
if (family) keys.push(`${prefix}_${family}`);
|
|
30
|
+
return keys;
|
|
31
|
+
};
|
|
32
|
+
function impactSoundKeys(material, def, blow) {
|
|
33
|
+
return { keys: ordered("impact", material, impactFamily(def), blow) };
|
|
34
|
+
}
|
|
35
|
+
function breakSoundKeys(material, def, blow) {
|
|
36
|
+
return { keys: ordered("break", material, impactFamily(def), blow) };
|
|
37
|
+
}
|
|
38
|
+
function ricochetSoundKeys(material, def) {
|
|
39
|
+
return { keys: ordered("ricochet", material, impactFamily(def), void 0) };
|
|
40
|
+
}
|
|
41
|
+
|
|
5
42
|
// src/renderer.ts
|
|
6
43
|
import { DEFERRED_QUEUE_ID, blastShove, warnOnce } from "@adatechnology/game-contracts";
|
|
7
44
|
import { createVfx, effectForEvent, EVENT_EFFECTS_MAP } from "@adatechnology/game-vfx";
|
|
@@ -174,10 +211,10 @@ var FragmentSystem = class {
|
|
|
174
211
|
*/
|
|
175
212
|
warmMeshes() {
|
|
176
213
|
const opaque = this.materialFor("warm", 8421504, false);
|
|
177
|
-
const
|
|
214
|
+
const glass2 = this.materialFor("warm", 8421504, true);
|
|
178
215
|
const geometries = [this.box, ...Array.from({ length: SHARD_SHAPES }, (_, index) => this.shardGeometry(index))];
|
|
179
216
|
const { geometry, material } = this.markParts();
|
|
180
|
-
return [...geometries.flatMap((shared) => [new this.THREE.Mesh(shared, opaque), new this.THREE.Mesh(shared,
|
|
217
|
+
return [...geometries.flatMap((shared) => [new this.THREE.Mesh(shared, opaque), new this.THREE.Mesh(shared, glass2)]), new this.THREE.Mesh(geometry, material)];
|
|
181
218
|
}
|
|
182
219
|
/**
|
|
183
220
|
* Bullet holes that stay on a fragment (`Fragment.marks`): dark discs that are children of its mesh, so they fall, spin and settle with it. The mesh is
|
|
@@ -528,6 +565,9 @@ var GroundSpots = class {
|
|
|
528
565
|
};
|
|
529
566
|
|
|
530
567
|
// src/pieces.ts
|
|
568
|
+
var SLAM_MIN_SPEED = 2;
|
|
569
|
+
var SLAM_BOUNCE = 0.2;
|
|
570
|
+
var SLAM_FLOOR_NORMAL = 0.7;
|
|
531
571
|
var SUB_DT = 1 / 120;
|
|
532
572
|
var MU = 0.7;
|
|
533
573
|
var SLEEP_AFTER = 0.5;
|
|
@@ -762,9 +802,34 @@ var PieceSystem = class {
|
|
|
762
802
|
const fixed = this.o.staticTopAt?.(x, z, y, step) ?? -Infinity;
|
|
763
803
|
return Math.max(top, fixed);
|
|
764
804
|
}
|
|
805
|
+
/** Stops a piece that ran into something on its way from `start` to `pos` (`sweep`): moves it back to where it met it and takes the speed into it. */
|
|
806
|
+
sweepOn(b, start, pos) {
|
|
807
|
+
const travel = Math.hypot(pos[0] - start[0], pos[1] - start[1], pos[2] - start[2]);
|
|
808
|
+
if (travel < 1e-3) return;
|
|
809
|
+
const hit = this.o.sweep?.(b, start, pos);
|
|
810
|
+
if (!hit || hit.normal[1] > SLAM_FLOOR_NORMAL) return;
|
|
811
|
+
const v = b.velocity;
|
|
812
|
+
const n = hit.normal;
|
|
813
|
+
const into = -(v[0] * n[0] + v[1] * n[1] + v[2] * n[2]);
|
|
814
|
+
if (into <= 0) return;
|
|
815
|
+
const k = Math.min(1, hit.distance / travel);
|
|
816
|
+
for (let i = 0; i < 3; i++) {
|
|
817
|
+
pos[i] = start[i] + (pos[i] - start[i]) * k;
|
|
818
|
+
v[i] += (1 + SLAM_BOUNCE) * into * n[i];
|
|
819
|
+
}
|
|
820
|
+
const w = b.angularVelocity;
|
|
821
|
+
w[0] *= 0.5;
|
|
822
|
+
w[1] *= 0.5;
|
|
823
|
+
w[2] *= 0.5;
|
|
824
|
+
if (into >= SLAM_MIN_SPEED && this.time - b.lastHit > 0.15) {
|
|
825
|
+
b.lastHit = this.time;
|
|
826
|
+
this.o.onSlam?.(b, hit, into);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
765
829
|
integrate(b, dt) {
|
|
766
830
|
const c = this.centerOf(b, this.p);
|
|
767
831
|
const pos = [c.x, c.y, c.z];
|
|
832
|
+
const start = [c.x, c.y, c.z];
|
|
768
833
|
const q = this.q.copy(b.root.quaternion);
|
|
769
834
|
const v = b.velocity;
|
|
770
835
|
const w = b.angularVelocity;
|
|
@@ -874,11 +939,11 @@ var PieceSystem = class {
|
|
|
874
939
|
if (contactedLast) touched = true;
|
|
875
940
|
if (!contactedLast) continue;
|
|
876
941
|
pos[1] += Math.max(0, maxPen - SLOP * 0.5);
|
|
877
|
-
let
|
|
878
|
-
for (const r of rs)
|
|
879
|
-
const restitution =
|
|
880
|
-
if (
|
|
881
|
-
landing =
|
|
942
|
+
let impact2 = 0;
|
|
943
|
+
for (const r of rs) impact2 = Math.max(impact2, -(v[1] + w[2] * r[0] - w[0] * r[2]));
|
|
944
|
+
const restitution = impact2 > 2 ? 0.25 : 0;
|
|
945
|
+
if (impact2 > landing) {
|
|
946
|
+
landing = impact2;
|
|
882
947
|
let sx = 0;
|
|
883
948
|
let sz = 0;
|
|
884
949
|
for (const r of rs) {
|
|
@@ -1009,6 +1074,7 @@ var PieceSystem = class {
|
|
|
1009
1074
|
v[0] *= fd;
|
|
1010
1075
|
v[2] *= fd;
|
|
1011
1076
|
}
|
|
1077
|
+
if (this.o.sweep && !b.held) this.sweepOn(b, start, pos);
|
|
1012
1078
|
b.root.quaternion.copy(q);
|
|
1013
1079
|
const off = this.ax.copy(b.centerV).applyQuaternion(q);
|
|
1014
1080
|
b.root.position.set(pos[0] - off.x, pos[1] - off.y, pos[2] - off.z);
|
|
@@ -2168,10 +2234,10 @@ var Visuals = class {
|
|
|
2168
2234
|
const sy = lerp(1, 0.25, melt);
|
|
2169
2235
|
const sxz = 1 + 0.18 * melt;
|
|
2170
2236
|
for (let i = range[0] * 3, v = 0; i < range[1] * 3; i += 3, v++) {
|
|
2171
|
-
const
|
|
2237
|
+
const noise3 = (Math.imul(v + 1, 2654435761) >>> 0) % 1e3 / 1e3;
|
|
2172
2238
|
arr[i] = cx + (work[i] - cx) * sxz;
|
|
2173
2239
|
arr[i + 2] = cz + (work[i + 2] - cz) * sxz;
|
|
2174
|
-
arr[i + 1] = Math.max(minY, minY + (work[i + 1] - minY) * sy -
|
|
2240
|
+
arr[i + 1] = Math.max(minY, minY + (work[i + 1] - minY) * sy - noise3 * melt * h * 0.04);
|
|
2175
2241
|
}
|
|
2176
2242
|
}
|
|
2177
2243
|
/** The colour attribute of the host's current geometry (created white when there is none) with vertex colours switched on in its material. */
|
|
@@ -2421,12 +2487,14 @@ var GLOW_FROM_C = 430;
|
|
|
2421
2487
|
var QUEUE_CAP = 4e3;
|
|
2422
2488
|
var MIN_FRAGMENTS_PER_SLICE = 8;
|
|
2423
2489
|
var STATIC_REFRESH = 0.5;
|
|
2490
|
+
var STATIC_REFRESH_SLICE_MS = 1;
|
|
2424
2491
|
var STATIC_SPAN = 3;
|
|
2425
2492
|
var FEET_BAND = 0.03;
|
|
2426
2493
|
var TIP_MIN_HEIGHT = 0.3;
|
|
2427
2494
|
var TIP_NARROW = 0.3;
|
|
2428
2495
|
var FIRE_CEILING_SECONDS = 3600;
|
|
2429
2496
|
var GENERIC_REFERENCE_RADIUS = 15;
|
|
2497
|
+
var SWEEP_CORE = 0.15;
|
|
2430
2498
|
var PRIMED_FIRE_RADIUS = 0.2;
|
|
2431
2499
|
var FLUID_COLORS = {
|
|
2432
2500
|
water: 9226495,
|
|
@@ -2469,6 +2537,7 @@ function impactKey(def) {
|
|
|
2469
2537
|
if (def.fracture === "tear") return "impact_soft";
|
|
2470
2538
|
return def.fracture === "none" ? "impact_metal" : "impact_wood";
|
|
2471
2539
|
}
|
|
2540
|
+
var shotKey = (def) => impactKey(def).replace("impact_", "shot_");
|
|
2472
2541
|
var dragSoundKey = (def) => impactKey(def).replace("impact_", "drag_");
|
|
2473
2542
|
function fractureKey(def, style) {
|
|
2474
2543
|
switch (style) {
|
|
@@ -2518,6 +2587,8 @@ var Renderer = class {
|
|
|
2518
2587
|
/** Objects to measure again; `staticAll` measures every one. */
|
|
2519
2588
|
__publicField(this, "staticDirty", /* @__PURE__ */ new Set());
|
|
2520
2589
|
__publicField(this, "staticAll", true);
|
|
2590
|
+
/** The periodic refresh: the objects still to measure again in this round, a slice per call. */
|
|
2591
|
+
__publicField(this, "staticRound", []);
|
|
2521
2592
|
/** Boxes of the pieces' meshes (as `staticBoxes`), with the time they were measured. */
|
|
2522
2593
|
__publicField(this, "pieceBoxes", /* @__PURE__ */ new WeakMap());
|
|
2523
2594
|
__publicField(this, "staticAt", 0);
|
|
@@ -2609,7 +2680,9 @@ var Renderer = class {
|
|
|
2609
2680
|
staticTopAt: (x, z, y, step) => this.staticTopAt(x, z, y, step),
|
|
2610
2681
|
gravity: this.gravity,
|
|
2611
2682
|
scene: o.scene,
|
|
2612
|
-
onHardLanding: (p, point, speed) => this.hardLanding(p, point, speed)
|
|
2683
|
+
onHardLanding: (p, point, speed) => this.hardLanding(p, point, speed),
|
|
2684
|
+
sweep: (p, from, to) => this.sweepPiece(p, from, to),
|
|
2685
|
+
onSlam: (p, hit, speed) => this.slam(p, hit, speed)
|
|
2613
2686
|
});
|
|
2614
2687
|
this.unsubscribe = this.world.on("*", (ev) => {
|
|
2615
2688
|
if (this.queue.length < QUEUE_CAP) this.queue.push(ev);
|
|
@@ -2799,26 +2872,58 @@ var Renderer = class {
|
|
|
2799
2872
|
const p = this.pieceSys.get(objectId);
|
|
2800
2873
|
if (p) this.pieceSys.impulse(p, impulse, point);
|
|
2801
2874
|
}
|
|
2875
|
+
/**
|
|
2876
|
+
* What a piece moving from `from` to `to` runs into: the core of its box (its thinnest half each way, at most `SWEEP_CORE`) swept through the world, the piece itself left out,
|
|
2877
|
+
* so a crate thrown by a blast stops at the wall instead of passing through it. Null for a world that cannot sweep.
|
|
2878
|
+
*/
|
|
2879
|
+
sweepPiece(p, from, to) {
|
|
2880
|
+
if (typeof this.world.sweepBox !== "function") return null;
|
|
2881
|
+
const d = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
|
|
2882
|
+
const travel = Math.hypot(d[0], d[1], d[2]);
|
|
2883
|
+
if (travel < 1e-4) return null;
|
|
2884
|
+
const core = Math.min(SWEEP_CORE, ...p.halfExtents);
|
|
2885
|
+
const box = { min: [from[0] - core, from[1] - core, from[2] - core], max: [from[0] + core, from[1] + core, from[2] + core] };
|
|
2886
|
+
const hit = this.world.sweepBox(box, d, travel, { ignore: p.objectId, spacing: Math.max(0.05, core) });
|
|
2887
|
+
if (!hit) return null;
|
|
2888
|
+
return { object: hit.object, part: hit.part, distance: hit.distance, point: hit.point, normal: hit.normal };
|
|
2889
|
+
}
|
|
2890
|
+
/** A piece thrown into a wall: the wall takes the blow of its weight where it met it, the piece takes it too, and each sounds as its material does. */
|
|
2891
|
+
slam(p, hit, speed) {
|
|
2892
|
+
const energy = 0.5 * p.mass * speed * speed;
|
|
2893
|
+
const direction = [-hit.normal[0], -hit.normal[1], -hit.normal[2]];
|
|
2894
|
+
this.landingSound(hit.object, hit.point, clamp(speed / 10, 0.2, 1));
|
|
2895
|
+
this.syncBound(p.objectId);
|
|
2896
|
+
try {
|
|
2897
|
+
this.world.hitPart(hit.object, hit.part, { type: "blunt", point: hit.point, energy, radius: 0.1, direction, normal: hit.normal, source: p.objectId });
|
|
2898
|
+
this.world.hit(p.objectId, { type: "blunt", point: hit.point, energy, radius: 0.08, direction: hit.normal, source: "slam" });
|
|
2899
|
+
} catch {
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2802
2902
|
hardLanding(p, point, speed) {
|
|
2803
|
-
|
|
2804
|
-
this.sound(key, point, clamp(speed / 10, 0.2, 1));
|
|
2903
|
+
this.landingSound(p.objectId, point, clamp(speed / 10, 0.2, 1));
|
|
2805
2904
|
this.syncBound(p.objectId);
|
|
2806
2905
|
try {
|
|
2807
2906
|
this.world.hit(p.objectId, { type: "blunt", point, energy: 0.5 * p.mass * speed * speed, radius: 0.08, direction: [0, -1, 0], source: "fall" });
|
|
2808
2907
|
} catch {
|
|
2809
2908
|
}
|
|
2810
2909
|
}
|
|
2811
|
-
|
|
2910
|
+
/** The impact of the heaviest part of an object (a piece landing): its material and the blunt blow of the fall. */
|
|
2911
|
+
landingSound(objectId, point, volume) {
|
|
2812
2912
|
const st = this.world.state(objectId);
|
|
2813
|
-
let best;
|
|
2913
|
+
let best = "";
|
|
2814
2914
|
let mass = -1;
|
|
2815
2915
|
for (const p of st?.parts ?? []) {
|
|
2816
2916
|
if (p.mass > mass) {
|
|
2817
2917
|
mass = p.mass;
|
|
2818
|
-
best =
|
|
2918
|
+
best = p.material;
|
|
2819
2919
|
}
|
|
2820
2920
|
}
|
|
2821
|
-
|
|
2921
|
+
this.impactSound(best, point, volume, "blunt");
|
|
2922
|
+
}
|
|
2923
|
+
/** The impact on a material by a blow: the legacy family key (`impactKey`) with the more specific keys to try first (`impactSoundKeys`). */
|
|
2924
|
+
impactSound(material, point, volume, blow) {
|
|
2925
|
+
const def = this.world.materials.get(material);
|
|
2926
|
+
this.sound(impactKey(def), point, volume, impactSoundKeys(material, def, blow));
|
|
2822
2927
|
}
|
|
2823
2928
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
2824
2929
|
// Update
|
|
@@ -2950,9 +3055,9 @@ var Renderer = class {
|
|
|
2950
3055
|
staticNow() {
|
|
2951
3056
|
if (this.time - this.staticAt > STATIC_REFRESH) {
|
|
2952
3057
|
this.staticAt = this.time;
|
|
2953
|
-
this.
|
|
3058
|
+
if (this.staticRound.length === 0) this.staticRound = [...this.bounds.keys()].reverse();
|
|
2954
3059
|
}
|
|
2955
|
-
if (!this.staticAll && this.staticDirty.size === 0) return this.staticBoxes;
|
|
3060
|
+
if (!this.staticAll && this.staticDirty.size === 0 && this.staticRound.length === 0) return this.staticBoxes;
|
|
2956
3061
|
const box = new this.THREE.Box3();
|
|
2957
3062
|
const measure = (b) => {
|
|
2958
3063
|
if (b.piece || this.pieceSys.get(b.id)) {
|
|
@@ -2972,14 +3077,21 @@ var Renderer = class {
|
|
|
2972
3077
|
}
|
|
2973
3078
|
this.staticBoxes.set(b.id, out.length > 6 ? out : []);
|
|
2974
3079
|
};
|
|
3080
|
+
const measureId = (id) => {
|
|
3081
|
+
const b = this.bounds.get(id);
|
|
3082
|
+
if (b) measure(b);
|
|
3083
|
+
else this.staticBoxes.delete(id);
|
|
3084
|
+
};
|
|
2975
3085
|
if (this.staticAll) {
|
|
2976
3086
|
this.staticBoxes.clear();
|
|
2977
3087
|
for (const b of this.bounds.values()) measure(b);
|
|
3088
|
+
this.staticRound = [];
|
|
2978
3089
|
} else {
|
|
2979
|
-
for (const id of this.staticDirty)
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
3090
|
+
for (const id of this.staticDirty) measureId(id);
|
|
3091
|
+
const until = performance.now() + STATIC_REFRESH_SLICE_MS;
|
|
3092
|
+
while (this.staticRound.length > 0) {
|
|
3093
|
+
measureId(this.staticRound.pop());
|
|
3094
|
+
if (performance.now() >= until) break;
|
|
2983
3095
|
}
|
|
2984
3096
|
}
|
|
2985
3097
|
this.staticAll = false;
|
|
@@ -3039,7 +3151,8 @@ var Renderer = class {
|
|
|
3039
3151
|
case "ricochet": {
|
|
3040
3152
|
const d = ev.direction;
|
|
3041
3153
|
this.vfx.burst("sparks", ev.point, d, 4 + Math.min(10, ev.energy / 150), 4);
|
|
3042
|
-
this.
|
|
3154
|
+
const glanced = this.partVis(ev.object, ev.part)?.material ?? "";
|
|
3155
|
+
this.sound("ricochet", ev.point, 0.7, ricochetSoundKeys(glanced, this.world.materials.get(glanced)));
|
|
3043
3156
|
break;
|
|
3044
3157
|
}
|
|
3045
3158
|
case "debris":
|
|
@@ -3074,15 +3187,15 @@ var Renderer = class {
|
|
|
3074
3187
|
this.startFire(ev);
|
|
3075
3188
|
this.activate(ev.object, ev.part);
|
|
3076
3189
|
this.sound("ignite", ev.point, 0.7);
|
|
3077
|
-
this.sound("burn_loop", ev.point, 0.5);
|
|
3190
|
+
this.sound("burn_loop", ev.point, 0.5, this.burning(ev.object, ev.part));
|
|
3078
3191
|
break;
|
|
3079
3192
|
case "primed":
|
|
3080
3193
|
if (ev.trigger !== "explosion") {
|
|
3081
3194
|
this.vfx.fire({ point: ev.point, radius: PRIMED_FIRE_RADIUS, life: ev.delay });
|
|
3082
3195
|
this.primedBurning.add(`${ev.object}|${ev.part}`);
|
|
3083
3196
|
this.activate(ev.object, ev.part);
|
|
3084
|
-
this.sound("
|
|
3085
|
-
this.sound("burn_loop", ev.point, 0.5);
|
|
3197
|
+
this.sound("primed", ev.point, 0.7, { seconds: ev.delay });
|
|
3198
|
+
this.sound("burn_loop", ev.point, 0.5, { fuel: "gas" });
|
|
3086
3199
|
}
|
|
3087
3200
|
break;
|
|
3088
3201
|
case "spall":
|
|
@@ -3108,15 +3221,15 @@ var Renderer = class {
|
|
|
3108
3221
|
this.stopFire(`${ev.object}|${ev.part}`);
|
|
3109
3222
|
this.vfx.burst("steam", f.point, [0, 1, 0], 14, 1.2);
|
|
3110
3223
|
}
|
|
3111
|
-
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));
|
|
3112
3225
|
this.activate(ev.object, ev.part);
|
|
3113
3226
|
this.sound("extinguish", f?.point ?? this.rootPoint(ev.object), 0.6);
|
|
3114
3227
|
break;
|
|
3115
3228
|
}
|
|
3116
3229
|
case "burnout": {
|
|
3117
3230
|
this.stopFire(`${ev.object}|${ev.part}`);
|
|
3118
|
-
this.primedBurning.delete(`${ev.object}|${ev.part}`);
|
|
3119
|
-
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));
|
|
3120
3233
|
this.vfx.burst("ash", ev.point, [0, 1, 0], 16, 1);
|
|
3121
3234
|
this.vfx.burst("smoke", ev.point, [0, 1, 0], 6, 0.8, { color: SMOKE_TONES.grey });
|
|
3122
3235
|
const pv = this.partVis(ev.object, ev.part);
|
|
@@ -3184,7 +3297,7 @@ var Renderer = class {
|
|
|
3184
3297
|
break;
|
|
3185
3298
|
}
|
|
3186
3299
|
case "explode":
|
|
3187
|
-
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" });
|
|
3188
3301
|
this.onExplode(ev);
|
|
3189
3302
|
break;
|
|
3190
3303
|
case "destroyed":
|
|
@@ -3195,8 +3308,14 @@ var Renderer = class {
|
|
|
3195
3308
|
const b = this.bounds.get(objectId);
|
|
3196
3309
|
return b ? [b.root.position.x, b.root.position.y, b.root.position.z] : [0, 0, 0];
|
|
3197
3310
|
}
|
|
3198
|
-
sound(key, point, volume) {
|
|
3199
|
-
this.o.onSound?.(key, point, volume);
|
|
3311
|
+
sound(key, point, volume, detail) {
|
|
3312
|
+
if (detail) this.o.onSound?.(key, point, volume, detail);
|
|
3313
|
+
else this.o.onSound?.(key, point, volume);
|
|
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;
|
|
3200
3319
|
}
|
|
3201
3320
|
partVis(objectId, partId) {
|
|
3202
3321
|
return this.bounds.get(objectId)?.parts.get(partId) ?? null;
|
|
@@ -3261,7 +3380,7 @@ var Renderer = class {
|
|
|
3261
3380
|
const reach = shown.reduce((widest, mv) => Math.max(widest, this.visuals.dentReach(mv, wp, ev.dent)), ev.dent.radius);
|
|
3262
3381
|
this.visuals.applyPartDent(shown, wp, wn, ev.dent, reach);
|
|
3263
3382
|
if (def?.tags?.includes("metal")) this.vfx.burst("sparks", ev.worldPoint, [wn.x, wn.y, wn.z], 4 + Math.min(8, ev.dent.depth * 150), 3.2);
|
|
3264
|
-
this.
|
|
3383
|
+
this.impactSound(pv.material, ev.worldPoint, clamp(0.3 + ev.dent.depth * 12, 0.3, 1), ev.blow);
|
|
3265
3384
|
}
|
|
3266
3385
|
onHole(ev) {
|
|
3267
3386
|
const b = this.bounds.get(ev.object);
|
|
@@ -3290,7 +3409,7 @@ var Renderer = class {
|
|
|
3290
3409
|
this.vfx.burst("dust", ev.worldPoint, [n.x, n.y, n.z], this.low ? 5 : 10, 0.7, puff);
|
|
3291
3410
|
if (ev.hole.through && ev.exitPoint) this.vfx.burst("dust", ev.exitPoint, [-n.x, -n.y, -n.z], this.low ? 10 : 20, 1.3, puff);
|
|
3292
3411
|
}
|
|
3293
|
-
this.sound(
|
|
3412
|
+
this.sound(shotKey(def), ev.worldPoint, 0.6, impactSoundKeys(pv.material, def, ev.blow));
|
|
3294
3413
|
}
|
|
3295
3414
|
onCrack(ev) {
|
|
3296
3415
|
const b = this.bounds.get(ev.object);
|
|
@@ -3300,13 +3419,13 @@ var Renderer = class {
|
|
|
3300
3419
|
const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
|
|
3301
3420
|
const mv = this.nearestMesh(pv, wp);
|
|
3302
3421
|
if (mv) {
|
|
3303
|
-
const
|
|
3422
|
+
const glass2 = !!def?.visual?.transparent;
|
|
3304
3423
|
const own = this.partColor(pv, def) ?? fallbackColor(pv.material, def);
|
|
3305
|
-
const color =
|
|
3306
|
-
const length =
|
|
3424
|
+
const color = glass2 ? 15267071 : new this.THREE.Color(own).multiplyScalar(0.45).getHex();
|
|
3425
|
+
const length = glass2 ? ev.crack.length : Math.min(ev.crack.length, OPAQUE_CRACK);
|
|
3307
3426
|
this.visuals.addCrack(mv, wp, this.worldDir(b, ev.crack.normal), ev.crack.rays, length, color, hashNumbers(ev.time, ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]));
|
|
3308
3427
|
}
|
|
3309
|
-
this.
|
|
3428
|
+
this.impactSound(pv.material, ev.worldPoint, 0.5, ev.blow);
|
|
3310
3429
|
}
|
|
3311
3430
|
/** Outward normal of the face of `mv` nearest to a world point: the thin axis of its world box (right for a wall; a sloped face is left to the vertex-range binding). */
|
|
3312
3431
|
faceNormal(mv, point) {
|
|
@@ -3363,7 +3482,7 @@ var Renderer = class {
|
|
|
3363
3482
|
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, fdef === def || !fdef ? color : fallbackColor(fr.material, fdef), false), ev.object, maxAlive);
|
|
3364
3483
|
}
|
|
3365
3484
|
if (!played) this.vfx.burst("dust", ev.opening.center, [0, 1, 0], 14, 1.2, { color });
|
|
3366
|
-
this.
|
|
3485
|
+
this.impactSound(pv?.material ?? "", ev.opening.center, 0.8, ev.blow);
|
|
3367
3486
|
}
|
|
3368
3487
|
/**
|
|
3369
3488
|
* A unit came out of a wall of units: the unit flies as a fragment with a puff of its dust, and the marks drawn on it (bullet holes, spalls, the impact
|
|
@@ -3377,11 +3496,11 @@ var Renderer = class {
|
|
|
3377
3496
|
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
3378
3497
|
for (const fr of ev.fragments) {
|
|
3379
3498
|
const fdef = this.world.materials.get(fr.material);
|
|
3380
|
-
const
|
|
3381
|
-
this.fragments.spawn(fr, this.fragments.materialFor(fr.material,
|
|
3499
|
+
const tone3 = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === def ? color : fallbackColor(fr.material, fdef);
|
|
3500
|
+
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, tone3, false), ev.object, maxAlive);
|
|
3382
3501
|
}
|
|
3383
3502
|
if (ev.torn) {
|
|
3384
|
-
this.
|
|
3503
|
+
this.impactSound(pv?.material ?? "", ev.center, 0.5, ev.blow);
|
|
3385
3504
|
return;
|
|
3386
3505
|
}
|
|
3387
3506
|
const T = this.THREE;
|
|
@@ -3391,7 +3510,7 @@ var Renderer = class {
|
|
|
3391
3510
|
for (const part of this.bounds.get(ev.object)?.parts.values() ?? []) for (const mv of part.meshes) this.visuals.clearDecals(mv, center, half, rotation);
|
|
3392
3511
|
this.vfx.peekWorkshop()?.decals.clearInside(ev.center, [half.x, half.y, half.z], ev.rotation);
|
|
3393
3512
|
this.vfx.burst("dust", ev.center, [0, 1, 0], 8, 0.8, { color });
|
|
3394
|
-
this.
|
|
3513
|
+
this.impactSound(pv?.material ?? "", ev.center, 0.7, ev.blow);
|
|
3395
3514
|
}
|
|
3396
3515
|
onFracture(ev) {
|
|
3397
3516
|
const b = this.bounds.get(ev.object);
|
|
@@ -3432,7 +3551,7 @@ var Renderer = class {
|
|
|
3432
3551
|
default:
|
|
3433
3552
|
this.vfx.burst("sparks", ev.point, up, 10, 4);
|
|
3434
3553
|
}
|
|
3435
|
-
this.sound(fractureKey(def, ev.style), ev.point, clamp(0.4 + ev.fragments.length * 0.03, 0.4, 1));
|
|
3554
|
+
this.sound(fractureKey(def, ev.style), ev.point, clamp(0.4 + ev.fragments.length * 0.03, 0.4, 1), breakSoundKeys(matId, def, ev.blow));
|
|
3436
3555
|
}
|
|
3437
3556
|
/** Spawns the fragments of a fracture from index `from`; what does not fit in this frame's budget is spawned by the next frames. */
|
|
3438
3557
|
spawnFragments(ev, from, look) {
|
|
@@ -3445,8 +3564,8 @@ var Renderer = class {
|
|
|
3445
3564
|
}
|
|
3446
3565
|
const fr = ev.fragments[i];
|
|
3447
3566
|
const fdef = this.world.materials.get(fr.material);
|
|
3448
|
-
const
|
|
3449
|
-
const mat = this.fragments.materialFor(fr.material,
|
|
3567
|
+
const tone3 = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === look.def || !fdef ? look.color : fallbackColor(fr.material, fdef);
|
|
3568
|
+
const mat = this.fragments.materialFor(fr.material, tone3, look.transparent || fdef?.visual?.transparent === true, look.opacity);
|
|
3450
3569
|
this.fragments.spawn(fr, mat, ev.object, maxAlive);
|
|
3451
3570
|
}
|
|
3452
3571
|
}
|
|
@@ -3569,10 +3688,11 @@ var Renderer = class {
|
|
|
3569
3688
|
const report = options.report;
|
|
3570
3689
|
const confinement = report?.find((r) => r.blast !== void 0)?.blast?.confinement ?? 1;
|
|
3571
3690
|
const energy = ev.energy * confinement;
|
|
3572
|
-
const reachOf = (objectId) => report?.find((r) => r.object === objectId)?.blast?.shelter ?? 1;
|
|
3691
|
+
const reachOf = (objectId, at) => report?.find((r) => r.object === objectId)?.blast?.shelter ?? this.world.blastReach?.(p, at, { ignore: ev.object ? [ev.object, objectId] : [objectId] }) ?? 1;
|
|
3573
3692
|
const shove = (pc) => {
|
|
3574
3693
|
this.pieceSys.centerOf(pc, c);
|
|
3575
|
-
|
|
3694
|
+
if (c.distanceTo(this.v2.set(p[0], p[1], p[2])) > ev.radius) return;
|
|
3695
|
+
const hit = blastShove({ point: p, energy, radius: ev.radius, reach: reachOf(pc.objectId, [c.x, c.y, c.z]) }, { position: [c.x, c.y, c.z], mass: pc.mass }, this.o.blastShove);
|
|
3576
3696
|
if (!hit) return;
|
|
3577
3697
|
const spread = pc.halfExtents[0];
|
|
3578
3698
|
const momentum = hit.speed * pc.mass;
|
|
@@ -3581,7 +3701,12 @@ var Renderer = class {
|
|
|
3581
3701
|
for (const pc of [...this.pieceSys.list]) shove(pc);
|
|
3582
3702
|
if (this.o.shoveable) this.loosen(p, ev.radius, energy, reachOf);
|
|
3583
3703
|
this.fragments.blast(p, ev.radius, Math.min(20, Math.sqrt(energy) * 0.08));
|
|
3584
|
-
this.sound("explode", p, clamp(0.5 + ev.radius / 10, 0.5, 1)
|
|
3704
|
+
this.sound("explode", p, clamp(0.5 + ev.radius / 10, 0.5, 1), {
|
|
3705
|
+
radius: ev.radius,
|
|
3706
|
+
...ev.effect !== void 0 ? { effect: ev.effect } : {},
|
|
3707
|
+
...options.effect !== void 0 ? { effect: options.effect } : {},
|
|
3708
|
+
...report ? { confinement } : {}
|
|
3709
|
+
});
|
|
3585
3710
|
}
|
|
3586
3711
|
/** The bound objects without a piece that the host lets a blast move (`shoveable`): the ones it reaches with enough speed become pieces and get the shove. */
|
|
3587
3712
|
loosen(point, radius, energy, reachOf) {
|
|
@@ -3592,7 +3717,8 @@ var Renderer = class {
|
|
|
3592
3717
|
const { half, center } = this.measure(b.root, this.meshesOf(b));
|
|
3593
3718
|
const world = center.clone().applyQuaternion(b.root.quaternion).add(b.root.position);
|
|
3594
3719
|
const mass = this.massOf(b.id);
|
|
3595
|
-
|
|
3720
|
+
if (world.distanceTo(this.v2.set(point[0], point[1], point[2])) > radius) continue;
|
|
3721
|
+
const hit = blastShove({ point, energy, radius, reach: reachOf(b.id, [world.x, world.y, world.z]) }, { position: [world.x, world.y, world.z], mass }, this.o.blastShove);
|
|
3596
3722
|
if (!hit) continue;
|
|
3597
3723
|
const piece = this.makePiece(b.id, { mass });
|
|
3598
3724
|
const body = this.pieceSys.get(b.id);
|
|
@@ -3616,9 +3742,9 @@ var Renderer = class {
|
|
|
3616
3742
|
this.fires.set(key, { object: ev.object, part: ev.part, point: ev.point, follow: { x: ev.point[0], y: ev.point[1], z: ev.point[2] } });
|
|
3617
3743
|
}
|
|
3618
3744
|
stopFire(key) {
|
|
3619
|
-
const
|
|
3620
|
-
if (!
|
|
3621
|
-
if (
|
|
3745
|
+
const fire2 = this.fires.get(key);
|
|
3746
|
+
if (!fire2) return;
|
|
3747
|
+
if (fire2.handle !== void 0) this.vfx.stop(fire2.handle);
|
|
3622
3748
|
this.fires.delete(key);
|
|
3623
3749
|
}
|
|
3624
3750
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -3655,9 +3781,9 @@ var Renderer = class {
|
|
|
3655
3781
|
if (ps.status === "melted") this.onGone(a.object, pv, "melted", def);
|
|
3656
3782
|
else if (ps.status === "ashes") this.onGone(a.object, pv, "ashes", def);
|
|
3657
3783
|
}
|
|
3658
|
-
const
|
|
3659
|
-
if (
|
|
3660
|
-
if (ps.burning && ps.status !== "broken" && ps.status !== "ashes" && ps.status !== "melted") this.emitFire(
|
|
3784
|
+
const fire2 = this.fires.get(key);
|
|
3785
|
+
if (fire2) {
|
|
3786
|
+
if (ps.burning && ps.status !== "broken" && ps.status !== "ashes" && ps.status !== "melted") this.emitFire(fire2, pv, def);
|
|
3661
3787
|
else this.stopFire(key);
|
|
3662
3788
|
}
|
|
3663
3789
|
const hot = def?.visual?.glows === true && ps.temperature > GLOW_FROM_C;
|
|
@@ -3787,10 +3913,12 @@ var Renderer = class {
|
|
|
3787
3913
|
this.scene.remove(b.root);
|
|
3788
3914
|
this.pieceSys.remove(b.id);
|
|
3789
3915
|
this.bounds.delete(b.id);
|
|
3916
|
+
this.staticDirty.add(b.id);
|
|
3790
3917
|
}
|
|
3791
3918
|
}
|
|
3792
3919
|
}
|
|
3793
3920
|
clear() {
|
|
3921
|
+
this.staticAll = true;
|
|
3794
3922
|
this.queue.length = 0;
|
|
3795
3923
|
this.slices.length = 0;
|
|
3796
3924
|
this.active.clear();
|
|
@@ -3817,6 +3945,8 @@ var Renderer = class {
|
|
|
3817
3945
|
this.rng = createRng(RNG_SEED);
|
|
3818
3946
|
this.time = 0;
|
|
3819
3947
|
this.staticAt = 0;
|
|
3948
|
+
this.staticRound = [];
|
|
3949
|
+
this.staticAll = true;
|
|
3820
3950
|
this.throttles.clear();
|
|
3821
3951
|
this.fragments.reset();
|
|
3822
3952
|
this.pieceSys.reset();
|
|
@@ -3874,43 +4004,283 @@ var Renderer = class {
|
|
|
3874
4004
|
};
|
|
3875
4005
|
|
|
3876
4006
|
// src/audio/recipes.ts
|
|
3877
|
-
import { DISTANCE_CURVES, sceneMany as
|
|
3878
|
-
|
|
4007
|
+
import { DISTANCE_CURVES, sceneMany as many2, sceneNoise as noise2, sceneRecipe as sceneRecipe2, sceneTone as tone2 } from "@adatechnology/game-audio";
|
|
4008
|
+
|
|
4009
|
+
// src/audio/blast-fire.ts
|
|
4010
|
+
import { sceneMany as many, sceneNoise as noise, sceneRecipe, sceneTone as tone } from "@adatechnology/game-audio";
|
|
4011
|
+
var BLAST_FALLOFF = { model: "inverse_square", k: 12e-4, cutoffK: 6e-3, minGain: 2e-3 };
|
|
4012
|
+
var BLAST_FAR_FALLOFF = { model: "inverse_square", k: 8e-5, scale: 0.3, minGain: 1e-3 };
|
|
4013
|
+
var FIRE_FALLOFF = { model: "inverse_square", k: 0.01, cutoffK: 0.01, minGain: 5e-3 };
|
|
4014
|
+
var BLAST_FAR_DISTANCE = 60;
|
|
4015
|
+
var BLAST_KINDS = ["grenade", "big", "fuel", "car", "pipe_bomb", "rocket", "flashbang", "molotov"];
|
|
4016
|
+
var BLAST_KIND_OF = {
|
|
4017
|
+
m67: "grenade",
|
|
4018
|
+
vog_25: "grenade",
|
|
4019
|
+
m79_40mm: "grenade",
|
|
4020
|
+
claymore: "grenade",
|
|
4021
|
+
generic: "grenade",
|
|
4022
|
+
c4: "big",
|
|
4023
|
+
dynamite: "big",
|
|
4024
|
+
fuel_station_tank: "big",
|
|
4025
|
+
propane_p13: "fuel",
|
|
4026
|
+
fuel_barrel: "fuel",
|
|
4027
|
+
fuel_pump: "fuel",
|
|
4028
|
+
car_fuel_tank: "car",
|
|
4029
|
+
pipe_bomb: "pipe_bomb",
|
|
4030
|
+
m72_66mm: "rocket",
|
|
4031
|
+
at4_84mm: "rocket",
|
|
4032
|
+
carl_gustaf_84mm: "rocket",
|
|
4033
|
+
pg7v: "rocket",
|
|
4034
|
+
flashbang: "flashbang",
|
|
4035
|
+
molotov: "molotov"
|
|
4036
|
+
};
|
|
4037
|
+
var BIG_BLAST_RADIUS = 8;
|
|
4038
|
+
var blast = (id, layers, sources, falloff = BLAST_FALLOFF) => ({
|
|
4039
|
+
...sceneRecipe(id, layers, ""),
|
|
4040
|
+
falloff,
|
|
4041
|
+
sources
|
|
4042
|
+
});
|
|
4043
|
+
var crack = (f, d, vol) => noise({ type: "highpass", f, a: 1e-3, d, vol });
|
|
4044
|
+
var debris = (count, from, to, vol) => [
|
|
4045
|
+
...many(count, () => noise({ type: "bandpass", f: [300, 1400], q: 2.5, d: [0.03, 0.09], vol, delay: [from, to] })),
|
|
4046
|
+
...many(Math.ceil(count / 3), () => noise({ type: "bandpass", f: [1800, 3600], q: 5, d: [0.02, 0.04], vol: [vol[0] * 0.6, vol[1] * 0.6], delay: [from + 0.1, to] }))
|
|
4047
|
+
];
|
|
4048
|
+
var shrapnel = (count, to) => many(count, () => noise({ type: "bandpass", f: [2500, 6e3], q: 6, d: [0.015, 0.045], vol: [0.08, 0.16], delay: [0.02, to] }));
|
|
4049
|
+
var glass = (count, from) => [
|
|
4050
|
+
noise({ type: "highpass", f: [2600, 3e3], d: 0.4, vol: 0.7, wet: 0.5, delay: from }),
|
|
4051
|
+
...many(count, () => tone({ f: [3e3, 7500], d: [0.05, 0.13], vol: 0.12, delay: [from, from + 0.7] }))
|
|
4052
|
+
];
|
|
4053
|
+
var BLAST_SOUNDS = [
|
|
4054
|
+
blast(
|
|
4055
|
+
"explosion_grenade",
|
|
4056
|
+
[
|
|
4057
|
+
crack([2300, 2700], 0.06, 2.4),
|
|
4058
|
+
noise({ f: [2500, 3e3], f2: 160, d: 0.7, vol: 2, wet: 0.35 }),
|
|
4059
|
+
tone({ f: [80, 92], f2: 32, d: 0.5, vol: 1.6 }),
|
|
4060
|
+
noise({ type: "bandpass", f: [620, 760], q: 0.8, d: 0.18, vol: 1.1 }),
|
|
4061
|
+
...shrapnel(14, 0.7),
|
|
4062
|
+
...debris(10, 0.35, 1.8, [0.1, 0.2])
|
|
4063
|
+
],
|
|
4064
|
+
["sdk: frag grenade (M67, VOG-25, 40 mm): sharp crack, short body, fragments striking, a little debris"]
|
|
4065
|
+
),
|
|
4066
|
+
blast(
|
|
4067
|
+
"explosion_big",
|
|
4068
|
+
[
|
|
4069
|
+
crack([1900, 2300], 0.08, 2.4),
|
|
4070
|
+
noise({ f: [2200, 2600], f2: 90, d: 1.6, vol: 2.2, wet: 0.45 }),
|
|
4071
|
+
tone({ f: [52, 60], f2: 22, d: 1.6, vol: 1.9 }),
|
|
4072
|
+
noise({ f: [280, 330], f2: 60, a: 0.25, d: 2.8, vol: 0.8, wet: 0.6, delay: 0.05 }),
|
|
4073
|
+
...debris(24, 0.5, 3.6, [0.12, 0.25])
|
|
4074
|
+
],
|
|
4075
|
+
["sdk: big charge (C4, dynamite, a fuel station tank): crack, long deep roar, debris falling for 2 to 4 s"]
|
|
4076
|
+
),
|
|
4077
|
+
blast(
|
|
4078
|
+
"explosion_fuel",
|
|
4079
|
+
[
|
|
4080
|
+
noise({ type: "highpass", f: [1700, 2e3], a: 1e-3, d: 0.05, vol: 1 }),
|
|
4081
|
+
tone({ f: [380, 450], f2: 300, d: 0.4, vol: 0.3 }),
|
|
4082
|
+
noise({ f: [1700, 2e3], f2: 150, a: 0.03, d: 1.4, vol: 2.1, wet: 0.4 }),
|
|
4083
|
+
tone({ f: [48, 56], f2: 25, d: 1.2, vol: 1.7 }),
|
|
4084
|
+
noise({ type: "bandpass", f: [450, 560], q: 0.6, a: 0.15, d: 2.2, vol: 0.85, wet: 0.5, delay: 0.1 }),
|
|
4085
|
+
...debris(10, 0.5, 2.6, [0.08, 0.16])
|
|
4086
|
+
],
|
|
4087
|
+
['sdk: a fuel vessel bursting (propane P13, fuel barrel, pump): the shell tearing, a fireball "whoomp" and its roar']
|
|
4088
|
+
),
|
|
4089
|
+
blast(
|
|
4090
|
+
"explosion_car",
|
|
4091
|
+
[
|
|
4092
|
+
noise({ type: "highpass", f: [1700, 2e3], a: 1e-3, d: 0.06, vol: 1.1 }),
|
|
4093
|
+
noise({ f: [1600, 1900], f2: 140, a: 0.03, d: 1.6, vol: 2.1, wet: 0.4 }),
|
|
4094
|
+
tone({ f: [44, 52], f2: 24, d: 1.4, vol: 1.8 }),
|
|
4095
|
+
noise({ type: "bandpass", f: [420, 520], q: 0.6, a: 0.15, d: 2.6, vol: 0.9, wet: 0.5, delay: 0.1 }),
|
|
4096
|
+
...glass(10, 0.04),
|
|
4097
|
+
tone({ f: [130, 150], f2: 85, d: 1.1, vol: 0.18, delay: [0.3, 0.5] }),
|
|
4098
|
+
...debris(16, 0.6, 3.2, [0.1, 0.2])
|
|
4099
|
+
],
|
|
4100
|
+
["sdk: a car going up: the fuel tank fireball, every window breaking, the body groaning, parts falling"]
|
|
4101
|
+
),
|
|
4102
|
+
blast(
|
|
4103
|
+
"explosion_pipe_bomb",
|
|
4104
|
+
[
|
|
4105
|
+
crack([2e3, 2400], 0.07, 2.2),
|
|
4106
|
+
noise({ f: [2e3, 2400], f2: 150, d: 0.8, vol: 1.9, wet: 0.35 }),
|
|
4107
|
+
tone({ f: [70, 82], f2: 30, d: 0.6, vol: 1.5 }),
|
|
4108
|
+
...many(6, () => tone({ f: [1800, 3200], f2: 1200, d: [0.06, 0.12], vol: 0.06, delay: [0.05, 0.6] })),
|
|
4109
|
+
...shrapnel(18, 0.9),
|
|
4110
|
+
...debris(8, 0.4, 1.8, [0.08, 0.16])
|
|
4111
|
+
],
|
|
4112
|
+
["sdk: pipe bomb: crack, body, the steel pipe flying apart in ringing pieces"]
|
|
4113
|
+
),
|
|
4114
|
+
blast(
|
|
4115
|
+
"explosion_rocket",
|
|
4116
|
+
[
|
|
4117
|
+
crack([2100, 2500], 0.08, 2.4),
|
|
4118
|
+
noise({ f: [2400, 2800], f2: 120, d: 1.1, vol: 2.1, wet: 0.4 }),
|
|
4119
|
+
tone({ f: [60, 70], f2: 26, d: 0.9, vol: 1.8 }),
|
|
4120
|
+
noise({ f: [350, 420], f2: 80, a: 0.08, d: 1.8, vol: 0.9, wet: 0.6, delay: 0.05 }),
|
|
4121
|
+
...shrapnel(10, 0.8),
|
|
4122
|
+
...debris(16, 0.4, 2.6, [0.1, 0.2])
|
|
4123
|
+
],
|
|
4124
|
+
["sdk: rocket or anti-tank warhead (M72, AT4, RPG): hard crack, heavy body, debris"]
|
|
4125
|
+
),
|
|
4126
|
+
blast(
|
|
4127
|
+
"explosion_flashbang",
|
|
4128
|
+
[crack([1400, 1700], 0.12, 2.8), noise({ f: [1900, 2200], f2: 300, d: 0.35, vol: 1.3, wet: 0.3 }), tone({ f: [110, 130], f2: 60, d: 0.2, vol: 0.8 })],
|
|
4129
|
+
["sdk: flashbang: a deafening crack with almost no body and no fragments"]
|
|
4130
|
+
),
|
|
4131
|
+
blast(
|
|
4132
|
+
"molotov_break_ignite",
|
|
4133
|
+
[
|
|
4134
|
+
...glass(8, 0),
|
|
4135
|
+
noise({ f: [280, 340], f2: 2200, a: 0.12, d: 0.9, vol: 1.5, wet: 0.6, delay: 0.05 }),
|
|
4136
|
+
tone({ f: [85, 95], f2: 55, d: 0.4, vol: 0.5, delay: 0.08 })
|
|
4137
|
+
],
|
|
4138
|
+
['sdk: molotov: the bottle breaking and the fuel catching with a "fuuush"'],
|
|
4139
|
+
FIRE_FALLOFF
|
|
4140
|
+
),
|
|
4141
|
+
blast(
|
|
4142
|
+
"explosion_far",
|
|
4143
|
+
[
|
|
4144
|
+
noise({ f: [550, 650], f2: 80, a: 0.03, d: 2.5, vol: 2, wet: 1.4 }),
|
|
4145
|
+
tone({ f: [42, 48], f2: 25, d: 1.8, vol: 1.2 }),
|
|
4146
|
+
noise({ f: [350, 420], f2: 70, a: 0.1, d: 1.6, vol: 0.6, wet: 1.5, delay: [0.6, 1] })
|
|
4147
|
+
],
|
|
4148
|
+
["sdk: any blast far away: no crack left, a dull thud and the echo rolling back"],
|
|
4149
|
+
BLAST_FAR_FALLOFF
|
|
4150
|
+
),
|
|
4151
|
+
blast(
|
|
4152
|
+
"explosion_indoor_tail",
|
|
4153
|
+
[
|
|
4154
|
+
...many(3, () => noise({ f: [650, 800], d: 0.12, vol: 0.5, delay: [0.04, 0.2] })),
|
|
4155
|
+
noise({ f: [850, 950], f2: 120, a: 0.05, d: 2.6, vol: 0.9, wet: 1.6, delay: 0.08 })
|
|
4156
|
+
],
|
|
4157
|
+
["sdk: the room answering a blast inside it: slaps off the walls and a boomy tail"]
|
|
4158
|
+
)
|
|
4159
|
+
];
|
|
4160
|
+
var FIRE_SOUNDS = [
|
|
4161
|
+
{
|
|
4162
|
+
...blast(
|
|
4163
|
+
"fire_ignite",
|
|
4164
|
+
[noise({ f: [230, 270], f2: 1500, a: 0.07, d: 0.5, vol: 1.6 }), tone({ f: [80, 90], f2: 55, d: 0.3, vol: 0.45 }), noise({ type: "highpass", f: 3e3, d: 0.3, vol: 0.1, delay: 0.1 })],
|
|
4165
|
+
['sdk: fire catching, a soft "fuuump"'],
|
|
4166
|
+
FIRE_FALLOFF
|
|
4167
|
+
),
|
|
4168
|
+
group: "fire_ignite"
|
|
4169
|
+
},
|
|
4170
|
+
{
|
|
4171
|
+
...blast("gas_leak", [noise({ type: "bandpass", f: [3200, 3800], q: 0.7, a: 0.08, d: { param: "seconds" }, vol: 0.3 })], ["sdk: gas jetting from a punctured vessel"], FIRE_FALLOFF),
|
|
4172
|
+
vars: { seconds: 2 }
|
|
4173
|
+
},
|
|
4174
|
+
{
|
|
4175
|
+
...blast(
|
|
4176
|
+
"fire_extinguish",
|
|
4177
|
+
[noise({ type: "highpass", f: [2300, 2700], a: 0.03, d: { param: "seconds" }, vol: 0.3 }), noise({ type: "bandpass", f: [800, 1e3], q: 0.5, d: 0.5, vol: 0.25 })],
|
|
4178
|
+
["sdk: water or foam on a fire: a steam puff and a sizzle"],
|
|
4179
|
+
FIRE_FALLOFF
|
|
4180
|
+
),
|
|
4181
|
+
vars: { seconds: 1.2 }
|
|
4182
|
+
},
|
|
4183
|
+
{ ...blast("fire_crackle", [noise({ type: "highpass", f: [1500, 3e3], d: [0.01, 0.025], vol: [1.2, 2.2] })], ["sdk: a crackle of the fire"], FIRE_FALLOFF), group: "fire_crackle" },
|
|
4184
|
+
{ ...blast("fire_pop", [noise({ type: "bandpass", f: [500, 1100], q: 1, d: [0.03, 0.06], vol: [3, 4.5] })], ["sdk: a log or a resin pocket popping"], FIRE_FALLOFF), group: "fire_crackle" }
|
|
4185
|
+
];
|
|
4186
|
+
var FIRE_LOOP = {
|
|
4187
|
+
id: "fire_loop",
|
|
4188
|
+
bus: "world",
|
|
4189
|
+
order: "chain_loop",
|
|
4190
|
+
source: { kind: "noise", loop: true },
|
|
4191
|
+
filters: { mode: "serial", bank: [{ type: "lowpass", frequency: { set: 950 }, q: 0.5 }] },
|
|
4192
|
+
volume: 0,
|
|
4193
|
+
sources: ["sdk: the roar under the crackles of a fire"]
|
|
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
|
+
];
|
|
4236
|
+
var BLAST_SOUND_OF = {
|
|
4237
|
+
grenade: "explosion_grenade",
|
|
4238
|
+
big: "explosion_big",
|
|
4239
|
+
fuel: "explosion_fuel",
|
|
4240
|
+
car: "explosion_car",
|
|
4241
|
+
pipe_bomb: "explosion_pipe_bomb",
|
|
4242
|
+
rocket: "explosion_rocket",
|
|
4243
|
+
flashbang: "explosion_flashbang",
|
|
4244
|
+
molotov: "molotov_break_ignite"
|
|
4245
|
+
};
|
|
4246
|
+
|
|
4247
|
+
// src/audio/recipes.ts
|
|
4248
|
+
var scene = (id, layers, line, vars) => sceneRecipe2(id, layers, line, vars ? { vars } : {});
|
|
3879
4249
|
var boomLayers = (big) => [
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
...big ? [
|
|
3884
|
-
...
|
|
4250
|
+
noise2({ f: 1200, f2: 120, d: 1.8, vol: 2, wet: 1.2 }),
|
|
4251
|
+
tone2({ f: 65, f2: 24, d: 1, vol: 1.8 }),
|
|
4252
|
+
noise2({ type: "highpass", f: 1500, d: 0.2, vol: 0.8 }),
|
|
4253
|
+
...big ? [noise2({ f: 500, f2: 90, a: 0.2, d: 2.4, vol: 1.2, wet: 1, delay: 0.15 })] : [],
|
|
4254
|
+
...many2(8, () => tone2({ f: [2600, 5600], d: 0.05, vol: 0.08, delay: [0.4, 1.6] }))
|
|
3885
4255
|
];
|
|
3886
4256
|
var SCENE_SOUNDS = [
|
|
3887
4257
|
scene(
|
|
3888
4258
|
"glass_shatter",
|
|
3889
|
-
[
|
|
4259
|
+
[noise2({ type: "highpass", f: 2800, d: 0.45, vol: 0.9, wet: 0.5 }), ...many2(12, () => tone2({ f: [3e3, 7500], d: [0.06, 0.14], vol: 0.18, delay: [0, 0.55] }))],
|
|
3890
4260
|
"L1359 glass"
|
|
3891
4261
|
),
|
|
3892
4262
|
scene(
|
|
3893
4263
|
"wood_hit",
|
|
3894
|
-
[
|
|
4264
|
+
[noise2({ type: "bandpass", f: 850, q: 1, d: 0.25, vol: 0.9, wet: 0.3 }), tone2({ f: 210, f2: 90, d: 0.1, vol: 0.6 }), noise2({ type: "highpass", f: 2e3, d: 0.04, vol: 0.5 })],
|
|
3895
4265
|
"L1360 wood"
|
|
3896
4266
|
),
|
|
3897
|
-
scene("thunk", [
|
|
3898
|
-
scene("ping", [
|
|
3899
|
-
scene("clang", [
|
|
3900
|
-
scene("chip", [
|
|
4267
|
+
scene("thunk", [noise2({ type: "bandpass", f: 700, q: 2, d: 0.07, vol: 0.4 })], "L1361 thunk"),
|
|
4268
|
+
scene("ping", [tone2({ f: [2600, 3500], f2: 1400, d: 0.14, vol: 0.18, wet: 0.3 })], "L1362 ping"),
|
|
4269
|
+
scene("clang", [tone2({ f: [520, 720], f2: 480, d: 0.5, vol: 0.35, wet: 0.4 }), tone2({ f: 1370, f2: 1300, d: 0.3, vol: 0.15 }), noise2({ type: "highpass", f: 2e3, d: 0.05, vol: 0.5 })], "L1363 clang"),
|
|
4270
|
+
scene("chip", [noise2({ type: "bandpass", f: 1600, q: 1.2, d: 0.06, vol: 0.5 })], "L1364 chip"),
|
|
3901
4271
|
scene(
|
|
3902
4272
|
"crumble",
|
|
3903
|
-
[
|
|
4273
|
+
[noise2({ f: 900, f2: 200, d: 0.6, vol: 0.9, wet: 0.3 }), ...many2(5, () => noise2({ type: "bandpass", f: [500, 1400], q: 2, d: 0.05, vol: 0.4, delay: [0.1, 0.5] }))],
|
|
3904
4274
|
"L1365 crumble"
|
|
3905
4275
|
),
|
|
3906
4276
|
// `thud(x, z, v)`: `v` is the `scale` param (default 1)
|
|
3907
|
-
scene("thud", [
|
|
4277
|
+
scene("thud", [tone2({ f: 75, f2: 38, d: 0.22, vol: { param: "scale", scale: 0.6 } }), noise2({ f: 420, d: 0.16, vol: { param: "scale", scale: 0.45 } })], "L1358 thud", { scale: 1 }),
|
|
3908
4278
|
// `hiss(x, z, d)`: `seconds` is the param `d` (default 1.2)
|
|
3909
|
-
scene("hiss", [
|
|
4279
|
+
scene("hiss", [noise2({ type: "highpass", f: 3500, a: 0.05, d: { param: "seconds" }, vol: 0.35 })], "L1366 hiss", { seconds: 1.2 }),
|
|
3910
4280
|
scene("boom", boomLayers(false), "L1367 boom"),
|
|
3911
4281
|
scene("boom_big", boomLayers(true), "L1367 boom (big)"),
|
|
3912
|
-
scene("burst", [
|
|
3913
|
-
scene("crack", [
|
|
4282
|
+
scene("burst", [noise2({ f: 700, f2: 120, d: 0.7, vol: 1.4, wet: 0.5 }), noise2({ type: "highpass", f: 3e3, a: 0.05, d: 1.2, vol: 0.4, delay: 0.1 })], "L1357 burst"),
|
|
4283
|
+
scene("crack", [noise2({ type: "highpass", f: 1800, d: 0.05, vol: 0.8 }), tone2({ f: 420, f2: 180, d: 0.06, vol: 0.4 })], "L1354 crack")
|
|
3914
4284
|
];
|
|
3915
4285
|
var EXPLOSION_BOOM = {
|
|
3916
4286
|
id: "explosion_boom",
|
|
@@ -3939,29 +4309,129 @@ var BURN_LOOP = {
|
|
|
3939
4309
|
volume: 0,
|
|
3940
4310
|
sources: ["cena:L1316 fire loop (silent in the page: its gain is never raised)", "sdk: the level while burning"]
|
|
3941
4311
|
};
|
|
3942
|
-
var drag = (id, layers) => ({ ...
|
|
4312
|
+
var drag = (id, layers) => ({ ...sceneRecipe2(id, layers, ""), sources: ["sdk: a base dragged on a floor, one grain per stretch"] });
|
|
3943
4313
|
var DRAG_SOUNDS = [
|
|
3944
|
-
drag("drag_wood", [
|
|
3945
|
-
drag("drag_metal", [
|
|
3946
|
-
drag("drag_glass", [
|
|
3947
|
-
drag("drag_masonry", [
|
|
3948
|
-
drag("drag_soft", [
|
|
4314
|
+
drag("drag_wood", [noise2({ type: "bandpass", f: 380, q: 1.4, d: 0.28, vol: 0.5 }), noise2({ type: "bandpass", f: [900, 1300], q: 3, d: 0.08, vol: 0.14 })]),
|
|
4315
|
+
drag("drag_metal", [noise2({ type: "bandpass", f: 1500, q: 4, d: 0.26, vol: 0.32 }), tone2({ f: [880, 1240], f2: 760, d: 0.18, vol: 0.05 })]),
|
|
4316
|
+
drag("drag_glass", [noise2({ type: "bandpass", f: 2600, q: 5, d: 0.2, vol: 0.22 })]),
|
|
4317
|
+
drag("drag_masonry", [noise2({ type: "bandpass", f: 260, q: 0.9, d: 0.3, vol: 0.62 }), ...many2(3, () => noise2({ type: "bandpass", f: [600, 1200], q: 2, d: 0.04, vol: 0.14, delay: [0, 0.2] }))]),
|
|
4318
|
+
drag("drag_soft", [noise2({ type: "lowpass", f: 420, d: 0.28, vol: 0.3 })])
|
|
3949
4319
|
];
|
|
3950
|
-
var leaf = (id, layers) => ({ ...
|
|
4320
|
+
var leaf = (id, layers) => ({ ...sceneRecipe2(id, layers, ""), sources: ["sdk: a door, lid or drawer worked by hand"] });
|
|
3951
4321
|
var LEAF_SOUNDS = [
|
|
3952
|
-
leaf("latch", [
|
|
3953
|
-
leaf("creak_wood", [
|
|
3954
|
-
leaf("creak_metal", [
|
|
3955
|
-
leaf("shut_wood", [
|
|
3956
|
-
leaf("shut_metal", [
|
|
3957
|
-
leaf("shut_soft", [
|
|
3958
|
-
leaf("rattle",
|
|
3959
|
-
leaf("slide", [
|
|
4322
|
+
leaf("latch", [noise2({ type: "bandpass", f: 2400, q: 4, d: 0.03, vol: 0.35 }), tone2({ f: 1900, f2: 1500, d: 0.025, vol: 0.06, delay: 0.035 })]),
|
|
4323
|
+
leaf("creak_wood", [tone2({ f: [190, 240], f2: 310, d: 0.5, vol: 0.05 }), noise2({ type: "bandpass", f: 520, q: 6, a: 0.05, d: 0.45, vol: 0.12 })]),
|
|
4324
|
+
leaf("creak_metal", [tone2({ f: [620, 760], f2: 900, d: 0.35, vol: 0.035 }), noise2({ type: "bandpass", f: 1400, q: 8, a: 0.04, d: 0.3, vol: 0.08 })]),
|
|
4325
|
+
leaf("shut_wood", [tone2({ f: 120, f2: 60, d: 0.14, vol: 0.5 }), noise2({ type: "bandpass", f: 600, q: 1.2, d: 0.1, vol: 0.55, wet: 0.25 }), noise2({ type: "bandpass", f: 2400, q: 4, d: 0.03, vol: 0.25, delay: 0.02 })]),
|
|
4326
|
+
leaf("shut_metal", [tone2({ f: 160, f2: 85, d: 0.18, vol: 0.55 }), noise2({ type: "bandpass", f: 900, q: 1.6, d: 0.12, vol: 0.45, wet: 0.3 }), tone2({ f: 1250, f2: 1150, d: 0.12, vol: 0.06 })]),
|
|
4327
|
+
leaf("shut_soft", [tone2({ f: 95, f2: 55, d: 0.12, vol: 0.35 }), noise2({ type: "lowpass", f: 500, d: 0.1, vol: 0.3 })]),
|
|
4328
|
+
leaf("rattle", many2(5, () => noise2({ type: "bandpass", f: [1600, 2600], q: 3, d: 0.035, vol: 0.32, delay: [0, 0.45] }))),
|
|
4329
|
+
leaf("slide", [noise2({ type: "bandpass", f: 700, q: 2, a: 0.04, d: 0.35, vol: 0.22 }), noise2({ type: "bandpass", f: 2200, q: 5, d: 0.3, vol: 0.05 })]),
|
|
4330
|
+
// an item taken in the hand: the fingers on a hard surface and the sleeve moving, or a soft thing gathered up (the kit's recordings answer these ids)
|
|
4331
|
+
leaf("grab_hard", [noise2({ type: "bandpass", f: 1800, q: 3, d: 0.03, vol: 0.18 }), noise2({ type: "bandpass", f: 900, q: 1.2, a: 0.02, d: 0.14, vol: 0.08 })]),
|
|
4332
|
+
leaf("grab_soft", [noise2({ type: "lowpass", f: 1400, a: 0.02, d: 0.16, vol: 0.14 })])
|
|
4333
|
+
];
|
|
4334
|
+
var impact = (id, layers) => ({ ...sceneRecipe2(id, layers, ""), sources: ["sdk: a blow on a material, by material and kind of blow (synthesised until the recorded samples arrive)"] });
|
|
4335
|
+
var IMPACT_SOUNDS = [
|
|
4336
|
+
// metal
|
|
4337
|
+
impact("metal_bullet", [noise2({ type: "highpass", f: 3e3, d: 0.03, vol: 0.8 }), tone2({ f: [2600, 3400], f2: 1800, d: 0.14, vol: 0.28, wet: 0.35 }), tone2({ f: [520, 720], f2: 480, d: 0.35, vol: 0.22, wet: 0.35 })]),
|
|
4338
|
+
impact("metal_blunt", [noise2({ type: "bandpass", f: 800, q: 1, d: 0.05, vol: 0.5 }), tone2({ f: [150, 210], f2: 120, d: 0.6, vol: 0.5, wet: 0.4 }), tone2({ f: [420, 520], f2: 400, d: 0.4, vol: 0.12 })]),
|
|
4339
|
+
impact("metal_blade", [noise2({ type: "bandpass", f: [3500, 4500], q: 6, a: 0.01, d: 0.18, vol: 0.45 }), tone2({ f: [2200, 2800], f2: 3e3, d: 0.15, vol: 0.06 })]),
|
|
4340
|
+
impact("metal_thin", [noise2({ type: "highpass", f: 2500, d: 0.04, vol: 0.6 }), tone2({ f: [900, 1300], f2: 700, d: 0.25, vol: 0.22, wet: 0.3 }), noise2({ type: "bandpass", f: 1200, q: 2, d: 0.08, vol: 0.3 })]),
|
|
4341
|
+
// wood (the bullet keeps the art scene's `wood_hit`)
|
|
4342
|
+
impact("wood_blunt", [tone2({ f: [110, 150], f2: 70, d: 0.14, vol: 0.6 }), noise2({ type: "bandpass", f: 500, q: 1.2, d: 0.12, vol: 0.55, wet: 0.2 })]),
|
|
4343
|
+
impact("wood_blade", [noise2({ type: "bandpass", f: [1800, 2400], q: 2, a: 0.01, d: 0.12, vol: 0.5 }), ...many2(3, () => noise2({ type: "bandpass", f: [2500, 4e3], q: 3, d: 0.02, vol: 0.22, delay: [0.02, 0.1] }))]),
|
|
4344
|
+
impact("mdf_puff", [noise2({ type: "lowpass", f: 900, d: 0.08, vol: 1 }), tone2({ f: [180, 230], f2: 110, d: 0.06, vol: 0.3 }), noise2({ type: "highpass", f: 3e3, a: 0.01, d: 0.2, vol: 0.1 })]),
|
|
4345
|
+
// glass and ceramic
|
|
4346
|
+
impact("glass_bullet", [noise2({ type: "highpass", f: 4e3, d: 0.05, vol: 0.7 }), tone2({ f: [3200, 4200], f2: 2600, d: 0.2, vol: 0.14, wet: 0.3 }), ...many2(4, () => tone2({ f: [4e3, 7e3], d: 0.05, vol: 0.07, delay: [0.02, 0.2] }))]),
|
|
4347
|
+
impact("glass_blunt", [noise2({ type: "bandpass", f: 2200, q: 3, d: 0.05, vol: 0.8 }), tone2({ f: [1400, 1800], f2: 1300, d: 0.2, vol: 0.16, wet: 0.3 })]),
|
|
4348
|
+
impact("glass_blade", [noise2({ type: "bandpass", f: 5e3, q: 8, a: 0.02, d: 0.25, vol: 0.3 })]),
|
|
4349
|
+
impact("ceramic_hit", [noise2({ type: "bandpass", f: 3e3, q: 2, d: 0.03, vol: 0.6 }), tone2({ f: [1800, 2400], f2: 1600, d: 0.1, vol: 0.2 })]),
|
|
4350
|
+
impact("ceramic_bullet", [noise2({ type: "highpass", f: 2500, d: 0.06, vol: 0.85 }), ...many2(5, () => tone2({ f: [2500, 5e3], d: 0.04, vol: 0.09, delay: [0, 0.15] }))]),
|
|
4351
|
+
// masonry and the ground
|
|
4352
|
+
impact("masonry_bullet", [noise2({ type: "bandpass", f: [1600, 2e3], q: 1, d: 0.05, vol: 1.8 }), noise2({ type: "lowpass", f: 600, d: 0.12, vol: 0.9, wet: 0.2 }), ...many2(4, () => noise2({ type: "bandpass", f: [2e3, 4e3], q: 3, d: 0.02, vol: 0.28, delay: [0.03, 0.3] }))]),
|
|
4353
|
+
impact("masonry_blunt", [tone2({ f: [70, 95], f2: 45, d: 0.12, vol: 0.55 }), noise2({ type: "lowpass", f: 400, d: 0.1, vol: 0.5 })]),
|
|
4354
|
+
impact("masonry_blade", [noise2({ type: "bandpass", f: [2500, 3500], q: 4, a: 0.01, d: 0.15, vol: 0.4 })]),
|
|
4355
|
+
impact("plaster_puff", [noise2({ type: "lowpass", f: 900, d: 0.09, vol: 0.7 }), noise2({ type: "highpass", f: 3e3, a: 0.01, d: 0.2, vol: 0.14 })]),
|
|
4356
|
+
impact("soil_bullet", [noise2({ type: "lowpass", f: 700, d: 0.08, vol: 2.2 }), ...many2(3, () => noise2({ type: "bandpass", f: [1200, 2200], q: 2, d: 0.03, vol: 0.18, delay: [0.02, 0.2] }))]),
|
|
4357
|
+
impact("soil_blunt", [tone2({ f: [55, 70], f2: 40, d: 0.1, vol: 0.45 }), noise2({ type: "lowpass", f: 300, d: 0.08, vol: 0.4 })]),
|
|
4358
|
+
impact("sand_hit", [noise2({ type: "bandpass", f: 3500, q: 0.8, a: 5e-3, d: 0.15, vol: 0.45 }), noise2({ type: "lowpass", f: 500, d: 0.06, vol: 0.45 })]),
|
|
4359
|
+
impact("gravel_hit", [noise2({ type: "lowpass", f: 500, d: 0.06, vol: 0.45 }), ...many2(6, () => noise2({ type: "bandpass", f: [1500, 3500], q: 3, d: 0.025, vol: 0.28, delay: [0, 0.18] }))]),
|
|
4360
|
+
// plastic, rubber, wax
|
|
4361
|
+
impact("plastic_hit", [noise2({ type: "bandpass", f: 1600, q: 3, d: 0.04, vol: 0.5 }), tone2({ f: [380, 520], f2: 300, d: 0.08, vol: 0.32 })]),
|
|
4362
|
+
impact("plastic_bullet", [noise2({ type: "bandpass", f: 2500, q: 2, d: 0.03, vol: 1.3 }), tone2({ f: [600, 800], f2: 450, d: 0.06, vol: 0.4 })]),
|
|
4363
|
+
impact("rubber_hit", [tone2({ f: [90, 130], f2: 60, d: 0.1, vol: 0.45 }), noise2({ type: "lowpass", f: 350, d: 0.06, vol: 0.28 })]),
|
|
4364
|
+
impact("wax_hit", [noise2({ type: "lowpass", f: 800, d: 0.05, vol: 0.45 }), tone2({ f: [240, 280], f2: 180, d: 0.05, vol: 0.22 })]),
|
|
4365
|
+
// soft things, paper, plants
|
|
4366
|
+
impact("soft_bullet", [noise2({ type: "lowpass", f: 450, a: 3e-3, d: 0.1, vol: 1.8 }), noise2({ type: "bandpass", f: 900, q: 1, d: 0.05, vol: 0.5 })]),
|
|
4367
|
+
impact("fabric_rip", [noise2({ type: "bandpass", f: [2e3, 2800], q: 1.5, a: 0.02, d: 0.25, vol: 0.4 }), ...many2(4, () => noise2({ type: "bandpass", f: [3e3, 5e3], q: 4, d: 0.02, vol: 0.14, delay: [0, 0.22] }))]),
|
|
4368
|
+
impact("paper_hit", [noise2({ type: "bandpass", f: 2200, q: 1, d: 0.05, vol: 0.45 })]),
|
|
4369
|
+
impact("paper_tear", [noise2({ type: "highpass", f: 2500, a: 0.02, d: 0.3, vol: 0.15 })]),
|
|
4370
|
+
impact("leaf_rustle", many2(6, () => noise2({ type: "bandpass", f: [2500, 6e3], q: 2, d: 0.05, vol: 0.16, delay: [0, 0.3] }))),
|
|
4371
|
+
impact("branch_cut", [noise2({ type: "bandpass", f: 1500, q: 2, d: 0.04, vol: 0.55 }), tone2({ f: [260, 340], f2: 150, d: 0.05, vol: 0.22 })]),
|
|
4372
|
+
impact("fruit_splat", [noise2({ type: "lowpass", f: 1200, d: 0.12, vol: 0.55 }), noise2({ type: "bandpass", f: 600, q: 0.7, d: 0.2, vol: 0.32 })]),
|
|
4373
|
+
// a round glancing off: steel whines down in pitch ("piiuu"), stone and concrete give a short crack with grit
|
|
4374
|
+
impact("ricochet_metal", [noise2({ type: "highpass", f: 3e3, d: 0.02, vol: 0.6 }), tone2({ f: [2800, 3600], f2: 900, d: 0.38, vol: 0.2, wet: 0.4 }), tone2({ f: [5200, 6400], f2: 2e3, d: 0.2, vol: 0.05 })]),
|
|
4375
|
+
impact("ricochet_stone", [noise2({ type: "bandpass", f: [2200, 2800], q: 2, d: 0.04, vol: 0.8 }), tone2({ f: [1800, 2400], f2: 1200, d: 0.08, vol: 0.1 }), ...many2(3, () => noise2({ type: "bandpass", f: [2500, 4500], q: 3, d: 0.015, vol: 0.2, delay: [0.02, 0.12] }))]),
|
|
4376
|
+
// breaks a material has of its own (the rest keep the art scene's shatter, crumble and splinter)
|
|
4377
|
+
impact("ceramic_break", [noise2({ type: "highpass", f: 1800, d: 0.3, vol: 0.85, wet: 0.4 }), ...many2(8, () => tone2({ f: [1800, 4500], d: [0.04, 0.1], vol: 0.15, delay: [0, 0.4] }))]),
|
|
4378
|
+
impact("tempered_burst", [noise2({ type: "highpass", f: 3500, d: 0.6, vol: 0.9, wet: 0.4 }), ...many2(16, () => tone2({ f: [4e3, 8e3], d: 0.04, vol: 0.08, delay: [0, 0.8] }))]),
|
|
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 })]),
|
|
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] }))])
|
|
3960
4381
|
];
|
|
3961
|
-
var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, ...LEAF_SOUNDS, EXPLOSION_BOOM], loops: [BURN_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] };
|
|
3962
4383
|
|
|
3963
4384
|
// src/audio/sound.ts
|
|
3964
|
-
import { GAME_SFX_BANK } from "@adatechnology/game-audio";
|
|
4385
|
+
import { FOLEY_BANK, GAME_SFX_BANK } from "@adatechnology/game-audio";
|
|
4386
|
+
var IMPACT_SOUND_KEYS = {
|
|
4387
|
+
impact_metal_ballistic: { sound: "metal_bullet" },
|
|
4388
|
+
impact_metal_blunt: { sound: "metal_blunt" },
|
|
4389
|
+
impact_metal_slash: { sound: "metal_blade" },
|
|
4390
|
+
impact_aluminium_ballistic: { sound: "metal_thin" },
|
|
4391
|
+
impact_aluminium_blunt: { sound: "metal_thin" },
|
|
4392
|
+
impact_copper_ballistic: { sound: "metal_thin" },
|
|
4393
|
+
impact_copper_blunt: { sound: "metal_thin" },
|
|
4394
|
+
impact_wood_ballistic: { sound: "wood_hit" },
|
|
4395
|
+
impact_wood_blunt: { sound: "wood_blunt" },
|
|
4396
|
+
impact_wood_slash: { sound: "wood_blade" },
|
|
4397
|
+
impact_mdf_ballistic: { sound: "mdf_puff" },
|
|
4398
|
+
impact_mdf_blunt: { sound: "mdf_puff", volumeScale: 0.8 },
|
|
4399
|
+
impact_glass_ballistic: { sound: "glass_bullet" },
|
|
4400
|
+
impact_glass_blunt: { sound: "glass_blunt" },
|
|
4401
|
+
impact_glass_slash: { sound: "glass_blade" },
|
|
4402
|
+
impact_ceramic: { sound: "ceramic_hit" },
|
|
4403
|
+
impact_ceramic_ballistic: { sound: "ceramic_bullet" },
|
|
4404
|
+
impact_masonry_ballistic: { sound: "masonry_bullet" },
|
|
4405
|
+
impact_masonry_blunt: { sound: "masonry_blunt" },
|
|
4406
|
+
impact_masonry_slash: { sound: "masonry_blade" },
|
|
4407
|
+
impact_plaster: { sound: "plaster_puff" },
|
|
4408
|
+
impact_soil: { sound: "soil_blunt" },
|
|
4409
|
+
impact_soil_ballistic: { sound: "soil_bullet" },
|
|
4410
|
+
impact_sand: { sound: "sand_hit" },
|
|
4411
|
+
impact_gravel: { sound: "gravel_hit" },
|
|
4412
|
+
impact_plastic: { sound: "plastic_hit" },
|
|
4413
|
+
impact_plastic_ballistic: { sound: "plastic_bullet" },
|
|
4414
|
+
impact_rubber: { sound: "rubber_hit" },
|
|
4415
|
+
impact_wax: { sound: "wax_hit" },
|
|
4416
|
+
impact_soft_ballistic: { sound: "soft_bullet" },
|
|
4417
|
+
impact_soft_slash: { sound: "fabric_rip" },
|
|
4418
|
+
impact_paper: { sound: "paper_hit" },
|
|
4419
|
+
impact_paper_slash: { sound: "paper_tear" },
|
|
4420
|
+
impact_plant: { sound: "leaf_rustle" },
|
|
4421
|
+
impact_plant_slash: { sound: "branch_cut" },
|
|
4422
|
+
impact_flesh_fruit: { sound: "fruit_splat" },
|
|
4423
|
+
ricochet_metal: { sound: "ricochet_metal" },
|
|
4424
|
+
ricochet_masonry: { sound: "ricochet_stone" },
|
|
4425
|
+
ricochet_soil: { sound: "ricochet_stone" },
|
|
4426
|
+
ricochet_ceramic: { sound: "ricochet_stone" },
|
|
4427
|
+
break_ceramic: { sound: "ceramic_break" },
|
|
4428
|
+
break_tempered_glass: { sound: "tempered_burst" },
|
|
4429
|
+
break_plastic: { sound: "plastic_crack" },
|
|
4430
|
+
break_mdf: { sound: "mdf_crumble" },
|
|
4431
|
+
break_soft: { sound: "fabric_rip" },
|
|
4432
|
+
break_paper: { sound: "paper_tear" },
|
|
4433
|
+
break_plant: { sound: "branch_cut" }
|
|
4434
|
+
};
|
|
3965
4435
|
var DESTRUCTION_SOUND_KEYS = {
|
|
3966
4436
|
art_scene: {
|
|
3967
4437
|
impact_metal: { sound: "clang" },
|
|
@@ -3969,6 +4439,12 @@ var DESTRUCTION_SOUND_KEYS = {
|
|
|
3969
4439
|
impact_glass: { sound: "ping" },
|
|
3970
4440
|
impact_masonry: { sound: "chip" },
|
|
3971
4441
|
impact_soft: { sound: "thud" },
|
|
4442
|
+
// a round strikes: the steel rings high, glass cracks, wood and masonry chip, a soft part takes a dull thunk
|
|
4443
|
+
shot_metal: { sound: "ping" },
|
|
4444
|
+
shot_wood: { sound: "chip" },
|
|
4445
|
+
shot_glass: { sound: "crack" },
|
|
4446
|
+
shot_masonry: { sound: "chip" },
|
|
4447
|
+
shot_soft: { sound: "thunk" },
|
|
3972
4448
|
drag_wood: { sound: "drag_wood" },
|
|
3973
4449
|
drag_metal: { sound: "drag_metal" },
|
|
3974
4450
|
drag_glass: { sound: "drag_glass" },
|
|
@@ -3994,22 +4470,38 @@ var DESTRUCTION_SOUND_KEYS = {
|
|
|
3994
4470
|
hood_shut: { sound: "shut_metal" },
|
|
3995
4471
|
trunk_shut: { sound: "shut_metal" },
|
|
3996
4472
|
door_close: { sound: "shut_wood", volumeScale: 0.7 },
|
|
4473
|
+
// a vehicle's leaves (the vehicle bank of game-vehicles-three): a car door shuts with a padded thump and a latch, not like a fridge
|
|
4474
|
+
vehicle_door_open: { sound: "car_door_open", fallback: "latch" },
|
|
4475
|
+
vehicle_door_shut: { sound: "car_door_close", fallback: "shut_metal" },
|
|
4476
|
+
vehicle_trunk_open: { sound: "car_door_open", fallback: "latch" },
|
|
4477
|
+
vehicle_trunk_shut: { sound: "car_trunk_close", fallback: "shut_metal" },
|
|
4478
|
+
vehicle_hood_open: { sound: "car_door_open", fallback: "latch" },
|
|
4479
|
+
vehicle_hood_shut: { sound: "car_hood_close", fallback: "shut_metal" },
|
|
3997
4480
|
drawer_open: { sound: "slide" },
|
|
3998
4481
|
drawer_close: { sound: "slide" },
|
|
3999
4482
|
lid_open: { sound: "latch" },
|
|
4000
4483
|
lid_close: { sound: "shut_soft" },
|
|
4001
4484
|
flap_open: { sound: "latch" },
|
|
4002
4485
|
flap_close: { sound: "shut_soft" },
|
|
4486
|
+
// an item taken in the hand, by what it is made of (game-audio's KIT_SAMPLE_ALIASES puts recordings on these ids)
|
|
4487
|
+
grab_metal: { sound: "grab_hard" },
|
|
4488
|
+
grab_wood: { sound: "grab_hard" },
|
|
4489
|
+
grab_glass: { sound: "grab_hard", volumeScale: 0.7 },
|
|
4490
|
+
grab_masonry: { sound: "grab_hard" },
|
|
4491
|
+
grab_soft: { sound: "grab_soft" },
|
|
4003
4492
|
shatter_glass: { sound: "glass_shatter" },
|
|
4004
4493
|
crumble: { sound: "crumble" },
|
|
4005
4494
|
splinter: { sound: "crack" },
|
|
4006
4495
|
ricochet: { sound: "ping" },
|
|
4007
4496
|
unscrew: { sound: "chip" },
|
|
4008
4497
|
detach: { sound: "thunk" },
|
|
4009
|
-
ignite: { sound: "
|
|
4010
|
-
|
|
4498
|
+
ignite: { sound: "fire_ignite" },
|
|
4499
|
+
primed: { sound: "gas_leak" },
|
|
4500
|
+
extinguish: { sound: "fire_extinguish" },
|
|
4011
4501
|
melt_sizzle: { sound: "hiss", params: { seconds: 0.6 } },
|
|
4012
|
-
|
|
4502
|
+
// the blast family comes from the explosion's effect (`BLAST_KIND_OF`); this rule answers only an explosion the kit cannot place
|
|
4503
|
+
explode: { sound: "explosion_grenade", above: { volume: 0.9, sound: "explosion_big" } },
|
|
4504
|
+
...IMPACT_SOUND_KEYS
|
|
4013
4505
|
},
|
|
4014
4506
|
game: {
|
|
4015
4507
|
impact_metal: { sound: "sfx_impact" },
|
|
@@ -4023,63 +4515,211 @@ var DESTRUCTION_SOUND_KEYS = {
|
|
|
4023
4515
|
explode: { sound: "explosion_boom" }
|
|
4024
4516
|
}
|
|
4025
4517
|
};
|
|
4026
|
-
var DEFAULT_FIRE = { perFire: 0.
|
|
4518
|
+
var DEFAULT_FIRE = { perFire: 0.08, max: 0.45 };
|
|
4519
|
+
var DEFAULT_CRACKLES = { base: 2, perSqrtFire: 3, max: 14, volume: 0.5 };
|
|
4520
|
+
var DEFAULT_INDOOR_CONFINEMENT = 1.3;
|
|
4027
4521
|
function createDestructionSound(engine, options = {}) {
|
|
4028
4522
|
engine.register(DESTRUCTION_SOUND_BANK);
|
|
4029
4523
|
engine.register(GAME_SFX_BANK);
|
|
4030
|
-
|
|
4524
|
+
engine.register(FOLEY_BANK);
|
|
4525
|
+
const fire2 = { ...DEFAULT_FIRE, ...options.fire };
|
|
4526
|
+
const crackles = options.crackles === false ? null : { ...DEFAULT_CRACKLES, ...options.crackles };
|
|
4527
|
+
const fireLoop = options.fireLoop ?? "fire_loop";
|
|
4528
|
+
const kinds = { ...BLAST_KIND_OF, ...options.blastKinds };
|
|
4529
|
+
const farDistance = options.blastFarDistance ?? BLAST_FAR_DISTANCE;
|
|
4530
|
+
const indoorFrom = options.indoorConfinement ?? DEFAULT_INDOOR_CONFINEMENT;
|
|
4031
4531
|
const rules = { ...DESTRUCTION_SOUND_KEYS.art_scene, ...options.style === "game" ? DESTRUCTION_SOUND_KEYS.game : {}, ...options.keys };
|
|
4032
|
-
|
|
4033
|
-
|
|
4532
|
+
const blastByKind = options.style !== "game" && !(options.keys && "explode" in options.keys);
|
|
4533
|
+
const channel = engine.channel({ seed: options.seed ?? 1 });
|
|
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();
|
|
4538
|
+
const fires = [];
|
|
4539
|
+
let nextCrackle = 0;
|
|
4540
|
+
const fuelFor = (detail) => detail?.fuel ?? (detail?.material !== void 0 ? fuelOf[detail.material] : void 0) ?? defaultFuel;
|
|
4034
4541
|
const setFire = () => {
|
|
4035
4542
|
if (options.burnLoop === false) return;
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
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) });
|
|
4040
4560
|
}
|
|
4041
|
-
|
|
4042
|
-
|
|
4561
|
+
};
|
|
4562
|
+
const forget = (point, fuel) => {
|
|
4563
|
+
let best = -1;
|
|
4564
|
+
let bestD = Infinity;
|
|
4565
|
+
fires.forEach((f, i) => {
|
|
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;
|
|
4568
|
+
if (d < bestD) {
|
|
4569
|
+
bestD = d;
|
|
4570
|
+
best = i;
|
|
4571
|
+
}
|
|
4572
|
+
});
|
|
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;
|
|
4580
|
+
};
|
|
4581
|
+
const blastSound = (point, volume, detail) => {
|
|
4582
|
+
const named = detail?.effect === void 0 ? void 0 : kinds[detail.effect];
|
|
4583
|
+
const kind = named ?? ((detail?.radius ?? 0) >= BIG_BLAST_RADIUS || volume >= 0.9 ? "big" : "grenade");
|
|
4584
|
+
const sound = BLAST_SOUND_OF[kind];
|
|
4585
|
+
const distance = engine.plan(sound, { position: point, seed: 1 })?.distance ?? 0;
|
|
4586
|
+
return kind !== "molotov" && distance > farDistance ? "explosion_far" : sound;
|
|
4043
4587
|
};
|
|
4044
4588
|
return {
|
|
4045
|
-
onSound(key, point, volume) {
|
|
4589
|
+
onSound(key, point, volume, detail) {
|
|
4046
4590
|
if (key === "burn_loop") {
|
|
4047
|
-
|
|
4591
|
+
fires.push({ point, fuel: fuelFor(detail) });
|
|
4048
4592
|
setFire();
|
|
4049
4593
|
return;
|
|
4050
4594
|
}
|
|
4051
4595
|
if (key === "burn_end") {
|
|
4052
|
-
|
|
4596
|
+
forget(point, detail?.fuel ?? (detail?.material !== void 0 ? fuelOf[detail.material] : void 0));
|
|
4053
4597
|
setFire();
|
|
4054
4598
|
return;
|
|
4055
4599
|
}
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4600
|
+
if (key === "explode" && blastByKind) {
|
|
4601
|
+
const sound2 = blastSound(point, volume, detail);
|
|
4602
|
+
engine.play(sound2, { position: point, volume });
|
|
4603
|
+
if (sound2 !== "explosion_far" && sound2 !== "molotov_break_ignite" && (detail?.confinement ?? 1) >= indoorFrom) engine.play("explosion_indoor_tail", { position: point, volume });
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
let rule;
|
|
4607
|
+
for (const k of detail?.keys ?? []) {
|
|
4608
|
+
if (k in rules) {
|
|
4609
|
+
rule = rules[k];
|
|
4610
|
+
break;
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
if (rule === void 0) rule = key in rules ? rules[key] : rules[key.replace(/^shot_/, "impact_")];
|
|
4614
|
+
if (rule === null) return;
|
|
4615
|
+
if (!rule) {
|
|
4616
|
+
if (engine.registry.sound(key)) engine.play(key, { position: point, volume });
|
|
4617
|
+
return;
|
|
4618
|
+
}
|
|
4619
|
+
const wanted = rule.above && volume >= rule.above.volume ? rule.above.sound : rule.sound;
|
|
4620
|
+
const sound = rule.fallback && !engine.registry.sound(wanted) ? rule.fallback : wanted;
|
|
4621
|
+
const params = detail?.seconds === void 0 ? rule.params : { ...rule.params, seconds: detail.seconds };
|
|
4622
|
+
engine.play(sound, { position: point, volume: volume * (rule.volumeScale ?? 1), ...params ? { params } : {} });
|
|
4060
4623
|
},
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4624
|
+
update(dt) {
|
|
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;
|
|
4628
|
+
nextCrackle -= dt;
|
|
4629
|
+
for (let i = 0; i < 4 && nextCrackle <= 0; i++) {
|
|
4630
|
+
const rate = Math.min(crackles.max, crackles.base * Math.min(1, weight) + crackles.perSqrtFire * Math.sqrt(weight));
|
|
4631
|
+
nextCrackle += (0.4 + channel.random() * 1.2) / rate;
|
|
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 });
|
|
4635
|
+
}
|
|
4636
|
+
if (nextCrackle < 0) nextCrackle = 0;
|
|
4637
|
+
},
|
|
4638
|
+
blastSound,
|
|
4639
|
+
rule(key, detail) {
|
|
4640
|
+
for (const k of detail?.keys ?? []) if (k in rules) return rules[k] ?? void 0;
|
|
4641
|
+
return rules[key] ?? void 0;
|
|
4642
|
+
},
|
|
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
|
|
4068
4651
|
};
|
|
4069
4652
|
}
|
|
4653
|
+
|
|
4654
|
+
// src/audio/samples.ts
|
|
4655
|
+
import { sampleBankFromManifest } from "@adatechnology/game-audio";
|
|
4656
|
+
var blast2 = (gain) => ({ bus: "world", gain, falloff: BLAST_FALLOFF, cutoffK: 6e-3, priority: 3 });
|
|
4657
|
+
var fire = (gain, group) => ({ bus: "world", gain, falloff: FIRE_FALLOFF, cutoffK: 0.01, ...group ? { group } : {} });
|
|
4658
|
+
var BLAST_FIRE_SAMPLES = {
|
|
4659
|
+
explosion_grenade: blast2(1),
|
|
4660
|
+
explosion_big: blast2(1),
|
|
4661
|
+
explosion_car: blast2(1),
|
|
4662
|
+
explosion_flashbang: blast2(1),
|
|
4663
|
+
molotov_break_ignite: blast2(0.8),
|
|
4664
|
+
// the far takes are already muffled: no extra distance lowpass
|
|
4665
|
+
explosion_far: { bus: "world", gain: 1, falloff: BLAST_FAR_FALLOFF, cutoffK: 0, priority: 2 },
|
|
4666
|
+
fire_ignite: fire(0.8, "fire_ignite"),
|
|
4667
|
+
fire_crackle: fire(0.8, "fire_crackle"),
|
|
4668
|
+
fire_pop: fire(0.6, "fire_crackle"),
|
|
4669
|
+
fire_extinguish: fire(0.7),
|
|
4670
|
+
gas_leak: fire(0.5),
|
|
4671
|
+
// the roar: recorded fires chained with a crossfade (the level comes from `createDestructionSound`, by how much burns)
|
|
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 } }
|
|
4677
|
+
};
|
|
4678
|
+
function blastFireSampleBank(manifest, options = {}) {
|
|
4679
|
+
const sounds = options.sounds ?? BLAST_FIRE_SAMPLES;
|
|
4680
|
+
const bank = sampleBankFromManifest(manifest, { id: options.id ?? "destruction-samples", ...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl }, sounds });
|
|
4681
|
+
const samples = (bank.samples ?? []).map((sample) => {
|
|
4682
|
+
const loop = sounds[sample.id]?.loop;
|
|
4683
|
+
return loop === void 0 ? sample : { ...sample, loop };
|
|
4684
|
+
});
|
|
4685
|
+
return { ...bank, samples };
|
|
4686
|
+
}
|
|
4070
4687
|
export {
|
|
4688
|
+
BIG_BLAST_RADIUS,
|
|
4689
|
+
BLAST_FALLOFF,
|
|
4690
|
+
BLAST_FAR_DISTANCE,
|
|
4691
|
+
BLAST_FAR_FALLOFF,
|
|
4692
|
+
BLAST_FIRE_SAMPLES,
|
|
4693
|
+
BLAST_KINDS,
|
|
4694
|
+
BLAST_KIND_OF,
|
|
4695
|
+
BLAST_SOUNDS,
|
|
4696
|
+
BLAST_SOUND_OF,
|
|
4071
4697
|
BURN_LOOP,
|
|
4698
|
+
DEFAULT_CRACKLES,
|
|
4072
4699
|
DEFAULT_FIRE,
|
|
4700
|
+
DEFAULT_INDOOR_CONFINEMENT,
|
|
4073
4701
|
DESTRUCTION_SOUND_BANK,
|
|
4074
4702
|
DESTRUCTION_SOUND_KEYS,
|
|
4075
4703
|
DRAG_SOUNDS,
|
|
4076
4704
|
EXPLOSION_BOOM,
|
|
4705
|
+
FIRE_FALLOFF,
|
|
4706
|
+
FIRE_LOOP,
|
|
4707
|
+
FIRE_SOUNDS,
|
|
4708
|
+
IMPACT_FAMILY_TAGS,
|
|
4709
|
+
IMPACT_SOUNDS,
|
|
4710
|
+
IMPACT_SOUND_KEYS,
|
|
4077
4711
|
SCENE_SOUNDS,
|
|
4712
|
+
blastFireSampleBank,
|
|
4713
|
+
breakSoundKeys,
|
|
4078
4714
|
createDestructionRenderer,
|
|
4079
4715
|
createDestructionSound,
|
|
4080
4716
|
dragSoundKey,
|
|
4081
4717
|
heatColor,
|
|
4718
|
+
impactFamily,
|
|
4082
4719
|
impactKey,
|
|
4720
|
+
impactSoundKeys,
|
|
4721
|
+
ricochetSoundKeys,
|
|
4722
|
+
shotKey,
|
|
4083
4723
|
specFromObject3D
|
|
4084
4724
|
};
|
|
4085
4725
|
//# sourceMappingURL=index.js.map
|