@adatechnology/game-test-room 0.7.0 → 0.9.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 +2 -0
- package/dist/index.d.ts +28 -3
- package/dist/index.global.js +2222 -173
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +95 -9
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
package/dist/index.global.js
CHANGED
|
@@ -341,6 +341,37 @@ var GameTestRoom = (() => {
|
|
|
341
341
|
}
|
|
342
342
|
return { ok: problems.length === 0, problems };
|
|
343
343
|
}
|
|
344
|
+
var DEFAULT_ENVIRONMENT = {
|
|
345
|
+
rain: 0,
|
|
346
|
+
windX: 0,
|
|
347
|
+
windZ: 0,
|
|
348
|
+
windSpeed: 0,
|
|
349
|
+
gust: 0,
|
|
350
|
+
fogFar: 220,
|
|
351
|
+
visibility: 220,
|
|
352
|
+
nightFactor: 0,
|
|
353
|
+
temperatureC: 20,
|
|
354
|
+
wetness: 0
|
|
355
|
+
};
|
|
356
|
+
function createEnvironmentBus(initial = {}) {
|
|
357
|
+
let state = { ...DEFAULT_ENVIRONMENT, ...initial };
|
|
358
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
359
|
+
return {
|
|
360
|
+
get: () => state,
|
|
361
|
+
set(patch) {
|
|
362
|
+
const changed = Object.keys(patch).filter((key) => patch[key] !== void 0 && patch[key] !== state[key]);
|
|
363
|
+
if (changed.length === 0) return;
|
|
364
|
+
const next = { ...state };
|
|
365
|
+
for (const key of changed) next[key] = patch[key];
|
|
366
|
+
state = next;
|
|
367
|
+
for (const listener of [...listeners]) listener(state, changed);
|
|
368
|
+
},
|
|
369
|
+
on(listener) {
|
|
370
|
+
listeners.add(listener);
|
|
371
|
+
return () => void listeners.delete(listener);
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
344
375
|
|
|
345
376
|
// ../game-destruction/dist/index.js
|
|
346
377
|
var FIRE_PRIME_C = 300;
|
|
@@ -836,9 +867,9 @@ var GameTestRoom = (() => {
|
|
|
836
867
|
const step = count > 1 && room / (count - 1) < spacing ? room / (count - 1) : spacing;
|
|
837
868
|
return Array.from({ length: count }, (_, i) => (i - (count - 1) / 2) * step);
|
|
838
869
|
};
|
|
839
|
-
var bar = (axis2, along,
|
|
870
|
+
var bar = (axis2, along, fixed2) => {
|
|
840
871
|
const a = [0, 0, 0], b = [0, 0, 0];
|
|
841
|
-
for (const [index, value] of
|
|
872
|
+
for (const [index, value] of fixed2) {
|
|
842
873
|
a[index] = value;
|
|
843
874
|
b[index] = value;
|
|
844
875
|
}
|
|
@@ -1159,7 +1190,8 @@ var GameTestRoom = (() => {
|
|
|
1159
1190
|
0.25
|
|
1160
1191
|
],
|
|
1161
1192
|
damageScale: {
|
|
1162
|
-
blunt: 1.5
|
|
1193
|
+
blunt: 1.5,
|
|
1194
|
+
shelter: 3
|
|
1163
1195
|
},
|
|
1164
1196
|
hitDebris: "shards",
|
|
1165
1197
|
breakDebris: "shards",
|
|
@@ -1210,7 +1242,10 @@ var GameTestRoom = (() => {
|
|
|
1210
1242
|
"glass",
|
|
1211
1243
|
"brittle"
|
|
1212
1244
|
],
|
|
1213
|
-
compressive: 2e5
|
|
1245
|
+
compressive: 2e5,
|
|
1246
|
+
damageScale: {
|
|
1247
|
+
shelter: 3
|
|
1248
|
+
}
|
|
1214
1249
|
},
|
|
1215
1250
|
{
|
|
1216
1251
|
id: "ceramic",
|
|
@@ -1890,10 +1925,14 @@ var GameTestRoom = (() => {
|
|
|
1890
1925
|
};
|
|
1891
1926
|
var DMG_PER_J = 0.02;
|
|
1892
1927
|
var BLAST_COUPLING = 0.1;
|
|
1928
|
+
var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
|
|
1929
|
+
var FRAGMENT_HIT_DEFAULTS = { max: 24, energyFactor: 0.5, hitType: "blunt", range: 6, flightTime: 0.4, spread: 10, aim: 40 };
|
|
1930
|
+
var CONFINEMENT_DEFAULTS = { rays: 26, reach: 1.5, gain: 1.5, power: 2, max: 3 };
|
|
1893
1931
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
1894
1932
|
var BREACH_RADIUS = 0.55;
|
|
1895
1933
|
var BREACH_BLAST_FACTOR = 6;
|
|
1896
1934
|
var BREACH_AREA = 0.25;
|
|
1935
|
+
var BREACH_BLAST_REACH = Infinity;
|
|
1897
1936
|
var RESIDUAL_CAPACITY = 0.15;
|
|
1898
1937
|
var UNIT_FRAGMENT_FACTOR = 4;
|
|
1899
1938
|
var BREACH_GRID_CAP = 4096;
|
|
@@ -1928,6 +1967,7 @@ var GameTestRoom = (() => {
|
|
|
1928
1967
|
const fireSpread = options.fireSpread ?? 0.6;
|
|
1929
1968
|
const breachRadius = options.breach?.radius ?? BREACH_RADIUS;
|
|
1930
1969
|
const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
|
|
1970
|
+
const breachBlastReach = options.breach?.blastReach ?? BREACH_BLAST_REACH;
|
|
1931
1971
|
const rng = createRng(options.seed ?? 1);
|
|
1932
1972
|
const rnd2 = () => rng.next();
|
|
1933
1973
|
let time = 0;
|
|
@@ -2283,7 +2323,7 @@ var GameTestRoom = (() => {
|
|
|
2283
2323
|
if (disc < 0) return true;
|
|
2284
2324
|
return -b - Math.sqrt(disc) > maxDist;
|
|
2285
2325
|
};
|
|
2286
|
-
const raycastInternal = (origin, dir, maxDist, ignore) => {
|
|
2326
|
+
const raycastInternal = (origin, dir, maxDist, ignore, worldOnly = false) => {
|
|
2287
2327
|
const d = vec.normalize(dir);
|
|
2288
2328
|
let best = null;
|
|
2289
2329
|
for (const o of objects.values()) {
|
|
@@ -2296,7 +2336,7 @@ var GameTestRoom = (() => {
|
|
|
2296
2336
|
if (!best || r.t0 < best.distance) best = { object: o.id, part: p.spec.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
|
|
2297
2337
|
}
|
|
2298
2338
|
}
|
|
2299
|
-
for (const im of impls.values()) {
|
|
2339
|
+
for (const im of worldOnly ? [] : impls.values()) {
|
|
2300
2340
|
for (const ps of im.impl.describe().parts) {
|
|
2301
2341
|
if (ignore?.has(`${im.impl.id}\0${ps.id}`)) continue;
|
|
2302
2342
|
const ov = im.poses.get(ps.id);
|
|
@@ -2709,7 +2749,7 @@ var GameTestRoom = (() => {
|
|
|
2709
2749
|
else if (chained && (trigger === "explosion" || 1 - p.st.hp / p.st.maxHp >= shotDamageFractionOf(p.spec))) prime(o, p, trigger, src);
|
|
2710
2750
|
return done;
|
|
2711
2751
|
};
|
|
2712
|
-
const ballistic2 = (o, p, entry, n, d, E, caliber, pathLen, src) => {
|
|
2752
|
+
const ballistic2 = (o, p, entry, n, d, E, caliber, pathLen, src, piercing = 1) => {
|
|
2713
2753
|
const mat = p.mat;
|
|
2714
2754
|
if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
|
|
2715
2755
|
return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
|
|
@@ -2722,7 +2762,9 @@ var GameTestRoom = (() => {
|
|
|
2722
2762
|
const exitLocal = objLocal(o, vec.madd(entry, d, Math.min(pathLen, effM)));
|
|
2723
2763
|
const coats = p.layers.length > 0 ? layersEnergy(p.layers, p.rt.spalls, face, objLocal(o, entry)) + layersEnergy(p.layers, p.rt.spalls, -face, exitLocal) : 0;
|
|
2724
2764
|
const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
|
|
2725
|
-
const
|
|
2765
|
+
const hard = mat.tags?.includes("metal") || mat.tags?.includes("masonry");
|
|
2766
|
+
const Ep = piercing === 1 || !hard ? E : E * piercing;
|
|
2767
|
+
const penetrated = Ep > thr;
|
|
2726
2768
|
const scale2 = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
|
|
2727
2769
|
const id = p.spec.id;
|
|
2728
2770
|
if (!penetrated && mat.ricochetDeg && angle < mat.ricochetDeg) {
|
|
@@ -2734,7 +2776,7 @@ var GameTestRoom = (() => {
|
|
|
2734
2776
|
}
|
|
2735
2777
|
if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
|
|
2736
2778
|
const brittle = mat.ductility < 0.3;
|
|
2737
|
-
const ratio =
|
|
2779
|
+
const ratio = Ep / Math.max(thr, 1e-6);
|
|
2738
2780
|
const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
|
|
2739
2781
|
let exitPoint;
|
|
2740
2782
|
if (penetrated) {
|
|
@@ -2764,7 +2806,7 @@ var GameTestRoom = (() => {
|
|
|
2764
2806
|
debris2(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
2765
2807
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
2766
2808
|
const dmg = applyDamage(o, p, E * DMG_PER_J * scale2, "ballistic", entry, d, E * 0.1, 12, src);
|
|
2767
|
-
return { penetrated, exitEnergy: penetrated ? E - thr : 0, exitPoint, damage: dmg };
|
|
2809
|
+
return { penetrated, exitEnergy: penetrated ? E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
|
|
2768
2810
|
};
|
|
2769
2811
|
const melee = (o, p, kind, E, point, n, d, contactR, src) => {
|
|
2770
2812
|
const mat = p.mat;
|
|
@@ -2792,54 +2834,110 @@ var GameTestRoom = (() => {
|
|
|
2792
2834
|
addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
|
|
2793
2835
|
return done;
|
|
2794
2836
|
};
|
|
2837
|
+
const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
|
|
2838
|
+
if (isLost(p)) return null;
|
|
2839
|
+
const { c, q } = partXf(o, p);
|
|
2840
|
+
const lp = toLocal(c, q, point);
|
|
2841
|
+
const dist = distanceToShape(p.spec.shape, lp);
|
|
2842
|
+
if (dist >= radius) return null;
|
|
2843
|
+
const sp = surfacePoint(p.spec.shape, lp);
|
|
2844
|
+
const wp = toWorld(c, q, sp.point);
|
|
2845
|
+
const reach = reachAt ? reachAt(wp, p) : 1;
|
|
2846
|
+
const f = (1 - dist / radius) ** 2 * reach;
|
|
2847
|
+
const Ein = energy * f * BLAST_COUPLING;
|
|
2848
|
+
const wn = rotateVec(q, sp.normal);
|
|
2849
|
+
if (!res.part) {
|
|
2850
|
+
res.part = p.spec.id;
|
|
2851
|
+
res.point = wp;
|
|
2852
|
+
}
|
|
2853
|
+
const mat = p.mat;
|
|
2854
|
+
if (Ein > 20) {
|
|
2855
|
+
if (mat.ductility >= 0.5) addDent(o, p, wp, wn, clamp2(0.05 + Math.sqrt(Ein) * 2e-3, 0.05, 0.4), clamp2(4e-3 + Ein * 2e-5, 4e-3, 0.08), src);
|
|
2856
|
+
else if (mat.ductility < 0.3 && Ein > 60) addCrack(o, p, wp, wn, clamp2(0.05 + Ein * 5e-4, 0.05, 1), src);
|
|
2857
|
+
debris2(o, p, mat.hitDebris, wp, wn, clamp2(Math.round(Ein / 80) + 1, 1, 12), clamp2(Math.sqrt(Ein) / 4, 2, 15), src);
|
|
2858
|
+
}
|
|
2859
|
+
const th = mat.thermal;
|
|
2860
|
+
if (th.ignitionC !== void 0 && p.st.fuel > 0) {
|
|
2861
|
+
p.st.temperature += 300 * f;
|
|
2862
|
+
o.active = true;
|
|
2863
|
+
if (f >= 0.25 && !p.st.burning) {
|
|
2864
|
+
p.st.temperature = Math.max(p.st.temperature, th.ignitionC + 1);
|
|
2865
|
+
checkIgnite(o, p, src);
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
wearJoints(o, p, Ein * 0.5, void 0, 0, src);
|
|
2869
|
+
const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
|
|
2870
|
+
if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
|
|
2871
|
+
frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
|
|
2872
|
+
return reach;
|
|
2873
|
+
}
|
|
2874
|
+
if (Ein > 20 && p.layers.length > 0) spallOuter(o, p, wp, faceOf(o, p, wn), wn, clamp2(0.05 + Math.sqrt(Ein) * 2e-3, 0.05, 0.4), src);
|
|
2875
|
+
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2876
|
+
res.damage += dealt;
|
|
2877
|
+
if (dist <= breachBlastReach * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2878
|
+
return reach;
|
|
2879
|
+
};
|
|
2795
2880
|
const blastObject = (o, point, energy, radius, src) => {
|
|
2796
2881
|
const start = cur.length;
|
|
2797
2882
|
const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
|
|
2798
|
-
for (const p of o.parts)
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
const
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
if (!res.part) {
|
|
2810
|
-
res.part = p.spec.id;
|
|
2811
|
-
res.point = wp;
|
|
2812
|
-
}
|
|
2813
|
-
const mat = p.mat;
|
|
2814
|
-
if (Ein > 20) {
|
|
2815
|
-
if (mat.ductility >= 0.5) addDent(o, p, wp, wn, clamp2(0.05 + Math.sqrt(Ein) * 2e-3, 0.05, 0.4), clamp2(4e-3 + Ein * 2e-5, 4e-3, 0.08), src);
|
|
2816
|
-
else if (mat.ductility < 0.3 && Ein > 60) addCrack(o, p, wp, wn, clamp2(0.05 + Ein * 5e-4, 0.05, 1), src);
|
|
2817
|
-
debris2(o, p, mat.hitDebris, wp, wn, clamp2(Math.round(Ein / 80) + 1, 1, 12), clamp2(Math.sqrt(Ein) / 4, 2, 15), src);
|
|
2818
|
-
}
|
|
2819
|
-
const th = mat.thermal;
|
|
2820
|
-
if (th.ignitionC !== void 0 && p.st.fuel > 0) {
|
|
2821
|
-
p.st.temperature += 300 * f;
|
|
2822
|
-
o.active = true;
|
|
2823
|
-
if (f >= 0.25 && !p.st.burning) {
|
|
2824
|
-
p.st.temperature = Math.max(p.st.temperature, th.ignitionC + 1);
|
|
2825
|
-
checkIgnite(o, p, src);
|
|
2826
|
-
}
|
|
2883
|
+
for (const p of o.parts) blastPart(o, p, point, energy, radius, src, res);
|
|
2884
|
+
res.events = cur.slice(start);
|
|
2885
|
+
return res;
|
|
2886
|
+
};
|
|
2887
|
+
const blastCfg = (call3) => {
|
|
2888
|
+
const pick3 = (key) => call3 && call3[key] !== void 0 ? call3[key] : options.blast?.[key];
|
|
2889
|
+
const merge = (v, defaults) => {
|
|
2890
|
+
if (!v) return null;
|
|
2891
|
+
const out = { ...defaults };
|
|
2892
|
+
if (v !== true) {
|
|
2893
|
+
for (const [key, value] of Object.entries(v)) if (value !== void 0) out[key] = value;
|
|
2827
2894
|
}
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2895
|
+
return out;
|
|
2896
|
+
};
|
|
2897
|
+
const conf = pick3("confinement");
|
|
2898
|
+
return {
|
|
2899
|
+
shelter: merge(pick3("shelter"), SHELTER_DEFAULTS),
|
|
2900
|
+
frag: merge(pick3("fragmentHits"), FRAGMENT_HIT_DEFAULTS),
|
|
2901
|
+
conf: typeof conf === "number" ? Number.isFinite(conf) ? Math.max(0, conf) : 1 : merge(conf, CONFINEMENT_DEFAULTS)
|
|
2902
|
+
};
|
|
2903
|
+
};
|
|
2904
|
+
const confinementRays = /* @__PURE__ */ new Map();
|
|
2905
|
+
const raysOf = (n) => {
|
|
2906
|
+
let rays = confinementRays.get(n);
|
|
2907
|
+
if (rays) return rays;
|
|
2908
|
+
const r = createRng((options.seed ?? 1) ^ 1540483477);
|
|
2909
|
+
const off = r.next();
|
|
2910
|
+
const phi0 = r.next() * Math.PI * 2;
|
|
2911
|
+
rays = [];
|
|
2912
|
+
for (let i = 0; i < n; i++) {
|
|
2913
|
+
const y = 1 - 2 * (i + off) / n;
|
|
2914
|
+
const s = Math.sqrt(Math.max(0, 1 - y * y));
|
|
2915
|
+
const phi = phi0 + i * 2.399963229728653;
|
|
2916
|
+
rays.push([Math.cos(phi) * s, y, Math.sin(phi) * s]);
|
|
2917
|
+
}
|
|
2918
|
+
confinementRays.set(n, rays);
|
|
2919
|
+
return rays;
|
|
2920
|
+
};
|
|
2921
|
+
const confinementOf = (conf, point, radius) => {
|
|
2922
|
+
if (conf === null) return 1;
|
|
2923
|
+
if (typeof conf === "number") return conf;
|
|
2924
|
+
let hits = 0;
|
|
2925
|
+
let sum2 = 0;
|
|
2926
|
+
for (const d of raysOf(Math.max(1, Math.round(conf.rays)))) {
|
|
2927
|
+
const h = raycastInternal(point, d, radius * conf.reach, void 0, true);
|
|
2928
|
+
if (h) {
|
|
2929
|
+
hits++;
|
|
2930
|
+
sum2 += h.distance;
|
|
2833
2931
|
}
|
|
2834
|
-
if (Ein > 20 && p.layers.length > 0) spallOuter(o, p, wp, faceOf(o, p, wn), wn, clamp2(0.05 + Math.sqrt(Ein) * 2e-3, 0.05, 0.4), src);
|
|
2835
|
-
const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
|
|
2836
|
-
res.damage += dealt;
|
|
2837
|
-
addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
2838
2932
|
}
|
|
2839
|
-
|
|
2840
|
-
|
|
2933
|
+
if (hits === 0) return 1;
|
|
2934
|
+
const closed = hits / Math.max(1, Math.round(conf.rays));
|
|
2935
|
+
const ratio = clamp2(radius / Math.max(sum2 / hits, 1e-3), 0, 2);
|
|
2936
|
+
return Math.min(Math.max(1, conf.max), 1 + conf.gain * closed ** conf.power * ratio);
|
|
2841
2937
|
};
|
|
2842
|
-
const
|
|
2938
|
+
const FLAG_LOST = 1;
|
|
2939
|
+
const FLAG_OPEN = 2;
|
|
2940
|
+
const blastPlain = (point, energy, radius, src) => {
|
|
2843
2941
|
const out = [];
|
|
2844
2942
|
for (const o of [...objects.values()]) {
|
|
2845
2943
|
if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
|
|
@@ -2847,16 +2945,225 @@ var GameTestRoom = (() => {
|
|
|
2847
2945
|
if (r.part) out.push(r);
|
|
2848
2946
|
}
|
|
2849
2947
|
for (const im of [...impls.values()]) {
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2948
|
+
if (implInRange(im, point, radius)) out.push(implHit(im, { type: "explosive", point, energy, radius, ...src !== void 0 ? { source: src } : {} }));
|
|
2949
|
+
}
|
|
2950
|
+
return out;
|
|
2951
|
+
};
|
|
2952
|
+
const implParts = (im) => im.impl.describe().parts.map((ps) => {
|
|
2953
|
+
const ov = im.poses.get(ps.id);
|
|
2954
|
+
return { ps, c: toWorld(im.position, im.rotation, ov?.pos ?? ps.position), q: quat.mul(im.rotation, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0])) };
|
|
2955
|
+
});
|
|
2956
|
+
const implInRange = (im, point, radius) => implParts(im).some(({ ps, c, q }) => distanceToShape(ps.shape, toLocal(c, q, point)) < radius);
|
|
2957
|
+
const blastScene = (point, energy, radius, src, cfg, only) => {
|
|
2958
|
+
const mult = confinementOf(cfg.conf, point, radius);
|
|
2959
|
+
const E = energy * mult;
|
|
2960
|
+
const shelter = cfg.shelter;
|
|
2961
|
+
const flags = /* @__PURE__ */ new Map();
|
|
2962
|
+
const inRange = [];
|
|
2963
|
+
for (const o of objects.values()) {
|
|
2964
|
+
if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
|
|
2965
|
+
inRange.push(o);
|
|
2966
|
+
if (shelter) for (const p of o.parts) flags.set(p, (isLost(p) ? FLAG_LOST : 0) | (p.st.opening || p.st.stripped ? FLAG_OPEN : 0));
|
|
2967
|
+
}
|
|
2968
|
+
const shields = /* @__PURE__ */ new Map();
|
|
2969
|
+
const reachAt = (tp, self) => {
|
|
2970
|
+
if (!shelter) return 1;
|
|
2971
|
+
const L = vec.distance(point, tp);
|
|
2972
|
+
if (L < 1e-4) return 1;
|
|
2973
|
+
const d = vec.scale(vec.sub(tp, point), 1 / L);
|
|
2974
|
+
let t = 1;
|
|
2975
|
+
for (const o of inRange) {
|
|
2976
|
+
if (raySphereMiss(point, d, toWorld(o.st.position, o.st.rotation, o.bc), o.br, L)) continue;
|
|
2977
|
+
for (const p of o.parts) {
|
|
2978
|
+
if (p === self) continue;
|
|
2979
|
+
const was = flags.get(p) ?? 0;
|
|
2980
|
+
if (was & FLAG_LOST) continue;
|
|
2981
|
+
const r = rayPart(o, p, point, d);
|
|
2982
|
+
if (!r) continue;
|
|
2983
|
+
const t0 = Math.max(r.t0, 0);
|
|
2984
|
+
const path = Math.min(r.t1, L) - t0;
|
|
2985
|
+
if (path < shelter.minPath) continue;
|
|
2986
|
+
const mid = vec.madd(point, d, t0 + path / 2);
|
|
2987
|
+
const opening = p.st.opening && insideOpening(o, p, mid);
|
|
2988
|
+
if (opening && was & FLAG_OPEN) continue;
|
|
2989
|
+
if (p.st.stripped && was & FLAG_OPEN) continue;
|
|
2990
|
+
const resistance = p.mat.hpPerKg * p.mat.density * path * (p.mat.damageScale?.shelter ?? 1);
|
|
2991
|
+
let ti = Math.exp(-resistance / shelter.k);
|
|
2992
|
+
if (isLost(p) || opening || p.st.stripped && !(was & FLAG_OPEN)) ti += (1 - ti) * shelter.brokenTransmission;
|
|
2993
|
+
t *= ti;
|
|
2994
|
+
if (cfg.frag) {
|
|
2995
|
+
let list = shields.get(p);
|
|
2996
|
+
if (!list) shields.set(p, list = []);
|
|
2997
|
+
list.push(tp);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
return t;
|
|
3002
|
+
};
|
|
3003
|
+
const items = [];
|
|
3004
|
+
let order = 0;
|
|
3005
|
+
for (const o of inRange) {
|
|
3006
|
+
if (only && only !== o) continue;
|
|
3007
|
+
for (const p of o.parts) {
|
|
3008
|
+
if (isLost(p)) continue;
|
|
3009
|
+
const dist = distToPart(o, p, point);
|
|
3010
|
+
if (dist < radius) items.push({ dist, order: order++, o, p });
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
for (const im of impls.values()) {
|
|
3014
|
+
if (only && only !== im) continue;
|
|
3015
|
+
let dist = Infinity;
|
|
3016
|
+
for (const { ps, c, q } of implParts(im)) dist = Math.min(dist, distanceToShape(ps.shape, toLocal(c, q, point)));
|
|
3017
|
+
if (dist < radius) items.push({ dist, order: order++, im });
|
|
3018
|
+
}
|
|
3019
|
+
items.sort((a, b) => a.dist - b.dist || a.order - b.order);
|
|
3020
|
+
const results = /* @__PURE__ */ new Map();
|
|
3021
|
+
const reports = /* @__PURE__ */ new Map();
|
|
3022
|
+
const report = (key) => {
|
|
3023
|
+
let r = reports.get(key);
|
|
3024
|
+
if (!r) reports.set(key, r = { shelter: 0, confinement: mult, fragmentHits: 0 });
|
|
3025
|
+
return r;
|
|
3026
|
+
};
|
|
3027
|
+
const thrown = [];
|
|
3028
|
+
const sweep = (from) => {
|
|
3029
|
+
if (!cfg.frag) return;
|
|
3030
|
+
for (let i = from; i < cur.length; i++) {
|
|
3031
|
+
const e = cur[i];
|
|
3032
|
+
if (e.type !== "fracture" && e.type !== "stage") continue;
|
|
3033
|
+
for (const frag of e.fragments ?? []) thrown.push({ frag, object: e.object, part: objects.get(e.object)?.byId.get(e.part) ?? null });
|
|
3034
|
+
}
|
|
3035
|
+
};
|
|
3036
|
+
for (const it of items) {
|
|
3037
|
+
if (it.o && it.p) {
|
|
3038
|
+
const o = it.o;
|
|
3039
|
+
let res = results.get(o);
|
|
3040
|
+
if (!res) results.set(o, res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] });
|
|
3041
|
+
const start = cur.length;
|
|
3042
|
+
const k = blastPart(o, it.p, point, E, radius, src, res, shelter ? reachAt : void 0);
|
|
3043
|
+
res.events.push(...cur.slice(start));
|
|
3044
|
+
sweep(start);
|
|
3045
|
+
if (k !== null) {
|
|
3046
|
+
const rep = report(o);
|
|
3047
|
+
rep.shelter = Math.max(rep.shelter, k);
|
|
3048
|
+
if (shelter) (rep.parts ?? (rep.parts = {}))[it.p.spec.id] = k;
|
|
3049
|
+
}
|
|
3050
|
+
} else if (it.im) {
|
|
3051
|
+
const im = it.im;
|
|
3052
|
+
let num = 0;
|
|
3053
|
+
let den = 0;
|
|
3054
|
+
for (const { ps, c, q } of implParts(im)) {
|
|
3055
|
+
const lp = toLocal(c, q, point);
|
|
3056
|
+
const dist = distanceToShape(ps.shape, lp);
|
|
3057
|
+
if (dist >= radius) continue;
|
|
3058
|
+
const f = (1 - dist / radius) ** 2;
|
|
3059
|
+
const sp = surfacePoint(ps.shape, lp);
|
|
3060
|
+
num += f * reachAt(toWorld(c, q, sp.point), null);
|
|
3061
|
+
den += f;
|
|
3062
|
+
}
|
|
3063
|
+
const k = den > 0 ? num / den : 1;
|
|
3064
|
+
const res = implHit(im, { type: "explosive", point, energy: E * k, radius, ...shelter ? { shelter: k } : {}, ...src !== void 0 ? { source: src } : {} });
|
|
3065
|
+
results.set(im, res);
|
|
3066
|
+
report(im).shelter = k;
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
if (cfg.frag && thrown.length > 0) {
|
|
3070
|
+
const g = cfg.frag;
|
|
3071
|
+
const energyOf2 = (f) => 0.5 * f.mass * (f.velocity[0] ** 2 + f.velocity[1] ** 2 + f.velocity[2] ** 2);
|
|
3072
|
+
const list = thrown.map((t, i) => ({ ...t, i, e: energyOf2(t.frag) })).sort((a, b) => b.e - a.e || a.i - b.i).slice(0, Math.max(0, Math.floor(g.max)));
|
|
3073
|
+
list.forEach((t, i) => throwFragment(t.frag, t.object, t.part, g, point, i, src, shields, results, reports, mult));
|
|
2857
3074
|
}
|
|
3075
|
+
const out = [];
|
|
3076
|
+
const reportShelter = (key, res) => {
|
|
3077
|
+
res.blast = reports.get(key) ?? { shelter: 1, confinement: mult, fragmentHits: 0 };
|
|
3078
|
+
};
|
|
3079
|
+
const summary = [];
|
|
3080
|
+
let fragmentHits = 0;
|
|
3081
|
+
for (const o of objects.values()) {
|
|
3082
|
+
const r = results.get(o);
|
|
3083
|
+
if (!r?.part) continue;
|
|
3084
|
+
reportShelter(o, r);
|
|
3085
|
+
out.push(r);
|
|
3086
|
+
for (const [part, factor] of Object.entries(r.blast?.parts ?? {})) summary.push({ object: o.id, part, factor });
|
|
3087
|
+
fragmentHits += r.blast?.fragmentHits ?? 0;
|
|
3088
|
+
}
|
|
3089
|
+
for (const im of impls.values()) {
|
|
3090
|
+
const r = results.get(im);
|
|
3091
|
+
if (!r) continue;
|
|
3092
|
+
reportShelter(im, r);
|
|
3093
|
+
out.push(r);
|
|
3094
|
+
if (shelter) summary.push({ object: im.impl.id, factor: r.blast?.shelter ?? 1 });
|
|
3095
|
+
fragmentHits += r.blast?.fragmentHits ?? 0;
|
|
3096
|
+
}
|
|
3097
|
+
emit2({ type: "blast", object: "", time, ...src !== void 0 ? { source: src } : {}, point, energy: E, radius, confinement: mult, shelter: summary, fragmentHits });
|
|
2858
3098
|
return out;
|
|
2859
3099
|
};
|
|
3100
|
+
const seedOf = (...nums) => {
|
|
3101
|
+
let h = 2166136261;
|
|
3102
|
+
for (const n of nums) {
|
|
3103
|
+
h = Math.imul(h ^ (Math.round(n * 1e3) | 0), 16777619);
|
|
3104
|
+
h ^= h >>> 15;
|
|
3105
|
+
}
|
|
3106
|
+
return h | 0;
|
|
3107
|
+
};
|
|
3108
|
+
const throwFragment = (frag, objectId, from, g, centre, index, src, shields, results, reports, mult) => {
|
|
3109
|
+
const v = frag.velocity;
|
|
3110
|
+
const speed = Math.hypot(v[0], v[1], v[2]);
|
|
3111
|
+
const energy = g.energyFactor * 0.5 * frag.mass * speed * speed;
|
|
3112
|
+
if (speed < 0.5 || energy < 1) return;
|
|
3113
|
+
const r = createRng(seedOf(options.seed ?? 1, centre[0], centre[1], centre[2], index, frag.center[0], frag.center[1], frag.center[2]));
|
|
3114
|
+
let dir = vec.scale(v, 1 / speed);
|
|
3115
|
+
const behind = from ? shields.get(from) : void 0;
|
|
3116
|
+
if (behind) {
|
|
3117
|
+
let best = Math.cos(g.aim * Math.PI / 180);
|
|
3118
|
+
for (const tp of behind) {
|
|
3119
|
+
const to = vec.sub(tp, frag.center);
|
|
3120
|
+
const len = vec.length(to);
|
|
3121
|
+
if (len < 1e-4) continue;
|
|
3122
|
+
const cos2 = vec.dot(dir, to) / len;
|
|
3123
|
+
if (cos2 >= best) {
|
|
3124
|
+
best = cos2;
|
|
3125
|
+
dir = vec.scale(to, 1 / len);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
const spread3 = Math.tan(g.spread * Math.PI / 180);
|
|
3130
|
+
dir = vec.normalize([dir[0] + (r.next() - 0.5) * 2 * spread3, dir[1] + (r.next() - 0.5) * 2 * spread3, dir[2] + (r.next() - 0.5) * 2 * spread3], dir);
|
|
3131
|
+
const range2 = Math.min(g.range, speed * g.flightTime);
|
|
3132
|
+
const origin = vec.madd(frag.center, dir, 0.02);
|
|
3133
|
+
const rh = raycastInternal(origin, dir, range2, from ? /* @__PURE__ */ new Set([`${objectId}\0${from.spec.id}`]) : void 0);
|
|
3134
|
+
if (!rh) return;
|
|
3135
|
+
const hit = {
|
|
3136
|
+
type: g.hitType,
|
|
3137
|
+
point: rh.point,
|
|
3138
|
+
direction: dir,
|
|
3139
|
+
energy,
|
|
3140
|
+
radius: 0.05,
|
|
3141
|
+
part: rh.part,
|
|
3142
|
+
...g.hitType === "ballistic" ? { caliber: clamp2(Math.max(...frag.size) * 1e3, 3, 30), mass: frag.mass * 1e3 } : {},
|
|
3143
|
+
...src !== void 0 ? { source: src } : {}
|
|
3144
|
+
};
|
|
3145
|
+
emit2({ type: "fragment_hit", object: objectId, time, ...src !== void 0 ? { source: src } : {}, from: { object: objectId, part: frag.part }, target: { object: rh.object, part: rh.part }, point: rh.point, direction: dir, energy, mass: frag.mass, speed });
|
|
3146
|
+
const im = impls.get(rh.object);
|
|
3147
|
+
const o = im ? void 0 : objects.get(rh.object);
|
|
3148
|
+
const key = im ?? o;
|
|
3149
|
+
if (!key) return;
|
|
3150
|
+
const res = im ? implHit(im, hit) : doHit(o, hit, o.byId.get(rh.part));
|
|
3151
|
+
const have = results.get(key);
|
|
3152
|
+
if (have) {
|
|
3153
|
+
have.damage += res.damage;
|
|
3154
|
+
have.events.push(...res.events);
|
|
3155
|
+
if (!have.part) have.part = res.part;
|
|
3156
|
+
if (res.penetrated) have.penetrated = true;
|
|
3157
|
+
} else results.set(key, { ...res, events: [...res.events] });
|
|
3158
|
+
let rep = reports.get(key);
|
|
3159
|
+
if (!rep) reports.set(key, rep = { shelter: 1, confinement: mult, fragmentHits: 0 });
|
|
3160
|
+
rep.fragmentHits++;
|
|
3161
|
+
};
|
|
3162
|
+
const blastWorld = (point, energy, radius, src, call3, only) => {
|
|
3163
|
+
const cfg = blastCfg(call3);
|
|
3164
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) return blastPlain(point, energy, radius, src);
|
|
3165
|
+
return blastScene(point, energy, radius, src, cfg, only);
|
|
3166
|
+
};
|
|
2860
3167
|
const heatObject = (o, point, T, radius, duration, src) => {
|
|
2861
3168
|
const start = cur.length;
|
|
2862
3169
|
const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
|
|
@@ -2893,7 +3200,7 @@ var GameTestRoom = (() => {
|
|
|
2893
3200
|
if (hit.type === "ballistic") {
|
|
2894
3201
|
const loc = locate(o, point, dir, 0.05, only);
|
|
2895
3202
|
if (loc) {
|
|
2896
|
-
const out = ballistic2(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src);
|
|
3203
|
+
const out = ballistic2(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit.piercing ?? 1);
|
|
2897
3204
|
res = { ...res, part: loc.p.spec.id, point: loc.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy };
|
|
2898
3205
|
}
|
|
2899
3206
|
} else if (hit.type === "blunt" || hit.type === "slash") {
|
|
@@ -2915,7 +3222,13 @@ var GameTestRoom = (() => {
|
|
|
2915
3222
|
}
|
|
2916
3223
|
}
|
|
2917
3224
|
} else if (hit.type === "explosive") {
|
|
2918
|
-
|
|
3225
|
+
const call3 = hit.blast || hit.confinement !== void 0 ? { ...hit.blast, ...hit.confinement !== void 0 ? { confinement: hit.confinement } : {} } : void 0;
|
|
3226
|
+
const cfg = blastCfg(call3);
|
|
3227
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) res = blastObject(o, point, E, hit.radius ?? 2, src);
|
|
3228
|
+
else {
|
|
3229
|
+
res = blastWorld(point, E, hit.radius ?? 2, src, call3, o)[0] ?? res;
|
|
3230
|
+
res.events = cur.slice(start);
|
|
3231
|
+
}
|
|
2919
3232
|
} else {
|
|
2920
3233
|
res = heatObject(o, point, hit.temperature ?? 800, hit.radius ?? 0.25, hit.duration ?? 1, src);
|
|
2921
3234
|
}
|
|
@@ -3147,15 +3460,15 @@ var GameTestRoom = (() => {
|
|
|
3147
3460
|
return best;
|
|
3148
3461
|
};
|
|
3149
3462
|
const detonatePrimed = (until) => {
|
|
3150
|
-
const
|
|
3463
|
+
const end3 = time;
|
|
3151
3464
|
for (let next = nextPrimed(until); next; next = nextPrimed(until)) {
|
|
3152
3465
|
primed.delete(next.p);
|
|
3153
|
-
time = Math.max(0, Math.min(
|
|
3466
|
+
time = Math.max(0, Math.min(end3, next.p.rt.chainDue));
|
|
3154
3467
|
next.p.rt.chainDue = void 0;
|
|
3155
3468
|
failPart(next.o, next.p, null, null, 0, 40);
|
|
3156
3469
|
drainBlasts();
|
|
3157
3470
|
}
|
|
3158
|
-
time =
|
|
3471
|
+
time = end3;
|
|
3159
3472
|
};
|
|
3160
3473
|
const releaseFront = () => {
|
|
3161
3474
|
if (frontQueue.length === 0) return;
|
|
@@ -3172,6 +3485,14 @@ var GameTestRoom = (() => {
|
|
|
3172
3485
|
for (const e of r.events) if (e.type === "collapse" && !r.value.some((h) => h.events.includes(e))) last.events.push(e);
|
|
3173
3486
|
return r.value;
|
|
3174
3487
|
};
|
|
3488
|
+
const blastImpl = (im, h) => {
|
|
3489
|
+
const call3 = h.blast || h.confinement !== void 0 ? { ...h.blast, ...h.confinement !== void 0 ? { confinement: h.confinement } : {} } : void 0;
|
|
3490
|
+
const cfg = blastCfg(call3);
|
|
3491
|
+
if (!cfg.shelter && !cfg.frag && cfg.conf === null) return implHit(im, h);
|
|
3492
|
+
const world2 = h.space === "local" ? toWorld(im.position, im.rotation, h.point) : h.point;
|
|
3493
|
+
const r = blastWorld(world2, h.energy ?? 0, h.radius ?? 2, h.source, call3, im);
|
|
3494
|
+
return r.find((x) => x.object === im.impl.id) ?? implHit(im, h);
|
|
3495
|
+
};
|
|
3175
3496
|
const whole = (fn) => {
|
|
3176
3497
|
const r = run(fn);
|
|
3177
3498
|
r.value.events = r.events;
|
|
@@ -3259,7 +3580,7 @@ var GameTestRoom = (() => {
|
|
|
3259
3580
|
},
|
|
3260
3581
|
hit(objectId, h) {
|
|
3261
3582
|
const im = impls.get(objectId);
|
|
3262
|
-
if (im) return whole(() => implHit(im, h));
|
|
3583
|
+
if (im) return whole(() => h.type === "explosive" ? blastImpl(im, h) : implHit(im, h));
|
|
3263
3584
|
const o = getObj(objectId);
|
|
3264
3585
|
return whole(() => doHit(o, h));
|
|
3265
3586
|
},
|
|
@@ -3270,14 +3591,14 @@ var GameTestRoom = (() => {
|
|
|
3270
3591
|
const p = getPart(o, partId);
|
|
3271
3592
|
return whole(() => doHit(o, h, p));
|
|
3272
3593
|
},
|
|
3273
|
-
explode(point, energy, radius, source) {
|
|
3274
|
-
return withCollapses(run(() => blastWorld(point, energy, radius, source)));
|
|
3594
|
+
explode(point, energy, radius, source, blast) {
|
|
3595
|
+
return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
|
|
3275
3596
|
},
|
|
3276
3597
|
raycast(origin, direction, maxDistance = Infinity) {
|
|
3277
3598
|
const r = raycastInternal(origin, direction, maxDistance);
|
|
3278
3599
|
return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
|
|
3279
3600
|
},
|
|
3280
|
-
shoot(origin, direction, energy, caliber, source) {
|
|
3601
|
+
shoot(origin, direction, energy, caliber, source, piercing) {
|
|
3281
3602
|
return withCollapses(run(() => {
|
|
3282
3603
|
const results = [];
|
|
3283
3604
|
const d = vec.normalize(direction);
|
|
@@ -3289,7 +3610,7 @@ var GameTestRoom = (() => {
|
|
|
3289
3610
|
if (!rh) break;
|
|
3290
3611
|
const im = impls.get(rh.object);
|
|
3291
3612
|
if (im) {
|
|
3292
|
-
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...source !== void 0 ? { source } : {} });
|
|
3613
|
+
const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...piercing !== void 0 ? { piercing } : {}, ...source !== void 0 ? { source } : {} });
|
|
3293
3614
|
r.part = rh.part;
|
|
3294
3615
|
r.point = rh.point;
|
|
3295
3616
|
r.exitEnergy = r.penetrated ? E * 0.5 : 0;
|
|
@@ -3303,7 +3624,7 @@ var GameTestRoom = (() => {
|
|
|
3303
3624
|
const o = getObj(rh.object);
|
|
3304
3625
|
const p = getPart(o, rh.part);
|
|
3305
3626
|
const start = cur.length;
|
|
3306
|
-
const out = ballistic2(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source);
|
|
3627
|
+
const out = ballistic2(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source, piercing ?? 1);
|
|
3307
3628
|
results.push({ object: o.id, part: p.spec.id, point: rh.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy, events: cur.slice(start) });
|
|
3308
3629
|
ignore.add(`${o.id}\0${p.spec.id}`);
|
|
3309
3630
|
if (!out.penetrated || !out.exitPoint) break;
|
|
@@ -3673,6 +3994,8 @@ var GameTestRoom = (() => {
|
|
|
3673
3994
|
stage: "damage stage",
|
|
3674
3995
|
expose: "rebar bare",
|
|
3675
3996
|
body: "body",
|
|
3997
|
+
blast: "blast",
|
|
3998
|
+
fragment_hit: "fragment hit",
|
|
3676
3999
|
destroyed: "destroyed"
|
|
3677
4000
|
},
|
|
3678
4001
|
fractureStyles: { shatter: "shatter", crumble: "crumble", splinter: "splinter", tear: "tear", snap: "snap", none: "none" },
|
|
@@ -3759,6 +4082,8 @@ var GameTestRoom = (() => {
|
|
|
3759
4082
|
stage: "est\xE1gio de dano",
|
|
3760
4083
|
expose: "vergalh\xE3o \xE0 mostra",
|
|
3761
4084
|
body: "corpo",
|
|
4085
|
+
blast: "explos\xE3o",
|
|
4086
|
+
fragment_hit: "estilha\xE7o atingiu",
|
|
3762
4087
|
destroyed: "destru\xEDdo"
|
|
3763
4088
|
},
|
|
3764
4089
|
fractureStyles: { shatter: "estilha\xE7ar", crumble: "esfarelar", splinter: "lascar", tear: "rasgar", snap: "partir", none: "nenhuma" },
|
|
@@ -3767,6 +4092,1447 @@ var GameTestRoom = (() => {
|
|
|
3767
4092
|
};
|
|
3768
4093
|
var resolveStrings = (locale) => locales[locale ?? "en"] ?? locales.en;
|
|
3769
4094
|
|
|
4095
|
+
// ../game-audio/dist/index.js
|
|
4096
|
+
var FNV_OFFSET = 2166136261;
|
|
4097
|
+
var FNV_PRIME = 16777619;
|
|
4098
|
+
var MIX = 2654435761;
|
|
4099
|
+
function createRng2(seed) {
|
|
4100
|
+
let state = seed >>> 0;
|
|
4101
|
+
return () => {
|
|
4102
|
+
state = state + 1831565813 >>> 0;
|
|
4103
|
+
let mixed = Math.imul(state ^ state >>> 15, state | 1);
|
|
4104
|
+
mixed ^= mixed + Math.imul(mixed ^ mixed >>> 7, mixed | 61);
|
|
4105
|
+
return ((mixed ^ mixed >>> 14) >>> 0) / 4294967296;
|
|
4106
|
+
};
|
|
4107
|
+
}
|
|
4108
|
+
function streamSeed(seed, name) {
|
|
4109
|
+
const label = String(name);
|
|
4110
|
+
let hash = FNV_OFFSET;
|
|
4111
|
+
for (let index = 0; index < label.length; index++) hash = Math.imul(hash ^ label.charCodeAt(index), FNV_PRIME) >>> 0;
|
|
4112
|
+
return Math.imul(seed >>> 0 ^ hash, MIX) >>> 0;
|
|
4113
|
+
}
|
|
4114
|
+
function streamFor(seed, name) {
|
|
4115
|
+
return createRng2(streamSeed(seed, name));
|
|
4116
|
+
}
|
|
4117
|
+
var DISTANCE_CURVES = {
|
|
4118
|
+
/** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
|
|
4119
|
+
game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
|
|
4120
|
+
/** The game's `grito`: `1 / (1 + d² × 0.006)`, silent below 0.02 (jogo L62129–62130). */
|
|
4121
|
+
game_scream: { model: "inverse_square", k: 6e-3, minGain: 0.02 },
|
|
4122
|
+
/** The game's `voz`: `1 / (1 + d² × 0.01)`, silent below 0.02 (jogo L62146–62147). */
|
|
4123
|
+
game_voice: { model: "inverse_square", k: 0.01, minGain: 0.02 },
|
|
4124
|
+
/** The game's `boom`: `1.4 / (1 + d² × 0.002)`, never silent (jogo L68280). */
|
|
4125
|
+
game_boom: { model: "inverse_square", k: 2e-3, scale: 1.4 },
|
|
4126
|
+
/** The art scene's `outAt`: `vol / (1 + d² × 0.008)` (cena L1320). */
|
|
4127
|
+
art_scene: { model: "inverse_square", k: 8e-3 },
|
|
4128
|
+
/** The game's animal calls: `max(0, 1 − d / 70) × 0.25`, silent at or below 0.005 (jogo L66627–66628). */
|
|
4129
|
+
animals: { model: "linear", range: 70, scale: 0.25, minGain: 5e-3 }
|
|
4130
|
+
};
|
|
4131
|
+
function falloffGain(falloff, volume, distance4) {
|
|
4132
|
+
if (!falloff || falloff.model === "none") return volume;
|
|
4133
|
+
if (falloff.model === "linear") {
|
|
4134
|
+
const level2 = Math.max(0, 1 - distance4 / falloff.range) * volume;
|
|
4135
|
+
return falloff.scale === void 0 ? level2 : level2 * falloff.scale;
|
|
4136
|
+
}
|
|
4137
|
+
const level = volume / (1 + distance4 * distance4 * falloff.k);
|
|
4138
|
+
return falloff.scale === void 0 ? level : falloff.scale * level;
|
|
4139
|
+
}
|
|
4140
|
+
function isSilent(falloff, gain) {
|
|
4141
|
+
if (!falloff || falloff.model === "none" || falloff.minGain === void 0) return false;
|
|
4142
|
+
return falloff.model === "linear" ? gain <= falloff.minGain : gain < falloff.minGain;
|
|
4143
|
+
}
|
|
4144
|
+
function cutoffDivisor(falloff, distance4) {
|
|
4145
|
+
if (!falloff || falloff.model !== "inverse_square" || falloff.cutoffK === void 0) return 1;
|
|
4146
|
+
return 1 + distance4 * falloff.cutoffK;
|
|
4147
|
+
}
|
|
4148
|
+
var DEFAULT_LISTENER = { position: [0, 0, 0], yaw: 0 };
|
|
4149
|
+
var PAN_LIMIT = 0.85;
|
|
4150
|
+
var PAN_MIN_DISTANCE = 2;
|
|
4151
|
+
function listenerDistance(listener, position, mode = "xz") {
|
|
4152
|
+
const dx = position[0] - listener.position[0];
|
|
4153
|
+
const dz = position[2] - listener.position[2];
|
|
4154
|
+
if (mode === "xz") return Math.hypot(dx, dz);
|
|
4155
|
+
return Math.hypot(dx, position[1] - listener.position[1], dz);
|
|
4156
|
+
}
|
|
4157
|
+
function panFor(listener, position) {
|
|
4158
|
+
const rightX = Math.cos(listener.yaw);
|
|
4159
|
+
const rightZ = -Math.sin(listener.yaw);
|
|
4160
|
+
const dx = position[0] - listener.position[0];
|
|
4161
|
+
const dz = position[2] - listener.position[2];
|
|
4162
|
+
const distance4 = Math.hypot(dx, dz) || 1;
|
|
4163
|
+
const pan = (dx * rightX + dz * rightZ) / Math.max(distance4, PAN_MIN_DISTANCE);
|
|
4164
|
+
return pan < -PAN_LIMIT ? -PAN_LIMIT : pan > PAN_LIMIT ? PAN_LIMIT : pan;
|
|
4165
|
+
}
|
|
4166
|
+
var voice_groups_default = {
|
|
4167
|
+
undead_voice: {
|
|
4168
|
+
maxConcurrent: 5,
|
|
4169
|
+
cooldown: 0.25,
|
|
4170
|
+
maxDistance: 40,
|
|
4171
|
+
sources: [
|
|
4172
|
+
"cena:L1342 `AU.groans > 4` refuses, so five play at once",
|
|
4173
|
+
"jogo:L66986 one groan every 0.25 s (`groanBudget`), only nearer than 40 m",
|
|
4174
|
+
"jogo:L70472 the budget counts down by the frame time"
|
|
4175
|
+
]
|
|
4176
|
+
}
|
|
4177
|
+
};
|
|
4178
|
+
var VOICE_GROUPS = voice_groups_default;
|
|
4179
|
+
var BUS_PRIORITY = { ui: 5, weapons: 4, voices: 3, world: 2, weather: 1, ambience: 1 };
|
|
4180
|
+
var DEFAULT_MAX_VOICES = 32;
|
|
4181
|
+
function busPriority(bus) {
|
|
4182
|
+
return BUS_PRIORITY[bus];
|
|
4183
|
+
}
|
|
4184
|
+
function createVoiceManager(options = {}) {
|
|
4185
|
+
const groups = new Map(Object.entries(options.groups ?? VOICE_GROUPS));
|
|
4186
|
+
const maxVoices = options.maxVoices ?? DEFAULT_MAX_VOICES;
|
|
4187
|
+
const budgets = /* @__PURE__ */ new Map();
|
|
4188
|
+
let voices = [];
|
|
4189
|
+
let nextId = 1;
|
|
4190
|
+
let peak = 0;
|
|
4191
|
+
let dropped = 0;
|
|
4192
|
+
const refuse = (reason) => {
|
|
4193
|
+
dropped += 1;
|
|
4194
|
+
return { accepted: false, reason };
|
|
4195
|
+
};
|
|
4196
|
+
const groupCheck = (request, evicted) => {
|
|
4197
|
+
const group = request.group === void 0 ? void 0 : groups.get(request.group);
|
|
4198
|
+
if (!group) return void 0;
|
|
4199
|
+
if (group.maxDistance !== void 0 && !((request.distance ?? 0) < group.maxDistance)) return "distance";
|
|
4200
|
+
if (group.cooldown !== void 0 && (budgets.get(request.group) ?? 0) > 0) return "cooldown";
|
|
4201
|
+
const members = voices.filter((voice) => voice.group === request.group);
|
|
4202
|
+
if (group.maxConcurrent === void 0 || members.length < group.maxConcurrent) return void 0;
|
|
4203
|
+
if (!group.steal) return "group_full";
|
|
4204
|
+
evicted.push(members[0].id);
|
|
4205
|
+
return void 0;
|
|
4206
|
+
};
|
|
4207
|
+
return {
|
|
4208
|
+
request(request) {
|
|
4209
|
+
const evicted = [];
|
|
4210
|
+
const refusal = groupCheck(request, evicted);
|
|
4211
|
+
if (refusal) return refuse(refusal);
|
|
4212
|
+
const score = request.priority * request.gain;
|
|
4213
|
+
const remaining = voices.filter((voice) => !evicted.includes(voice.id));
|
|
4214
|
+
if (remaining.length >= maxVoices) {
|
|
4215
|
+
const lowest = remaining.reduce((low, voice) => voice.score < low.score ? voice : low);
|
|
4216
|
+
if (score <= lowest.score) return refuse("global_full");
|
|
4217
|
+
evicted.push(lowest.id);
|
|
4218
|
+
dropped += 1;
|
|
4219
|
+
}
|
|
4220
|
+
const group = request.group === void 0 ? void 0 : groups.get(request.group);
|
|
4221
|
+
if (group?.cooldown !== void 0) budgets.set(request.group, group.cooldown);
|
|
4222
|
+
const id = nextId++;
|
|
4223
|
+
voices = [...voices.filter((voice) => !evicted.includes(voice.id)), { id, ...request.group === void 0 ? {} : { group: request.group }, score, remaining: request.length }];
|
|
4224
|
+
peak = Math.max(peak, voices.length);
|
|
4225
|
+
return { accepted: true, id, evicted };
|
|
4226
|
+
},
|
|
4227
|
+
release(id) {
|
|
4228
|
+
voices = voices.filter((voice) => voice.id !== id);
|
|
4229
|
+
},
|
|
4230
|
+
setLength(id, seconds) {
|
|
4231
|
+
const voice = voices.find((entry) => entry.id === id);
|
|
4232
|
+
if (voice) voice.remaining = seconds;
|
|
4233
|
+
},
|
|
4234
|
+
advance(dt) {
|
|
4235
|
+
for (const [group, budget] of budgets) budgets.set(group, budget - dt);
|
|
4236
|
+
const ended = [];
|
|
4237
|
+
for (const voice of voices) {
|
|
4238
|
+
voice.remaining -= dt;
|
|
4239
|
+
if (voice.remaining <= 0) ended.push(voice.id);
|
|
4240
|
+
}
|
|
4241
|
+
if (ended.length > 0) voices = voices.filter((voice) => voice.remaining > 0);
|
|
4242
|
+
return ended;
|
|
4243
|
+
},
|
|
4244
|
+
setGroup(id, group) {
|
|
4245
|
+
if (group) groups.set(id, group);
|
|
4246
|
+
else groups.delete(id);
|
|
4247
|
+
},
|
|
4248
|
+
stats: () => ({ active: voices.length, peak, dropped, maxVoices })
|
|
4249
|
+
};
|
|
4250
|
+
}
|
|
4251
|
+
var DEFAULT_TAIL = 0.05;
|
|
4252
|
+
var DEFAULT_NOISE_SECONDS = 1.5;
|
|
4253
|
+
var OCCLUSION_GAIN = 0.5;
|
|
4254
|
+
var OCCLUSION_CUTOFF = 0.6;
|
|
4255
|
+
function isParamRef(range2) {
|
|
4256
|
+
return typeof range2 === "object" && !Array.isArray(range2);
|
|
4257
|
+
}
|
|
4258
|
+
function draw(range2, scope) {
|
|
4259
|
+
if (typeof range2 === "number") return range2;
|
|
4260
|
+
if (isParamRef(range2)) {
|
|
4261
|
+
const value = scope.params[range2.param];
|
|
4262
|
+
if (value === void 0) throw new Error(`game-audio: the recipe reads the param "${range2.param}", which was not given`);
|
|
4263
|
+
return value * (range2.scale ?? 1) + (range2.add ?? 0);
|
|
4264
|
+
}
|
|
4265
|
+
const [min2, max2] = range2;
|
|
4266
|
+
return min2 + scope.random() * (max2 - min2);
|
|
4267
|
+
}
|
|
4268
|
+
function drawTime(time, scope) {
|
|
4269
|
+
if (time === "duration" || typeof time === "string" && time.endsWith("d")) return time;
|
|
4270
|
+
return draw(time, scope);
|
|
4271
|
+
}
|
|
4272
|
+
function timeOf(time, duration) {
|
|
4273
|
+
if (typeof time === "number") return time;
|
|
4274
|
+
if (time === "duration") return duration;
|
|
4275
|
+
return Number(time.slice(0, -1)) * duration;
|
|
4276
|
+
}
|
|
4277
|
+
function drawAutomation(automation, scope) {
|
|
4278
|
+
const pending = {};
|
|
4279
|
+
for (const key of Object.keys(automation)) {
|
|
4280
|
+
if (key === "set" && "set" in automation) pending.value = draw(automation.set, scope);
|
|
4281
|
+
else if (key === "from" && "from" in automation) pending.from = draw(automation.from, scope);
|
|
4282
|
+
else if (key === "to" && automation.to !== void 0) pending.to = automation.to === "from" ? pending.from ?? pending.value ?? 0 : draw(automation.to, scope);
|
|
4283
|
+
else if (key === "curve") pending.curve = automation.curve;
|
|
4284
|
+
else if (key === "over" && automation.over !== void 0) pending.over = drawTime(automation.over, scope);
|
|
4285
|
+
}
|
|
4286
|
+
return pending;
|
|
4287
|
+
}
|
|
4288
|
+
function finishAutomation(pending, duration) {
|
|
4289
|
+
if (pending.from !== void 0 && pending.to === void 0) return { from: pending.from };
|
|
4290
|
+
if (pending.from !== void 0) return { from: pending.from, to: pending.to, curve: pending.curve ?? "linear", at: timeOf(pending.over ?? "duration", duration) };
|
|
4291
|
+
if (pending.to === void 0 || pending.over === void 0) return { value: pending.value };
|
|
4292
|
+
return { value: pending.value, to: pending.to, curve: pending.curve ?? "linear", at: timeOf(pending.over, duration) };
|
|
4293
|
+
}
|
|
4294
|
+
var startFrequency = (automation) => automation.from ?? automation.value ?? 0;
|
|
4295
|
+
function drawFilter(spec, scope) {
|
|
4296
|
+
const pending = { spec, frequency: {} };
|
|
4297
|
+
for (const key of Object.keys(spec)) {
|
|
4298
|
+
if (key === "frequency") pending.frequency = drawAutomation(spec.frequency, scope);
|
|
4299
|
+
else if (key === "q" && spec.q !== void 0) pending.q = draw(spec.q, scope);
|
|
4300
|
+
else if (key === "gain" && spec.gain !== void 0) pending.gain = draw(spec.gain, scope);
|
|
4301
|
+
else if (key === "level" && spec.level !== void 0) pending.level = draw(spec.level, scope);
|
|
4302
|
+
}
|
|
4303
|
+
return pending;
|
|
4304
|
+
}
|
|
4305
|
+
function drawModulator(modulator, scope) {
|
|
4306
|
+
const pending = { modulator, rate: 0 };
|
|
4307
|
+
for (const key of Object.keys(modulator)) {
|
|
4308
|
+
if (key === "rate") pending.rate = draw(modulator.rate, scope);
|
|
4309
|
+
else if (key === "depth" && !(typeof modulator.depth === "object" && "scaleOf" in modulator.depth)) pending.depth = draw(modulator.depth, scope);
|
|
4310
|
+
}
|
|
4311
|
+
return pending;
|
|
4312
|
+
}
|
|
4313
|
+
function drawVoice(source, scope) {
|
|
4314
|
+
const pending = { frequency: {}, vibratoRate: 0, formants: [], breath: { frequency: 0, q: source.breath.q, gain: 0 } };
|
|
4315
|
+
for (const key of Object.keys(source)) {
|
|
4316
|
+
if (key === "frequency") pending.frequency = drawAutomation(source.frequency, scope);
|
|
4317
|
+
else if (key === "vibrato") pending.vibratoRate = draw(source.vibrato.rate, scope);
|
|
4318
|
+
else if (key === "formants") pending.formants = source.formants.map((formant) => ({ frequency: draw(formant.frequency, scope), q: formant.q }));
|
|
4319
|
+
else if (key === "breath") pending.breath = { frequency: draw(source.breath.frequency, scope), q: source.breath.q, gain: draw(source.breath.gain, scope) };
|
|
4320
|
+
}
|
|
4321
|
+
return pending;
|
|
4322
|
+
}
|
|
4323
|
+
function drawLayer(layer2, scope) {
|
|
4324
|
+
const pending = { at: 0, gain: 1, filters: [], modulators: [] };
|
|
4325
|
+
for (const key of Object.keys(layer2)) {
|
|
4326
|
+
if (key === "at" && layer2.at !== void 0) pending.at = draw(layer2.at, scope);
|
|
4327
|
+
else if (key === "duration" && layer2.duration !== void 0) pending.duration = draw(layer2.duration, scope);
|
|
4328
|
+
else if (key === "gain" && layer2.gain !== void 0) pending.gain = draw(layer2.gain, scope);
|
|
4329
|
+
else if (key === "source" && layer2.source.kind === "osc") pending.automation = drawAutomation(layer2.source.frequency, scope);
|
|
4330
|
+
else if (key === "source" && layer2.source.kind === "formant_voice") pending.voice = drawVoice(layer2.source, scope);
|
|
4331
|
+
else if (key === "filters" && layer2.filters) pending.filters = layer2.filters.bank.map((spec) => drawFilter(spec, scope));
|
|
4332
|
+
else if (key === "modulators" && layer2.modulators) pending.modulators = layer2.modulators.map((modulator) => drawModulator(modulator, scope));
|
|
4333
|
+
}
|
|
4334
|
+
return pending;
|
|
4335
|
+
}
|
|
4336
|
+
function drawOffset(source, duration, scope, noiseSeconds) {
|
|
4337
|
+
if (source.kind === "formant_voice") return scope.random();
|
|
4338
|
+
if (source.kind !== "noise" || source.offset === void 0) return 0;
|
|
4339
|
+
if (typeof source.offset === "number") return source.offset;
|
|
4340
|
+
if (source.offset === "fit") return scope.random() * Math.max(0, noiseSeconds - duration);
|
|
4341
|
+
return scope.random() * (source.offsetScale ?? 1);
|
|
4342
|
+
}
|
|
4343
|
+
function finishSource(layer2, pending, duration, offset) {
|
|
4344
|
+
const source = layer2.source;
|
|
4345
|
+
if (source.kind === "noise") return { kind: "noise", offset, loop: source.loop === true };
|
|
4346
|
+
if (source.kind === "osc") return { kind: "osc", wave: source.wave, frequency: finishAutomation(pending.automation ?? {}, duration) };
|
|
4347
|
+
const voice = pending.voice;
|
|
4348
|
+
return {
|
|
4349
|
+
kind: "formant_voice",
|
|
4350
|
+
wave: source.wave,
|
|
4351
|
+
frequency: finishAutomation(voice.frequency, duration),
|
|
4352
|
+
vibrato: { rate: voice.vibratoRate, depth: startFrequency(voice.frequency) * source.vibrato.depth },
|
|
4353
|
+
formants: voice.formants,
|
|
4354
|
+
breath: { ...voice.breath, offset },
|
|
4355
|
+
mix: source.mix
|
|
4356
|
+
};
|
|
4357
|
+
}
|
|
4358
|
+
function finishModulator(pending, sourceFrequency) {
|
|
4359
|
+
const { modulator } = pending;
|
|
4360
|
+
const depth = typeof modulator.depth === "object" && "scaleOf" in modulator.depth ? sourceFrequency * modulator.depth.factor : pending.depth ?? 0;
|
|
4361
|
+
return { target: modulator.target, ...modulator.wave ? { wave: modulator.wave } : {}, rate: pending.rate, depth, ...modulator.base === void 0 ? {} : { base: modulator.base } };
|
|
4362
|
+
}
|
|
4363
|
+
function finishEnvelope(envelope2, duration, level) {
|
|
4364
|
+
return envelope2.map(([time, value, curve, fixed2]) => ({ time: timeOf(time, duration), value: fixed2 === "fixed" ? value : value * level, curve }));
|
|
4365
|
+
}
|
|
4366
|
+
function planLayer(layer2, scope, defaults, levelInEnvelope, soundLevel) {
|
|
4367
|
+
const pending = drawLayer(layer2, scope);
|
|
4368
|
+
const duration = pending.duration ?? defaults.duration;
|
|
4369
|
+
if (duration === void 0) throw new Error("game-audio: a layer needs a duration (its own or the recipe\u2019s)");
|
|
4370
|
+
const offset = drawOffset(layer2.source, duration, scope, defaults.noiseSeconds);
|
|
4371
|
+
const level = pending.gain * soundLevel;
|
|
4372
|
+
const sourceFrequency = layer2.source.kind === "noise" ? 0 : startFrequency(pending.automation ?? pending.voice?.frequency ?? {});
|
|
4373
|
+
const planned = {
|
|
4374
|
+
at: pending.at,
|
|
4375
|
+
duration,
|
|
4376
|
+
tail: layer2.tail ?? defaults.tail ?? DEFAULT_TAIL,
|
|
4377
|
+
level,
|
|
4378
|
+
...layer2.wet === void 0 ? {} : { wet: layer2.wet },
|
|
4379
|
+
source: finishSource(layer2, pending, duration, offset),
|
|
4380
|
+
...layer2.filters ? {
|
|
4381
|
+
filters: {
|
|
4382
|
+
mode: layer2.filters.mode,
|
|
4383
|
+
bank: pending.filters.map(({ spec, frequency, q, gain, level: filterLevel }) => ({
|
|
4384
|
+
type: spec.type,
|
|
4385
|
+
frequency: finishAutomation(frequency, duration),
|
|
4386
|
+
...q === void 0 ? {} : { q },
|
|
4387
|
+
...gain === void 0 ? {} : { gain },
|
|
4388
|
+
...filterLevel === void 0 ? {} : { level: filterLevel }
|
|
4389
|
+
}))
|
|
4390
|
+
}
|
|
4391
|
+
} : {},
|
|
4392
|
+
modulators: pending.modulators.map((modulator) => finishModulator(modulator, sourceFrequency)),
|
|
4393
|
+
envelope: finishEnvelope(layer2.envelope, duration, levelInEnvelope ? level : 1)
|
|
4394
|
+
};
|
|
4395
|
+
if (!layer2.repeat) return [planned];
|
|
4396
|
+
return Array.from({ length: layer2.repeat.count }, (_, index) => ({ ...planned, at: planned.at + index * (layer2.repeat?.spacing ?? 0) }));
|
|
4397
|
+
}
|
|
4398
|
+
function scaleAutomation(automation, divisor) {
|
|
4399
|
+
const scale2 = (value) => value === void 0 ? void 0 : value / divisor;
|
|
4400
|
+
const scaled2 = { ...automation };
|
|
4401
|
+
for (const key of ["value", "from", "to"]) if (automation[key] !== void 0) scaled2[key] = scale2(automation[key]);
|
|
4402
|
+
return scaled2;
|
|
4403
|
+
}
|
|
4404
|
+
function muffle(layer2, divisor) {
|
|
4405
|
+
if (divisor === 1 || !layer2.filters) return layer2;
|
|
4406
|
+
const bank = layer2.filters.bank.map((filter) => filter.type === "lowpass" ? { ...filter, frequency: scaleAutomation(filter.frequency, divisor) } : filter);
|
|
4407
|
+
return { ...layer2, filters: { ...layer2.filters, bank } };
|
|
4408
|
+
}
|
|
4409
|
+
var layerEnd = (layer2) => layer2.at + layer2.duration + layer2.tail;
|
|
4410
|
+
function planSound(recipe, input = {}) {
|
|
4411
|
+
const listener = input.listener ?? DEFAULT_LISTENER;
|
|
4412
|
+
const distance4 = input.distance ?? (input.position ? listenerDistance(listener, input.position, input.distanceMode) : 0);
|
|
4413
|
+
const occlusion = Math.min(1, Math.max(0, input.occlusion ?? 0));
|
|
4414
|
+
const order = recipe.order ?? "layered";
|
|
4415
|
+
const falloffLevel = falloffGain(recipe.falloff, input.volume ?? 1, distance4);
|
|
4416
|
+
const level = occlusion > 0 ? falloffLevel * (1 - OCCLUSION_GAIN * occlusion) : falloffLevel;
|
|
4417
|
+
const silent = isSilent(recipe.falloff, falloffLevel);
|
|
4418
|
+
const base = {
|
|
4419
|
+
id: recipe.id,
|
|
4420
|
+
bus: recipe.bus,
|
|
4421
|
+
order,
|
|
4422
|
+
...recipe.group === void 0 ? {} : { group: recipe.group },
|
|
4423
|
+
priority: recipe.priority ?? busPriority(recipe.bus),
|
|
4424
|
+
level,
|
|
4425
|
+
distance: distance4,
|
|
4426
|
+
...recipe.pan ? { pan: input.position ? panFor(listener, input.position) : 0 } : {}
|
|
4427
|
+
};
|
|
4428
|
+
if (silent) return recipe.keepSilent ? { ...base, silent: true, length: 0, layers: [] } : null;
|
|
4429
|
+
const scope = { random: input.random ?? createRng2(1), params: { ...input.params } };
|
|
4430
|
+
for (const [name, range2] of Object.entries(recipe.vars ?? {})) if (scope.params[name] === void 0) scope.params[name] = draw(range2, scope);
|
|
4431
|
+
const defaults = {
|
|
4432
|
+
...recipe.duration === void 0 ? {} : { duration: draw(recipe.duration, scope) },
|
|
4433
|
+
...recipe.tail === void 0 ? {} : { tail: recipe.tail },
|
|
4434
|
+
noiseSeconds: input.noiseSeconds ?? DEFAULT_NOISE_SECONDS
|
|
4435
|
+
};
|
|
4436
|
+
const divisor = cutoffDivisor(recipe.falloff, distance4) / (1 - OCCLUSION_CUTOFF * occlusion);
|
|
4437
|
+
const layers = recipe.layers.flatMap((layer2) => planLayer(layer2, scope, defaults, order !== "layered", level)).map((layer2) => muffle(layer2, divisor));
|
|
4438
|
+
return { ...base, silent: false, length: Math.max(0, ...layers.map(layerEnd)), layers };
|
|
4439
|
+
}
|
|
4440
|
+
var LOOP_START_ABOVE = 2e-3;
|
|
4441
|
+
var LOOP_SMOOTHING = { volume: 0.15, frequency: 0.08 };
|
|
4442
|
+
function createLoopState(recipe, open = false) {
|
|
4443
|
+
const startAbove = recipe.startAbove ?? LOOP_START_ABOVE;
|
|
4444
|
+
let created = open;
|
|
4445
|
+
let volume = recipe.volume ?? 0;
|
|
4446
|
+
return {
|
|
4447
|
+
set(state) {
|
|
4448
|
+
volume = state.volume ?? volume;
|
|
4449
|
+
const create = !created;
|
|
4450
|
+
if (create && !(volume >= startAbove)) return null;
|
|
4451
|
+
created = true;
|
|
4452
|
+
const frequency = recipe.source.kind === "osc" && state.frequency ? state.frequency : void 0;
|
|
4453
|
+
return { create, volume, ...frequency === void 0 ? {} : { frequency } };
|
|
4454
|
+
},
|
|
4455
|
+
get created() {
|
|
4456
|
+
return created;
|
|
4457
|
+
},
|
|
4458
|
+
reset() {
|
|
4459
|
+
created = false;
|
|
4460
|
+
}
|
|
4461
|
+
};
|
|
4462
|
+
}
|
|
4463
|
+
var mix_profiles_default = [
|
|
4464
|
+
{
|
|
4465
|
+
id: "game",
|
|
4466
|
+
master: 0.5,
|
|
4467
|
+
noise: { seconds: 1.5, create: "lazy" },
|
|
4468
|
+
sources: ["jogo:L62077-62081 master 0.5 to the output", "jogo:L62082-62088 noise 1.5 s made by the first sound that needs it"]
|
|
4469
|
+
},
|
|
4470
|
+
{
|
|
4471
|
+
id: "art_scene",
|
|
4472
|
+
master: 0.85,
|
|
4473
|
+
compressor: { threshold: -12, ratio: 5 },
|
|
4474
|
+
reverb: { seconds: 2.2, decay: 3.4, wet: 0.4 },
|
|
4475
|
+
noise: { seconds: 2, create: "eager" },
|
|
4476
|
+
sources: [
|
|
4477
|
+
"cena:L1308 compressor -12 dB, ratio 5",
|
|
4478
|
+
"cena:L1309 master 0.85",
|
|
4479
|
+
"cena:L1310 convolver, impulse 2.2 s of (1 - i/len)^3.4",
|
|
4480
|
+
"cena:L1311 wet 0.4",
|
|
4481
|
+
"cena:L1312 noise 2 s made with the chain"
|
|
4482
|
+
]
|
|
4483
|
+
}
|
|
4484
|
+
];
|
|
4485
|
+
var MIX_PROFILES = Object.fromEntries(
|
|
4486
|
+
mix_profiles_default.map((profile) => [profile.id, profile])
|
|
4487
|
+
);
|
|
4488
|
+
var DEFAULT_MIX = "art_scene";
|
|
4489
|
+
function resolveMix(mix, profiles = MIX_PROFILES) {
|
|
4490
|
+
if (typeof mix !== "string") return mix;
|
|
4491
|
+
const profile = profiles[mix];
|
|
4492
|
+
if (!profile) throw new Error(`game-audio: unknown mix profile "${mix}" (known: ${Object.keys(profiles).join(", ")})`);
|
|
4493
|
+
return profile;
|
|
4494
|
+
}
|
|
4495
|
+
function busGain(setting) {
|
|
4496
|
+
if (!setting) return 1;
|
|
4497
|
+
return setting.muted ? 0 : setting.gain ?? 1;
|
|
4498
|
+
}
|
|
4499
|
+
function isPlainObject2(value) {
|
|
4500
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4501
|
+
}
|
|
4502
|
+
function mergeRecords(base, patch) {
|
|
4503
|
+
const merged = { ...base };
|
|
4504
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
4505
|
+
if (value === void 0) continue;
|
|
4506
|
+
const current = merged[key];
|
|
4507
|
+
merged[key] = isPlainObject2(current) && isPlainObject2(value) ? mergeRecords(current, value) : value;
|
|
4508
|
+
}
|
|
4509
|
+
return merged;
|
|
4510
|
+
}
|
|
4511
|
+
function createSoundRegistry(banks = []) {
|
|
4512
|
+
const sounds = /* @__PURE__ */ new Map();
|
|
4513
|
+
const loops = /* @__PURE__ */ new Map();
|
|
4514
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4515
|
+
const registry = {
|
|
4516
|
+
register(bank) {
|
|
4517
|
+
for (const sound of bank.sounds ?? []) {
|
|
4518
|
+
sounds.set(sound.id, sound);
|
|
4519
|
+
owners.set(sound.id, bank.id);
|
|
4520
|
+
}
|
|
4521
|
+
for (const loop of bank.loops ?? []) {
|
|
4522
|
+
loops.set(loop.id, loop);
|
|
4523
|
+
owners.set(loop.id, bank.id);
|
|
4524
|
+
}
|
|
4525
|
+
},
|
|
4526
|
+
patch(id, patch) {
|
|
4527
|
+
const sound = sounds.get(id);
|
|
4528
|
+
if (sound) sounds.set(id, mergeRecords(sound, patch));
|
|
4529
|
+
const loop = loops.get(id);
|
|
4530
|
+
if (loop) loops.set(id, mergeRecords(loop, patch));
|
|
4531
|
+
},
|
|
4532
|
+
remove(id) {
|
|
4533
|
+
sounds.delete(id);
|
|
4534
|
+
loops.delete(id);
|
|
4535
|
+
owners.delete(id);
|
|
4536
|
+
},
|
|
4537
|
+
sound: (id) => sounds.get(id),
|
|
4538
|
+
loop: (id) => loops.get(id),
|
|
4539
|
+
soundIds: () => [...sounds.keys()],
|
|
4540
|
+
loopIds: () => [...loops.keys()],
|
|
4541
|
+
bankOf: (id) => owners.get(id)
|
|
4542
|
+
};
|
|
4543
|
+
for (const bank of banks) registry.register(bank);
|
|
4544
|
+
return registry;
|
|
4545
|
+
}
|
|
4546
|
+
var game_sfx_default = {
|
|
4547
|
+
sources: ["jogo:L62089-62107"],
|
|
4548
|
+
note: "The game's sfx table P: [lowpass cutoff Hz, peak gain, seconds]. Played as a noise burst with the game's distance curve (falloff game_sfx) and a 0.05 s tail. Team names in `team`.",
|
|
4549
|
+
classes: [
|
|
4550
|
+
{ id: "pistol", team: "pistola", bus: "weapons", cutoff: 1800, gain: 0.9, seconds: 0.18 },
|
|
4551
|
+
{ id: "shotgun", team: "escopeta", bus: "weapons", cutoff: 900, gain: 1.2, seconds: 0.35 },
|
|
4552
|
+
{ id: "rifle", team: "fuzil", bus: "weapons", cutoff: 1400, gain: 1.1, seconds: 0.28 },
|
|
4553
|
+
{ id: "long_rifle", team: "rifle", bus: "weapons", cutoff: 1100, gain: 1.3, seconds: 0.45 },
|
|
4554
|
+
{ id: "impact", team: "impacto", bus: "world", cutoff: 2500, gain: 0.25, seconds: 0.05 },
|
|
4555
|
+
{ id: "flesh", team: "carne", bus: "world", cutoff: 600, gain: 0.35, seconds: 0.08 },
|
|
4556
|
+
{ id: "step", team: "passo", bus: "world", cutoff: 400, gain: 0.08, seconds: 0.05 },
|
|
4557
|
+
{ id: "glass", team: "vidro", bus: "world", cutoff: 5e3, gain: 0.5, seconds: 0.3 },
|
|
4558
|
+
{ id: "wood", team: "madeira", bus: "world", cutoff: 700, gain: 0.5, seconds: 0.2 },
|
|
4559
|
+
{ id: "reload", team: "recarga", bus: "weapons", cutoff: 3e3, gain: 0.15, seconds: 0.06 },
|
|
4560
|
+
{ id: "empty", team: "vazio", bus: "weapons", cutoff: 4e3, gain: 0.12, seconds: 0.03 },
|
|
4561
|
+
{ id: "explosion", team: "explosao", bus: "world", cutoff: 200, gain: 1.6, seconds: 1.4 }
|
|
4562
|
+
],
|
|
4563
|
+
fallback: { id: "unknown", team: "(any other name)", bus: "world", cutoff: 1e3, gain: 0.5, seconds: 0.1 }
|
|
4564
|
+
};
|
|
4565
|
+
var GAME_SFX = game_sfx_default;
|
|
4566
|
+
var GAME_SFX_CLASSES = GAME_SFX.classes;
|
|
4567
|
+
var GAME_SFX_FALLBACK = GAME_SFX.fallback;
|
|
4568
|
+
function noiseClassRecipe(row, id = `sfx_${row.id}`) {
|
|
4569
|
+
return {
|
|
4570
|
+
id,
|
|
4571
|
+
bus: row.bus,
|
|
4572
|
+
order: "burst",
|
|
4573
|
+
duration: row.seconds,
|
|
4574
|
+
tail: 0.05,
|
|
4575
|
+
layers: [
|
|
4576
|
+
{
|
|
4577
|
+
source: { kind: "noise", offset: "random" },
|
|
4578
|
+
filters: { mode: "serial", bank: [{ type: "lowpass", frequency: { set: row.cutoff } }] },
|
|
4579
|
+
envelope: [
|
|
4580
|
+
[0, row.gain, "set"],
|
|
4581
|
+
["1d", 1e-3, "exp", "fixed"]
|
|
4582
|
+
]
|
|
4583
|
+
}
|
|
4584
|
+
],
|
|
4585
|
+
falloff: DISTANCE_CURVES.game_sfx,
|
|
4586
|
+
sources: [...GAME_SFX.sources.map((source) => `${source} ${row.team}`)]
|
|
4587
|
+
};
|
|
4588
|
+
}
|
|
4589
|
+
var GAME_SFX_BANK = { id: "game_sfx", sounds: [...GAME_SFX_CLASSES, GAME_SFX_FALLBACK].map((row) => noiseClassRecipe(row)) };
|
|
4590
|
+
function fillNoise(random, length3) {
|
|
4591
|
+
const samples = new Float32Array(length3);
|
|
4592
|
+
for (let index = 0; index < length3; index++) samples[index] = random() * 2 - 1;
|
|
4593
|
+
return samples;
|
|
4594
|
+
}
|
|
4595
|
+
function fillImpulse(random, options) {
|
|
4596
|
+
const size = Math.floor(options.length);
|
|
4597
|
+
return Array.from({ length: options.channels }, () => {
|
|
4598
|
+
const samples = new Float32Array(size);
|
|
4599
|
+
for (let index = 0; index < options.length; index++) {
|
|
4600
|
+
const value = (random() * 2 - 1) * Math.exp(options.decay * Math.log(1 - index / options.length));
|
|
4601
|
+
if (index < size) samples[index] = value;
|
|
4602
|
+
}
|
|
4603
|
+
return samples;
|
|
4604
|
+
});
|
|
4605
|
+
}
|
|
4606
|
+
function bufferOf(context, channels, requestedLength) {
|
|
4607
|
+
const buffer = context.createBuffer(channels.length, requestedLength, context.sampleRate);
|
|
4608
|
+
channels.forEach((samples, channel) => buffer.getChannelData(channel).set(samples));
|
|
4609
|
+
return buffer;
|
|
4610
|
+
}
|
|
4611
|
+
function buildChain(context, profile, random) {
|
|
4612
|
+
let compressor;
|
|
4613
|
+
if (profile.compressor) {
|
|
4614
|
+
compressor = context.createDynamicsCompressor();
|
|
4615
|
+
compressor.threshold.value = profile.compressor.threshold;
|
|
4616
|
+
compressor.ratio.value = profile.compressor.ratio;
|
|
4617
|
+
if (profile.compressor.knee !== void 0) compressor.knee.value = profile.compressor.knee;
|
|
4618
|
+
if (profile.compressor.attack !== void 0) compressor.attack.value = profile.compressor.attack;
|
|
4619
|
+
if (profile.compressor.release !== void 0) compressor.release.value = profile.compressor.release;
|
|
4620
|
+
compressor.connect(context.destination);
|
|
4621
|
+
}
|
|
4622
|
+
const master = context.createGain();
|
|
4623
|
+
master.gain.value = profile.master;
|
|
4624
|
+
master.connect(compressor ?? context.destination);
|
|
4625
|
+
if (!profile.reverb) {
|
|
4626
|
+
return {
|
|
4627
|
+
master,
|
|
4628
|
+
disconnect: () => {
|
|
4629
|
+
master.disconnect();
|
|
4630
|
+
compressor?.disconnect();
|
|
4631
|
+
}
|
|
4632
|
+
};
|
|
4633
|
+
}
|
|
4634
|
+
const convolver = context.createConvolver();
|
|
4635
|
+
const length3 = context.sampleRate * profile.reverb.seconds;
|
|
4636
|
+
convolver.buffer = bufferOf(context, fillImpulse(random, { channels: 2, length: length3, decay: profile.reverb.decay }), length3);
|
|
4637
|
+
const wet = context.createGain();
|
|
4638
|
+
wet.gain.value = profile.reverb.wet;
|
|
4639
|
+
wet.connect(convolver);
|
|
4640
|
+
convolver.connect(master);
|
|
4641
|
+
return {
|
|
4642
|
+
master,
|
|
4643
|
+
wet,
|
|
4644
|
+
disconnect: () => {
|
|
4645
|
+
wet.disconnect();
|
|
4646
|
+
convolver.disconnect();
|
|
4647
|
+
master.disconnect();
|
|
4648
|
+
compressor?.disconnect();
|
|
4649
|
+
}
|
|
4650
|
+
};
|
|
4651
|
+
}
|
|
4652
|
+
function globalContext() {
|
|
4653
|
+
const scope = globalThis;
|
|
4654
|
+
const Constructor = scope.AudioContext ?? scope.webkitAudioContext;
|
|
4655
|
+
return Constructor ? new Constructor() : null;
|
|
4656
|
+
}
|
|
4657
|
+
function unlockContext(context) {
|
|
4658
|
+
if (context.state === "suspended") void context.resume();
|
|
4659
|
+
}
|
|
4660
|
+
function assign(param2, automation, time) {
|
|
4661
|
+
if (automation.value !== void 0) param2.value = automation.value;
|
|
4662
|
+
if (automation.from !== void 0) param2.setValueAtTime(automation.from, time);
|
|
4663
|
+
}
|
|
4664
|
+
function ramp(param2, curve, value, at3) {
|
|
4665
|
+
if (curve === "set") param2.setValueAtTime(value, at3);
|
|
4666
|
+
else if (curve === "exp") param2.exponentialRampToValueAtTime(value, at3);
|
|
4667
|
+
else param2.linearRampToValueAtTime(value, at3);
|
|
4668
|
+
}
|
|
4669
|
+
function rampTo(param2, automation, time) {
|
|
4670
|
+
if (automation.to !== void 0 && automation.at !== void 0) ramp(param2, automation.curve ?? "linear", automation.to, time + automation.at);
|
|
4671
|
+
}
|
|
4672
|
+
function applyEnvelope(param2, points, time) {
|
|
4673
|
+
for (const point of points) ramp(param2, point.curve, point.value, time + point.time);
|
|
4674
|
+
}
|
|
4675
|
+
function noiseSource(target, loop) {
|
|
4676
|
+
const source = target.context.createBufferSource();
|
|
4677
|
+
source.buffer = target.noise();
|
|
4678
|
+
if (loop) source.loop = true;
|
|
4679
|
+
return source;
|
|
4680
|
+
}
|
|
4681
|
+
function filterNode(context, filter, time) {
|
|
4682
|
+
const node = context.createBiquadFilter();
|
|
4683
|
+
node.type = filter.type;
|
|
4684
|
+
assign(node.frequency, filter.frequency, time);
|
|
4685
|
+
if (filter.q !== void 0) node.Q.value = filter.q;
|
|
4686
|
+
if (filter.gain !== void 0) node.gain.value = filter.gain;
|
|
4687
|
+
return node;
|
|
4688
|
+
}
|
|
4689
|
+
function gainNode(context, value) {
|
|
4690
|
+
const node = context.createGain();
|
|
4691
|
+
node.gain.value = value;
|
|
4692
|
+
return node;
|
|
4693
|
+
}
|
|
4694
|
+
function voiceOf(sources) {
|
|
4695
|
+
return {
|
|
4696
|
+
sources,
|
|
4697
|
+
stop() {
|
|
4698
|
+
for (const source of sources) {
|
|
4699
|
+
try {
|
|
4700
|
+
source.stop();
|
|
4701
|
+
} catch {
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
};
|
|
4706
|
+
}
|
|
4707
|
+
var fixed = (range2) => typeof range2 === "number" ? range2 : 0;
|
|
4708
|
+
var fixedFrequency = (automation) => fixed("set" in automation ? automation.set : automation.from);
|
|
4709
|
+
function sourceOf(recipe, target) {
|
|
4710
|
+
if (recipe.source.kind === "noise") return { source: noiseSource(target, true) };
|
|
4711
|
+
const oscillator = target.context.createOscillator();
|
|
4712
|
+
oscillator.type = recipe.source.wave;
|
|
4713
|
+
if (recipe.source.frequency) oscillator.frequency.value = fixedFrequency(recipe.source.frequency);
|
|
4714
|
+
return { source: oscillator, frequency: oscillator.frequency };
|
|
4715
|
+
}
|
|
4716
|
+
function stopAll(sources) {
|
|
4717
|
+
return () => {
|
|
4718
|
+
for (const source of sources) {
|
|
4719
|
+
try {
|
|
4720
|
+
source.stop();
|
|
4721
|
+
} catch {
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
};
|
|
4725
|
+
}
|
|
4726
|
+
function realizeGameLoop(recipe, target) {
|
|
4727
|
+
const { context } = target;
|
|
4728
|
+
const gain = context.createGain();
|
|
4729
|
+
const filters = (recipe.filters?.bank ?? []).map(() => context.createBiquadFilter());
|
|
4730
|
+
gain.gain.value = 0;
|
|
4731
|
+
const last = filters.reduce((previous, filter) => previous ? previous.connect(filter) : filter, void 0);
|
|
4732
|
+
last?.connect(gain);
|
|
4733
|
+
gain.connect(target.output);
|
|
4734
|
+
const { source, frequency } = sourceOf(recipe, target);
|
|
4735
|
+
recipe.filters?.bank.forEach((spec, index) => {
|
|
4736
|
+
const filter = filters[index];
|
|
4737
|
+
filter.type = spec.type;
|
|
4738
|
+
filter.frequency.value = fixedFrequency(spec.frequency);
|
|
4739
|
+
if (spec.q !== void 0) filter.Q.value = fixed(spec.q);
|
|
4740
|
+
});
|
|
4741
|
+
const sources = [source];
|
|
4742
|
+
for (const modulator of recipe.modulators ?? []) {
|
|
4743
|
+
const lfo = context.createOscillator();
|
|
4744
|
+
const depth = context.createGain();
|
|
4745
|
+
const stage = context.createGain();
|
|
4746
|
+
if (modulator.wave) lfo.type = modulator.wave;
|
|
4747
|
+
lfo.frequency.value = fixed(modulator.rate);
|
|
4748
|
+
depth.gain.value = fixed(modulator.depth);
|
|
4749
|
+
stage.gain.value = modulator.base ?? 0;
|
|
4750
|
+
lfo.connect(depth);
|
|
4751
|
+
depth.connect(stage.gain);
|
|
4752
|
+
last?.disconnect();
|
|
4753
|
+
last?.connect(stage);
|
|
4754
|
+
stage.connect(gain);
|
|
4755
|
+
lfo.start();
|
|
4756
|
+
sources.push(lfo);
|
|
4757
|
+
}
|
|
4758
|
+
source.connect(filters[0] ?? gain);
|
|
4759
|
+
source.start();
|
|
4760
|
+
return { gain, source, ...frequency ? { frequency } : {}, stop: stopAll(sources) };
|
|
4761
|
+
}
|
|
4762
|
+
function realizeChainLoop(recipe, target, volume) {
|
|
4763
|
+
const { context } = target;
|
|
4764
|
+
const { source, frequency } = sourceOf(recipe, target);
|
|
4765
|
+
const filters = (recipe.filters?.bank ?? []).map(
|
|
4766
|
+
(spec) => filterNode(context, { type: spec.type, frequency: { value: fixedFrequency(spec.frequency) }, ...spec.q === void 0 ? {} : { q: fixed(spec.q) } }, target.time)
|
|
4767
|
+
);
|
|
4768
|
+
const gain = gainNode(context, volume);
|
|
4769
|
+
const lfos = (recipe.modulators ?? []).map((modulator) => {
|
|
4770
|
+
const lfo = context.createOscillator();
|
|
4771
|
+
if (modulator.wave) lfo.type = modulator.wave;
|
|
4772
|
+
lfo.frequency.value = fixed(modulator.rate);
|
|
4773
|
+
const depth = gainNode(context, fixed(modulator.depth));
|
|
4774
|
+
lfo.connect(depth);
|
|
4775
|
+
depth.connect(gain.gain);
|
|
4776
|
+
return lfo;
|
|
4777
|
+
});
|
|
4778
|
+
let tail = source;
|
|
4779
|
+
for (const filter of filters) tail = tail.connect(filter);
|
|
4780
|
+
tail.connect(gain);
|
|
4781
|
+
gain.connect(target.output);
|
|
4782
|
+
source.start();
|
|
4783
|
+
for (const lfo of lfos) lfo.start();
|
|
4784
|
+
return { gain, source, ...frequency ? { frequency } : {}, stop: stopAll([source, ...lfos]) };
|
|
4785
|
+
}
|
|
4786
|
+
var INERT = { set() {
|
|
4787
|
+
}, stop() {
|
|
4788
|
+
} };
|
|
4789
|
+
function createLoopHandle(recipe, host, volume) {
|
|
4790
|
+
if (!recipe) return INERT;
|
|
4791
|
+
const loopRecipe = recipe;
|
|
4792
|
+
const isChain = loopRecipe.order === "chain_loop";
|
|
4793
|
+
const opening = volume ?? loopRecipe.volume ?? 0;
|
|
4794
|
+
const smoothing = loopRecipe.smoothing ?? LOOP_SMOOTHING;
|
|
4795
|
+
let state = createLoopState(loopRecipe);
|
|
4796
|
+
let nodes;
|
|
4797
|
+
const entry = {
|
|
4798
|
+
get created() {
|
|
4799
|
+
return nodes !== void 0;
|
|
4800
|
+
},
|
|
4801
|
+
rewire() {
|
|
4802
|
+
nodes?.gain.disconnect();
|
|
4803
|
+
nodes?.gain.connect(host.output(loopRecipe.bus));
|
|
4804
|
+
},
|
|
4805
|
+
stop() {
|
|
4806
|
+
nodes?.stop();
|
|
4807
|
+
nodes?.gain.disconnect();
|
|
4808
|
+
nodes = void 0;
|
|
4809
|
+
state = createLoopState(loopRecipe);
|
|
4810
|
+
host.unregister(entry);
|
|
4811
|
+
}
|
|
4812
|
+
};
|
|
4813
|
+
const create = (context, startVolume) => {
|
|
4814
|
+
if (loopRecipe.source.kind === "noise") host.prepareNoise();
|
|
4815
|
+
const target = { context, output: host.output(loopRecipe.bus), noise: host.noise, time: context.currentTime };
|
|
4816
|
+
const made = isChain ? realizeChainLoop(loopRecipe, target, startVolume) : realizeGameLoop(loopRecipe, target);
|
|
4817
|
+
host.register(entry);
|
|
4818
|
+
return made;
|
|
4819
|
+
};
|
|
4820
|
+
const open = () => {
|
|
4821
|
+
const context = host.ready();
|
|
4822
|
+
if (!context) return;
|
|
4823
|
+
nodes = create(context, opening);
|
|
4824
|
+
state = createLoopState({ ...loopRecipe, volume: opening }, true);
|
|
4825
|
+
};
|
|
4826
|
+
if (isChain) open();
|
|
4827
|
+
return {
|
|
4828
|
+
set(next) {
|
|
4829
|
+
if (isChain && !nodes) open();
|
|
4830
|
+
const context = host.ready();
|
|
4831
|
+
if (!context) return;
|
|
4832
|
+
const action = state.set(next);
|
|
4833
|
+
if (!action) return;
|
|
4834
|
+
nodes ?? (nodes = create(context, 0));
|
|
4835
|
+
nodes.gain.gain.setTargetAtTime(action.volume, context.currentTime, smoothing.volume);
|
|
4836
|
+
if (action.frequency !== void 0 && nodes.frequency) nodes.frequency.setTargetAtTime(action.frequency, context.currentTime, smoothing.frequency);
|
|
4837
|
+
},
|
|
4838
|
+
stop: entry.stop
|
|
4839
|
+
};
|
|
4840
|
+
}
|
|
4841
|
+
function createNoiseStore(random) {
|
|
4842
|
+
const store = {
|
|
4843
|
+
random,
|
|
4844
|
+
reset() {
|
|
4845
|
+
store.samples = void 0;
|
|
4846
|
+
store.buffer = void 0;
|
|
4847
|
+
}
|
|
4848
|
+
};
|
|
4849
|
+
return store;
|
|
4850
|
+
}
|
|
4851
|
+
function prepareNoise(store, context, seconds) {
|
|
4852
|
+
store.samples ?? (store.samples = fillNoise(store.random, Math.floor(context.sampleRate * seconds)));
|
|
4853
|
+
return store.samples;
|
|
4854
|
+
}
|
|
4855
|
+
function noiseBuffer(store, context, seconds) {
|
|
4856
|
+
const samples = prepareNoise(store, context, seconds);
|
|
4857
|
+
store.buffer ?? (store.buffer = bufferOf(context, [samples], context.sampleRate * seconds));
|
|
4858
|
+
return store.buffer;
|
|
4859
|
+
}
|
|
4860
|
+
var end = (layer2, start) => start + layer2.duration + layer2.tail;
|
|
4861
|
+
var firstFilter = (layer2) => layer2.filters?.bank[0];
|
|
4862
|
+
var offsetOf = (layer2) => layer2.source.kind === "noise" ? layer2.source.offset : 0;
|
|
4863
|
+
function setFilter(node, filter, time) {
|
|
4864
|
+
if (!filter) return;
|
|
4865
|
+
node.type = filter.type;
|
|
4866
|
+
assign(node.frequency, filter.frequency, time);
|
|
4867
|
+
if (filter.q !== void 0) node.Q.value = filter.q;
|
|
4868
|
+
}
|
|
4869
|
+
function realizeBurst(plan, target) {
|
|
4870
|
+
const { context } = target;
|
|
4871
|
+
const layer2 = plan.layers[0];
|
|
4872
|
+
const time = target.time + layer2.at;
|
|
4873
|
+
const gain = context.createGain();
|
|
4874
|
+
gain.connect(target.output);
|
|
4875
|
+
const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
|
|
4876
|
+
const filter = context.createBiquadFilter();
|
|
4877
|
+
source.connect(filter);
|
|
4878
|
+
filter.connect(gain);
|
|
4879
|
+
setFilter(filter, firstFilter(layer2), time);
|
|
4880
|
+
applyEnvelope(gain.gain, layer2.envelope, time);
|
|
4881
|
+
source.start(time, offsetOf(layer2));
|
|
4882
|
+
source.stop(end(layer2, time));
|
|
4883
|
+
return voiceOf([source]);
|
|
4884
|
+
}
|
|
4885
|
+
function realizeScream(plan, target) {
|
|
4886
|
+
const { context } = target;
|
|
4887
|
+
const layer2 = plan.layers[0];
|
|
4888
|
+
const time = target.time + layer2.at;
|
|
4889
|
+
const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
|
|
4890
|
+
const gain = context.createGain();
|
|
4891
|
+
gain.connect(target.output);
|
|
4892
|
+
for (const band of layer2.filters?.bank ?? []) {
|
|
4893
|
+
const filter = context.createBiquadFilter();
|
|
4894
|
+
filter.type = band.type;
|
|
4895
|
+
if (band.q !== void 0) filter.Q.value = band.q;
|
|
4896
|
+
assign(filter.frequency, band.frequency, time);
|
|
4897
|
+
rampTo(filter.frequency, band.frequency, time);
|
|
4898
|
+
const level = context.createGain();
|
|
4899
|
+
level.gain.value = band.level ?? 1;
|
|
4900
|
+
source.connect(filter);
|
|
4901
|
+
filter.connect(level);
|
|
4902
|
+
level.connect(gain);
|
|
4903
|
+
}
|
|
4904
|
+
const sources = [source];
|
|
4905
|
+
const lfos = layer2.modulators.map((modulator) => {
|
|
4906
|
+
const lfo = context.createOscillator();
|
|
4907
|
+
const depth = context.createGain();
|
|
4908
|
+
if (modulator.wave) lfo.type = modulator.wave;
|
|
4909
|
+
lfo.frequency.value = modulator.rate;
|
|
4910
|
+
depth.gain.value = modulator.depth;
|
|
4911
|
+
lfo.connect(depth);
|
|
4912
|
+
depth.connect(gain.gain);
|
|
4913
|
+
return lfo;
|
|
4914
|
+
});
|
|
4915
|
+
applyEnvelope(gain.gain, layer2.envelope, time);
|
|
4916
|
+
source.start(time, offsetOf(layer2));
|
|
4917
|
+
source.stop(end(layer2, time));
|
|
4918
|
+
for (const lfo of lfos) {
|
|
4919
|
+
lfo.start(time);
|
|
4920
|
+
lfo.stop(end(layer2, time));
|
|
4921
|
+
sources.push(lfo);
|
|
4922
|
+
}
|
|
4923
|
+
return voiceOf(sources);
|
|
4924
|
+
}
|
|
4925
|
+
function realizeGroan(plan, target) {
|
|
4926
|
+
const { context } = target;
|
|
4927
|
+
const layer2 = plan.layers[0];
|
|
4928
|
+
const time = target.time + layer2.at;
|
|
4929
|
+
const oscillator = context.createOscillator();
|
|
4930
|
+
const gain = context.createGain();
|
|
4931
|
+
const filter = context.createBiquadFilter();
|
|
4932
|
+
const band = firstFilter(layer2);
|
|
4933
|
+
if (layer2.source.kind === "osc") oscillator.type = layer2.source.wave;
|
|
4934
|
+
if (band) {
|
|
4935
|
+
filter.type = band.type;
|
|
4936
|
+
if (band.q !== void 0) filter.Q.value = band.q;
|
|
4937
|
+
}
|
|
4938
|
+
if (layer2.source.kind === "osc") {
|
|
4939
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
4940
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
4941
|
+
}
|
|
4942
|
+
if (band) assign(filter.frequency, band.frequency, time);
|
|
4943
|
+
applyEnvelope(gain.gain, layer2.envelope, time);
|
|
4944
|
+
oscillator.connect(filter);
|
|
4945
|
+
filter.connect(gain);
|
|
4946
|
+
gain.connect(target.output);
|
|
4947
|
+
oscillator.start(time);
|
|
4948
|
+
oscillator.stop(end(layer2, time));
|
|
4949
|
+
return voiceOf([oscillator]);
|
|
4950
|
+
}
|
|
4951
|
+
function realizeBoom(plan, target) {
|
|
4952
|
+
const { context } = target;
|
|
4953
|
+
const layer2 = plan.layers[0];
|
|
4954
|
+
const time = target.time + layer2.at;
|
|
4955
|
+
const oscillator = context.createOscillator();
|
|
4956
|
+
const gain = context.createGain();
|
|
4957
|
+
if (layer2.source.kind === "osc") {
|
|
4958
|
+
oscillator.type = layer2.source.wave;
|
|
4959
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
4960
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
4961
|
+
}
|
|
4962
|
+
applyEnvelope(gain.gain, layer2.envelope, time);
|
|
4963
|
+
oscillator.connect(gain);
|
|
4964
|
+
gain.connect(target.output);
|
|
4965
|
+
oscillator.start(time);
|
|
4966
|
+
oscillator.stop(end(layer2, time));
|
|
4967
|
+
return voiceOf([oscillator]);
|
|
4968
|
+
}
|
|
4969
|
+
function realizeStatic(plan, target) {
|
|
4970
|
+
const { context } = target;
|
|
4971
|
+
const layer2 = plan.layers[0];
|
|
4972
|
+
const time = target.time + layer2.at;
|
|
4973
|
+
const gain = context.createGain();
|
|
4974
|
+
const source = context.createBufferSource();
|
|
4975
|
+
const filter = context.createBiquadFilter();
|
|
4976
|
+
source.buffer = target.noise();
|
|
4977
|
+
setFilter(filter, firstFilter(layer2), time);
|
|
4978
|
+
applyEnvelope(gain.gain, layer2.envelope, time);
|
|
4979
|
+
source.connect(filter);
|
|
4980
|
+
filter.connect(gain);
|
|
4981
|
+
gain.connect(target.output);
|
|
4982
|
+
source.start(time, offsetOf(layer2));
|
|
4983
|
+
source.stop(end(layer2, time));
|
|
4984
|
+
return voiceOf([source]);
|
|
4985
|
+
}
|
|
4986
|
+
function realizeChord(plan, target) {
|
|
4987
|
+
const { context } = target;
|
|
4988
|
+
const layers = plan.layers;
|
|
4989
|
+
const oscillators = layers.map(() => context.createOscillator());
|
|
4990
|
+
const gain = context.createGain();
|
|
4991
|
+
for (let index = layers.length - 1; index >= 0; index--) {
|
|
4992
|
+
const source = layers[index].source;
|
|
4993
|
+
if (source.kind === "osc") oscillators[index].type = source.wave;
|
|
4994
|
+
}
|
|
4995
|
+
layers.forEach((layer2, index) => {
|
|
4996
|
+
if (layer2.source.kind === "osc") assign(oscillators[index].frequency, layer2.source.frequency, target.time + layer2.at);
|
|
4997
|
+
});
|
|
4998
|
+
applyEnvelope(gain.gain, layers[0].envelope, target.time + layers[0].at);
|
|
4999
|
+
for (const oscillator of oscillators) oscillator.connect(gain);
|
|
5000
|
+
gain.connect(target.output);
|
|
5001
|
+
layers.forEach((layer2, index) => oscillators[index].start(target.time + layer2.at));
|
|
5002
|
+
layers.forEach((layer2, index) => oscillators[index].stop(end(layer2, target.time + layer2.at)));
|
|
5003
|
+
return voiceOf(oscillators);
|
|
5004
|
+
}
|
|
5005
|
+
function realizeCalls(plan, target) {
|
|
5006
|
+
const { context } = target;
|
|
5007
|
+
const gain = context.createGain();
|
|
5008
|
+
const filter = context.createBiquadFilter();
|
|
5009
|
+
if (plan.silent) return voiceOf([]);
|
|
5010
|
+
const band = firstFilter(plan.layers[0]);
|
|
5011
|
+
if (band) filter.type = band.type;
|
|
5012
|
+
filter.connect(gain);
|
|
5013
|
+
gain.connect(target.output);
|
|
5014
|
+
const oscillators = plan.layers.map((layer2) => {
|
|
5015
|
+
const time = target.time + layer2.at;
|
|
5016
|
+
const oscillator = context.createOscillator();
|
|
5017
|
+
if (layer2.source.kind === "osc") {
|
|
5018
|
+
oscillator.type = layer2.source.wave;
|
|
5019
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
5020
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
5021
|
+
}
|
|
5022
|
+
if (band) {
|
|
5023
|
+
assign(filter.frequency, band.frequency, time);
|
|
5024
|
+
if (band.q !== void 0) filter.Q.value = band.q;
|
|
5025
|
+
}
|
|
5026
|
+
const envelope2 = context.createGain();
|
|
5027
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
5028
|
+
oscillator.connect(envelope2);
|
|
5029
|
+
envelope2.connect(filter);
|
|
5030
|
+
oscillator.start(time);
|
|
5031
|
+
oscillator.stop(end(layer2, time));
|
|
5032
|
+
return oscillator;
|
|
5033
|
+
});
|
|
5034
|
+
return voiceOf(oscillators);
|
|
5035
|
+
}
|
|
5036
|
+
var end2 = (layer2, start) => start + layer2.duration + layer2.tail;
|
|
5037
|
+
function outputStage(plan, layer2, target) {
|
|
5038
|
+
const { context } = target;
|
|
5039
|
+
const level = gainNode(context, layer2.level);
|
|
5040
|
+
let node = level;
|
|
5041
|
+
if (plan.pan !== void 0 && typeof context.createStereoPanner === "function") {
|
|
5042
|
+
const panner = context.createStereoPanner();
|
|
5043
|
+
panner.pan.value = plan.pan;
|
|
5044
|
+
level.connect(panner);
|
|
5045
|
+
node = panner;
|
|
5046
|
+
}
|
|
5047
|
+
node.connect(target.output);
|
|
5048
|
+
if (layer2.wet && target.wet) {
|
|
5049
|
+
const send = gainNode(context, layer2.wet);
|
|
5050
|
+
node.connect(send);
|
|
5051
|
+
send.connect(target.wet);
|
|
5052
|
+
}
|
|
5053
|
+
return level;
|
|
5054
|
+
}
|
|
5055
|
+
function realizeNoiseLayer(plan, layer2, target) {
|
|
5056
|
+
const { context } = target;
|
|
5057
|
+
const time = target.time + layer2.at;
|
|
5058
|
+
const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
|
|
5059
|
+
const filters = (layer2.filters?.bank ?? []).map((filter) => {
|
|
5060
|
+
const node = filterNode(context, filter, time);
|
|
5061
|
+
rampTo(node.frequency, filter.frequency, time);
|
|
5062
|
+
return node;
|
|
5063
|
+
});
|
|
5064
|
+
const envelope2 = context.createGain();
|
|
5065
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
5066
|
+
let tail = source;
|
|
5067
|
+
for (const filter of filters) tail = tail.connect(filter);
|
|
5068
|
+
tail.connect(envelope2);
|
|
5069
|
+
envelope2.connect(outputStage(plan, layer2, target));
|
|
5070
|
+
source.start(time, layer2.source.kind === "noise" ? layer2.source.offset : 0);
|
|
5071
|
+
source.stop(end2(layer2, time));
|
|
5072
|
+
return [source];
|
|
5073
|
+
}
|
|
5074
|
+
function realizeToneLayer(plan, layer2, target) {
|
|
5075
|
+
const { context } = target;
|
|
5076
|
+
const time = target.time + layer2.at;
|
|
5077
|
+
const oscillator = context.createOscillator();
|
|
5078
|
+
if (layer2.source.kind === "osc") {
|
|
5079
|
+
oscillator.type = layer2.source.wave;
|
|
5080
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
5081
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
5082
|
+
}
|
|
5083
|
+
const envelope2 = context.createGain();
|
|
5084
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
5085
|
+
oscillator.connect(envelope2);
|
|
5086
|
+
envelope2.connect(outputStage(plan, layer2, target));
|
|
5087
|
+
oscillator.start(time);
|
|
5088
|
+
oscillator.stop(end2(layer2, time));
|
|
5089
|
+
return [oscillator];
|
|
5090
|
+
}
|
|
5091
|
+
function realizeVoiceLayer(plan, layer2, target) {
|
|
5092
|
+
const source = layer2.source;
|
|
5093
|
+
if (source.kind !== "formant_voice") return [];
|
|
5094
|
+
const { context } = target;
|
|
5095
|
+
const time = target.time + layer2.at;
|
|
5096
|
+
const oscillator = context.createOscillator();
|
|
5097
|
+
oscillator.type = source.wave;
|
|
5098
|
+
assign(oscillator.frequency, source.frequency, time);
|
|
5099
|
+
rampTo(oscillator.frequency, source.frequency, time);
|
|
5100
|
+
const vibrato = context.createOscillator();
|
|
5101
|
+
vibrato.frequency.value = source.vibrato.rate;
|
|
5102
|
+
const vibratoDepth = gainNode(context, source.vibrato.depth);
|
|
5103
|
+
vibrato.connect(vibratoDepth);
|
|
5104
|
+
vibratoDepth.connect(oscillator.frequency);
|
|
5105
|
+
const formants = source.formants.map((formant) => filterNode(context, { type: "bandpass", frequency: { value: formant.frequency }, q: formant.q }, time));
|
|
5106
|
+
const breath = noiseSource(target, true);
|
|
5107
|
+
const breathBand = filterNode(context, { type: "bandpass", frequency: { value: source.breath.frequency }, q: source.breath.q }, time);
|
|
5108
|
+
const breathLevel = gainNode(context, source.breath.gain);
|
|
5109
|
+
const mix = gainNode(context, source.mix);
|
|
5110
|
+
for (const formant of formants) oscillator.connect(formant);
|
|
5111
|
+
breath.connect(breathBand);
|
|
5112
|
+
breathBand.connect(breathLevel);
|
|
5113
|
+
for (const formant of formants) formant.connect(mix);
|
|
5114
|
+
breathLevel.connect(mix);
|
|
5115
|
+
const envelope2 = context.createGain();
|
|
5116
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
5117
|
+
mix.connect(envelope2);
|
|
5118
|
+
envelope2.connect(outputStage(plan, layer2, target));
|
|
5119
|
+
const stop = end2(layer2, time);
|
|
5120
|
+
oscillator.start(time);
|
|
5121
|
+
vibrato.start(time);
|
|
5122
|
+
breath.start(time, source.breath.offset);
|
|
5123
|
+
oscillator.stop(stop);
|
|
5124
|
+
vibrato.stop(stop);
|
|
5125
|
+
breath.stop(stop);
|
|
5126
|
+
return [oscillator, vibrato, breath];
|
|
5127
|
+
}
|
|
5128
|
+
function realizeLayered(plan, target) {
|
|
5129
|
+
const sources = plan.layers.flatMap((layer2) => {
|
|
5130
|
+
if (layer2.source.kind === "noise") return realizeNoiseLayer(plan, layer2, target);
|
|
5131
|
+
if (layer2.source.kind === "osc") return realizeToneLayer(plan, layer2, target);
|
|
5132
|
+
return realizeVoiceLayer(plan, layer2, target);
|
|
5133
|
+
});
|
|
5134
|
+
return voiceOf(sources);
|
|
5135
|
+
}
|
|
5136
|
+
function realizeSiren(plan, target) {
|
|
5137
|
+
const { context } = target;
|
|
5138
|
+
const layer2 = plan.layers[0];
|
|
5139
|
+
const time = target.time + layer2.at;
|
|
5140
|
+
const oscillator = context.createOscillator();
|
|
5141
|
+
if (layer2.source.kind === "osc") {
|
|
5142
|
+
oscillator.type = layer2.source.wave;
|
|
5143
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
5144
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
5145
|
+
}
|
|
5146
|
+
const depths = layer2.modulators.map((modulator) => {
|
|
5147
|
+
const lfo = context.createOscillator();
|
|
5148
|
+
if (modulator.wave) lfo.type = modulator.wave;
|
|
5149
|
+
lfo.frequency.value = modulator.rate;
|
|
5150
|
+
const depth = gainNode(context, modulator.depth);
|
|
5151
|
+
lfo.connect(depth);
|
|
5152
|
+
if (modulator.target === "frequency") depth.connect(oscillator.frequency);
|
|
5153
|
+
return { lfo, depth, target: modulator.target };
|
|
5154
|
+
});
|
|
5155
|
+
const lfos = depths.map(({ lfo }) => lfo);
|
|
5156
|
+
const envelope2 = context.createGain();
|
|
5157
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
5158
|
+
for (const { depth, target: modulated } of depths) if (modulated === "gain") depth.connect(envelope2.gain);
|
|
5159
|
+
const filters = (layer2.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
|
|
5160
|
+
let tail = oscillator;
|
|
5161
|
+
for (const filter of filters) tail = tail.connect(filter);
|
|
5162
|
+
tail.connect(envelope2);
|
|
5163
|
+
envelope2.connect(target.output);
|
|
5164
|
+
if (layer2.wet && target.wet) {
|
|
5165
|
+
const send = gainNode(context, layer2.wet);
|
|
5166
|
+
envelope2.connect(send);
|
|
5167
|
+
send.connect(target.wet);
|
|
5168
|
+
}
|
|
5169
|
+
const stop = end2(layer2, time);
|
|
5170
|
+
oscillator.start(time);
|
|
5171
|
+
for (const lfo of lfos) lfo.start(time);
|
|
5172
|
+
oscillator.stop(stop);
|
|
5173
|
+
for (const lfo of lfos) lfo.stop(stop);
|
|
5174
|
+
return voiceOf([oscillator, ...lfos]);
|
|
5175
|
+
}
|
|
5176
|
+
function noiseLayer(layer2, target, time) {
|
|
5177
|
+
const { context } = target;
|
|
5178
|
+
const source = noiseSource(target, layer2.source.kind === "noise" && layer2.source.loop);
|
|
5179
|
+
const filters = (layer2.filters?.bank ?? []).map((filter) => filterNode(context, filter, time));
|
|
5180
|
+
const envelope2 = context.createGain();
|
|
5181
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
5182
|
+
let tail = source;
|
|
5183
|
+
for (const filter of filters) tail = tail.connect(filter);
|
|
5184
|
+
tail.connect(envelope2).connect(target.output);
|
|
5185
|
+
source.start(time, layer2.source.kind === "noise" ? layer2.source.offset : 0);
|
|
5186
|
+
source.stop(time + layer2.duration + layer2.tail);
|
|
5187
|
+
return source;
|
|
5188
|
+
}
|
|
5189
|
+
function toneLayer(layer2, target, time) {
|
|
5190
|
+
const { context } = target;
|
|
5191
|
+
const oscillator = context.createOscillator();
|
|
5192
|
+
if (layer2.source.kind === "osc") {
|
|
5193
|
+
oscillator.type = layer2.source.wave;
|
|
5194
|
+
assign(oscillator.frequency, layer2.source.frequency, time);
|
|
5195
|
+
rampTo(oscillator.frequency, layer2.source.frequency, time);
|
|
5196
|
+
}
|
|
5197
|
+
const envelope2 = context.createGain();
|
|
5198
|
+
applyEnvelope(envelope2.gain, layer2.envelope, time);
|
|
5199
|
+
oscillator.connect(envelope2).connect(target.output);
|
|
5200
|
+
oscillator.start(time);
|
|
5201
|
+
oscillator.stop(time + layer2.duration + layer2.tail);
|
|
5202
|
+
return oscillator;
|
|
5203
|
+
}
|
|
5204
|
+
function realizeWeapon(plan, target) {
|
|
5205
|
+
const sources = plan.layers.map((layer2) => {
|
|
5206
|
+
const time = target.time + layer2.at;
|
|
5207
|
+
return layer2.source.kind === "noise" ? noiseLayer(layer2, target, time) : toneLayer(layer2, target, time);
|
|
5208
|
+
});
|
|
5209
|
+
return voiceOf(sources);
|
|
5210
|
+
}
|
|
5211
|
+
var BUILDERS = {
|
|
5212
|
+
layered: realizeLayered,
|
|
5213
|
+
burst: realizeBurst,
|
|
5214
|
+
scream: realizeScream,
|
|
5215
|
+
groan: realizeGroan,
|
|
5216
|
+
boom: realizeBoom,
|
|
5217
|
+
static: realizeStatic,
|
|
5218
|
+
chord: realizeChord,
|
|
5219
|
+
calls: realizeCalls,
|
|
5220
|
+
siren: realizeSiren,
|
|
5221
|
+
weapon: realizeWeapon
|
|
5222
|
+
};
|
|
5223
|
+
function realize(plan, target) {
|
|
5224
|
+
return BUILDERS[plan.order](plan, target);
|
|
5225
|
+
}
|
|
5226
|
+
var usesNoise = (recipe) => recipe.layers.some((layer2) => layer2.source.kind !== "osc");
|
|
5227
|
+
var planUsesNoise = (plan) => plan.layers.some((layer2) => layer2.source.kind !== "osc");
|
|
5228
|
+
function createAudioEngine(options = {}) {
|
|
5229
|
+
const seed = options.seed ?? 1;
|
|
5230
|
+
const registry = createSoundRegistry(options.banks);
|
|
5231
|
+
const voices = createVoiceManager({ ...options.groups ? { groups: options.groups } : {}, ...options.maxVoices === void 0 ? {} : { maxVoices: options.maxVoices } });
|
|
5232
|
+
const engineRandom = createRng2(seed);
|
|
5233
|
+
const shared = createNoiseStore(engineRandom);
|
|
5234
|
+
const busSettings = /* @__PURE__ */ new Map();
|
|
5235
|
+
const busNodes = /* @__PURE__ */ new Map();
|
|
5236
|
+
const meters = /* @__PURE__ */ new Map();
|
|
5237
|
+
const active = /* @__PURE__ */ new Map();
|
|
5238
|
+
const loops = /* @__PURE__ */ new Set();
|
|
5239
|
+
const named = /* @__PURE__ */ new Map();
|
|
5240
|
+
let mix = resolveMix(options.mix ?? DEFAULT_MIX, options.profiles);
|
|
5241
|
+
let context = null;
|
|
5242
|
+
let chain = null;
|
|
5243
|
+
let listener = DEFAULT_LISTENER;
|
|
5244
|
+
let lastTime = 0;
|
|
5245
|
+
const outputFor = (bus) => busNodes.get(bus) ?? chain?.master;
|
|
5246
|
+
const watched = /* @__PURE__ */ new Set();
|
|
5247
|
+
const meterOf = (bus) => {
|
|
5248
|
+
const existing = meters.get(bus);
|
|
5249
|
+
if (existing) return existing;
|
|
5250
|
+
const node = busNodes.get(bus);
|
|
5251
|
+
if (!context || !node || typeof context.createAnalyser !== "function") return void 0;
|
|
5252
|
+
const analyser = context.createAnalyser();
|
|
5253
|
+
analyser.fftSize = 1024;
|
|
5254
|
+
node.connect(analyser);
|
|
5255
|
+
const made = { analyser, data: new Float32Array(analyser.fftSize) };
|
|
5256
|
+
meters.set(bus, made);
|
|
5257
|
+
return made;
|
|
5258
|
+
};
|
|
5259
|
+
const ensureBus = (bus) => {
|
|
5260
|
+
if (!context || !chain || busNodes.has(bus)) return;
|
|
5261
|
+
const node = context.createGain();
|
|
5262
|
+
node.connect(chain.master);
|
|
5263
|
+
busNodes.set(bus, node);
|
|
5264
|
+
if (watched.has(bus)) meterOf(bus);
|
|
5265
|
+
};
|
|
5266
|
+
const ready = () => {
|
|
5267
|
+
if (context) return context;
|
|
5268
|
+
const created = (options.createContext ?? globalContext)();
|
|
5269
|
+
if (!created) return null;
|
|
5270
|
+
context = created;
|
|
5271
|
+
chain = buildChain(created, mix, engineRandom);
|
|
5272
|
+
if (mix.noise.create === "eager") noiseBuffer(shared, created, mix.noise.seconds);
|
|
5273
|
+
for (const [bus, setting] of Object.entries(mix.buses ?? {})) {
|
|
5274
|
+
busSettings.set(bus, { ...setting, ...busSettings.get(bus) });
|
|
5275
|
+
}
|
|
5276
|
+
for (const [bus, setting] of busSettings) {
|
|
5277
|
+
ensureBus(bus);
|
|
5278
|
+
busNodes.get(bus).gain.value = busGain(setting);
|
|
5279
|
+
}
|
|
5280
|
+
lastTime = created.currentTime;
|
|
5281
|
+
return created;
|
|
5282
|
+
};
|
|
5283
|
+
const advance = () => {
|
|
5284
|
+
if (!context) return;
|
|
5285
|
+
const elapsed = context.currentTime - lastTime;
|
|
5286
|
+
lastTime = context.currentTime;
|
|
5287
|
+
if (elapsed > 0) for (const id of voices.advance(elapsed)) active.delete(id);
|
|
5288
|
+
};
|
|
5289
|
+
const inputFor = (recipe, playOptions, random) => ({
|
|
5290
|
+
random,
|
|
5291
|
+
listener,
|
|
5292
|
+
noiseSeconds: mix.noise.seconds,
|
|
5293
|
+
...options.distance ? { distanceMode: options.distance } : {},
|
|
5294
|
+
...playOptions.volume === void 0 ? {} : { volume: playOptions.volume },
|
|
5295
|
+
...playOptions.distance === void 0 ? {} : { distance: playOptions.distance },
|
|
5296
|
+
...playOptions.position ? { position: playOptions.position } : {},
|
|
5297
|
+
...playOptions.params ? { params: playOptions.params } : {},
|
|
5298
|
+
...options.occlusion && playOptions.position && recipe.falloff?.model !== "none" ? { occlusion: options.occlusion(playOptions.position) } : {}
|
|
5299
|
+
});
|
|
5300
|
+
const start = (plan, store, voiceId, delay = 0) => {
|
|
5301
|
+
const audio = context;
|
|
5302
|
+
const realized = realize(plan, {
|
|
5303
|
+
context: audio,
|
|
5304
|
+
output: outputFor(plan.bus),
|
|
5305
|
+
...chain?.wet ? { wet: chain.wet } : {},
|
|
5306
|
+
noise: () => noiseBuffer(store, audio, mix.noise.seconds),
|
|
5307
|
+
time: audio.currentTime + delay
|
|
5308
|
+
});
|
|
5309
|
+
if (voiceId === void 0) return null;
|
|
5310
|
+
voices.setLength(voiceId, plan.length + delay);
|
|
5311
|
+
active.set(voiceId, realized);
|
|
5312
|
+
return {
|
|
5313
|
+
id: voiceId,
|
|
5314
|
+
plan,
|
|
5315
|
+
stop() {
|
|
5316
|
+
realized.stop();
|
|
5317
|
+
voices.release(voiceId);
|
|
5318
|
+
active.delete(voiceId);
|
|
5319
|
+
}
|
|
5320
|
+
};
|
|
5321
|
+
};
|
|
5322
|
+
const admit = (recipe, gain, distance4) => {
|
|
5323
|
+
const decision = voices.request({ ...recipe.group ? { group: recipe.group } : {}, priority: recipe.priority ?? busPriority(recipe.bus), gain, distance: distance4, length: 0 });
|
|
5324
|
+
if (!decision.accepted) return null;
|
|
5325
|
+
for (const id of decision.evicted) {
|
|
5326
|
+
active.get(id)?.stop();
|
|
5327
|
+
active.delete(id);
|
|
5328
|
+
}
|
|
5329
|
+
return decision.id;
|
|
5330
|
+
};
|
|
5331
|
+
const playWith = (id, playOptions, random, store) => {
|
|
5332
|
+
const recipe = registry.sound(id);
|
|
5333
|
+
if (!recipe || busSettings.get(recipe.bus)?.muted) return null;
|
|
5334
|
+
const distance4 = playOptions.distance ?? (playOptions.position ? listenerDistance(listener, playOptions.position, options.distance) : 0);
|
|
5335
|
+
const level = falloffGain(recipe.falloff, playOptions.volume ?? 1, distance4);
|
|
5336
|
+
const silent = isSilent(recipe.falloff, level);
|
|
5337
|
+
if (silent && !recipe.keepSilent) return null;
|
|
5338
|
+
const audio = ready();
|
|
5339
|
+
if (!audio) return null;
|
|
5340
|
+
unlockContext(audio);
|
|
5341
|
+
advance();
|
|
5342
|
+
const voiceId = silent ? void 0 : admit(recipe, level, distance4);
|
|
5343
|
+
if (voiceId === null) return null;
|
|
5344
|
+
if (!silent && usesNoise(recipe)) prepareNoise(store, audio, mix.noise.seconds);
|
|
5345
|
+
const plan = planSound(recipe, inputFor(recipe, playOptions, playOptions.seed === void 0 ? random : createRng2(playOptions.seed)));
|
|
5346
|
+
return plan ? start(plan, store, voiceId, playOptions.delay) : null;
|
|
5347
|
+
};
|
|
5348
|
+
const randomFor = (playOptions) => {
|
|
5349
|
+
if (playOptions.channel === void 0) return engineRandom;
|
|
5350
|
+
const existing = named.get(playOptions.channel);
|
|
5351
|
+
if (existing) return existing;
|
|
5352
|
+
const stream = streamFor(seed, playOptions.channel);
|
|
5353
|
+
named.set(playOptions.channel, stream);
|
|
5354
|
+
return stream;
|
|
5355
|
+
};
|
|
5356
|
+
const planWith = (id, playOptions, random) => {
|
|
5357
|
+
const recipe = registry.sound(id);
|
|
5358
|
+
return recipe ? planSound(recipe, inputFor(recipe, playOptions, random)) : null;
|
|
5359
|
+
};
|
|
5360
|
+
const loopHost = {
|
|
5361
|
+
ready,
|
|
5362
|
+
output: outputFor,
|
|
5363
|
+
prepareNoise: () => context && prepareNoise(shared, context, mix.noise.seconds),
|
|
5364
|
+
noise: () => noiseBuffer(shared, context, mix.noise.seconds),
|
|
5365
|
+
register: (loop) => void loops.add(loop),
|
|
5366
|
+
unregister: (loop) => void loops.delete(loop)
|
|
5367
|
+
};
|
|
5368
|
+
return {
|
|
5369
|
+
registry,
|
|
5370
|
+
register: (bank) => registry.register(bank),
|
|
5371
|
+
plan: (id, playOptions = {}) => planWith(id, playOptions, createRng2(playOptions.seed ?? seed)),
|
|
5372
|
+
play: (id, playOptions = {}) => playWith(id, playOptions, randomFor(playOptions), shared),
|
|
5373
|
+
playPlan(plan, playOptions = {}) {
|
|
5374
|
+
const audio = ready();
|
|
5375
|
+
if (!audio) return null;
|
|
5376
|
+
advance();
|
|
5377
|
+
const recipe = { id: plan.id, bus: plan.bus, layers: [], sources: [], priority: plan.priority, ...plan.group ? { group: plan.group } : {} };
|
|
5378
|
+
const voiceId = plan.silent ? void 0 : admit(recipe, plan.level, plan.distance);
|
|
5379
|
+
if (voiceId === null) return null;
|
|
5380
|
+
if (planUsesNoise(plan)) prepareNoise(shared, audio, mix.noise.seconds);
|
|
5381
|
+
return start(plan, shared, voiceId, playOptions.delay);
|
|
5382
|
+
},
|
|
5383
|
+
loop: (id, loopOptions = {}) => createLoopHandle(registry.loop(id), loopHost, loopOptions.volume),
|
|
5384
|
+
channel(channelOptions) {
|
|
5385
|
+
const random = channelOptions.random ?? (channelOptions.seed === void 0 ? engineRandom : createRng2(channelOptions.seed));
|
|
5386
|
+
const store = channelOptions.ownNoise ? createNoiseStore(random) : shared;
|
|
5387
|
+
return {
|
|
5388
|
+
random,
|
|
5389
|
+
play: (id, playOptions = {}) => playWith(id, playOptions, random, store),
|
|
5390
|
+
plan: (id, playOptions = {}) => planWith(id, playOptions, createRng2(playOptions.seed ?? seed))
|
|
5391
|
+
};
|
|
5392
|
+
},
|
|
5393
|
+
setListener(next) {
|
|
5394
|
+
listener = next;
|
|
5395
|
+
},
|
|
5396
|
+
setBus(id, setting) {
|
|
5397
|
+
busSettings.set(id, { ...busSettings.get(id), ...setting });
|
|
5398
|
+
if (!context) return;
|
|
5399
|
+
ensureBus(id);
|
|
5400
|
+
busNodes.get(id).gain.value = busGain(busSettings.get(id));
|
|
5401
|
+
},
|
|
5402
|
+
setMix(next) {
|
|
5403
|
+
const previous = mix;
|
|
5404
|
+
mix = resolveMix(next, options.profiles);
|
|
5405
|
+
if (!context || !chain) return;
|
|
5406
|
+
const old = chain;
|
|
5407
|
+
chain = buildChain(context, mix, engineRandom);
|
|
5408
|
+
if (mix.noise.seconds !== previous.noise.seconds) shared.reset();
|
|
5409
|
+
for (const node of busNodes.values()) {
|
|
5410
|
+
node.disconnect();
|
|
5411
|
+
node.connect(chain.master);
|
|
5412
|
+
}
|
|
5413
|
+
for (const loop of loops) loop.rewire();
|
|
5414
|
+
old.disconnect();
|
|
5415
|
+
},
|
|
5416
|
+
setMaster(gain) {
|
|
5417
|
+
mix = { ...mix, master: gain };
|
|
5418
|
+
if (chain) chain.master.gain.value = gain;
|
|
5419
|
+
},
|
|
5420
|
+
unlock() {
|
|
5421
|
+
const audio = ready();
|
|
5422
|
+
if (audio) unlockContext(audio);
|
|
5423
|
+
},
|
|
5424
|
+
level(bus) {
|
|
5425
|
+
watched.add(bus);
|
|
5426
|
+
ensureBus(bus);
|
|
5427
|
+
const meter = meterOf(bus);
|
|
5428
|
+
if (!meter) return 0;
|
|
5429
|
+
meter.analyser.getFloatTimeDomainData(meter.data);
|
|
5430
|
+
let sum2 = 0;
|
|
5431
|
+
for (const sample of meter.data) sum2 += sample * sample;
|
|
5432
|
+
return Math.sqrt(sum2 / meter.data.length);
|
|
5433
|
+
},
|
|
5434
|
+
stats() {
|
|
5435
|
+
advance();
|
|
5436
|
+
const voiceStats = voices.stats();
|
|
5437
|
+
return {
|
|
5438
|
+
context: context ? context.state : "none",
|
|
5439
|
+
voices: voiceStats.active,
|
|
5440
|
+
peak: voiceStats.peak,
|
|
5441
|
+
dropped: voiceStats.dropped,
|
|
5442
|
+
maxVoices: voiceStats.maxVoices,
|
|
5443
|
+
loops: [...loops].filter((loop) => loop.created).length
|
|
5444
|
+
};
|
|
5445
|
+
},
|
|
5446
|
+
dispose() {
|
|
5447
|
+
for (const id of active.keys()) voices.release(id);
|
|
5448
|
+
for (const loop of [...loops]) loop.stop();
|
|
5449
|
+
active.clear();
|
|
5450
|
+
void context?.close();
|
|
5451
|
+
context = null;
|
|
5452
|
+
chain = null;
|
|
5453
|
+
busNodes.clear();
|
|
5454
|
+
meters.clear();
|
|
5455
|
+
shared.reset();
|
|
5456
|
+
}
|
|
5457
|
+
};
|
|
5458
|
+
}
|
|
5459
|
+
function createAudioSink(engine) {
|
|
5460
|
+
const playing = /* @__PURE__ */ new Map();
|
|
5461
|
+
return {
|
|
5462
|
+
engine,
|
|
5463
|
+
play(request) {
|
|
5464
|
+
const handle = engine.play(request.sound, {
|
|
5465
|
+
...request.position ? { position: request.position } : {},
|
|
5466
|
+
...request.volume === void 0 ? {} : { volume: request.volume },
|
|
5467
|
+
...request.params ? { params: request.params } : {},
|
|
5468
|
+
...request.seed === void 0 ? {} : { seed: request.seed }
|
|
5469
|
+
});
|
|
5470
|
+
if (!handle) return null;
|
|
5471
|
+
playing.set(handle.id, handle);
|
|
5472
|
+
return { id: handle.id };
|
|
5473
|
+
},
|
|
5474
|
+
stop(handle) {
|
|
5475
|
+
playing.get(handle.id)?.stop();
|
|
5476
|
+
playing.delete(handle.id);
|
|
5477
|
+
},
|
|
5478
|
+
loop(sound) {
|
|
5479
|
+
const loop = engine.loop(sound);
|
|
5480
|
+
return { set: (state) => loop.set({ ...state.volume === void 0 ? {} : { volume: state.volume }, ...state.frequency === void 0 ? {} : { frequency: state.frequency } }), stop: () => loop.stop() };
|
|
5481
|
+
},
|
|
5482
|
+
setListener(listener) {
|
|
5483
|
+
engine.setListener(listener);
|
|
5484
|
+
},
|
|
5485
|
+
register(bank) {
|
|
5486
|
+
engine.register(bank);
|
|
5487
|
+
}
|
|
5488
|
+
};
|
|
5489
|
+
}
|
|
5490
|
+
var BENCH_BANK = {
|
|
5491
|
+
id: "audio_bench",
|
|
5492
|
+
sounds: [
|
|
5493
|
+
{
|
|
5494
|
+
id: "bench_ping",
|
|
5495
|
+
bus: "ui",
|
|
5496
|
+
pan: true,
|
|
5497
|
+
duration: 0.25,
|
|
5498
|
+
layers: [
|
|
5499
|
+
{
|
|
5500
|
+
gain: 0.5,
|
|
5501
|
+
wet: 0.3,
|
|
5502
|
+
source: { kind: "osc", wave: "sine", frequency: { from: 880 } },
|
|
5503
|
+
envelope: [
|
|
5504
|
+
[0, 1e-4, "set", "fixed"],
|
|
5505
|
+
[2e-3, 1, "exp"],
|
|
5506
|
+
["1d", 1e-4, "exp", "fixed"]
|
|
5507
|
+
]
|
|
5508
|
+
}
|
|
5509
|
+
],
|
|
5510
|
+
falloff: DISTANCE_CURVES.art_scene,
|
|
5511
|
+
sources: ["sdk: bench probe, a 880 Hz ping through the art scene output stage"]
|
|
5512
|
+
},
|
|
5513
|
+
{
|
|
5514
|
+
id: "bench_hiss",
|
|
5515
|
+
bus: "ui",
|
|
5516
|
+
pan: true,
|
|
5517
|
+
duration: [0.3, 0.9],
|
|
5518
|
+
layers: [
|
|
5519
|
+
{
|
|
5520
|
+
gain: 0.4,
|
|
5521
|
+
source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
|
|
5522
|
+
filters: { mode: "serial", bank: [{ type: "highpass", frequency: { set: 3e3 }, q: 0.7 }] },
|
|
5523
|
+
envelope: [
|
|
5524
|
+
[0, 1e-4, "set", "fixed"],
|
|
5525
|
+
[2e-3, 1, "exp"],
|
|
5526
|
+
["1d", 1e-4, "exp", "fixed"]
|
|
5527
|
+
]
|
|
5528
|
+
}
|
|
5529
|
+
],
|
|
5530
|
+
falloff: DISTANCE_CURVES.art_scene,
|
|
5531
|
+
sources: ["sdk: bench probe, a highpass hiss of random length"]
|
|
5532
|
+
}
|
|
5533
|
+
]
|
|
5534
|
+
};
|
|
5535
|
+
|
|
3770
5536
|
// ../game-vfx/dist/index.js
|
|
3771
5537
|
var __defProp2 = Object.defineProperty;
|
|
3772
5538
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -6974,7 +8740,7 @@ var GameTestRoom = (() => {
|
|
|
6974
8740
|
mixed ^= mixed >>> 16;
|
|
6975
8741
|
return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
|
|
6976
8742
|
}
|
|
6977
|
-
function
|
|
8743
|
+
function streamFor2(eventSeed) {
|
|
6978
8744
|
return lehmer(mixEventSeed(eventSeed));
|
|
6979
8745
|
}
|
|
6980
8746
|
var TABLE = {
|
|
@@ -7070,10 +8836,10 @@ var GameTestRoom = (() => {
|
|
|
7070
8836
|
if (!best) return;
|
|
7071
8837
|
const offsetX = clamp22(x - best.x, -0.2, 0.2);
|
|
7072
8838
|
const offsetY = clamp22(y - best.y, -0.17, 0.17);
|
|
7073
|
-
for (let
|
|
8839
|
+
for (let draw2 = 0; draw2 < OBJECT_ID_DRAWS2; draw2++) random.next();
|
|
7074
8840
|
const chipTurn = random.between(0, 6);
|
|
7075
8841
|
const chipScale = random.between(0.8, 1.3) * scale2;
|
|
7076
|
-
for (let
|
|
8842
|
+
for (let draw2 = 0; draw2 < OBJECT_ID_DRAWS2; draw2++) random.next();
|
|
7077
8843
|
best.holes.push({ x: offsetX, y: offsetY, scale: scale2, chipTurn, chipScale });
|
|
7078
8844
|
impact(host.engine, "concrete", [ORIGIN2[0] + x, y, ORIGIN2[2] + 0.13], [0, 0, 1], 0.8);
|
|
7079
8845
|
} else if (action === "collapse") {
|
|
@@ -7972,7 +9738,7 @@ var GameTestRoom = (() => {
|
|
|
7972
9738
|
}
|
|
7973
9739
|
cue(step) {
|
|
7974
9740
|
const args = this.value(step.args);
|
|
7975
|
-
for (let
|
|
9741
|
+
for (let draw2 = 0; draw2 < (step.draws ?? 0); draw2++) this.deps.engine.random.next();
|
|
7976
9742
|
this.deps.onCue?.({ cue: step.cue, args, time: this.time });
|
|
7977
9743
|
}
|
|
7978
9744
|
scheduleSteps(step) {
|
|
@@ -9023,9 +10789,9 @@ var GameTestRoom = (() => {
|
|
|
9023
10789
|
this.color[i3] = lerp4(this.rgbStart[i3], this.rgbEnd[i3], t);
|
|
9024
10790
|
this.color[i3 + 1] = lerp4(this.rgbStart[i3 + 1], this.rgbEnd[i3 + 1], t);
|
|
9025
10791
|
this.color[i3 + 2] = lerp4(this.rgbStart[i3 + 2], this.rgbEnd[i3 + 2], t);
|
|
9026
|
-
let
|
|
9027
|
-
if (def.flicker)
|
|
9028
|
-
this.alpha[i] = clamp4(
|
|
10792
|
+
let envelope2 = t < def.fadeIn ? t / def.fadeIn : 1 - (t - def.fadeIn) / (1 - def.fadeIn);
|
|
10793
|
+
if (def.flicker) envelope2 *= 1 - def.flicker * (0.5 + 0.5 * Math.sin(age * 38 + this.seed[i] * 50));
|
|
10794
|
+
this.alpha[i] = clamp4(envelope2, 0, 1) * def.alpha * this.alphaMul[i];
|
|
9029
10795
|
top = i + 1;
|
|
9030
10796
|
}
|
|
9031
10797
|
this.high = top;
|
|
@@ -9730,10 +11496,10 @@ var GameTestRoom = (() => {
|
|
|
9730
11496
|
this.velocity[offset + 1] = this.direction.y * speed + (options.v ? options.v[1] : 0);
|
|
9731
11497
|
this.velocity[offset + 2] = this.direction.z * speed + (options.v ? options.v[2] : 0);
|
|
9732
11498
|
const start = hexToRgb(options.c0 ?? 16777215);
|
|
9733
|
-
const
|
|
11499
|
+
const end3 = options.c1 != null ? hexToRgb(options.c1) : start;
|
|
9734
11500
|
for (let channel = 0; channel < FLOATS_PER_VECTOR; channel++) {
|
|
9735
11501
|
this.colorStart[offset + channel] = start[channel];
|
|
9736
|
-
this.colorEnd[offset + channel] =
|
|
11502
|
+
this.colorEnd[offset + channel] = end3[channel];
|
|
9737
11503
|
}
|
|
9738
11504
|
this.life[index] = this.maxLife[index] = drawRange(random, options.life || 1);
|
|
9739
11505
|
this.startSize[index] = drawRange(random, options.size || 0.1);
|
|
@@ -9868,10 +11634,10 @@ var GameTestRoom = (() => {
|
|
|
9868
11634
|
this.velocity[offset + 1] = this.direction.y * speed + (options.v ? options.v[1] : 0);
|
|
9869
11635
|
this.velocity[offset + 2] = this.direction.z * speed + (options.v ? options.v[2] : 0);
|
|
9870
11636
|
const start = hexToRgb(options.c0 ?? 16765056);
|
|
9871
|
-
const
|
|
11637
|
+
const end3 = options.c1 != null ? hexToRgb(options.c1) : start;
|
|
9872
11638
|
for (let channel = 0; channel < FLOATS_PER_VECTOR2; channel++) {
|
|
9873
11639
|
this.colorStart[offset + channel] = start[channel];
|
|
9874
|
-
this.colorEnd[offset + channel] =
|
|
11640
|
+
this.colorEnd[offset + channel] = end3[channel];
|
|
9875
11641
|
}
|
|
9876
11642
|
this.life[index] = this.maxLife[index] = drawRange(random, options.life || 0.4);
|
|
9877
11643
|
this.length[index] = options.len || 0.02;
|
|
@@ -10255,7 +12021,7 @@ var GameTestRoom = (() => {
|
|
|
10255
12021
|
function streamOf(seed) {
|
|
10256
12022
|
if (randomMode === "shared") return sharedRandom;
|
|
10257
12023
|
handles.events++;
|
|
10258
|
-
return
|
|
12024
|
+
return streamFor2(seed ?? Math.imul(handles.events, AUTO_SEED) + (options.seed ?? 0));
|
|
10259
12025
|
}
|
|
10260
12026
|
function detonate(def, point, call3) {
|
|
10261
12027
|
composeExplosion(core, def, { x: point[0], y: point[1], z: point[2], direction: call3.direction, random: call3.random, handle: call3.handle });
|
|
@@ -10834,12 +12600,12 @@ void main(){
|
|
|
10834
12600
|
if (typeof document === "undefined") return blankCanvas();
|
|
10835
12601
|
return document.createElement("canvas");
|
|
10836
12602
|
}
|
|
10837
|
-
function paint(createCanvas,
|
|
12603
|
+
function paint(createCanvas, draw2) {
|
|
10838
12604
|
const canvas = createCanvas();
|
|
10839
12605
|
canvas.width = TEXTURE_SIZE;
|
|
10840
12606
|
canvas.height = TEXTURE_SIZE;
|
|
10841
12607
|
const context = canvas.getContext("2d");
|
|
10842
|
-
if (context)
|
|
12608
|
+
if (context) draw2(context, TEXTURE_SIZE);
|
|
10843
12609
|
return canvas;
|
|
10844
12610
|
}
|
|
10845
12611
|
function drawScorch(context, size, random) {
|
|
@@ -11697,7 +13463,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11697
13463
|
var __defProp3 = Object.defineProperty;
|
|
11698
13464
|
var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11699
13465
|
var __publicField2 = (obj, key, value) => __defNormalProp2(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
11700
|
-
function
|
|
13466
|
+
function createRng3(seed) {
|
|
11701
13467
|
let a = seed >>> 0;
|
|
11702
13468
|
return () => {
|
|
11703
13469
|
a = a + 1831565813 >>> 0;
|
|
@@ -11745,7 +13511,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11745
13511
|
__publicField2(this, "m");
|
|
11746
13512
|
__publicField2(this, "v");
|
|
11747
13513
|
__publicField2(this, "w");
|
|
11748
|
-
__publicField2(this, "rng",
|
|
13514
|
+
__publicField2(this, "rng", createRng3(1374496523));
|
|
11749
13515
|
__publicField2(this, "markGeometry", null);
|
|
11750
13516
|
__publicField2(this, "markMaterial", null);
|
|
11751
13517
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
@@ -11776,7 +13542,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11776
13542
|
const idx = seed % 6;
|
|
11777
13543
|
let g = this.shardGeos[idx];
|
|
11778
13544
|
if (!g) {
|
|
11779
|
-
const r =
|
|
13545
|
+
const r = createRng3(1e3 + idx);
|
|
11780
13546
|
const n = 3 + Math.floor(r() * 3);
|
|
11781
13547
|
const shape = new this.THREE.Shape();
|
|
11782
13548
|
const pts = [];
|
|
@@ -11981,9 +13747,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11981
13747
|
};
|
|
11982
13748
|
var MAX_RADIUS = 1.2;
|
|
11983
13749
|
var GroundSpots = class {
|
|
11984
|
-
constructor(THREE,
|
|
13750
|
+
constructor(THREE, scene2, groundAt, cap) {
|
|
11985
13751
|
__publicField2(this, "THREE", THREE);
|
|
11986
|
-
__publicField2(this, "scene",
|
|
13752
|
+
__publicField2(this, "scene", scene2);
|
|
11987
13753
|
__publicField2(this, "groundAt", groundAt);
|
|
11988
13754
|
__publicField2(this, "cap", cap);
|
|
11989
13755
|
__publicField2(this, "spots", []);
|
|
@@ -12381,9 +14147,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12381
14147
|
}
|
|
12382
14148
|
};
|
|
12383
14149
|
var RebarCages = class {
|
|
12384
|
-
constructor(THREE,
|
|
14150
|
+
constructor(THREE, scene2, radius = 4e-3) {
|
|
12385
14151
|
__publicField2(this, "THREE", THREE);
|
|
12386
|
-
__publicField2(this, "scene",
|
|
14152
|
+
__publicField2(this, "scene", scene2);
|
|
12387
14153
|
__publicField2(this, "cages", /* @__PURE__ */ new Map());
|
|
12388
14154
|
__publicField2(this, "geometry");
|
|
12389
14155
|
__publicField2(this, "material");
|
|
@@ -12990,7 +14756,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12990
14756
|
/** A star of jagged lines: `rays` rays up to `length` metres, seeded so the same event always draws the same star. */
|
|
12991
14757
|
addCrack(mv, worldPoint, worldNormal, rays, length3, color, seed) {
|
|
12992
14758
|
const { s, pos, quat: quat2 } = this.decalPose(mv.mesh, worldPoint, worldNormal, 2e-3);
|
|
12993
|
-
const rnd2 =
|
|
14759
|
+
const rnd2 = createRng3(seed);
|
|
12994
14760
|
const verts = [];
|
|
12995
14761
|
const n = clamp6(Math.round(rays), 3, 16);
|
|
12996
14762
|
const L = Math.max(0.02, length3) / s;
|
|
@@ -13191,10 +14957,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13191
14957
|
const sy = lerp5(1, 0.25, melt);
|
|
13192
14958
|
const sxz = 1 + 0.18 * melt;
|
|
13193
14959
|
for (let i = range2[0] * 3, v = 0; i < range2[1] * 3; i += 3, v++) {
|
|
13194
|
-
const
|
|
14960
|
+
const noise2 = (Math.imul(v + 1, 2654435761) >>> 0) % 1e3 / 1e3;
|
|
13195
14961
|
arr[i] = cx + (work[i] - cx) * sxz;
|
|
13196
14962
|
arr[i + 2] = cz + (work[i + 2] - cz) * sxz;
|
|
13197
|
-
arr[i + 1] = Math.max(minY, minY + (work[i + 1] - minY) * sy -
|
|
14963
|
+
arr[i + 1] = Math.max(minY, minY + (work[i + 1] - minY) * sy - noise2 * melt * h * 0.04);
|
|
13198
14964
|
}
|
|
13199
14965
|
}
|
|
13200
14966
|
/** The colour attribute of the host's current geometry (created white when there is none) with vertex colours switched on in its material. */
|
|
@@ -13517,10 +15283,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13517
15283
|
__publicField2(this, "cages");
|
|
13518
15284
|
__publicField2(this, "active", /* @__PURE__ */ new Map());
|
|
13519
15285
|
__publicField2(this, "fires", /* @__PURE__ */ new Map());
|
|
15286
|
+
__publicField2(this, "primedBurning", /* @__PURE__ */ new Set());
|
|
13520
15287
|
__publicField2(this, "leaks", []);
|
|
13521
15288
|
__publicField2(this, "homeTags", []);
|
|
13522
15289
|
__publicField2(this, "throttles", /* @__PURE__ */ new Map());
|
|
13523
|
-
__publicField2(this, "rng",
|
|
15290
|
+
__publicField2(this, "rng", createRng3(12648430));
|
|
13524
15291
|
__publicField2(this, "vfx");
|
|
13525
15292
|
__publicField2(this, "owner", `destruction-${++rendererCount}`);
|
|
13526
15293
|
__publicField2(this, "stopLanding");
|
|
@@ -13856,8 +15623,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13856
15623
|
case "primed":
|
|
13857
15624
|
if (ev.trigger !== "explosion") {
|
|
13858
15625
|
this.vfx.fire({ point: ev.point, radius: PRIMED_FIRE_RADIUS, life: ev.delay });
|
|
15626
|
+
this.primedBurning.add(`${ev.object}|${ev.part}`);
|
|
13859
15627
|
this.activate(ev.object, ev.part);
|
|
13860
15628
|
this.sound("ignite", ev.point, 0.7);
|
|
15629
|
+
this.sound("burn_loop", ev.point, 0.5);
|
|
13861
15630
|
}
|
|
13862
15631
|
break;
|
|
13863
15632
|
case "spall":
|
|
@@ -13875,16 +15644,20 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13875
15644
|
break;
|
|
13876
15645
|
case "extinguish": {
|
|
13877
15646
|
const f = this.fires.get(`${ev.object}|${ev.part}`);
|
|
15647
|
+
const primed = this.primedBurning.delete(`${ev.object}|${ev.part}`);
|
|
13878
15648
|
if (f) {
|
|
13879
15649
|
this.stopFire(`${ev.object}|${ev.part}`);
|
|
13880
15650
|
this.vfx.burst("steam", f.point, [0, 1, 0], 14, 1.2);
|
|
13881
15651
|
}
|
|
15652
|
+
if (f || primed) this.sound("burn_end", f?.point ?? this.rootPoint(ev.object), 0.5);
|
|
13882
15653
|
this.activate(ev.object, ev.part);
|
|
13883
15654
|
this.sound("extinguish", f?.point ?? this.rootPoint(ev.object), 0.6);
|
|
13884
15655
|
break;
|
|
13885
15656
|
}
|
|
13886
15657
|
case "burnout": {
|
|
13887
15658
|
this.stopFire(`${ev.object}|${ev.part}`);
|
|
15659
|
+
this.primedBurning.delete(`${ev.object}|${ev.part}`);
|
|
15660
|
+
this.sound("burn_end", ev.point, 0.5);
|
|
13888
15661
|
this.vfx.burst("ash", ev.point, [0, 1, 0], 16, 1);
|
|
13889
15662
|
this.vfx.burst("smoke", ev.point, [0, 1, 0], 6, 0.8, { color: SMOKE_TONES.grey });
|
|
13890
15663
|
const pv = this.partVis(ev.object, ev.part);
|
|
@@ -13926,6 +15699,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13926
15699
|
break;
|
|
13927
15700
|
}
|
|
13928
15701
|
case "explode":
|
|
15702
|
+
this.primedBurning.delete(`${ev.object}|${ev.part}`);
|
|
13929
15703
|
this.onExplode(ev);
|
|
13930
15704
|
break;
|
|
13931
15705
|
case "destroyed":
|
|
@@ -14009,7 +15783,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14009
15783
|
if (!b || !pv) return;
|
|
14010
15784
|
const def = this.world.materials.get(pv.material);
|
|
14011
15785
|
const n = this.worldDir(b, ev.hole.normal);
|
|
14012
|
-
const
|
|
15786
|
+
const draw2 = (point, normal) => {
|
|
14013
15787
|
const wp = new this.THREE.Vector3(point[0], point[1], point[2]);
|
|
14014
15788
|
const mv = this.nearestMesh(pv, wp);
|
|
14015
15789
|
if (!mv) return;
|
|
@@ -14020,8 +15794,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14020
15794
|
this.visuals.addHole(mv, wp, normal, ev.hole.diameter);
|
|
14021
15795
|
}
|
|
14022
15796
|
};
|
|
14023
|
-
|
|
14024
|
-
if (ev.hole.through && ev.exitPoint)
|
|
15797
|
+
draw2(ev.worldPoint, n);
|
|
15798
|
+
if (ev.hole.through && ev.exitPoint) draw2(ev.exitPoint, n.clone().negate());
|
|
14025
15799
|
if (def?.tags?.includes("metal")) this.vfx.burst("sparks", ev.worldPoint, [n.x, n.y, n.z], 6, 3.5);
|
|
14026
15800
|
this.sound(impactKey(def), ev.worldPoint, 0.6);
|
|
14027
15801
|
}
|
|
@@ -14219,6 +15993,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14219
15993
|
if (options.visual !== false) this.playExplosion(ev, options);
|
|
14220
15994
|
this.vfx.push(p, ev.radius, 4);
|
|
14221
15995
|
const c = this.v1;
|
|
15996
|
+
const report = options.report;
|
|
15997
|
+
const confinement = report?.find((r) => r.blast !== void 0)?.blast?.confinement ?? 1;
|
|
15998
|
+
const energy = ev.energy * confinement;
|
|
14222
15999
|
for (const pc of this.pieceSys.list) {
|
|
14223
16000
|
this.pieceSys.centerOf(pc, c);
|
|
14224
16001
|
const dx = c.x - p[0];
|
|
@@ -14227,13 +16004,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14227
16004
|
const d = Math.hypot(dx, dy, dz);
|
|
14228
16005
|
if (d > ev.radius) continue;
|
|
14229
16006
|
const fall = (1 - d / ev.radius) ** 2;
|
|
14230
|
-
const
|
|
16007
|
+
const reach = report?.find((r) => r.object === pc.objectId)?.blast?.shelter ?? 1;
|
|
16008
|
+
const speed = Math.min(30, Math.sqrt(2 * energy * reach * 0.02 / pc.mass)) * fall;
|
|
14231
16009
|
const inv = d > 1e-4 ? 1 / d : 0;
|
|
14232
16010
|
const dir = d > 1e-4 ? [dx * inv, dy * inv + 0.3, dz * inv] : [0, 1, 0];
|
|
14233
16011
|
const spread2 = pc.halfExtents[0];
|
|
14234
16012
|
this.pieceSys.impulse(pc, [dir[0] * speed * pc.mass, dir[1] * speed * pc.mass, dir[2] * speed * pc.mass], [c.x + (this.rng() - 0.5) * spread2, c.y + spread2 * 0.5, c.z + (this.rng() - 0.5) * spread2]);
|
|
14235
16013
|
}
|
|
14236
|
-
this.fragments.blast(p, ev.radius, Math.min(20, Math.sqrt(
|
|
16014
|
+
this.fragments.blast(p, ev.radius, Math.min(20, Math.sqrt(energy) * 0.08));
|
|
14237
16015
|
this.sound("explode", p, clamp6(0.5 + ev.radius / 10, 0.5, 1));
|
|
14238
16016
|
}
|
|
14239
16017
|
playExplosion(ev, options) {
|
|
@@ -14416,6 +16194,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14416
16194
|
this.fragments.clear();
|
|
14417
16195
|
this.ground.clear();
|
|
14418
16196
|
this.cages.clear();
|
|
16197
|
+
this.primedBurning.clear();
|
|
14419
16198
|
this.vfx.sprites().clear();
|
|
14420
16199
|
this.ownVfx?.world.clear();
|
|
14421
16200
|
}
|
|
@@ -14444,12 +16223,197 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14444
16223
|
this.disposed = true;
|
|
14445
16224
|
}
|
|
14446
16225
|
};
|
|
16226
|
+
var ATTACK = 2e-3;
|
|
16227
|
+
var lengthOf = (d, attack) => {
|
|
16228
|
+
if (typeof d === "number") return attack + d;
|
|
16229
|
+
if (Array.isArray(d)) return [attack + d[0], attack + d[1]];
|
|
16230
|
+
return { ...d, add: (d.add ?? 0) + attack };
|
|
16231
|
+
};
|
|
16232
|
+
var envelope = (attack) => [
|
|
16233
|
+
[0, 1e-4, "set", "fixed"],
|
|
16234
|
+
[attack, 1, "exp"],
|
|
16235
|
+
["1d", 1e-4, "exp", "fixed"]
|
|
16236
|
+
];
|
|
16237
|
+
function noise(o) {
|
|
16238
|
+
const a = o.a ?? ATTACK;
|
|
16239
|
+
const over = typeof o.d === "number" ? o.d : 0;
|
|
16240
|
+
return {
|
|
16241
|
+
filters: {
|
|
16242
|
+
mode: "serial",
|
|
16243
|
+
bank: [{ type: o.type ?? "lowpass", frequency: o.f2 === void 0 ? { set: o.f } : { set: o.f, to: o.f2, curve: "exp", over }, q: o.q ?? 0.7 }]
|
|
16244
|
+
},
|
|
16245
|
+
duration: lengthOf(o.d, a),
|
|
16246
|
+
gain: o.vol,
|
|
16247
|
+
...o.delay === void 0 ? {} : { at: o.delay },
|
|
16248
|
+
...o.wet === void 0 ? {} : { wet: o.wet },
|
|
16249
|
+
source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
|
|
16250
|
+
envelope: envelope(a)
|
|
16251
|
+
};
|
|
16252
|
+
}
|
|
16253
|
+
function tone(o) {
|
|
16254
|
+
return {
|
|
16255
|
+
source: { kind: "osc", wave: o.wave ?? "sine", frequency: { from: o.f, to: o.f2 ?? "from", curve: "exp", over: "duration" } },
|
|
16256
|
+
duration: lengthOf(o.d, ATTACK),
|
|
16257
|
+
gain: o.vol,
|
|
16258
|
+
...o.delay === void 0 ? {} : { at: o.delay },
|
|
16259
|
+
...o.wet === void 0 ? {} : { wet: o.wet },
|
|
16260
|
+
envelope: envelope(ATTACK)
|
|
16261
|
+
};
|
|
16262
|
+
}
|
|
16263
|
+
var many = (count, make) => Array.from({ length: count }, make);
|
|
16264
|
+
var scene = (id, layers, line, vars) => ({
|
|
16265
|
+
id,
|
|
16266
|
+
bus: "world",
|
|
16267
|
+
pan: true,
|
|
16268
|
+
layers,
|
|
16269
|
+
falloff: DISTANCE_CURVES.art_scene,
|
|
16270
|
+
...vars ? { vars } : {},
|
|
16271
|
+
sources: [`cena:${line}`]
|
|
16272
|
+
});
|
|
16273
|
+
var boomLayers = (big) => [
|
|
16274
|
+
noise({ f: 1200, f2: 120, d: 1.8, vol: 2, wet: 1.2 }),
|
|
16275
|
+
tone({ f: 65, f2: 24, d: 1, vol: 1.8 }),
|
|
16276
|
+
noise({ type: "highpass", f: 1500, d: 0.2, vol: 0.8 }),
|
|
16277
|
+
...big ? [noise({ f: 500, f2: 90, a: 0.2, d: 2.4, vol: 1.2, wet: 1, delay: 0.15 })] : [],
|
|
16278
|
+
...many(8, () => tone({ f: [2600, 5600], d: 0.05, vol: 0.08, delay: [0.4, 1.6] }))
|
|
16279
|
+
];
|
|
16280
|
+
var SCENE_SOUNDS = [
|
|
16281
|
+
scene(
|
|
16282
|
+
"glass_shatter",
|
|
16283
|
+
[noise({ type: "highpass", f: 2800, d: 0.45, vol: 0.9, wet: 0.5 }), ...many(12, () => tone({ f: [3e3, 7500], d: [0.06, 0.14], vol: 0.18, delay: [0, 0.55] }))],
|
|
16284
|
+
"L1359 glass"
|
|
16285
|
+
),
|
|
16286
|
+
scene(
|
|
16287
|
+
"wood_hit",
|
|
16288
|
+
[noise({ type: "bandpass", f: 850, q: 1, d: 0.25, vol: 0.9, wet: 0.3 }), tone({ f: 210, f2: 90, d: 0.1, vol: 0.6 }), noise({ type: "highpass", f: 2e3, d: 0.04, vol: 0.5 })],
|
|
16289
|
+
"L1360 wood"
|
|
16290
|
+
),
|
|
16291
|
+
scene("thunk", [noise({ type: "bandpass", f: 700, q: 2, d: 0.07, vol: 0.4 })], "L1361 thunk"),
|
|
16292
|
+
scene("ping", [tone({ f: [2600, 3500], f2: 1400, d: 0.14, vol: 0.18, wet: 0.3 })], "L1362 ping"),
|
|
16293
|
+
scene("clang", [tone({ f: [520, 720], f2: 480, d: 0.5, vol: 0.35, wet: 0.4 }), tone({ f: 1370, f2: 1300, d: 0.3, vol: 0.15 }), noise({ type: "highpass", f: 2e3, d: 0.05, vol: 0.5 })], "L1363 clang"),
|
|
16294
|
+
scene("chip", [noise({ type: "bandpass", f: 1600, q: 1.2, d: 0.06, vol: 0.5 })], "L1364 chip"),
|
|
16295
|
+
scene(
|
|
16296
|
+
"crumble",
|
|
16297
|
+
[noise({ f: 900, f2: 200, d: 0.6, vol: 0.9, wet: 0.3 }), ...many(5, () => noise({ type: "bandpass", f: [500, 1400], q: 2, d: 0.05, vol: 0.4, delay: [0.1, 0.5] }))],
|
|
16298
|
+
"L1365 crumble"
|
|
16299
|
+
),
|
|
16300
|
+
// `thud(x, z, v)`: `v` is the `scale` param (default 1)
|
|
16301
|
+
scene("thud", [tone({ f: 75, f2: 38, d: 0.22, vol: { param: "scale", scale: 0.6 } }), noise({ f: 420, d: 0.16, vol: { param: "scale", scale: 0.45 } })], "L1358 thud", { scale: 1 }),
|
|
16302
|
+
// `hiss(x, z, d)`: `seconds` is the param `d` (default 1.2)
|
|
16303
|
+
scene("hiss", [noise({ type: "highpass", f: 3500, a: 0.05, d: { param: "seconds" }, vol: 0.35 })], "L1366 hiss", { seconds: 1.2 }),
|
|
16304
|
+
scene("boom", boomLayers(false), "L1367 boom"),
|
|
16305
|
+
scene("boom_big", boomLayers(true), "L1367 boom (big)"),
|
|
16306
|
+
scene("burst", [noise({ f: 700, f2: 120, d: 0.7, vol: 1.4, wet: 0.5 }), noise({ type: "highpass", f: 3e3, a: 0.05, d: 1.2, vol: 0.4, delay: 0.1 })], "L1357 burst"),
|
|
16307
|
+
scene("crack", [noise({ type: "highpass", f: 1800, d: 0.05, vol: 0.8 }), tone({ f: 420, f2: 180, d: 0.06, vol: 0.4 })], "L1354 crack")
|
|
16308
|
+
];
|
|
16309
|
+
var EXPLOSION_BOOM = {
|
|
16310
|
+
id: "explosion_boom",
|
|
16311
|
+
bus: "world",
|
|
16312
|
+
order: "boom",
|
|
16313
|
+
duration: 1.6,
|
|
16314
|
+
tail: 0.1,
|
|
16315
|
+
layers: [
|
|
16316
|
+
{
|
|
16317
|
+
source: { kind: "osc", wave: "sine", frequency: { from: 70, to: 28, curve: "exp", over: 1.2 } },
|
|
16318
|
+
envelope: [
|
|
16319
|
+
[0, 1, "set"],
|
|
16320
|
+
["1d", 1e-3, "exp", "fixed"]
|
|
16321
|
+
]
|
|
16322
|
+
}
|
|
16323
|
+
],
|
|
16324
|
+
falloff: DISTANCE_CURVES.game_boom,
|
|
16325
|
+
sources: ["jogo:L68278-68285 boom"]
|
|
16326
|
+
};
|
|
16327
|
+
var BURN_LOOP = {
|
|
16328
|
+
id: "burn_loop",
|
|
16329
|
+
bus: "world",
|
|
16330
|
+
order: "chain_loop",
|
|
16331
|
+
source: { kind: "noise", loop: true },
|
|
16332
|
+
filters: { mode: "serial", bank: [{ type: "bandpass", frequency: { set: 1800 }, q: 0.6 }] },
|
|
16333
|
+
volume: 0,
|
|
16334
|
+
sources: ["cena:L1316 fire loop (silent in the page: its gain is never raised)", "sdk: the level while burning"]
|
|
16335
|
+
};
|
|
16336
|
+
var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, EXPLOSION_BOOM], loops: [BURN_LOOP] };
|
|
16337
|
+
var DESTRUCTION_SOUND_KEYS = {
|
|
16338
|
+
art_scene: {
|
|
16339
|
+
impact_metal: { sound: "clang" },
|
|
16340
|
+
impact_wood: { sound: "wood_hit" },
|
|
16341
|
+
impact_glass: { sound: "ping" },
|
|
16342
|
+
impact_masonry: { sound: "chip" },
|
|
16343
|
+
impact_soft: { sound: "thud" },
|
|
16344
|
+
shatter_glass: { sound: "glass_shatter" },
|
|
16345
|
+
crumble: { sound: "crumble" },
|
|
16346
|
+
splinter: { sound: "crack" },
|
|
16347
|
+
ricochet: { sound: "ping" },
|
|
16348
|
+
unscrew: { sound: "chip" },
|
|
16349
|
+
detach: { sound: "thunk" },
|
|
16350
|
+
ignite: { sound: "hiss", params: { seconds: 0.5 } },
|
|
16351
|
+
extinguish: { sound: "hiss" },
|
|
16352
|
+
melt_sizzle: { sound: "hiss", params: { seconds: 0.6 } },
|
|
16353
|
+
explode: { sound: "boom", above: { volume: 0.9, sound: "boom_big" } }
|
|
16354
|
+
},
|
|
16355
|
+
game: {
|
|
16356
|
+
impact_metal: { sound: "sfx_impact" },
|
|
16357
|
+
impact_wood: { sound: "sfx_wood" },
|
|
16358
|
+
impact_glass: { sound: "sfx_glass" },
|
|
16359
|
+
impact_masonry: { sound: "sfx_impact" },
|
|
16360
|
+
impact_soft: { sound: "sfx_flesh" },
|
|
16361
|
+
shatter_glass: { sound: "sfx_glass" },
|
|
16362
|
+
crumble: { sound: "sfx_impact" },
|
|
16363
|
+
splinter: { sound: "sfx_wood" },
|
|
16364
|
+
explode: { sound: "explosion_boom" }
|
|
16365
|
+
}
|
|
16366
|
+
};
|
|
16367
|
+
var DEFAULT_FIRE = { perFire: 0.05, max: 0.2 };
|
|
16368
|
+
function createDestructionSound(engine, options = {}) {
|
|
16369
|
+
engine.register(DESTRUCTION_SOUND_BANK);
|
|
16370
|
+
engine.register(GAME_SFX_BANK);
|
|
16371
|
+
const fire = { ...DEFAULT_FIRE, ...options.fire };
|
|
16372
|
+
const rules = { ...DESTRUCTION_SOUND_KEYS.art_scene, ...options.style === "game" ? DESTRUCTION_SOUND_KEYS.game : {}, ...options.keys };
|
|
16373
|
+
let loop;
|
|
16374
|
+
let burning = 0;
|
|
16375
|
+
const setFire = () => {
|
|
16376
|
+
if (options.burnLoop === false) return;
|
|
16377
|
+
if (burning <= 0) {
|
|
16378
|
+
loop?.stop();
|
|
16379
|
+
loop = void 0;
|
|
16380
|
+
return;
|
|
16381
|
+
}
|
|
16382
|
+
loop ?? (loop = engine.loop("burn_loop"));
|
|
16383
|
+
loop.set({ volume: Math.min(fire.max, fire.perFire * burning) });
|
|
16384
|
+
};
|
|
16385
|
+
return {
|
|
16386
|
+
onSound(key, point, volume) {
|
|
16387
|
+
if (key === "burn_loop") {
|
|
16388
|
+
burning += 1;
|
|
16389
|
+
setFire();
|
|
16390
|
+
return;
|
|
16391
|
+
}
|
|
16392
|
+
if (key === "burn_end") {
|
|
16393
|
+
if (burning > 0) burning -= 1;
|
|
16394
|
+
setFire();
|
|
16395
|
+
return;
|
|
16396
|
+
}
|
|
16397
|
+
const rule = rules[key];
|
|
16398
|
+
if (!rule) return;
|
|
16399
|
+
const sound = rule.above && volume >= rule.above.volume ? rule.above.sound : rule.sound;
|
|
16400
|
+
engine.play(sound, { position: point, volume: volume * (rule.volumeScale ?? 1), ...rule.params ? { params: rule.params } : {} });
|
|
16401
|
+
},
|
|
16402
|
+
rule: (key) => rules[key] ?? void 0,
|
|
16403
|
+
burning: () => burning,
|
|
16404
|
+
dispose() {
|
|
16405
|
+
loop?.stop();
|
|
16406
|
+
loop = void 0;
|
|
16407
|
+
burning = 0;
|
|
16408
|
+
}
|
|
16409
|
+
};
|
|
16410
|
+
}
|
|
14447
16411
|
|
|
14448
16412
|
// ../game-weapons/dist/index.js
|
|
14449
16413
|
var __defProp4 = Object.defineProperty;
|
|
14450
16414
|
var __defNormalProp3 = (obj, key, value) => key in obj ? __defProp4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
14451
16415
|
var __publicField3 = (obj, key, value) => __defNormalProp3(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
14452
|
-
var
|
|
16416
|
+
var createRng4 = (seed) => {
|
|
14453
16417
|
let a = seed >>> 0;
|
|
14454
16418
|
return () => {
|
|
14455
16419
|
a = a + 1831565813 >>> 0;
|
|
@@ -26598,9 +28562,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
26598
28562
|
this.caliber = init.caliber;
|
|
26599
28563
|
this.action = init.def.action;
|
|
26600
28564
|
this.rules = init.rules;
|
|
26601
|
-
this.rng =
|
|
26602
|
-
this.faultRng =
|
|
26603
|
-
this.fuseRng =
|
|
28565
|
+
this.rng = createRng4(init.seed);
|
|
28566
|
+
this.faultRng = createRng4((init.seed ^ FAULT_RNG_SALT) >>> 0);
|
|
28567
|
+
this.fuseRng = createRng4((init.seed ^ FUSE_RNG_SALT) >>> 0);
|
|
26604
28568
|
this.explosive = init.explosive ?? null;
|
|
26605
28569
|
this._safety = init.rules.startSafe;
|
|
26606
28570
|
this._condition = findById(init.rules.conditions, init.condition ?? init.rules.defaultCondition, "condition");
|
|
@@ -26862,7 +28826,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
26862
28826
|
this.isCompatible = init.isCompatible;
|
|
26863
28827
|
this.makeUnderbarrel = init.makeUnderbarrel;
|
|
26864
28828
|
this.seed = init.seed;
|
|
26865
|
-
this.breakRng =
|
|
28829
|
+
this.breakRng = createRng4((init.seed ^ BREAK_RNG_SALT) >>> 0);
|
|
26866
28830
|
this.fitted = (init.snapshot?.attachments ?? init.def.fitted ?? []).map(copyOf);
|
|
26867
28831
|
if (init.snapshot) this.stockState = init.snapshot.stock;
|
|
26868
28832
|
this.fitted = this.fitted.map((part) => part.shotsLeft === void 0 ? this.withDurability(part) : part);
|
|
@@ -27121,10 +29085,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27121
29085
|
__publicField3(this, "lastRoundAt", Number.NEGATIVE_INFINITY);
|
|
27122
29086
|
}
|
|
27123
29087
|
/** When the barrels of a rotary gun are at speed: `upSeconds` after the pull began, and at once while they still spin from the last round. */
|
|
27124
|
-
spunUpAt(spin, start,
|
|
29088
|
+
spunUpAt(spin, start, end3) {
|
|
27125
29089
|
if (!spin || start - this.lastRoundAt <= spin.holdSeconds + EPS3) return 0;
|
|
27126
29090
|
if (this.spinSince === void 0 || start - this.spinFrameEnd > SPIN_PULL_GAP) this.spinSince = start;
|
|
27127
|
-
this.spinFrameEnd =
|
|
29091
|
+
this.spinFrameEnd = end3;
|
|
27128
29092
|
return this.spinSince + spin.upSeconds;
|
|
27129
29093
|
}
|
|
27130
29094
|
oncePerPull(held) {
|
|
@@ -27133,7 +29097,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27133
29097
|
this.latched = true;
|
|
27134
29098
|
return true;
|
|
27135
29099
|
}
|
|
27136
|
-
fireFirearm(a, origin, dir, start,
|
|
29100
|
+
fireFirearm(a, origin, dir, start, end3, held, source) {
|
|
27137
29101
|
if (this._reloading) {
|
|
27138
29102
|
if (this.loadStep !== null && this._ammo > 0) this.stopReload(start);
|
|
27139
29103
|
else return result("reloading");
|
|
@@ -27143,10 +29107,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27143
29107
|
this.emit({ type: "empty", at: start });
|
|
27144
29108
|
return result("empty");
|
|
27145
29109
|
}
|
|
27146
|
-
const t0 = Math.max(start, this.readyAt, this.spunUpAt(a.spin, start,
|
|
27147
|
-
if (t0 >
|
|
29110
|
+
const t0 = Math.max(start, this.readyAt, this.spunUpAt(a.spin, start, end3));
|
|
29111
|
+
if (t0 > end3 + EPS3) return result("cooldown");
|
|
27148
29112
|
const res = result("ok");
|
|
27149
|
-
const times = planRounds(a, this.mode, this._ammo, t0,
|
|
29113
|
+
const times = planRounds(a, this.mode, this._ammo, t0, end3);
|
|
27150
29114
|
let lastAt = t0;
|
|
27151
29115
|
for (const t of times) {
|
|
27152
29116
|
const fault = rollFault({ def: this.def, caliber: this.caliber, condition: this._condition, ammoQuality: this._ammoQuality, heat: this._heat, rules: this.faultRules(), random: this.faultRng });
|
|
@@ -27262,7 +29226,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27262
29226
|
this.pend = { src: "trigger", amt: d };
|
|
27263
29227
|
}
|
|
27264
29228
|
const start = this.clock - overlap;
|
|
27265
|
-
const
|
|
29229
|
+
const end3 = start + d;
|
|
27266
29230
|
this.settle(start);
|
|
27267
29231
|
const dir = normalize2(direction);
|
|
27268
29232
|
let res;
|
|
@@ -27273,10 +29237,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27273
29237
|
else if (launcher) res = launcher.trigger(origin, dir, d, held, source);
|
|
27274
29238
|
else switch (a.kind) {
|
|
27275
29239
|
case "firearm":
|
|
27276
|
-
res = this.fireFirearm(a, origin, dir, start,
|
|
29240
|
+
res = this.fireFirearm(a, origin, dir, start, end3, held, source);
|
|
27277
29241
|
break;
|
|
27278
29242
|
case "melee":
|
|
27279
|
-
res = this.fireMelee(a, origin, dir, start,
|
|
29243
|
+
res = this.fireMelee(a, origin, dir, start, end3);
|
|
27280
29244
|
break;
|
|
27281
29245
|
case "flame":
|
|
27282
29246
|
case "heat":
|
|
@@ -27286,7 +29250,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27286
29250
|
res = this.fireExtinguisher(a, origin, dir, d);
|
|
27287
29251
|
break;
|
|
27288
29252
|
case "throwable":
|
|
27289
|
-
res = this.fireThrowable(a, origin, dir, held, start,
|
|
29253
|
+
res = this.fireThrowable(a, origin, dir, held, start, end3);
|
|
27290
29254
|
break;
|
|
27291
29255
|
case "launcher":
|
|
27292
29256
|
res = this.fireLauncher(a, origin, dir, start, held);
|
|
@@ -27298,7 +29262,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27298
29262
|
res = this.oncePerPull(held) ? result("ok", { uses: [{ kind: "hand", reach: a.reach, origin, direction: dir }] }) : result("cooldown");
|
|
27299
29263
|
break;
|
|
27300
29264
|
}
|
|
27301
|
-
this.advance(Math.max(0,
|
|
29265
|
+
this.advance(Math.max(0, end3 - this.clock));
|
|
27302
29266
|
this.settle(this.clock);
|
|
27303
29267
|
return res;
|
|
27304
29268
|
}
|
|
@@ -27330,9 +29294,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27330
29294
|
this.emit({ type: "shot", shots: 0, rounds: 1, mode: this.mode, at: t });
|
|
27331
29295
|
return result("ok", { uses: launcherUses(a, this.explosive, origin, dir), recoil: [a.recoil[0], a.recoil[1]], noise: a.noise ?? 0, rounds: 1, roundTimes: [t - start] });
|
|
27332
29296
|
}
|
|
27333
|
-
fireMelee(a, origin, dir, start,
|
|
29297
|
+
fireMelee(a, origin, dir, start, end3) {
|
|
27334
29298
|
const t0 = Math.max(start, this.readyAt);
|
|
27335
|
-
if (t0 >
|
|
29299
|
+
if (t0 > end3 + EPS3) return result("cooldown");
|
|
27336
29300
|
this.readyAt = t0 + 60 / a.rpm;
|
|
27337
29301
|
const use = { kind: "melee", damage: a.damage, energy: a.energy, radius: a.radius, reach: a.reach, origin, direction: dir };
|
|
27338
29302
|
return result("ok", { uses: [use], noise: a.noise });
|
|
@@ -27366,8 +29330,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27366
29330
|
return result("ok", { uses: [throwableUse({ action: a, explosive: this.explosive, origin, direction: dir, fuse, rules: this.rules.explosives, ...extra })], noise: a.noise });
|
|
27367
29331
|
}
|
|
27368
29332
|
/** A timer throwable is cooked from the pull; impact, remote and wire ones (and a long fuse that sets down) act at once. */
|
|
27369
|
-
fireThrowable(a, origin, dir, held, start,
|
|
27370
|
-
if (this.cook) return this.cookStep(a, origin, dir,
|
|
29333
|
+
fireThrowable(a, origin, dir, held, start, end3) {
|
|
29334
|
+
if (this.cook) return this.cookStep(a, origin, dir, end3);
|
|
27371
29335
|
if (!this.oncePerPull(held)) return result("cooldown");
|
|
27372
29336
|
if (this._ammo <= 0) return result("empty");
|
|
27373
29337
|
const fuse = this.sampleThrowFuse(a);
|
|
@@ -27377,7 +29341,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
27377
29341
|
}
|
|
27378
29342
|
this.cook = { start, fuse, origin, direction: dir };
|
|
27379
29343
|
this.emit({ type: "cook", phase: "start", fuse, at: start });
|
|
27380
|
-
return this.cookStep(a, origin, dir,
|
|
29344
|
+
return this.cookStep(a, origin, dir, end3);
|
|
27381
29345
|
}
|
|
27382
29346
|
/** The trigger stays down on a cooked throwable: nothing happens until the fuse runs out in the hand. */
|
|
27383
29347
|
cookStep(a, origin, dir, now) {
|
|
@@ -29734,17 +31698,17 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
29734
31698
|
}
|
|
29735
31699
|
}
|
|
29736
31700
|
const start = time;
|
|
29737
|
-
const
|
|
31701
|
+
const end3 = Math.min(def.duration, time + Math.max(0, dt));
|
|
29738
31702
|
for (let i = 0; i < keys.length && !done; i++) {
|
|
29739
31703
|
const key = keys[i];
|
|
29740
31704
|
const spanStart = key.t;
|
|
29741
31705
|
const spanEnd = i + 1 < keys.length ? keys[i + 1].t : def.duration;
|
|
29742
|
-
if ((key.action === "open" || key.action === "close") && !fired.has(i) &&
|
|
31706
|
+
if ((key.action === "open" || key.action === "close") && !fired.has(i) && end3 >= spanStart) {
|
|
29743
31707
|
fired.add(i);
|
|
29744
31708
|
if (key.action === "open") world.open(containerId);
|
|
29745
31709
|
else world.close(containerId);
|
|
29746
31710
|
}
|
|
29747
|
-
const overlap = Math.min(
|
|
31711
|
+
const overlap = Math.min(end3, spanEnd) - Math.max(start, spanStart);
|
|
29748
31712
|
if (overlap <= 0) continue;
|
|
29749
31713
|
if (key.action === "drink") {
|
|
29750
31714
|
const ev = world.drink(containerId, (key.rate ?? DEFAULT_DRINK_RATE) * overlap);
|
|
@@ -29758,7 +31722,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
29758
31722
|
}
|
|
29759
31723
|
}
|
|
29760
31724
|
if (!done) {
|
|
29761
|
-
time =
|
|
31725
|
+
time = end3;
|
|
29762
31726
|
done = time >= def.duration;
|
|
29763
31727
|
}
|
|
29764
31728
|
follow(dt);
|
|
@@ -30360,8 +32324,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
30360
32324
|
bedHeight = roadY * 1e3 + z * 14 - (x - params.roadSlopeAnchorX) * 5;
|
|
30361
32325
|
roughness = 0.55;
|
|
30362
32326
|
}
|
|
30363
|
-
const
|
|
30364
|
-
bed[index] = bedHeight + (
|
|
32327
|
+
const noise2 = valueNoise(column * 0.35, row * 0.35) * 0.6 + valueNoise(column * 1.3, row * 1.3) * 0.4;
|
|
32328
|
+
bed[index] = bedHeight + (noise2 - 0.5) * roughness * 2;
|
|
30365
32329
|
vertical[index] = region === 0 || region === 2 ? 0.95 : 1;
|
|
30366
32330
|
pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, row * 0.21) * 0.5 + hashCell(column, row) * 0.5) * (region === 3 ? 1.25 : 1);
|
|
30367
32331
|
}
|
|
@@ -30518,8 +32482,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
30518
32482
|
for (let column = 0; column < columns; column++) {
|
|
30519
32483
|
const index = (region.firstRow + row) * columns + column;
|
|
30520
32484
|
const centre = add2(add2(region.origin, scale(columnAxis, (column + 0.5) * cell)), scale(region.rowAxis, (row + 0.5) * cell));
|
|
30521
|
-
const
|
|
30522
|
-
bed[index] = centre[1] * 1e3 + (
|
|
32485
|
+
const noise2 = valueNoise(column * 0.35, (region.firstRow + row) * 0.35) * 0.6 + valueNoise(column * 1.3, (region.firstRow + row) * 1.3) * 0.4;
|
|
32486
|
+
bed[index] = centre[1] * 1e3 + (noise2 - 0.5) * strip.roughness * 2;
|
|
30523
32487
|
vertical[index] = region.isVertical ? 0.95 : 1;
|
|
30524
32488
|
pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, (region.firstRow + row) * 0.21) * 0.5 + valueNoise(column * 0.9, row * 0.9) * 0.5);
|
|
30525
32489
|
}
|
|
@@ -31432,8 +33396,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
31432
33396
|
const b2x = ty * b1z - tz * b1y;
|
|
31433
33397
|
const b2y = tz * b1x - tx * b1z;
|
|
31434
33398
|
const b2z = tx * b1y - ty * b1x;
|
|
31435
|
-
const
|
|
31436
|
-
const r = this.radiusAt(state, gravity, t, r0, speed0) *
|
|
33399
|
+
const end3 = Math.min(1, 0.35 + Math.min(k, 1 - k) * 8);
|
|
33400
|
+
const r = this.radiusAt(state, gravity, t, r0, speed0) * end3;
|
|
31437
33401
|
for (let j = 0; j < sides; j++) {
|
|
31438
33402
|
const a = j / sides * Math.PI * 2;
|
|
31439
33403
|
const c = Math.cos(a);
|
|
@@ -31467,15 +33431,15 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
31467
33431
|
};
|
|
31468
33432
|
var rendererCount2 = 0;
|
|
31469
33433
|
function createFluidRenderer(THREE, options) {
|
|
31470
|
-
const { scene, fluids } = options;
|
|
33434
|
+
const { scene: scene2, fluids } = options;
|
|
31471
33435
|
const quality = options.quality ?? "medium";
|
|
31472
33436
|
const profile = profileOf(quality);
|
|
31473
33437
|
const materials = new MaterialCache(THREE, options.envMap ?? null);
|
|
31474
33438
|
const root = new THREE.Group();
|
|
31475
33439
|
root.name = "fluid_renderer";
|
|
31476
|
-
|
|
33440
|
+
scene2.add(root);
|
|
31477
33441
|
const vfx = options.vfx ?? createVfx({ profile: quality, gravity: fluids.gravity, sprites: { capacity: profile.maxParticles } });
|
|
31478
|
-
const ownVfx = options.vfx ? void 0 : createVfxRenderer(THREE, { scene, camera: options.camera ?? new THREE.PerspectiveCamera(), vfx, pointScale: options.pointScale ?? 600 });
|
|
33442
|
+
const ownVfx = options.vfx ? void 0 : createVfxRenderer(THREE, { scene: scene2, camera: options.camera ?? new THREE.PerspectiveCamera(), vfx, pointScale: options.pointScale ?? 600 });
|
|
31479
33443
|
const particles = new FluidSprites(vfx, `fluids-${++rendererCount2}`);
|
|
31480
33444
|
const dropGeometry = new THREE.SphereGeometry(1, 8, 6);
|
|
31481
33445
|
const bodies = /* @__PURE__ */ new Map();
|
|
@@ -31612,7 +33576,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
|
|
|
31612
33576
|
ownVfx?.dispose();
|
|
31613
33577
|
dropGeometry.dispose();
|
|
31614
33578
|
materials.dispose();
|
|
31615
|
-
|
|
33579
|
+
scene2.remove(root);
|
|
31616
33580
|
root.clear();
|
|
31617
33581
|
},
|
|
31618
33582
|
stats() {
|
|
@@ -32683,6 +34647,16 @@ void main(){
|
|
|
32683
34647
|
"ui.slow": "Slow motion 0.25\xD7",
|
|
32684
34648
|
"ui.fast": "Time \xD710",
|
|
32685
34649
|
"ui.tap": "Tap",
|
|
34650
|
+
"ui.blast.shelter": "Shelter",
|
|
34651
|
+
"ui.blast.fragmentHits": "Fragments",
|
|
34652
|
+
"ui.blast.confinement": "Confinement",
|
|
34653
|
+
"ui.blast.shelter.hint": "Blasts: walls and other parts between the blast and the target take their share",
|
|
34654
|
+
"ui.blast.fragmentHits.hint": "Blasts: the pieces of what breaks fly on and hit what is behind",
|
|
34655
|
+
"ui.blast.confinement.hint": "Blasts: pressure builds up in an enclosed room",
|
|
34656
|
+
"metric.blast_shelter": "Blast reaching (%)",
|
|
34657
|
+
"metric.blast_confinement": "Blast pressure (x)",
|
|
34658
|
+
"metric.blast_fragments": "Fragment hits",
|
|
34659
|
+
"act.blast_report": "Blast: {shelter}% reached the most sheltered target, pressure x{confinement}, {fragments} fragment hits, {n} targets",
|
|
32686
34660
|
"ui.quality": "Quality",
|
|
32687
34661
|
"ui.quality.low": "low",
|
|
32688
34662
|
"ui.quality.medium": "medium",
|
|
@@ -33008,6 +34982,16 @@ void main(){
|
|
|
33008
34982
|
"ui.slow": "C\xE2mera lenta 0,25\xD7",
|
|
33009
34983
|
"ui.fast": "Tempo \xD710",
|
|
33010
34984
|
"ui.tap": "Torneira",
|
|
34985
|
+
"ui.blast.shelter": "Abrigo",
|
|
34986
|
+
"ui.blast.fragmentHits": "Estilha\xE7os",
|
|
34987
|
+
"ui.blast.confinement": "Confinamento",
|
|
34988
|
+
"ui.blast.shelter.hint": "Explos\xF5es: paredes e outras partes entre a explos\xE3o e o alvo ficam com a sua parte",
|
|
34989
|
+
"ui.blast.fragmentHits.hint": "Explos\xF5es: os peda\xE7os do que quebra voam e acertam o que est\xE1 atr\xE1s",
|
|
34990
|
+
"ui.blast.confinement.hint": "Explos\xF5es: a press\xE3o sobe num ambiente fechado",
|
|
34991
|
+
"metric.blast_shelter": "Explos\xE3o que chegou (%)",
|
|
34992
|
+
"metric.blast_confinement": "Press\xE3o da explos\xE3o (x)",
|
|
34993
|
+
"metric.blast_fragments": "Estilha\xE7os que acertaram",
|
|
34994
|
+
"act.blast_report": "Explos\xE3o: {shelter}% chegou ao alvo mais abrigado, press\xE3o x{confinement}, {fragments} estilha\xE7os acertaram, {n} alvos",
|
|
33011
34995
|
"ui.quality": "Qualidade",
|
|
33012
34996
|
"ui.quality.low": "baixa",
|
|
33013
34997
|
"ui.quality.medium": "m\xE9dia",
|
|
@@ -33454,10 +35438,10 @@ void main(){
|
|
|
33454
35438
|
}
|
|
33455
35439
|
return { name: "shelf", parts, joints, tags: ["furniture"] };
|
|
33456
35440
|
};
|
|
33457
|
-
function createArena(THREE, size,
|
|
35441
|
+
function createArena(THREE, size, scene2) {
|
|
33458
35442
|
const group = new THREE.Group();
|
|
33459
35443
|
group.name = "test-room-arena";
|
|
33460
|
-
|
|
35444
|
+
scene2.background = new THREE.Color(10466504);
|
|
33461
35445
|
group.add(new THREE.HemisphereLight(13624319, 7037527, 0.75));
|
|
33462
35446
|
const sun = new THREE.DirectionalLight(16777215, 2.1);
|
|
33463
35447
|
sun.position.set(7, 13, 8);
|
|
@@ -33730,9 +35714,19 @@ void main(){
|
|
|
33730
35714
|
|
|
33731
35715
|
// src/explosives.ts
|
|
33732
35716
|
var CONE_BLAST_RADIUS = 0.5;
|
|
35717
|
+
var summarizeBlast = (results) => {
|
|
35718
|
+
const reports = results.flatMap((r) => r.blast ? [r.blast] : []);
|
|
35719
|
+
if (reports.length === 0) return void 0;
|
|
35720
|
+
return {
|
|
35721
|
+
shelter: Math.min(...reports.map((r) => r.shelter)),
|
|
35722
|
+
confinement: reports[0].confinement,
|
|
35723
|
+
fragmentHits: reports.reduce((n, r) => n + r.fragmentHits, 0),
|
|
35724
|
+
targets: reports.length
|
|
35725
|
+
};
|
|
35726
|
+
};
|
|
33733
35727
|
var toArray = (vector) => [vector.x, vector.y, vector.z];
|
|
33734
35728
|
function createExplosives(context) {
|
|
33735
|
-
const { THREE, scene, camera: camera2, world, renderer, arsenal, translate, format, action } = context;
|
|
35729
|
+
const { THREE, scene: scene2, camera: camera2, world, renderer, arsenal, translate, format, action } = context;
|
|
33736
35730
|
const projectiles = [];
|
|
33737
35731
|
const meshes = [];
|
|
33738
35732
|
const up = new THREE.Vector3(0, 1, 0);
|
|
@@ -33745,7 +35739,7 @@ void main(){
|
|
|
33745
35739
|
const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color, roughness: 0.6, metalness }));
|
|
33746
35740
|
mesh.castShadow = true;
|
|
33747
35741
|
mesh.position.set(at3[0], at3[1], at3[2]);
|
|
33748
|
-
|
|
35742
|
+
scene2.add(mesh);
|
|
33749
35743
|
meshes.push(mesh);
|
|
33750
35744
|
return mesh;
|
|
33751
35745
|
};
|
|
@@ -33800,7 +35794,7 @@ void main(){
|
|
|
33800
35794
|
flag.position.y = 0.46;
|
|
33801
35795
|
marker.add(stake, flag);
|
|
33802
35796
|
marker.position.set(at3[0] + 0.12, at3[1] - 0.03, at3[2]);
|
|
33803
|
-
|
|
35797
|
+
scene2.add(marker);
|
|
33804
35798
|
meshes.push(marker);
|
|
33805
35799
|
return marker;
|
|
33806
35800
|
};
|
|
@@ -33808,7 +35802,7 @@ void main(){
|
|
|
33808
35802
|
const geometry = new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(...from), new THREE.Vector3(...to)]);
|
|
33809
35803
|
const line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 15262920 }));
|
|
33810
35804
|
line.name = "charge_wire";
|
|
33811
|
-
|
|
35805
|
+
scene2.add(line);
|
|
33812
35806
|
meshes.push(line);
|
|
33813
35807
|
return line;
|
|
33814
35808
|
};
|
|
@@ -33876,17 +35870,27 @@ void main(){
|
|
|
33876
35870
|
if (box2.isEmpty()) box2.expandByPoint(object.position);
|
|
33877
35871
|
const at3 = mostExposedPoint(box2, detonation);
|
|
33878
35872
|
if (!at3) continue;
|
|
33879
|
-
|
|
35873
|
+
const results = world.explode(at3, detonation.energy * blastEffect(detonation, at3).falloff, CONE_BLAST_RADIUS, "player", context.blast());
|
|
35874
|
+
reached += results.length;
|
|
35875
|
+
told(results);
|
|
33880
35876
|
}
|
|
33881
35877
|
return reached;
|
|
33882
35878
|
};
|
|
35879
|
+
const told = (results) => {
|
|
35880
|
+
const summary = summarizeBlast(results);
|
|
35881
|
+
if (!summary) return;
|
|
35882
|
+
context.blasted(summary);
|
|
35883
|
+
action("blast_report", translate("act.blast_report", { shelter: format(summary.shelter * 100, 0), confinement: format(summary.confinement, 2), fragments: summary.fragmentHits, n: summary.targets }), { data: { ...summary } });
|
|
35884
|
+
};
|
|
33883
35885
|
const detonate = (projectile, detonation) => {
|
|
33884
35886
|
const weaponName = context.toolName(projectile.weapon);
|
|
33885
35887
|
const reach = Math.max(detonation.injuryRadius, detonation.lethalRadius);
|
|
33886
|
-
const
|
|
35888
|
+
const results = detonation.energy <= 0 || detonation.cone ? [] : world.explode(detonation.point, detonation.energy, reach, "player", context.blast());
|
|
35889
|
+
const reached = detonation.energy <= 0 ? 0 : detonation.cone ? coneBlast(detonation) : results.length;
|
|
35890
|
+
if (!detonation.cone) told(results);
|
|
33887
35891
|
if (detonation.energy > 0) {
|
|
33888
35892
|
const isPlayed = context.effects.explosion(detonation.explosive, detonation.point, { direction: detonation.direction }) !== null;
|
|
33889
|
-
renderer.explode(detonation.point, detonation.energy, reach, { visual: !isPlayed });
|
|
35893
|
+
renderer.explode(detonation.point, detonation.energy, reach, { visual: !isPlayed, report: results });
|
|
33890
35894
|
}
|
|
33891
35895
|
if (detonation.stunSeconds !== void 0) renderer.emit("flash", detonation.point, [0, 1, 0], 1, 1);
|
|
33892
35896
|
const onPlayer = blastEffect(detonation, toArray(camera2.position));
|
|
@@ -34183,7 +36187,7 @@ void main(){
|
|
|
34183
36187
|
}
|
|
34184
36188
|
|
|
34185
36189
|
// src/metrics.ts
|
|
34186
|
-
var ROOM_METRICS = ["fps", "frame_ms", "draw_calls", "triangles", "objects", "fragments", "particles", "decals"];
|
|
36190
|
+
var ROOM_METRICS = ["fps", "frame_ms", "draw_calls", "triangles", "objects", "fragments", "particles", "decals", "blast_shelter", "blast_confinement", "blast_fragments"];
|
|
34187
36191
|
var WINDOW_S = 0.5;
|
|
34188
36192
|
function createRoomMetrics(sources) {
|
|
34189
36193
|
const wanted = new Set(sources.ids);
|
|
@@ -34220,6 +36224,12 @@ void main(){
|
|
|
34220
36224
|
const render = ownsRender ? { calls, triangles } : gl.info?.render;
|
|
34221
36225
|
put("draw_calls", render?.calls);
|
|
34222
36226
|
put("triangles", render?.triangles);
|
|
36227
|
+
const blast = sources.blast?.();
|
|
36228
|
+
if (blast) {
|
|
36229
|
+
put("blast_shelter", blast.shelter * 100);
|
|
36230
|
+
put("blast_confinement", blast.confinement);
|
|
36231
|
+
put("blast_fragments", blast.fragmentHits);
|
|
36232
|
+
}
|
|
34223
36233
|
if (!["objects", "fragments", "particles", "decals"].some((id) => wanted.has(id))) return out;
|
|
34224
36234
|
const destruction = sources.destruction.stats();
|
|
34225
36235
|
const fluids = sources.fluids.stats();
|
|
@@ -34483,13 +36493,13 @@ void main(){
|
|
|
34483
36493
|
if (!g || g.done) break;
|
|
34484
36494
|
g.done = true;
|
|
34485
36495
|
const [a, b] = ev.parts;
|
|
34486
|
-
const
|
|
36496
|
+
const many2 = g.list.length > 1;
|
|
34487
36497
|
c.log.add({
|
|
34488
36498
|
channel: "event",
|
|
34489
36499
|
type: "detach",
|
|
34490
36500
|
objectId: ev.object,
|
|
34491
36501
|
itemId: c.itemIdOf(ev.object),
|
|
34492
|
-
text:
|
|
36502
|
+
text: many2 ? t(`ev.detach.many.${ev.by === "tool" ? "tool" : "break"}`, { obj: c.nameOf(ev.object), joints: joints(g.list.length), pieces: pieces(g.groups) }) : t("ev.detach", { obj: c.nameOf(ev.object), a, b, by: t(`by.${ev.by}`), pieces: pieces(g.groups) }),
|
|
34493
36503
|
data: { joint: ev.joint, joints: g.list.map((j) => j.joint), parts: ev.parts, by: ev.by, groups: g.groups }
|
|
34494
36504
|
});
|
|
34495
36505
|
break;
|
|
@@ -35490,23 +37500,23 @@ void main(){
|
|
|
35490
37500
|
}
|
|
35491
37501
|
if (sheet.timeline) {
|
|
35492
37502
|
const { segments, ticks, metric } = sheet.timeline;
|
|
35493
|
-
const
|
|
37503
|
+
const end3 = Math.max(1e-3, ...segments.map((segment) => segment.end), ...ticks);
|
|
35494
37504
|
const strip = mk("div", "tr-timeline");
|
|
35495
37505
|
for (const segment of segments) {
|
|
35496
37506
|
const bar2 = mk("div", "tr-segment", segment.name);
|
|
35497
|
-
bar2.style.left = `${segment.start /
|
|
35498
|
-
bar2.style.width = `${(segment.end - segment.start) /
|
|
37507
|
+
bar2.style.left = `${segment.start / end3 * 100}%`;
|
|
37508
|
+
bar2.style.width = `${(segment.end - segment.start) / end3 * 100}%`;
|
|
35499
37509
|
bar2.style.background = segment.color;
|
|
35500
37510
|
strip.appendChild(bar2);
|
|
35501
37511
|
}
|
|
35502
37512
|
for (const tick of ticks) {
|
|
35503
37513
|
const mark = mk("u", "tr-tick");
|
|
35504
|
-
mark.style.left = `${tick /
|
|
37514
|
+
mark.style.left = `${tick / end3 * 100}%`;
|
|
35505
37515
|
strip.appendChild(mark);
|
|
35506
37516
|
}
|
|
35507
37517
|
const head = mk("s", "tr-playhead");
|
|
35508
37518
|
strip.appendChild(head);
|
|
35509
|
-
playhead = { node: head, metric, end };
|
|
37519
|
+
playhead = { node: head, metric, end: end3 };
|
|
35510
37520
|
card.appendChild(strip);
|
|
35511
37521
|
}
|
|
35512
37522
|
if (sheet.note) card.appendChild(mk("p", "sub", sheet.note));
|
|
@@ -35901,6 +37911,8 @@ void main(){
|
|
|
35901
37911
|
o.onFast(fast);
|
|
35902
37912
|
});
|
|
35903
37913
|
const tapBtn = ctl(t("ui.tap"), () => o.onTap());
|
|
37914
|
+
const blastBtns = ["shelter", "fragmentHits", "confinement"].map((key) => ({ key, b: ctl(t(`ui.blast.${key}`), () => o.onBlast(key)) }));
|
|
37915
|
+
for (const { key, b } of blastBtns) b.title = t(`ui.blast.${key}.hint`);
|
|
35904
37916
|
const qSel = mk2("select");
|
|
35905
37917
|
for (const q of ["low", "medium", "high"]) {
|
|
35906
37918
|
const op = mk2("option", void 0, t(`ui.quality.${q}`));
|
|
@@ -36105,6 +38117,7 @@ void main(){
|
|
|
36105
38117
|
}
|
|
36106
38118
|
slotsEl.forEach((b, i) => b.classList.toggle("on", i === s.slot));
|
|
36107
38119
|
tapBtn.classList.toggle("on", s.tap);
|
|
38120
|
+
for (const { key: key2, b } of blastBtns) b.classList.toggle("on", s.blast[key2]);
|
|
36108
38121
|
count.textContent = t("ui.objects", { n: s.objects });
|
|
36109
38122
|
const ak = JSON.stringify(s.animations);
|
|
36110
38123
|
if (ak !== lastAnim) {
|
|
@@ -36187,9 +38200,9 @@ void main(){
|
|
|
36187
38200
|
gl.setSize(width(), height());
|
|
36188
38201
|
const dom = gl.domElement;
|
|
36189
38202
|
if (dom && !dom.hasAttribute?.("tabindex")) dom.setAttribute?.("tabindex", "0");
|
|
36190
|
-
const
|
|
38203
|
+
const scene2 = options.scene ?? new THREE.Scene();
|
|
36191
38204
|
const camera2 = options.camera ?? new THREE.PerspectiveCamera(72, width() / height(), 0.05, 140);
|
|
36192
|
-
if (!options.camera)
|
|
38205
|
+
if (!options.camera) scene2.add(camera2);
|
|
36193
38206
|
const resize = () => {
|
|
36194
38207
|
const w = width();
|
|
36195
38208
|
const h = height();
|
|
@@ -36200,8 +38213,8 @@ void main(){
|
|
|
36200
38213
|
const onResize = () => resize();
|
|
36201
38214
|
window.addEventListener("resize", onResize);
|
|
36202
38215
|
const arenaSize = options.arenaSize ?? 14;
|
|
36203
|
-
const arena = createArena(THREE, arenaSize,
|
|
36204
|
-
|
|
38216
|
+
const arena = createArena(THREE, arenaSize, scene2);
|
|
38217
|
+
scene2.add(arena.group);
|
|
36205
38218
|
const ownWorld = !options.world;
|
|
36206
38219
|
const world = options.world ?? createDestruction({ seed: 1, ...options.worldOptions, materials: [...DEMO_MATERIALS, ...options.worldOptions?.materials ?? []] });
|
|
36207
38220
|
const pristineWorld = ownWorld ? world.serialize() : null;
|
|
@@ -36229,15 +38242,22 @@ void main(){
|
|
|
36229
38242
|
let simTime = 0;
|
|
36230
38243
|
const log = createLog({ exportCancelled: () => t("log.export_cancelled") });
|
|
36231
38244
|
log.setClock(() => simTime);
|
|
38245
|
+
const ownAudio = options.audio === void 0;
|
|
38246
|
+
const audio = options.audio === false ? null : options.audio ?? createAudioEngine();
|
|
38247
|
+
const destructionSound = audio ? createDestructionSound(audio) : null;
|
|
38248
|
+
const playSound = (key, point, volume) => {
|
|
38249
|
+
destructionSound?.onSound(key, point, volume);
|
|
38250
|
+
options.onSound?.(key, point, volume);
|
|
38251
|
+
};
|
|
36232
38252
|
const craterField = options.craters ? createGroundField() : void 0;
|
|
36233
38253
|
const craterPatch = craterField ? createGroundPatch(THREE, { field: craterField, material: new THREE.MeshStandardMaterial({ color: 9342602, roughness: 0.95 }) }) : void 0;
|
|
36234
38254
|
if (craterPatch) {
|
|
36235
38255
|
const floor2 = arena.group.getObjectByName("floor");
|
|
36236
38256
|
if (floor2) floor2.visible = false;
|
|
36237
|
-
|
|
38257
|
+
scene2.add(craterPatch.mesh);
|
|
36238
38258
|
}
|
|
36239
38259
|
const vfx = createVfxRenderer(THREE, {
|
|
36240
|
-
scene,
|
|
38260
|
+
scene: scene2,
|
|
36241
38261
|
camera: camera2,
|
|
36242
38262
|
overlay: container,
|
|
36243
38263
|
quality,
|
|
@@ -36249,13 +38269,13 @@ void main(){
|
|
|
36249
38269
|
})
|
|
36250
38270
|
});
|
|
36251
38271
|
const renderer = createDestructionRenderer(THREE, {
|
|
36252
|
-
scene,
|
|
38272
|
+
scene: scene2,
|
|
36253
38273
|
world,
|
|
36254
38274
|
vfx: vfx.world,
|
|
36255
38275
|
quality,
|
|
36256
38276
|
groundAt,
|
|
36257
38277
|
pointScale,
|
|
36258
|
-
onSound:
|
|
38278
|
+
onSound: playSound,
|
|
36259
38279
|
onPiece: (piece) => {
|
|
36260
38280
|
rendererPieces.set(piece.objectId, piece);
|
|
36261
38281
|
loose.push(piece);
|
|
@@ -36277,10 +38297,10 @@ void main(){
|
|
|
36277
38297
|
if (best) splitOf.set(piece.objectId, best);
|
|
36278
38298
|
}
|
|
36279
38299
|
});
|
|
36280
|
-
const fluidView = createFluidRenderer(THREE, { scene, fluids, quality, pointScale, vfx: vfx.world });
|
|
38300
|
+
const fluidView = createFluidRenderer(THREE, { scene: scene2, fluids, quality, pointScale, vfx: vfx.world });
|
|
36281
38301
|
const filmRandom = createFilmLcg(1);
|
|
36282
38302
|
const film = createFilmManager(THREE, {
|
|
36283
|
-
scene,
|
|
38303
|
+
scene: scene2,
|
|
36284
38304
|
camera: camera2,
|
|
36285
38305
|
random: filmRandom,
|
|
36286
38306
|
fluids,
|
|
@@ -36477,7 +38497,7 @@ void main(){
|
|
|
36477
38497
|
for (const o of arena.objects) {
|
|
36478
38498
|
const root = buildFromSpec(THREE, o.spec, { colors: o.colors });
|
|
36479
38499
|
root.position.set(o.position[0], o.position[1], o.position[2]);
|
|
36480
|
-
|
|
38500
|
+
scene2.add(root);
|
|
36481
38501
|
root.updateMatrixWorld(true);
|
|
36482
38502
|
world.add(structuredCloneSpec(o.spec), { id: o.id, position: o.position });
|
|
36483
38503
|
registerEntry(o.id, o.id, root, o.spec, S.text[`arena.${o.id}`] ?? o.id, void 0, void 0, true, false);
|
|
@@ -36504,7 +38524,7 @@ void main(){
|
|
|
36504
38524
|
root.position.set(pos[0], pos[1], pos[2]);
|
|
36505
38525
|
root.rotation.y = rotationY ?? player.yaw;
|
|
36506
38526
|
root.name = item.id;
|
|
36507
|
-
|
|
38527
|
+
scene2.add(root);
|
|
36508
38528
|
root.updateMatrixWorld(true);
|
|
36509
38529
|
const objectId = `${item.id}_${++counter}`;
|
|
36510
38530
|
let own;
|
|
@@ -36691,7 +38711,7 @@ void main(){
|
|
|
36691
38711
|
g.setAttribute("position", new THREE.Float32BufferAttribute(6, 3));
|
|
36692
38712
|
const line = new THREE.Line(g, tracerMat);
|
|
36693
38713
|
line.frustumCulled = false;
|
|
36694
|
-
|
|
38714
|
+
scene2.add(line);
|
|
36695
38715
|
tr = { line, life: 0 };
|
|
36696
38716
|
tracers.push(tr);
|
|
36697
38717
|
}
|
|
@@ -36807,15 +38827,15 @@ void main(){
|
|
|
36807
38827
|
energy = Math.max(energy, s.energy);
|
|
36808
38828
|
const results = world.shoot(s.origin, s.direction, s.energy, s.caliber, "player");
|
|
36809
38829
|
const r0 = results.find((r) => r.part && r.point);
|
|
36810
|
-
let
|
|
38830
|
+
let end3 = [s.origin[0] + s.direction[0] * 40, s.origin[1] + s.direction[1] * 40, s.origin[2] + s.direction[2] * 40];
|
|
36811
38831
|
if (r0?.point && r0.part) {
|
|
36812
38832
|
hits++;
|
|
36813
|
-
|
|
38833
|
+
end3 = r0.point;
|
|
36814
38834
|
if (!first) first = { object: r0.object, part: r0.part, point: r0.point, normal: [0, 1, 0], distance: dist3(s.origin, r0.point) };
|
|
36815
38835
|
const speed = Math.max(80, s.speed);
|
|
36816
38836
|
pushPiece(r0.object, s.direction, 2 * s.energy / speed, r0.point);
|
|
36817
38837
|
}
|
|
36818
|
-
if (shots.length <= 3 || rnd2() < 0.34) tracer(m,
|
|
38838
|
+
if (shots.length <= 3 || rnd2() < 0.34) tracer(m, end3);
|
|
36819
38839
|
}
|
|
36820
38840
|
});
|
|
36821
38841
|
player.kick(recoil[0], recoil[1]);
|
|
@@ -36979,9 +38999,15 @@ void main(){
|
|
|
36979
38999
|
bodies.push({ id: "player", box: new THREE.Box3(new THREE.Vector3(x - PLAYER_RADIUS, feet, z - PLAYER_RADIUS), new THREE.Vector3(x + PLAYER_RADIUS, y + 0.1, z + PLAYER_RADIUS)) });
|
|
36980
39000
|
return bodies;
|
|
36981
39001
|
}
|
|
39002
|
+
let blastOptions = { ...options.blast };
|
|
39003
|
+
let lastBlast;
|
|
39004
|
+
const blastOn = (key) => Boolean(blastOptions[key]);
|
|
39005
|
+
const toggleBlast = (key) => {
|
|
39006
|
+
blastOptions = { ...blastOptions, [key]: blastOn(key) ? false : true };
|
|
39007
|
+
};
|
|
36982
39008
|
const explosives = createExplosives({
|
|
36983
39009
|
THREE,
|
|
36984
|
-
scene,
|
|
39010
|
+
scene: scene2,
|
|
36985
39011
|
camera: camera2,
|
|
36986
39012
|
world,
|
|
36987
39013
|
renderer,
|
|
@@ -36993,6 +39019,10 @@ void main(){
|
|
|
36993
39019
|
toolName,
|
|
36994
39020
|
withoutHeld,
|
|
36995
39021
|
groundAt,
|
|
39022
|
+
blast: () => blastOptions,
|
|
39023
|
+
blasted: (summary) => {
|
|
39024
|
+
lastBlast = summary;
|
|
39025
|
+
},
|
|
36996
39026
|
translate: t,
|
|
36997
39027
|
format: num,
|
|
36998
39028
|
action,
|
|
@@ -37419,7 +39449,7 @@ void main(){
|
|
|
37419
39449
|
let metricsAcc = 0;
|
|
37420
39450
|
let ownsLoop = false;
|
|
37421
39451
|
const roomMetricIds = options.metrics === false ? [] : options.metrics ?? ROOM_METRICS;
|
|
37422
|
-
const roomMetrics = createRoomMetrics({ gl, destruction: renderer, fluids: fluidView, effects: vfx.world, ids: roomMetricIds });
|
|
39452
|
+
const roomMetrics = createRoomMetrics({ gl, destruction: renderer, fluids: fluidView, effects: vfx.world, ids: roomMetricIds, blast: () => lastBlast });
|
|
37423
39453
|
let qualityName = quality;
|
|
37424
39454
|
const syncContainers = (sdt) => {
|
|
37425
39455
|
for (const e of entries.values()) {
|
|
@@ -37478,6 +39508,10 @@ void main(){
|
|
|
37478
39508
|
s.update(dt);
|
|
37479
39509
|
flushEvents(id, s);
|
|
37480
39510
|
}
|
|
39511
|
+
if (audio) {
|
|
39512
|
+
const look = camera2.getWorldDirection(listenerDirection);
|
|
39513
|
+
audio.setListener({ position: [camera2.position.x, camera2.position.y, camera2.position.z], yaw: Math.atan2(-look.x, -look.z) });
|
|
39514
|
+
}
|
|
37481
39515
|
for (const update2 of frameListeners) update2(dt);
|
|
37482
39516
|
for (const tr of tracers) {
|
|
37483
39517
|
if (tr.life > 0) {
|
|
@@ -37534,6 +39568,7 @@ void main(){
|
|
|
37534
39568
|
timeScale,
|
|
37535
39569
|
fast: fastForward,
|
|
37536
39570
|
tap: isTapOn(),
|
|
39571
|
+
blast: { shelter: blastOn("shelter"), fragmentHits: blastOn("fragmentHits"), confinement: blastOn("confinement") },
|
|
37537
39572
|
quality: qualityName,
|
|
37538
39573
|
objects: [...entries.values()].filter((e) => !e.arena).length
|
|
37539
39574
|
};
|
|
@@ -37587,7 +39622,7 @@ void main(){
|
|
|
37587
39622
|
const ev = evs[pl.fired++];
|
|
37588
39623
|
action("animation_event", t("act.anim_event", { name: pl.entry.name, clip: pl.clip.id, type: ev.type }), { objectId: pl.entry.so.objectId, itemId: pl.entry.so.itemId, data: { clip: pl.clip.id, type: ev.type, at: ev.t, ...ev.data ?? {} } });
|
|
37589
39624
|
const key = ev.type === "sound" ? ev.data?.key : void 0;
|
|
37590
|
-
if (typeof key === "string")
|
|
39625
|
+
if (typeof key === "string") playSound(key, v3(pl.entry.so.root.getWorldPosition(tmpV2)), 1);
|
|
37591
39626
|
}
|
|
37592
39627
|
if (pl.t >= pl.clip.duration) {
|
|
37593
39628
|
playing.splice(i, 1);
|
|
@@ -37680,6 +39715,9 @@ void main(){
|
|
|
37680
39715
|
};
|
|
37681
39716
|
const hostListeners = /* @__PURE__ */ new Set();
|
|
37682
39717
|
const frameListeners = /* @__PURE__ */ new Set();
|
|
39718
|
+
const listenerDirection = new THREE.Vector3();
|
|
39719
|
+
const environment = options.environment ?? createEnvironmentBus();
|
|
39720
|
+
const audioSink = audio ? createAudioSink(audio) : void 0;
|
|
37683
39721
|
function emitHost(event) {
|
|
37684
39722
|
for (const listener of hostListeners) listener(event);
|
|
37685
39723
|
}
|
|
@@ -37726,9 +39764,11 @@ void main(){
|
|
|
37726
39764
|
}
|
|
37727
39765
|
const host = {
|
|
37728
39766
|
THREE,
|
|
37729
|
-
scene,
|
|
39767
|
+
scene: scene2,
|
|
37730
39768
|
camera: camera2,
|
|
37731
39769
|
locale,
|
|
39770
|
+
...audioSink ? { audio: audioSink } : {},
|
|
39771
|
+
environment,
|
|
37732
39772
|
groundAt,
|
|
37733
39773
|
effects: vfx,
|
|
37734
39774
|
tool: () => ({
|
|
@@ -37977,11 +40017,13 @@ void main(){
|
|
|
37977
40017
|
const room = {
|
|
37978
40018
|
world,
|
|
37979
40019
|
renderer,
|
|
37980
|
-
scene,
|
|
40020
|
+
scene: scene2,
|
|
37981
40021
|
camera: camera2,
|
|
37982
40022
|
arsenal,
|
|
37983
40023
|
fluids,
|
|
37984
40024
|
fluidRenderer: fluidView,
|
|
40025
|
+
audio,
|
|
40026
|
+
environment,
|
|
37985
40027
|
log,
|
|
37986
40028
|
get tool() {
|
|
37987
40029
|
return tool;
|
|
@@ -38042,6 +40084,10 @@ void main(){
|
|
|
38042
40084
|
heldTilt = rad;
|
|
38043
40085
|
},
|
|
38044
40086
|
toggleTap,
|
|
40087
|
+
blast: () => ({ ...blastOptions }),
|
|
40088
|
+
setBlast: (next) => {
|
|
40089
|
+
blastOptions = { ...blastOptions, ...next };
|
|
40090
|
+
},
|
|
38045
40091
|
pause() {
|
|
38046
40092
|
paused = true;
|
|
38047
40093
|
},
|
|
@@ -38075,6 +40121,8 @@ void main(){
|
|
|
38075
40121
|
return { room: roomMetrics.sample(), module: moduleMetrics() };
|
|
38076
40122
|
},
|
|
38077
40123
|
dispose() {
|
|
40124
|
+
destructionSound?.dispose();
|
|
40125
|
+
if (ownAudio) audio?.dispose();
|
|
38078
40126
|
for (const detach of detachers.values()) detach();
|
|
38079
40127
|
detachers.clear();
|
|
38080
40128
|
for (const store of benches.values()) store.dispose();
|
|
@@ -38150,6 +40198,7 @@ void main(){
|
|
|
38150
40198
|
setTool(loadout[index]);
|
|
38151
40199
|
},
|
|
38152
40200
|
onTap: () => toggleTap(),
|
|
40201
|
+
onBlast: (key) => toggleBlast(key),
|
|
38153
40202
|
onPlay: (objectId, clipId) => {
|
|
38154
40203
|
playAnimation(clipId, objectId);
|
|
38155
40204
|
},
|
|
@@ -38179,7 +40228,7 @@ void main(){
|
|
|
38179
40228
|
last = now;
|
|
38180
40229
|
if (!scenarioRunning) update(dt);
|
|
38181
40230
|
const restoreCamera = vfx.applyCamera();
|
|
38182
|
-
gl.render(
|
|
40231
|
+
gl.render(scene2, camera2);
|
|
38183
40232
|
restoreCamera();
|
|
38184
40233
|
roomMetrics.rendered();
|
|
38185
40234
|
};
|