@adatechnology/game-characters-three 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -686,7 +686,7 @@ var createBestBuilder = (THREE, { types, random = Math.random }) => {
686
686
  const TORSO_P = [[0.18, 0.86], [0.205, 0.95], [0.195, 1.03], [0.19, 1.12], [0.225, 1.27], [0.26, 1.4], [0.265, 1.47], [0.21, 1.55], [0.085, 1.6]];
687
687
  const TORSO_F = [[0.17, 0.86], [0.215, 0.95], [0.2, 1.02], [0.165, 1.1], [0.2, 1.25], [0.245, 1.38], [0.25, 1.46], [0.2, 1.54], [0.085, 1.6]];
688
688
  const torsoGeo = (sx, sz, fem) => {
689
- const g = new THREE.LatheGeometry(new THREE.SplineCurve((fem ? TORSO_F : TORSO_P).map((p) => new THREE.Vector2(p[0], p[1]))).getPoints(Math.max(8, Math.round(30 * LK))), Math.max(6, Math.round(24 * LK)));
689
+ const g = new THREE.LatheGeometry(new THREE.SplineCurve((fem ? TORSO_F : TORSO_P).map((p2) => new THREE.Vector2(p2[0], p2[1]))).getPoints(Math.max(8, Math.round(30 * LK))), Math.max(6, Math.round(24 * LK)));
690
690
  g.scale(1.12 * (sx || 1), 1, 0.64 * (sz || 1));
691
691
  return g;
692
692
  };
@@ -774,34 +774,34 @@ var createBestBuilder = (THREE, { types, random = Math.random }) => {
774
774
  const skinFinal = P.char ? 1840658 : P.wet ? col(skinC, 8032160, 0.55) : skinC;
775
775
  const skinM = M(skinFinal);
776
776
  const topM = M(P.char ? col(topC, 1380880, 0.75) : P.wet ? col(topC, 1712688, 0.45) : col(topC, GRAY, d * 0.3)), pantsM = M(col(pantC, GRAY, d * 0.3)), shoeM = M(T.shoeColor), red = M(RED);
777
- const add = (p, geo, m, x, y, z2, rx, ry, rz) => {
777
+ const add = (p2, geo, m, x, y, z2, rx, ry, rz) => {
778
778
  const o = new THREE.Mesh(geo, m);
779
779
  o.position.set(x || 0, y || 0, z2 || 0);
780
780
  if (rx || ry || rz) o.rotation.set(rx || 0, ry || 0, rz || 0);
781
- p.add(o);
781
+ p2.add(o);
782
782
  return o;
783
783
  };
784
- const ball = (p, r, m, x, y, z2) => add(p, SPH(r, SEG, 12), m, x, y, z2);
784
+ const ball = (p2, r, m, x, y, z2) => add(p2, SPH(r, SEG, 12), m, x, y, z2);
785
785
  topM_ref = topM;
786
786
  const SKM = {}, skM = (c) => SKM[c] || (SKM[c] = new THREE.MeshStandardMaterial({ color: c, roughness: 0.3, metalness: 0, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
787
- const flat = (p, x, y, zz, sx, sy, c) => {
787
+ const flat = (p2, x, y, zz, sx, sy, c) => {
788
788
  const m = new THREE.Mesh(BLOB, skM(c));
789
789
  m.rotation.set(PI / 2, 0, BR() * 6.28);
790
790
  m.scale.set(sx, 1, sy);
791
791
  m.position.set(x, y, zz);
792
- p.add(m);
792
+ p2.add(m);
793
793
  return m;
794
794
  };
795
- const splat = (p, x, y, zz, r, c, n) => {
796
- const a = flat(p, x, y, zz + 4e-3, r, r * (0.7 + BR() * 0.5), c);
795
+ const splat = (p2, x, y, zz, r, c, n) => {
796
+ const a = flat(p2, x, y, zz + 4e-3, r, r * (0.7 + BR() * 0.5), c);
797
797
  for (let k = 0; k < (n || 0); k++) {
798
798
  const an = BR() * 6.28, dd = r * (1.2 + BR() * 0.9), q = r * (0.12 + BR() * 0.18);
799
- flat(p, x + Math.cos(an) * dd, y + Math.sin(an) * dd, zz + 4e-3, q, q * 0.7, c);
799
+ flat(p2, x + Math.cos(an) * dd, y + Math.sin(an) * dd, zz + 4e-3, q, q * 0.7, c);
800
800
  }
801
- if (n) run(p, x + r * 0.15, y - r * 0.5, y - r * (2.2 + BR() * 1.2), zz + 3e-3, zz, c, r * 0.35);
801
+ if (n) run(p2, x + r * 0.15, y - r * 0.5, y - r * (2.2 + BR() * 1.2), zz + 3e-3, zz, c, r * 0.35);
802
802
  return a;
803
803
  };
804
- const run = (p, x, y0, y1, z0, z1, c, w) => {
804
+ const run = (p2, x, y0, y1, z0, z1, c, w) => {
805
805
  const L = y0 - y1, sh = new THREE.Shape();
806
806
  sh.moveTo(-w / 2, 0);
807
807
  sh.lineTo(w / 2, 0);
@@ -811,30 +811,30 @@ var createBestBuilder = (THREE, { types, random = Math.random }) => {
811
811
  const g = new THREE.ShapeGeometry(sh), m = new THREE.Mesh(g, skM(c));
812
812
  m.position.set(x, y0, z0);
813
813
  m.rotation.x = -Math.atan2(z0 - z1, L);
814
- p.add(m);
814
+ p2.add(m);
815
815
  const bl = new THREE.Mesh(SPH(w * 0.42, 10, 8), skM(c));
816
816
  bl.scale.set(1, 1.35, 0.5);
817
817
  bl.position.set(x + w * 0.03, y1, z1);
818
- p.add(bl);
818
+ p2.add(bl);
819
819
  return bl;
820
820
  };
821
- const stump = (p, r, y, up) => {
822
- const st = ball(p, r * 1.02, red, 0, y, 0);
821
+ const stump = (p2, r, y, up) => {
822
+ const st = ball(p2, r * 1.02, red, 0, y, 0);
823
823
  st.scale.set(1, 0.35, 1);
824
- add(p, CYL(r * 0.32, r * 0.36, 0.07, 10), M(15261900), 0, y + (up ? 0.04 : -0.04), 0);
824
+ add(p2, CYL(r * 0.32, r * 0.36, 0.07, 10), M(15261900), 0, y + (up ? 0.04 : -0.04), 0);
825
825
  };
826
- const ring = (p, r, h, m, y, sx, sz) => {
827
- const o = add(p, CYL(r, r, h, 24), m, 0, y, 0);
826
+ const ring = (p2, r, h, m, y, sx, sz) => {
827
+ const o = add(p2, CYL(r, r, h, 24), m, 0, y, 0);
828
828
  o.scale.set(sx || 1.12, 1, sz || 0.7);
829
829
  return o;
830
830
  };
831
831
  const root = new THREE.Group(), fig = new THREE.Group();
832
- const drip = (p, x, y, zz, kind, every) => {
832
+ const drip = (p2, x, y, zz, kind, every) => {
833
833
  if (d >= 3 && kind !== "bile") return;
834
834
  const o = new THREE.Object3D();
835
835
  o.position.set(x, y, zz);
836
836
  o.userData.drip = { kind: kind || (d < 1 ? "blood" : "old"), every: every || (d < 1 ? 1.2 : 2.4) };
837
- p.add(o);
837
+ p2.add(o);
838
838
  };
839
839
  fig.scale.set(P.s * P.bulk, P.s, P.s * Math.min(1.15, P.bulk));
840
840
  fig.position.y = P.drop * P.s;
@@ -2007,7 +2007,7 @@ var createPersonKit = (THREE, { random: R, createCanvas = () => document.createE
2007
2007
  }
2008
2008
  function torsoGeo(fem) {
2009
2009
  const P = fem ? [[0.17, 0.86], [0.215, 0.95], [0.2, 1.02], [0.165, 1.1], [0.2, 1.25], [0.245, 1.38], [0.25, 1.46], [0.2, 1.54], [0.085, 1.6]] : [[0.18, 0.86], [0.205, 0.95], [0.195, 1.03], [0.19, 1.12], [0.225, 1.27], [0.26, 1.4], [0.265, 1.47], [0.21, 1.55], [0.085, 1.6]];
2010
- const pts = new THREE.SplineCurve(P.map((p) => new THREE.Vector2(p[0], p[1]))).getPoints(30);
2010
+ const pts = new THREE.SplineCurve(P.map((p2) => new THREE.Vector2(p2[0], p2[1]))).getPoints(30);
2011
2011
  const g = new THREE.LatheGeometry(pts, 24);
2012
2012
  g.scale(1.12, 1, 0.64);
2013
2013
  return g;
@@ -2362,15 +2362,15 @@ var createPersonBuilder = (THREE, options = {}) => {
2362
2362
  }
2363
2363
  const MOVES_IDLE = MOVES.idle;
2364
2364
  function pose(c, z, W, t) {
2365
- const p = c.ph + t, freq = z ? 3.4 : 5.6, ph = t * freq + c.ph;
2366
- const breathe = reduce ? 0 : Math.sin(p * 1.6) * 0.012;
2365
+ const p2 = c.ph + t, freq = z ? 3.4 : 5.6, ph = t * freq + c.ph;
2366
+ const breathe = reduce ? 0 : Math.sin(p2 * 1.6) * 0.012;
2367
2367
  c.upper.scale.y = 1 + breathe;
2368
2368
  const hunch = c.hunch + (z ? 0.38 : 0) + W * 0.05;
2369
2369
  c.upper.rotation.x = hunch;
2370
2370
  c.upper.rotation.y = W * Math.sin(ph) * 0.1;
2371
2371
  c.upper.rotation.z = z ? Math.sin(ph * 0.5) * 0.06 * W + 0.05 : 0;
2372
2372
  c.fig.position.y = -W * Math.abs(Math.sin(ph)) * 0.035;
2373
- c.headG.rotation.y = reduce ? 0 : Math.sin(p * 0.5) * 0.25 * (z ? 0.3 : 1) * (W ? 0.4 : 1) * (t ? 1 : 0);
2373
+ c.headG.rotation.y = reduce ? 0 : Math.sin(p2 * 0.5) * 0.25 * (z ? 0.3 : 1) * (W ? 0.4 : 1) * (t ? 1 : 0);
2374
2374
  c.headG.rotation.x = -hunch * 0.6 + (z ? 0.15 : 0);
2375
2375
  c.headG.rotation.z = z ? 0.22 : 0;
2376
2376
  c.legs.forEach((l, k) => {
@@ -2383,7 +2383,7 @@ var createPersonBuilder = (THREE, options = {}) => {
2383
2383
  const raise = z ? 1 : 0;
2384
2384
  const base = a.hold ? -0.2 : 0;
2385
2385
  const swing = -a.s * Math.sin(ph) * (z ? 0.12 : 0.42) * W * (a.grip ? 0 : 1);
2386
- a.g.rotation.x = (a.grip && !z ? -0.75 : base - raise * (z ? 1.12 : 1.3)) + swing + (z && !reduce && t ? Math.sin(p * 3 + a.s * 2) * 0.08 : 0);
2386
+ a.g.rotation.x = (a.grip && !z ? -0.75 : base - raise * (z ? 1.12 : 1.3)) + swing + (z && !reduce && t ? Math.sin(p2 * 3 + a.s * 2) * 0.08 : 0);
2387
2387
  a.g.rotation.z = a.s * (0.07 + (z ? 0.1 : 0)) + (a.grip && !z ? -a.s * 0.35 : 0);
2388
2388
  a.el.rotation.x = z ? -0.18 : a.grip ? -1.1 : -(0.12 + (a.hold ? 0.25 : 0) + W * (0.25 + Math.max(0, a.s * Math.sin(ph)) * 0.3));
2389
2389
  if (a.fingers) {
@@ -2408,16 +2408,16 @@ var createPersonBuilder = (THREE, options = {}) => {
2408
2408
  c.mouth.visible = true;
2409
2409
  c.bags.material.opacity = 0.9;
2410
2410
  }
2411
- function buildPerson(p, zed) {
2411
+ function buildPerson(p2, zed) {
2412
2412
  const root = new THREE.Group(), chars = [];
2413
- if (p.b) {
2414
- const c = build(p.b, 0);
2413
+ if (p2.b) {
2414
+ const c = build(p2.b, 0);
2415
2415
  chars.push(c);
2416
2416
  root.add(c.root);
2417
- if (zed && !p.kid) zombify(c, p.zcat);
2418
- c.z = zed && !p.kid;
2417
+ if (zed && !p2.kid) zombify(c, p2.zcat);
2418
+ c.z = zed && !p2.kid;
2419
2419
  }
2420
- (p.kids || []).forEach((k) => {
2420
+ (p2.kids || []).forEach((k) => {
2421
2421
  const c = build(k, 0);
2422
2422
  c.root.position.x = k.x;
2423
2423
  c.root.rotation.y = -k.x * 0.4;
@@ -2675,11 +2675,11 @@ var createFlyingPieces = (three, { random, groundAt = () => 0, onRest, max = 40
2675
2675
  };
2676
2676
  return {
2677
2677
  pieces,
2678
- launch(source, scene, direction, dress, tag) {
2678
+ launch(source, scene2, direction, dress, tag) {
2679
2679
  source.updateWorldMatrix(true, true);
2680
2680
  const clone = source.clone(true);
2681
2681
  source.matrixWorld.decompose(clone.position, clone.quaternion, clone.scale);
2682
- scene.add(clone);
2682
+ scene2.add(clone);
2683
2683
  dress?.(clone);
2684
2684
  return add(
2685
2685
  clone,
@@ -2829,7 +2829,7 @@ var woundBodyOf = (root) => {
2829
2829
  import { segmentsBelow } from "@adatechnology/game-characters";
2830
2830
  var worldPosition = (three, object) => object.getWorldPosition(new three.Vector3());
2831
2831
  var createWounds = (three, options) => {
2832
- const { random, scene, bridge } = options;
2832
+ const { random, scene: scene2, bridge, sound } = options;
2833
2833
  const materials = options.materials ?? createWoundMaterials(three);
2834
2834
  const flying = createFlyingPieces(three, { random, ...options.groundAt ? { groundAt: options.groundAt } : {}, ...options.maxPieces ? { max: options.maxPieces } : {} });
2835
2835
  const states = /* @__PURE__ */ new Map();
@@ -2838,9 +2838,9 @@ var createWounds = (three, options) => {
2838
2838
  if (!found) throw new Error("game-characters-three: this body is not tracked by the wounds (call `track` first)");
2839
2839
  return found;
2840
2840
  };
2841
- const burstBlood = (change, object) => {
2841
+ const burstBlood = (state, change, object, direction) => {
2842
2842
  const point = worldPosition(three, object);
2843
- bridge?.burst(change, [point.x, point.y, point.z]);
2843
+ bridge?.burst(change, [point.x, point.y, point.z], state.body.blood, direction ? [direction.x, direction.y, direction.z] : void 0);
2844
2844
  };
2845
2845
  const sideOf = (object, body) => new three.Vector3(object.position.x >= 0 ? 1 : -1, 0, 0).applyQuaternion(body.root.quaternion);
2846
2846
  const hideBelow = (state, id) => {
@@ -2855,7 +2855,7 @@ var createWounds = (three, options) => {
2855
2855
  }
2856
2856
  const { node, radius, flip } = target;
2857
2857
  const direction = sideOf(node, state.body);
2858
- const fell = flying.launch(node, scene, direction, (clone) => {
2858
+ const fell = flying.launch(node, scene2, direction, (clone) => {
2859
2859
  makeCut(three, materials, random, clone, radius, true);
2860
2860
  }, id);
2861
2861
  void fell;
@@ -2869,7 +2869,7 @@ var createWounds = (three, options) => {
2869
2869
  for (let o = pendulum.node; o; o = o.parent) if (!o.visible) shown = false;
2870
2870
  if (!shown) state.pendulums.splice(i, 1);
2871
2871
  }
2872
- burstBlood("severed", node);
2872
+ burstBlood(state, "severed", node, direction);
2873
2873
  if (id === "head") state.dead = true;
2874
2874
  };
2875
2875
  const destroy = (state, id, direction) => {
@@ -2878,14 +2878,14 @@ var createWounds = (three, options) => {
2878
2878
  const { node, radius, flip } = target;
2879
2879
  const box = new three.Box3().setFromObject(node);
2880
2880
  explodeChunks(three, materials, random, box, direction, (chunk, velocity, spin) => {
2881
- scene.add(chunk);
2881
+ scene2.add(chunk);
2882
2882
  flying.throwObject(chunk, velocity, spin, id);
2883
2883
  });
2884
2884
  node.visible = false;
2885
2885
  makeStump(three, materials, random, node.parent, node.position, radius, flip);
2886
2886
  state.gone.add(id);
2887
2887
  hideBelow(state, id);
2888
- burstBlood("destroyed", node);
2888
+ burstBlood(state, "destroyed", node, direction);
2889
2889
  if (id === "head") state.dead = true;
2890
2890
  };
2891
2891
  const breakBone = (state, id) => {
@@ -2905,7 +2905,7 @@ var createWounds = (three, options) => {
2905
2905
  const split = (state, direction) => {
2906
2906
  const upper = state.body.rig.upper;
2907
2907
  if (!upper.visible) return;
2908
- flying.launch(upper, scene, direction, (clone) => {
2908
+ flying.launch(upper, scene2, direction, (clone) => {
2909
2909
  makeCut(three, materials, random, clone, 0.16, false);
2910
2910
  }, "torso");
2911
2911
  upper.visible = false;
@@ -2913,7 +2913,7 @@ var createWounds = (three, options) => {
2913
2913
  for (const id of ["torso", "neck", "head", "jaw", "upperArmL", "upperArmR", "forearmL", "forearmR", "handL", "handR"]) state.gone.add(id);
2914
2914
  state.pendulums.length = 0;
2915
2915
  state.dead = true;
2916
- burstBlood("split", upper);
2916
+ burstBlood(state, "split", upper, direction);
2917
2917
  };
2918
2918
  const direction3 = (v, fallback) => v ? new three.Vector3(v[0], v[1], v[2]).normalize() : fallback;
2919
2919
  return {
@@ -2947,6 +2947,10 @@ var createWounds = (three, options) => {
2947
2947
  }
2948
2948
  if (event.type !== "body") return;
2949
2949
  const id = event.part;
2950
+ if (sound) {
2951
+ const heard = worldPosition(three, (id ? body.nodes[id]?.node : void 0) ?? body.root);
2952
+ sound.body(event, [heard.x, heard.y, heard.z]);
2953
+ }
2950
2954
  const fallback = new three.Vector3(0, 0.2, 1);
2951
2955
  const direction = direction3(event.exit, fallback);
2952
2956
  switch (event.change) {
@@ -2973,8 +2977,10 @@ var createWounds = (three, options) => {
2973
2977
  break;
2974
2978
  case "wound": {
2975
2979
  const target = id ? body.nodes[id] : void 0;
2980
+ const at = worldPosition(three, target?.node ?? body.root);
2981
+ bridge?.wound([at.x, at.y, at.z], body.blood, event.exit ? [direction.x, direction.y, direction.z] : void 0);
2976
2982
  if (target && event.exit) gouge(three, materials, random, target.node, id.replace(/[LR]$/, ""), worldPosition(three, target.node), direction.clone().negate(), direction, 60, (chunk, velocity, spin) => {
2977
- scene.add(chunk);
2983
+ scene2.add(chunk);
2978
2984
  flying.throwObject(chunk, velocity, spin, id);
2979
2985
  });
2980
2986
  break;
@@ -3020,8 +3026,28 @@ var createWounds = (three, options) => {
3020
3026
  };
3021
3027
 
3022
3028
  // src/wounds/fluids-bridge.ts
3029
+ var colorOf = (hex2) => Number.parseInt(hex2.replace(/^#|^0x/i, ""), 16);
3030
+ var bloodLookOfAge = (age, options = {}) => ({
3031
+ ...age.blood < (options.thinBelow ?? 0.3) ? { liquid: options.thinLiquid ?? "old_blood" } : {},
3032
+ tint: { fresh: colorOf(age.freshBloodColor), dry: colorOf(age.dryBloodColor) },
3033
+ amount: age.blood
3034
+ });
3023
3035
  var BURST_LITERS = { severed: 0.35, destroyed: 0.6, split: 0.9 };
3024
- var createFluidsBridge = ({ sink, liquid = "blood", spurtSpeed = 1.4 }) => {
3036
+ var BURST_DROPS = { severed: 4, destroyed: 7, split: 10 };
3037
+ var createFluidsBridge = ({ sink, film, spray, burstDrops = BURST_DROPS, liquid = "blood", spurtSpeed = 1.4 }) => {
3038
+ const throwDrops = (point, wanted, look, direction, speed, radius) => {
3039
+ const amount = look?.amount ?? 1;
3040
+ const count = amount <= 0 ? 0 : Math.max(1, Math.round(wanted * Math.min(1, amount)));
3041
+ return film && count > 0 ? film.splat({
3042
+ fluid: look?.liquid ?? liquid,
3043
+ point,
3044
+ count,
3045
+ ...direction ? { direction } : {},
3046
+ ...speed !== void 0 ? { speed } : {},
3047
+ ...radius !== void 0 ? { radius } : {},
3048
+ ...look?.tint ? { tint: { fresh: look.tint.fresh, dry: look.tint.dry } } : {}
3049
+ }) : 0;
3050
+ };
3025
3051
  const bleed = (point, litersPerSecond, dt, direction = [0, -1, 0], pressurized = false) => {
3026
3052
  const liters = litersPerSecond * dt;
3027
3053
  if (liters <= 0) return;
@@ -3033,13 +3059,265 @@ var createFluidsBridge = ({ sink, liquid = "blood", spurtSpeed = 1.4 }) => {
3033
3059
  handle(event) {
3034
3060
  if (event.type === "leak" && event.fluid === "blood") bleed(event.point, event.rate, 1, event.direction, event.pressurized);
3035
3061
  },
3036
- burst: (change, point) => sink.splash(liquid, point, BURST_LITERS[change])
3062
+ burst(change, point, look, direction) {
3063
+ sink.splash(look?.liquid ?? liquid, point, BURST_LITERS[change]);
3064
+ throwDrops(point, burstDrops[change], look, direction);
3065
+ },
3066
+ wound: (point, look, direction) => throwDrops(point, spray?.count ?? 3, look, direction, spray?.speed, spray?.radius)
3037
3067
  };
3038
3068
  };
3039
3069
 
3040
3070
  // src/test-area/module.ts
3041
3071
  import { createCharacters, createRng as createRng3 } from "@adatechnology/game-characters";
3042
3072
 
3073
+ // src/audio/sound.ts
3074
+ import { GAME_SFX_BANK } from "@adatechnology/game-audio";
3075
+
3076
+ // src/audio/recipes.ts
3077
+ import { DISTANCE_CURVES, sceneNoise, sceneRecipe, sceneTone, sceneVoice } from "@adatechnology/game-audio";
3078
+ var UNDEAD_VOICE_GROUP = "undead_voice";
3079
+ var voiced = { bus: "voices", group: UNDEAD_VOICE_GROUP };
3080
+ var p = (param) => ({ param });
3081
+ var SCREAM_HOARSE = {
3082
+ id: "scream_hoarse",
3083
+ bus: "voices",
3084
+ order: "scream",
3085
+ duration: [1, 1.4],
3086
+ layers: [
3087
+ {
3088
+ source: { kind: "noise", offset: "random" },
3089
+ filters: {
3090
+ mode: "parallel",
3091
+ bank: [
3092
+ { type: "bandpass", q: 6, frequency: { from: 700 * 1.15, to: 700 * 0.8, curve: "linear", over: "duration" }, level: 1 },
3093
+ { type: "bandpass", q: 8, frequency: { from: 1150 * 1.15, to: 1150 * 0.8, curve: "linear", over: "duration" }, level: 0.7 },
3094
+ { type: "bandpass", q: 10, frequency: { from: 2600 * 1.15, to: 2600 * 0.8, curve: "linear", over: "duration" }, level: 0.35 }
3095
+ ]
3096
+ },
3097
+ modulators: [{ target: "gain", wave: "square", rate: [31, 40], depth: 0.35 }],
3098
+ envelope: [
3099
+ [0, 0, "set"],
3100
+ [0.06, 0.9, "linear"],
3101
+ ["0.6d", 0.9, "set"],
3102
+ ["1d", 1e-3, "exp", "fixed"]
3103
+ ]
3104
+ }
3105
+ ],
3106
+ falloff: DISTANCE_CURVES.game_scream,
3107
+ sources: ["jogo:L62128-62142 grito"]
3108
+ };
3109
+ var groanLayer = (base) => ({
3110
+ source: {
3111
+ kind: "osc",
3112
+ wave: "sawtooth",
3113
+ frequency: typeof base === "number" ? { from: base, to: base * 0.7, curve: "linear", over: "duration" } : { from: base, to: { ...base, scale: 0.7 }, curve: "linear", over: "duration" }
3114
+ },
3115
+ filters: { mode: "serial", bank: [{ type: "bandpass", q: 3, frequency: { set: typeof base === "number" ? base * 3 : { ...base, scale: 3 } } }] },
3116
+ envelope: [
3117
+ [0, 0, "set"],
3118
+ [0.15, 0.25, "linear"],
3119
+ ["1d", 1e-3, "exp", "fixed"]
3120
+ ]
3121
+ });
3122
+ var GROAN = {
3123
+ id: "groan",
3124
+ ...voiced,
3125
+ order: "groan",
3126
+ vars: { base: [95, 135], dur: [0.9, 1.5] },
3127
+ duration: p("dur"),
3128
+ layers: [groanLayer(p("base"))],
3129
+ falloff: DISTANCE_CURVES.game_voice,
3130
+ sources: ["jogo:L62143-62155 voz gemido"]
3131
+ };
3132
+ var RUNNER_GROAN = {
3133
+ id: "runner_groan",
3134
+ ...voiced,
3135
+ order: "groan",
3136
+ vars: { dur: [0.9, 1.5] },
3137
+ duration: p("dur"),
3138
+ layers: [groanLayer(300)],
3139
+ falloff: DISTANCE_CURVES.game_voice,
3140
+ sources: ["jogo:L62143-62155 voz corredor"]
3141
+ };
3142
+ var scene = (id, layers, line, options = {}) => sceneRecipe(id, layers, line, options);
3143
+ var groan = (id, vars, voice, line) => scene(id, [sceneVoice(voice)], line, { vars, ...voiced });
3144
+ var GROAN_WALKER = groan("groan_walker", { f0: [72, 107], d: [0.9, 2], fA: [480, 680], fB: [1050, 1350] }, { f0: p("f0"), f1: 0, d: p("d"), fA: p("fA"), fB: p("fB"), noise: 0.35, vol: 0.65 }, "L1345 groan (walker)");
3145
+ var GROAN_BRUTE = groan("groan_brute", { f0: [48, 62], d: [0.9, 2], fA: [480, 680], fB: [1050, 1350] }, { f0: p("f0"), f1: 0, d: p("d"), fA: p("fA"), fB: p("fB"), noise: 0.35, vol: 0.65 }, "L1345 groan (brute)");
3146
+ var GROAN_RUNNER = groan("groan_runner", { f0: [160, 250], d: [0.45, 0.85] }, { f0: p("f0"), f1: 0, d: p("d"), fA: 950, fB: 2300, noise: 0.6, vol: 0.5, wave: "square" }, "L1345 groan (runner)");
3147
+ var SCREAM = scene(
3148
+ "scream",
3149
+ [
3150
+ sceneVoice({ f0: 520, f1: 880, d: 2.6, fA: 1800, fB: 3200, noise: 0.9, vol: 1.6 }),
3151
+ sceneVoice({ f0: 640, f1: 1020, d: 2.4, fA: 2400, fB: 3800, noise: 0.6, vol: 1.1, wave: "square" })
3152
+ ],
3153
+ "L1347 scream",
3154
+ { bus: "voices" }
3155
+ );
3156
+ var INHALE = scene("inhale", [sceneNoise({ type: "bandpass", f: 1400, q: 1.5, a: 0.6, d: 0.9, vol: 0.5 })], "L1348 inhale", { bus: "voices" });
3157
+ var LEAP = scene("leap", [sceneVoice({ f0: 200, f1: 330, d: 0.5, fA: 1100, fB: 2500, noise: 0.7, vol: 0.6, wave: "square" }), sceneNoise({ type: "bandpass", f: 600, f2: 1600, d: 0.5, vol: 0.4 })], "L1349 leap", { bus: "voices" });
3158
+ var GRAB = scene("grab", [sceneNoise({ type: "bandpass", f: 500, q: 1, d: 0.25, vol: 0.7 }), sceneTone({ wave: "sawtooth", f: 90, f2: 60, d: 0.4, vol: 0.4 })], "L1350 grab", { bus: "voices" });
3159
+ var BITE = scene(
3160
+ "bite",
3161
+ [
3162
+ sceneNoise({ f: 1400, d: 0.2, vol: 1.1 }),
3163
+ ...[0, 1, 2, 3].map((k) => sceneNoise({ type: "highpass", f: 2500, d: 0.03, vol: 0.5, delay: 0.03 + k * 0.04 })),
3164
+ sceneTone({ wave: "sawtooth", f: 320, f2: 170, d: 0.5, vol: 0.35, delay: 0.05 })
3165
+ ],
3166
+ "L1351 bite",
3167
+ { bus: "voices" }
3168
+ );
3169
+ var HURT = scene("hurt", [sceneTone({ wave: "sawtooth", f: 150, f2: 95, d: 0.22, vol: 0.3 }), sceneNoise({ f: 600, d: 0.1, vol: 0.4 })], "L1352 hurt", { bus: "voices" });
3170
+ var FLESH = scene("flesh", [sceneNoise({ f: 650, d: 0.08, vol: 0.6 }), sceneTone({ f: 90, f2: 45, d: 0.07, vol: 0.5 })], "L1353 flesh");
3171
+ var FLESH_HEAD = scene("flesh_head", [sceneNoise({ f: 1100, d: 0.12, vol: 0.9 }), sceneTone({ f: 130, f2: 45, d: 0.07, vol: 0.5 })], "L1353 flesh (head)");
3172
+ var SEVER = scene(
3173
+ "sever",
3174
+ [sceneNoise({ type: "bandpass", f: 500, q: 0.8, d: 0.3, vol: 1 }), sceneNoise({ type: "highpass", f: 2400, d: 0.06, vol: 0.7, delay: 0.02 }), sceneTone({ f: 110, f2: 40, d: 0.15, vol: 0.6 })],
3175
+ "L1355 sever"
3176
+ );
3177
+ var GORE = scene(
3178
+ "gore",
3179
+ [
3180
+ sceneNoise({ f: 900, f2: 200, d: 0.4, vol: 1.1, wet: 0.3 }),
3181
+ ...[0, 1, 2].map((k) => sceneNoise({ type: "bandpass", f: [400, 800], q: 2, d: 0.08, vol: 0.5, delay: 0.08 + k * 0.07 }))
3182
+ ],
3183
+ "L1356 gore"
3184
+ );
3185
+ var CHARACTER_SOUNDS = [
3186
+ SCREAM_HOARSE,
3187
+ GROAN,
3188
+ RUNNER_GROAN,
3189
+ GROAN_WALKER,
3190
+ GROAN_BRUTE,
3191
+ GROAN_RUNNER,
3192
+ SCREAM,
3193
+ INHALE,
3194
+ LEAP,
3195
+ GRAB,
3196
+ BITE,
3197
+ HURT,
3198
+ FLESH,
3199
+ FLESH_HEAD,
3200
+ SEVER,
3201
+ GORE
3202
+ ];
3203
+ var CHARACTER_SOUND_BANK = { id: "characters_three", sounds: CHARACTER_SOUNDS };
3204
+
3205
+ // src/audio/voices.json
3206
+ var voices_default = {
3207
+ sources: [
3208
+ "cena:L1345 groan(z): the Brutamonte has its own, a chase speed above 3.4 is a runner's, every other body groans like a walker",
3209
+ "cena:L1238, L1248, L1249 the scream and the inhale belong to the Gritador, the leap to the Saltador",
3210
+ "cena:L1226 the groan when a body wakes (FEIGN to CHASE) and the game L67275/L67328 voz('corredor') when one notices the player",
3211
+ "jogo:L66986 the groan of the game: 'corredor' for the runner category, 'gemido' for the rest; jogo:L66989 screamAt plays 'grito'",
3212
+ "types.json: the voice line of each Bestiario type"
3213
+ ],
3214
+ types: {
3215
+ walker: { origin: "team", groan: "groan_walker", gameGroan: "groan" },
3216
+ runner: { origin: "team", groan: "groan_runner", gameGroan: "runner_groan" },
3217
+ crawler: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
3218
+ brute: { origin: "team", groan: "groan_brute", gameGroan: "groan" },
3219
+ sniffer: {
3220
+ origin: "extra",
3221
+ groan: "groan_runner",
3222
+ gameGroan: "groan",
3223
+ idle: [{ sound: "inhale" }, { sound: "groan_runner", delay: 0.6 }]
3224
+ },
3225
+ leaper: { origin: "team", groan: "groan_runner", gameGroan: "groan", special: [{ sound: "leap" }] },
3226
+ bloater: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
3227
+ stalker: { origin: "rule", groan: "groan_runner", gameGroan: "groan", idle: [] },
3228
+ blind: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
3229
+ screamer: {
3230
+ origin: "team",
3231
+ groan: "groan_walker",
3232
+ gameGroan: "groan",
3233
+ special: [{ sound: "inhale" }, { sound: "scream", delay: 1.5 }],
3234
+ gameSpecial: [{ sound: "scream_hoarse" }]
3235
+ },
3236
+ charred: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
3237
+ drowned: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
3238
+ devourer: {
3239
+ origin: "extra",
3240
+ groan: "groan_walker",
3241
+ gameGroan: "groan",
3242
+ idle: [{ sound: "groan_walker" }, { sound: "bite", delay: 0.4 }]
3243
+ }
3244
+ }
3245
+ };
3246
+
3247
+ // src/audio/sound.ts
3248
+ var DATA = voices_default;
3249
+ var UNDEAD_VOICES = DATA.types;
3250
+ var UNDEAD_VOICE_SOURCES = DATA.sources;
3251
+ var BODY_SOUNDS = {
3252
+ art_scene: {
3253
+ wound: { sound: "flesh", head: "flesh_head" },
3254
+ severed: { sound: "sever" },
3255
+ destroyed: { sound: "gore" },
3256
+ split: { sound: "gore" }
3257
+ },
3258
+ game: {
3259
+ wound: { sound: "sfx_flesh" }
3260
+ }
3261
+ };
3262
+ var PLAYER_SOUNDS = { grab: "grab", bite: "bite", hurt: "hurt" };
3263
+ var HEAD_PARTS = /* @__PURE__ */ new Set(["head", "jaw"]);
3264
+ function createCharacterSound(sink, options = {}) {
3265
+ sink.register?.(CHARACTER_SOUND_BANK);
3266
+ sink.register?.(GAME_SFX_BANK);
3267
+ const style = options.style ?? "art_scene";
3268
+ const voices = { ...UNDEAD_VOICES, ...options.voices };
3269
+ const rules = { ...BODY_SOUNDS.art_scene, ...style === "game" ? BODY_SOUNDS.game : {}, ...options.body };
3270
+ const queue = [];
3271
+ const voiceOf = (typeId) => {
3272
+ const own = voices[typeId];
3273
+ if (own === null) return void 0;
3274
+ return own ?? (options.fallbackType ? voices[options.fallbackType] ?? void 0 : void 0);
3275
+ };
3276
+ const stepsOf = (typeId, moment) => {
3277
+ const voice = voiceOf(typeId);
3278
+ if (!voice) return [];
3279
+ const groan2 = style === "game" ? voice.gameGroan : voice.groan;
3280
+ if (moment === "special") return (style === "game" ? voice.gameSpecial ?? voice.special : voice.special) ?? [];
3281
+ if (moment === "alert") return voice.alert ?? [{ sound: style === "game" ? "runner_groan" : groan2 }];
3282
+ return voice.idle ?? [{ sound: groan2 }];
3283
+ };
3284
+ const start = (sound, at, volume) => void sink.play({ sound, position: at, volume });
3285
+ return {
3286
+ stepsOf,
3287
+ voice(typeId, moment, position, volume = 1) {
3288
+ const steps = stepsOf(typeId, moment);
3289
+ for (const step of steps) {
3290
+ if (step.delay && step.delay > 0) queue.push({ sound: step.sound, at: position, volume, wait: step.delay });
3291
+ else start(step.sound, position, volume);
3292
+ }
3293
+ return steps.length;
3294
+ },
3295
+ body(event, position, volume = 1) {
3296
+ if (event.type !== "body") return false;
3297
+ const rule = rules[event.change];
3298
+ if (!rule) return false;
3299
+ const head = event.part !== void 0 && HEAD_PARTS.has(event.part);
3300
+ start(head && rule.head ? rule.head : rule.sound, position, volume);
3301
+ return true;
3302
+ },
3303
+ player: (kind, volume = 1) => void sink.play({ sound: PLAYER_SOUNDS[kind], volume }),
3304
+ update(dt) {
3305
+ for (let i = queue.length - 1; i >= 0; i--) {
3306
+ const item = queue[i];
3307
+ item.wait -= dt;
3308
+ if (item.wait <= 0) {
3309
+ queue.splice(i, 1);
3310
+ start(item.sound, item.at, item.volume);
3311
+ }
3312
+ }
3313
+ },
3314
+ pending: () => queue.length,
3315
+ dispose() {
3316
+ queue.length = 0;
3317
+ }
3318
+ };
3319
+ }
3320
+
3043
3321
  // src/test-area/crowd.ts
3044
3322
  import { encodePoseHeight } from "@adatechnology/game-characters";
3045
3323
  var FIELD = { x0: 12, x1: 30, z0: -16, z1: 16 };
@@ -3306,7 +3584,7 @@ var TEXT = {
3306
3584
  hit_preset: "What the Fire hit button does to the body you look at",
3307
3585
  protection: "Worn by the bodies created from now on"
3308
3586
  },
3309
- actions: { heal: "Heal the aimed body", kill: "Kill the aimed body", infect: "Bite the aimed person (accelerated)", spawn_100: "Spawn 100 in the crowd", spawn_1000: "Spawn 1000 in the crowd", fire_preset: "Fire the hit" },
3587
+ actions: { heal: "Heal the aimed body", kill: "Kill the aimed body", infect: "Bite the aimed person (accelerated)", spawn_100: "Spawn 100 in the crowd", spawn_1000: "Spawn 1000 in the crowd", fire_preset: "Fire the hit", voice: "Make the aimed zombie speak (groan, by the moment)", special_call: "Make the aimed zombie use its call (scream, leap)" },
3310
3588
  options: {
3311
3589
  penetration: { physical: "Physical", "team-game": "Team game" },
3312
3590
  death_rule: { "vitality-or-head": "Vitality or head", "head-only": "Head only" },
@@ -3351,7 +3629,7 @@ var TEXT = {
3351
3629
  hit_preset: "O que o bot\xE3o Disparar faz no corpo para o qual voc\xEA olha",
3352
3630
  protection: "Vestida pelos corpos criados daqui em diante"
3353
3631
  },
3354
- actions: { heal: "Curar o corpo mirado", kill: "Matar o corpo mirado", infect: "Morder a pessoa mirada (acelerado)", spawn_100: "P\xF4r 100 na multid\xE3o", spawn_1000: "P\xF4r 1000 na multid\xE3o", fire_preset: "Disparar o golpe" },
3632
+ actions: { heal: "Curar o corpo mirado", kill: "Matar o corpo mirado", infect: "Morder a pessoa mirada (acelerado)", spawn_100: "P\xF4r 100 na multid\xE3o", spawn_1000: "P\xF4r 1000 na multid\xE3o", fire_preset: "Disparar o golpe", voice: "Fazer o zumbi mirado falar (gemido, pelo momento)", special_call: "Fazer o zumbi mirado usar o chamado (grito, salto)" },
3355
3633
  options: {
3356
3634
  penetration: { physical: "F\xEDsica", "team-game": "Do jogo" },
3357
3635
  death_rule: { "vitality-or-head": "Vitalidade ou cabe\xE7a", "head-only": "S\xF3 a cabe\xE7a" },
@@ -3406,6 +3684,7 @@ function createCharactersTestModule(options = {}) {
3406
3684
  let best = null;
3407
3685
  let people = null;
3408
3686
  let wounds = null;
3687
+ let sound = null;
3409
3688
  let crowd = null;
3410
3689
  let selected = null;
3411
3690
  let time = 0;
@@ -3498,6 +3777,9 @@ function createCharactersTestModule(options = {}) {
3498
3777
  if (Math.abs(scale - 1) > 0.01) object.scale.setScalar(scale);
3499
3778
  const entry = { id, kind: made.kind, cardId: made.cardId, root: object, body: woundBodyOf(object), eyes: three ? findEyes(three, object) : null, built: made.built, character, walk: 0, tilt: 0 };
3500
3779
  entry.character = wrap(entry, character);
3780
+ const age = made.kind === "zombie" ? undead?.traitsOf(character)?.age : void 0;
3781
+ const blood = age !== void 0 ? options.bloodOf?.(age) : void 0;
3782
+ if (entry.body && blood) entry.body.blood = blood;
3501
3783
  entries.set(id, entry);
3502
3784
  selected ?? (selected = entry);
3503
3785
  trackEntry(entry);
@@ -3580,8 +3862,8 @@ function createCharactersTestModule(options = {}) {
3580
3862
  const selectedOrFirst = () => selected ?? entries.values().next().value ?? null;
3581
3863
  const fireOn = (entry) => {
3582
3864
  const preset2 = hitPresetOf(settings.hit);
3583
- const p = entry.root.position;
3584
- entry.character.hit(hitFor(preset2, [p.x, p.y, p.z], 1.3, kit.calibers));
3865
+ const p2 = entry.root.position;
3866
+ entry.character.hit(hitFor(preset2, [p2.x, p2.y, p2.z], 1.3, kit.calibers));
3585
3867
  };
3586
3868
  const ensureCrowd = () => {
3587
3869
  if (crowd) return crowd;
@@ -3616,7 +3898,14 @@ function createCharactersTestModule(options = {}) {
3616
3898
  if (infection.reanimated) target.character = wrap(target, undead.turn(target.character));
3617
3899
  }
3618
3900
  } else if (change.id === "fire_preset" && target) fireOn(target);
3619
- else if (change.id === "spawn_100" || change.id === "spawn_1000") {
3901
+ else if ((change.id === "voice" || change.id === "special_call") && sound) {
3902
+ const speaker = target?.kind === "zombie" ? target : [...entries.values()].find((entry) => entry.kind === "zombie");
3903
+ if (speaker) {
3904
+ const at = speaker.root.getWorldPosition(new host.THREE.Vector3());
3905
+ const hurried = settings.moment === "alert" || settings.moment === "chase" || settings.moment === "sprint";
3906
+ sound.voice(speaker.cardId, change.id === "special_call" ? "special" : hurried ? "alert" : "idle", [at.x, at.y + 1.5, at.z]);
3907
+ }
3908
+ } else if (change.id === "spawn_100" || change.id === "spawn_1000") {
3620
3909
  ensureBuilders(host?.THREE ?? three);
3621
3910
  ensureCrowd().spawn(change.id === "spawn_100" ? 100 : 1e3);
3622
3911
  }
@@ -3721,6 +4010,8 @@ function createCharactersTestModule(options = {}) {
3721
4010
  withHint(choice("hit_preset", HIT_PRESETS.map((preset2) => preset2.id), "9x19"), text.hints.hit_preset),
3722
4011
  withHint(choice("protection", ["none", "vest_police", "vest_military", "helmet_combat", "turnout_gear", "vest_helmet"], "none"), text.hints.protection),
3723
4012
  { kind: "action", id: "fire_preset", name: text.actions.fire_preset },
4013
+ { kind: "action", id: "voice", name: text.actions.voice },
4014
+ { kind: "action", id: "special_call", name: text.actions.special_call },
3724
4015
  { kind: "action", id: "heal", name: text.actions.heal },
3725
4016
  { kind: "action", id: "kill", name: text.actions.kill },
3726
4017
  { kind: "action", id: "infect", name: text.actions.infect },
@@ -3748,9 +4039,10 @@ function createCharactersTestModule(options = {}) {
3748
4039
  attach(lent) {
3749
4040
  host = lent;
3750
4041
  ensureBuilders(lent.THREE);
3751
- const scene = lent.scene;
3752
- const bridge = options.fluids ? createFluidsBridge({ sink: options.fluids }) : void 0;
3753
- wounds = createWounds(lent.THREE, { random, scene, groundAt: (x, z) => lent.groundAt(x, z), ...bridge ? { bridge } : {} });
4042
+ const scene2 = lent.scene;
4043
+ const bridge = options.fluids ? createFluidsBridge({ sink: options.fluids, ...options.film ? { film: options.film } : {} }) : void 0;
4044
+ sound = lent.audio && options.sound !== false ? createCharacterSound(lent.audio, options.sound ?? {}) : null;
4045
+ wounds = createWounds(lent.THREE, { random, scene: scene2, groundAt: (x, z) => lent.groundAt(x, z), ...bridge ? { bridge } : {}, ...sound ? { sound } : {} });
3754
4046
  for (const entry of entries.values()) trackEntry(entry);
3755
4047
  const stopChange = lent.bench?.onChange(onChange);
3756
4048
  const stopFrame = lent.onFrame((dt) => {
@@ -3763,6 +4055,7 @@ function createCharactersTestModule(options = {}) {
3763
4055
  animate(entry, dt);
3764
4056
  }
3765
4057
  wounds?.update(dt);
4058
+ sound?.update(dt);
3766
4059
  crowd?.update(dt, lent.camera);
3767
4060
  });
3768
4061
  return () => {
@@ -3771,6 +4064,8 @@ function createCharactersTestModule(options = {}) {
3771
4064
  clearOverlays();
3772
4065
  wounds?.dispose();
3773
4066
  wounds = null;
4067
+ sound?.dispose();
4068
+ sound = null;
3774
4069
  if (crowd) {
3775
4070
  crowd.group.parent?.remove(crowd.group);
3776
4071
  crowd = null;
@@ -3804,7 +4099,10 @@ export {
3804
4099
  BAKE_NEUTRAL_POSE,
3805
4100
  BODY_CHUNK,
3806
4101
  BODY_MATERIAL_DEF,
4102
+ BODY_SOUNDS,
3807
4103
  CHARACTERS_AREA_ID,
4104
+ CHARACTER_SOUNDS,
4105
+ CHARACTER_SOUND_BANK,
3808
4106
  EXTRAS,
3809
4107
  FACE_FEATURES,
3810
4108
  HAND_ITEMS,
@@ -3817,16 +4115,22 @@ export {
3817
4115
  LOD_SEGMENTS,
3818
4116
  PENDULUM,
3819
4117
  PERSON_ITEM_PREFIX,
4118
+ PLAYER_SOUNDS,
4119
+ UNDEAD_VOICES,
4120
+ UNDEAD_VOICE_GROUP,
4121
+ UNDEAD_VOICE_SOURCES,
3820
4122
  ZOMBIE_ITEM_PREFIX,
3821
4123
  bakeBody,
3822
4124
  bakeVariants,
3823
4125
  blobGeometry,
3824
4126
  blobRadii,
4127
+ bloodLookOfAge,
3825
4128
  buildInstancedBody,
3826
4129
  createBestBuilder,
3827
4130
  createBodyBatch,
3828
4131
  createBodyChunk,
3829
4132
  createBodyMaterial,
4133
+ createCharacterSound,
3830
4134
  createCharactersTestModule,
3831
4135
  createEye,
3832
4136
  createFluidsBridge,