@adatechnology/game-characters-three 0.1.3 → 0.2.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.
@@ -24,7 +24,10 @@ var GameCharactersThree = (() => {
24
24
  BAKE_NEUTRAL_POSE: () => BAKE_NEUTRAL_POSE,
25
25
  BODY_CHUNK: () => BODY_CHUNK,
26
26
  BODY_MATERIAL_DEF: () => BODY_MATERIAL_DEF,
27
+ BODY_SOUNDS: () => BODY_SOUNDS,
27
28
  CHARACTERS_AREA_ID: () => CHARACTERS_AREA_ID,
29
+ CHARACTER_SOUNDS: () => CHARACTER_SOUNDS,
30
+ CHARACTER_SOUND_BANK: () => CHARACTER_SOUND_BANK,
28
31
  EXTRAS: () => EXTRAS,
29
32
  FACE_FEATURES: () => FACE_FEATURES,
30
33
  HAND_ITEMS: () => HAND_ITEMS,
@@ -37,16 +40,22 @@ var GameCharactersThree = (() => {
37
40
  LOD_SEGMENTS: () => LOD_SEGMENTS,
38
41
  PENDULUM: () => PENDULUM,
39
42
  PERSON_ITEM_PREFIX: () => PERSON_ITEM_PREFIX,
43
+ PLAYER_SOUNDS: () => PLAYER_SOUNDS,
44
+ UNDEAD_VOICES: () => UNDEAD_VOICES,
45
+ UNDEAD_VOICE_GROUP: () => UNDEAD_VOICE_GROUP,
46
+ UNDEAD_VOICE_SOURCES: () => UNDEAD_VOICE_SOURCES,
40
47
  ZOMBIE_ITEM_PREFIX: () => ZOMBIE_ITEM_PREFIX,
41
48
  bakeBody: () => bakeBody,
42
49
  bakeVariants: () => bakeVariants,
43
50
  blobGeometry: () => blobGeometry,
44
51
  blobRadii: () => blobRadii,
52
+ bloodLookOfAge: () => bloodLookOfAge,
45
53
  buildInstancedBody: () => buildInstancedBody,
46
54
  createBestBuilder: () => createBestBuilder,
47
55
  createBodyBatch: () => createBodyBatch,
48
56
  createBodyChunk: () => createBodyChunk,
49
57
  createBodyMaterial: () => createBodyMaterial,
58
+ createCharacterSound: () => createCharacterSound,
50
59
  createCharactersTestModule: () => createCharactersTestModule,
51
60
  createEye: () => createEye,
52
61
  createFluidsBridge: () => createFluidsBridge,
@@ -2864,7 +2873,7 @@ var GameCharactersThree = (() => {
2864
2873
  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]];
2865
2874
  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]];
2866
2875
  const torsoGeo = (sx, sz, fem) => {
2867
- 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)));
2876
+ 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)));
2868
2877
  g.scale(1.12 * (sx || 1), 1, 0.64 * (sz || 1));
2869
2878
  return g;
2870
2879
  };
@@ -2952,34 +2961,34 @@ var GameCharactersThree = (() => {
2952
2961
  const skinFinal = P.char ? 1840658 : P.wet ? col(skinC, 8032160, 0.55) : skinC;
2953
2962
  const skinM = M(skinFinal);
2954
2963
  const topM = M(P.char ? col(topC, 1380880, 0.75) : P.wet ? col(topC, 1712688, 0.45) : col(topC, GRAY2, d * 0.3)), pantsM = M(col(pantC, GRAY2, d * 0.3)), shoeM = M(T.shoeColor), red = M(RED2);
2955
- const add = (p, geo, m, x, y, z2, rx, ry, rz) => {
2964
+ const add = (p2, geo, m, x, y, z2, rx, ry, rz) => {
2956
2965
  const o = new THREE.Mesh(geo, m);
2957
2966
  o.position.set(x || 0, y || 0, z2 || 0);
2958
2967
  if (rx || ry || rz) o.rotation.set(rx || 0, ry || 0, rz || 0);
2959
- p.add(o);
2968
+ p2.add(o);
2960
2969
  return o;
2961
2970
  };
2962
- const ball = (p, r, m, x, y, z2) => add(p, SPH(r, SEG, 12), m, x, y, z2);
2971
+ const ball = (p2, r, m, x, y, z2) => add(p2, SPH(r, SEG, 12), m, x, y, z2);
2963
2972
  topM_ref = topM;
2964
2973
  const SKM = {}, skM = (c) => SKM[c] || (SKM[c] = new THREE.MeshStandardMaterial({ color: c, roughness: 0.3, metalness: 0, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
2965
- const flat = (p, x, y, zz, sx, sy, c) => {
2974
+ const flat = (p2, x, y, zz, sx, sy, c) => {
2966
2975
  const m = new THREE.Mesh(BLOB, skM(c));
2967
2976
  m.rotation.set(PI / 2, 0, BR() * 6.28);
2968
2977
  m.scale.set(sx, 1, sy);
2969
2978
  m.position.set(x, y, zz);
2970
- p.add(m);
2979
+ p2.add(m);
2971
2980
  return m;
2972
2981
  };
2973
- const splat = (p, x, y, zz, r, c, n) => {
2974
- const a = flat(p, x, y, zz + 4e-3, r, r * (0.7 + BR() * 0.5), c);
2982
+ const splat = (p2, x, y, zz, r, c, n) => {
2983
+ const a = flat(p2, x, y, zz + 4e-3, r, r * (0.7 + BR() * 0.5), c);
2975
2984
  for (let k = 0; k < (n || 0); k++) {
2976
2985
  const an = BR() * 6.28, dd = r * (1.2 + BR() * 0.9), q = r * (0.12 + BR() * 0.18);
2977
- flat(p, x + Math.cos(an) * dd, y + Math.sin(an) * dd, zz + 4e-3, q, q * 0.7, c);
2986
+ flat(p2, x + Math.cos(an) * dd, y + Math.sin(an) * dd, zz + 4e-3, q, q * 0.7, c);
2978
2987
  }
2979
- 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);
2988
+ 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);
2980
2989
  return a;
2981
2990
  };
2982
- const run = (p, x, y0, y1, z0, z1, c, w) => {
2991
+ const run = (p2, x, y0, y1, z0, z1, c, w) => {
2983
2992
  const L = y0 - y1, sh = new THREE.Shape();
2984
2993
  sh.moveTo(-w / 2, 0);
2985
2994
  sh.lineTo(w / 2, 0);
@@ -2989,30 +2998,30 @@ var GameCharactersThree = (() => {
2989
2998
  const g = new THREE.ShapeGeometry(sh), m = new THREE.Mesh(g, skM(c));
2990
2999
  m.position.set(x, y0, z0);
2991
3000
  m.rotation.x = -Math.atan2(z0 - z1, L);
2992
- p.add(m);
3001
+ p2.add(m);
2993
3002
  const bl = new THREE.Mesh(SPH(w * 0.42, 10, 8), skM(c));
2994
3003
  bl.scale.set(1, 1.35, 0.5);
2995
3004
  bl.position.set(x + w * 0.03, y1, z1);
2996
- p.add(bl);
3005
+ p2.add(bl);
2997
3006
  return bl;
2998
3007
  };
2999
- const stump = (p, r, y, up) => {
3000
- const st = ball(p, r * 1.02, red, 0, y, 0);
3008
+ const stump = (p2, r, y, up) => {
3009
+ const st = ball(p2, r * 1.02, red, 0, y, 0);
3001
3010
  st.scale.set(1, 0.35, 1);
3002
- add(p, CYL(r * 0.32, r * 0.36, 0.07, 10), M(15261900), 0, y + (up ? 0.04 : -0.04), 0);
3011
+ add(p2, CYL(r * 0.32, r * 0.36, 0.07, 10), M(15261900), 0, y + (up ? 0.04 : -0.04), 0);
3003
3012
  };
3004
- const ring = (p, r, h, m, y, sx, sz) => {
3005
- const o = add(p, CYL(r, r, h, 24), m, 0, y, 0);
3013
+ const ring = (p2, r, h, m, y, sx, sz) => {
3014
+ const o = add(p2, CYL(r, r, h, 24), m, 0, y, 0);
3006
3015
  o.scale.set(sx || 1.12, 1, sz || 0.7);
3007
3016
  return o;
3008
3017
  };
3009
3018
  const root = new THREE.Group(), fig = new THREE.Group();
3010
- const drip = (p, x, y, zz, kind, every) => {
3019
+ const drip = (p2, x, y, zz, kind, every) => {
3011
3020
  if (d >= 3 && kind !== "bile") return;
3012
3021
  const o = new THREE.Object3D();
3013
3022
  o.position.set(x, y, zz);
3014
3023
  o.userData.drip = { kind: kind || (d < 1 ? "blood" : "old"), every: every || (d < 1 ? 1.2 : 2.4) };
3015
- p.add(o);
3024
+ p2.add(o);
3016
3025
  };
3017
3026
  fig.scale.set(P.s * P.bulk, P.s, P.s * Math.min(1.15, P.bulk));
3018
3027
  fig.position.y = P.drop * P.s;
@@ -4182,7 +4191,7 @@ var GameCharactersThree = (() => {
4182
4191
  }
4183
4192
  function torsoGeo(fem) {
4184
4193
  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]];
4185
- const pts = new THREE.SplineCurve(P.map((p) => new THREE.Vector2(p[0], p[1]))).getPoints(30);
4194
+ const pts = new THREE.SplineCurve(P.map((p2) => new THREE.Vector2(p2[0], p2[1]))).getPoints(30);
4186
4195
  const g = new THREE.LatheGeometry(pts, 24);
4187
4196
  g.scale(1.12, 1, 0.64);
4188
4197
  return g;
@@ -4537,15 +4546,15 @@ var GameCharactersThree = (() => {
4537
4546
  }
4538
4547
  const MOVES_IDLE = MOVES.idle;
4539
4548
  function pose(c, z, W, t) {
4540
- const p = c.ph + t, freq = z ? 3.4 : 5.6, ph = t * freq + c.ph;
4541
- const breathe = reduce ? 0 : Math.sin(p * 1.6) * 0.012;
4549
+ const p2 = c.ph + t, freq = z ? 3.4 : 5.6, ph = t * freq + c.ph;
4550
+ const breathe = reduce ? 0 : Math.sin(p2 * 1.6) * 0.012;
4542
4551
  c.upper.scale.y = 1 + breathe;
4543
4552
  const hunch = c.hunch + (z ? 0.38 : 0) + W * 0.05;
4544
4553
  c.upper.rotation.x = hunch;
4545
4554
  c.upper.rotation.y = W * Math.sin(ph) * 0.1;
4546
4555
  c.upper.rotation.z = z ? Math.sin(ph * 0.5) * 0.06 * W + 0.05 : 0;
4547
4556
  c.fig.position.y = -W * Math.abs(Math.sin(ph)) * 0.035;
4548
- c.headG.rotation.y = reduce ? 0 : Math.sin(p * 0.5) * 0.25 * (z ? 0.3 : 1) * (W ? 0.4 : 1) * (t ? 1 : 0);
4557
+ c.headG.rotation.y = reduce ? 0 : Math.sin(p2 * 0.5) * 0.25 * (z ? 0.3 : 1) * (W ? 0.4 : 1) * (t ? 1 : 0);
4549
4558
  c.headG.rotation.x = -hunch * 0.6 + (z ? 0.15 : 0);
4550
4559
  c.headG.rotation.z = z ? 0.22 : 0;
4551
4560
  c.legs.forEach((l, k) => {
@@ -4558,7 +4567,7 @@ var GameCharactersThree = (() => {
4558
4567
  const raise = z ? 1 : 0;
4559
4568
  const base = a.hold ? -0.2 : 0;
4560
4569
  const swing = -a.s * Math.sin(ph) * (z ? 0.12 : 0.42) * W * (a.grip ? 0 : 1);
4561
- 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);
4570
+ 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);
4562
4571
  a.g.rotation.z = a.s * (0.07 + (z ? 0.1 : 0)) + (a.grip && !z ? -a.s * 0.35 : 0);
4563
4572
  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));
4564
4573
  if (a.fingers) {
@@ -4583,16 +4592,16 @@ var GameCharactersThree = (() => {
4583
4592
  c.mouth.visible = true;
4584
4593
  c.bags.material.opacity = 0.9;
4585
4594
  }
4586
- function buildPerson(p, zed) {
4595
+ function buildPerson(p2, zed) {
4587
4596
  const root = new THREE.Group(), chars = [];
4588
- if (p.b) {
4589
- const c = build(p.b, 0);
4597
+ if (p2.b) {
4598
+ const c = build(p2.b, 0);
4590
4599
  chars.push(c);
4591
4600
  root.add(c.root);
4592
- if (zed && !p.kid) zombify(c, p.zcat);
4593
- c.z = zed && !p.kid;
4601
+ if (zed && !p2.kid) zombify(c, p2.zcat);
4602
+ c.z = zed && !p2.kid;
4594
4603
  }
4595
- (p.kids || []).forEach((k) => {
4604
+ (p2.kids || []).forEach((k) => {
4596
4605
  const c = build(k, 0);
4597
4606
  c.root.position.x = k.x;
4598
4607
  c.root.rotation.y = -k.x * 0.4;
@@ -4850,11 +4859,11 @@ var GameCharactersThree = (() => {
4850
4859
  };
4851
4860
  return {
4852
4861
  pieces,
4853
- launch(source, scene, direction, dress, tag) {
4862
+ launch(source, scene2, direction, dress, tag) {
4854
4863
  source.updateWorldMatrix(true, true);
4855
4864
  const clone = source.clone(true);
4856
4865
  source.matrixWorld.decompose(clone.position, clone.quaternion, clone.scale);
4857
- scene.add(clone);
4866
+ scene2.add(clone);
4858
4867
  dress?.(clone);
4859
4868
  return add(
4860
4869
  clone,
@@ -5003,7 +5012,7 @@ var GameCharactersThree = (() => {
5003
5012
  // src/wounds/wounds.ts
5004
5013
  var worldPosition = (three, object) => object.getWorldPosition(new three.Vector3());
5005
5014
  var createWounds = (three, options) => {
5006
- const { random, scene, bridge } = options;
5015
+ const { random, scene: scene2, bridge, sound } = options;
5007
5016
  const materials = options.materials ?? createWoundMaterials(three);
5008
5017
  const flying = createFlyingPieces(three, { random, ...options.groundAt ? { groundAt: options.groundAt } : {}, ...options.maxPieces ? { max: options.maxPieces } : {} });
5009
5018
  const states = /* @__PURE__ */ new Map();
@@ -5012,9 +5021,9 @@ var GameCharactersThree = (() => {
5012
5021
  if (!found) throw new Error("game-characters-three: this body is not tracked by the wounds (call `track` first)");
5013
5022
  return found;
5014
5023
  };
5015
- const burstBlood = (change, object) => {
5024
+ const burstBlood = (state, change, object, direction) => {
5016
5025
  const point = worldPosition(three, object);
5017
- bridge?.burst(change, [point.x, point.y, point.z]);
5026
+ bridge?.burst(change, [point.x, point.y, point.z], state.body.blood, direction ? [direction.x, direction.y, direction.z] : void 0);
5018
5027
  };
5019
5028
  const sideOf2 = (object, body) => new three.Vector3(object.position.x >= 0 ? 1 : -1, 0, 0).applyQuaternion(body.root.quaternion);
5020
5029
  const hideBelow = (state, id) => {
@@ -5029,7 +5038,7 @@ var GameCharactersThree = (() => {
5029
5038
  }
5030
5039
  const { node, radius, flip } = target;
5031
5040
  const direction = sideOf2(node, state.body);
5032
- const fell = flying.launch(node, scene, direction, (clone) => {
5041
+ const fell = flying.launch(node, scene2, direction, (clone) => {
5033
5042
  makeCut(three, materials, random, clone, radius, true);
5034
5043
  }, id);
5035
5044
  void fell;
@@ -5043,7 +5052,7 @@ var GameCharactersThree = (() => {
5043
5052
  for (let o = pendulum.node; o; o = o.parent) if (!o.visible) shown = false;
5044
5053
  if (!shown) state.pendulums.splice(i, 1);
5045
5054
  }
5046
- burstBlood("severed", node);
5055
+ burstBlood(state, "severed", node, direction);
5047
5056
  if (id === "head") state.dead = true;
5048
5057
  };
5049
5058
  const destroy = (state, id, direction) => {
@@ -5052,14 +5061,14 @@ var GameCharactersThree = (() => {
5052
5061
  const { node, radius, flip } = target;
5053
5062
  const box = new three.Box3().setFromObject(node);
5054
5063
  explodeChunks(three, materials, random, box, direction, (chunk, velocity, spin) => {
5055
- scene.add(chunk);
5064
+ scene2.add(chunk);
5056
5065
  flying.throwObject(chunk, velocity, spin, id);
5057
5066
  });
5058
5067
  node.visible = false;
5059
5068
  makeStump(three, materials, random, node.parent, node.position, radius, flip);
5060
5069
  state.gone.add(id);
5061
5070
  hideBelow(state, id);
5062
- burstBlood("destroyed", node);
5071
+ burstBlood(state, "destroyed", node, direction);
5063
5072
  if (id === "head") state.dead = true;
5064
5073
  };
5065
5074
  const breakBone = (state, id) => {
@@ -5079,7 +5088,7 @@ var GameCharactersThree = (() => {
5079
5088
  const split2 = (state, direction) => {
5080
5089
  const upper = state.body.rig.upper;
5081
5090
  if (!upper.visible) return;
5082
- flying.launch(upper, scene, direction, (clone) => {
5091
+ flying.launch(upper, scene2, direction, (clone) => {
5083
5092
  makeCut(three, materials, random, clone, 0.16, false);
5084
5093
  }, "torso");
5085
5094
  upper.visible = false;
@@ -5087,7 +5096,7 @@ var GameCharactersThree = (() => {
5087
5096
  for (const id of ["torso", "neck", "head", "jaw", "upperArmL", "upperArmR", "forearmL", "forearmR", "handL", "handR"]) state.gone.add(id);
5088
5097
  state.pendulums.length = 0;
5089
5098
  state.dead = true;
5090
- burstBlood("split", upper);
5099
+ burstBlood(state, "split", upper, direction);
5091
5100
  };
5092
5101
  const direction3 = (v, fallback) => v ? new three.Vector3(v[0], v[1], v[2]).normalize() : fallback;
5093
5102
  return {
@@ -5121,6 +5130,10 @@ var GameCharactersThree = (() => {
5121
5130
  }
5122
5131
  if (event.type !== "body") return;
5123
5132
  const id = event.part;
5133
+ if (sound) {
5134
+ const heard = worldPosition(three, (id ? body.nodes[id]?.node : void 0) ?? body.root);
5135
+ sound.body(event, [heard.x, heard.y, heard.z]);
5136
+ }
5124
5137
  const fallback = new three.Vector3(0, 0.2, 1);
5125
5138
  const direction = direction3(event.exit, fallback);
5126
5139
  switch (event.change) {
@@ -5147,8 +5160,10 @@ var GameCharactersThree = (() => {
5147
5160
  break;
5148
5161
  case "wound": {
5149
5162
  const target = id ? body.nodes[id] : void 0;
5163
+ const at = worldPosition(three, target?.node ?? body.root);
5164
+ bridge?.wound([at.x, at.y, at.z], body.blood, event.exit ? [direction.x, direction.y, direction.z] : void 0);
5150
5165
  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) => {
5151
- scene.add(chunk);
5166
+ scene2.add(chunk);
5152
5167
  flying.throwObject(chunk, velocity, spin, id);
5153
5168
  });
5154
5169
  break;
@@ -5194,8 +5209,28 @@ var GameCharactersThree = (() => {
5194
5209
  };
5195
5210
 
5196
5211
  // src/wounds/fluids-bridge.ts
5212
+ var colorOf = (hex2) => Number.parseInt(hex2.replace(/^#|^0x/i, ""), 16);
5213
+ var bloodLookOfAge = (age, options = {}) => ({
5214
+ ...age.blood < (options.thinBelow ?? 0.3) ? { liquid: options.thinLiquid ?? "old_blood" } : {},
5215
+ tint: { fresh: colorOf(age.freshBloodColor), dry: colorOf(age.dryBloodColor) },
5216
+ amount: age.blood
5217
+ });
5197
5218
  var BURST_LITERS = { severed: 0.35, destroyed: 0.6, split: 0.9 };
5198
- var createFluidsBridge = ({ sink, liquid = "blood", spurtSpeed = 1.4 }) => {
5219
+ var BURST_DROPS = { severed: 4, destroyed: 7, split: 10 };
5220
+ var createFluidsBridge = ({ sink, film, spray, burstDrops = BURST_DROPS, liquid = "blood", spurtSpeed = 1.4 }) => {
5221
+ const throwDrops = (point, wanted, look, direction, speed, radius) => {
5222
+ const amount = look?.amount ?? 1;
5223
+ const count = amount <= 0 ? 0 : Math.max(1, Math.round(wanted * Math.min(1, amount)));
5224
+ return film && count > 0 ? film.splat({
5225
+ fluid: look?.liquid ?? liquid,
5226
+ point,
5227
+ count,
5228
+ ...direction ? { direction } : {},
5229
+ ...speed !== void 0 ? { speed } : {},
5230
+ ...radius !== void 0 ? { radius } : {},
5231
+ ...look?.tint ? { tint: { fresh: look.tint.fresh, dry: look.tint.dry } } : {}
5232
+ }) : 0;
5233
+ };
5199
5234
  const bleed = (point, litersPerSecond, dt, direction = [0, -1, 0], pressurized = false) => {
5200
5235
  const liters = litersPerSecond * dt;
5201
5236
  if (liters <= 0) return;
@@ -5207,10 +5242,462 @@ var GameCharactersThree = (() => {
5207
5242
  handle(event) {
5208
5243
  if (event.type === "leak" && event.fluid === "blood") bleed(event.point, event.rate, 1, event.direction, event.pressurized);
5209
5244
  },
5210
- burst: (change, point) => sink.splash(liquid, point, BURST_LITERS[change])
5245
+ burst(change, point, look, direction) {
5246
+ sink.splash(look?.liquid ?? liquid, point, BURST_LITERS[change]);
5247
+ throwDrops(point, burstDrops[change], look, direction);
5248
+ },
5249
+ wound: (point, look, direction) => throwDrops(point, spray?.count ?? 3, look, direction, spray?.speed, spray?.radius)
5211
5250
  };
5212
5251
  };
5213
5252
 
5253
+ // ../game-audio/dist/index.js
5254
+ var DISTANCE_CURVES = {
5255
+ /** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
5256
+ game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
5257
+ /** The game's `grito`: `1 / (1 + d² × 0.006)`, silent below 0.02 (jogo L62129–62130). */
5258
+ game_scream: { model: "inverse_square", k: 6e-3, minGain: 0.02 },
5259
+ /** The game's `voz`: `1 / (1 + d² × 0.01)`, silent below 0.02 (jogo L62146–62147). */
5260
+ game_voice: { model: "inverse_square", k: 0.01, minGain: 0.02 },
5261
+ /** The game's `boom`: `1.4 / (1 + d² × 0.002)`, never silent (jogo L68280). */
5262
+ game_boom: { model: "inverse_square", k: 2e-3, scale: 1.4 },
5263
+ /** The art scene's `outAt`: `vol / (1 + d² × 0.008)` (cena L1320). */
5264
+ art_scene: { model: "inverse_square", k: 8e-3 },
5265
+ /** The game's animal calls: `max(0, 1 − d / 70) × 0.25`, silent at or below 0.005 (jogo L66627–66628). */
5266
+ animals: { model: "linear", range: 70, scale: 0.25, minGain: 5e-3 }
5267
+ };
5268
+ var mix_profiles_default = [
5269
+ {
5270
+ id: "game",
5271
+ master: 0.5,
5272
+ noise: { seconds: 1.5, create: "lazy" },
5273
+ 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"]
5274
+ },
5275
+ {
5276
+ id: "art_scene",
5277
+ master: 0.85,
5278
+ compressor: { threshold: -12, ratio: 5 },
5279
+ reverb: { seconds: 2.2, decay: 3.4, wet: 0.4 },
5280
+ noise: { seconds: 2, create: "eager" },
5281
+ sources: [
5282
+ "cena:L1308 compressor -12 dB, ratio 5",
5283
+ "cena:L1309 master 0.85",
5284
+ "cena:L1310 convolver, impulse 2.2 s of (1 - i/len)^3.4",
5285
+ "cena:L1311 wet 0.4",
5286
+ "cena:L1312 noise 2 s made with the chain"
5287
+ ]
5288
+ }
5289
+ ];
5290
+ var MIX_PROFILES = Object.fromEntries(
5291
+ mix_profiles_default.map((profile) => [profile.id, profile])
5292
+ );
5293
+ var game_sfx_default = {
5294
+ sources: ["jogo:L62089-62107"],
5295
+ 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`.",
5296
+ classes: [
5297
+ { id: "pistol", team: "pistola", bus: "weapons", cutoff: 1800, gain: 0.9, seconds: 0.18 },
5298
+ { id: "shotgun", team: "escopeta", bus: "weapons", cutoff: 900, gain: 1.2, seconds: 0.35 },
5299
+ { id: "rifle", team: "fuzil", bus: "weapons", cutoff: 1400, gain: 1.1, seconds: 0.28 },
5300
+ { id: "long_rifle", team: "rifle", bus: "weapons", cutoff: 1100, gain: 1.3, seconds: 0.45 },
5301
+ { id: "impact", team: "impacto", bus: "world", cutoff: 2500, gain: 0.25, seconds: 0.05 },
5302
+ { id: "flesh", team: "carne", bus: "world", cutoff: 600, gain: 0.35, seconds: 0.08 },
5303
+ { id: "step", team: "passo", bus: "world", cutoff: 400, gain: 0.08, seconds: 0.05 },
5304
+ { id: "glass", team: "vidro", bus: "world", cutoff: 5e3, gain: 0.5, seconds: 0.3 },
5305
+ { id: "wood", team: "madeira", bus: "world", cutoff: 700, gain: 0.5, seconds: 0.2 },
5306
+ { id: "reload", team: "recarga", bus: "weapons", cutoff: 3e3, gain: 0.15, seconds: 0.06 },
5307
+ { id: "empty", team: "vazio", bus: "weapons", cutoff: 4e3, gain: 0.12, seconds: 0.03 },
5308
+ { id: "explosion", team: "explosao", bus: "world", cutoff: 200, gain: 1.6, seconds: 1.4 }
5309
+ ],
5310
+ fallback: { id: "unknown", team: "(any other name)", bus: "world", cutoff: 1e3, gain: 0.5, seconds: 0.1 }
5311
+ };
5312
+ var GAME_SFX = game_sfx_default;
5313
+ var GAME_SFX_CLASSES = GAME_SFX.classes;
5314
+ var GAME_SFX_FALLBACK = GAME_SFX.fallback;
5315
+ function noiseClassRecipe(row, id = `sfx_${row.id}`) {
5316
+ return {
5317
+ id,
5318
+ bus: row.bus,
5319
+ order: "burst",
5320
+ duration: row.seconds,
5321
+ tail: 0.05,
5322
+ layers: [
5323
+ {
5324
+ source: { kind: "noise", offset: "random" },
5325
+ filters: { mode: "serial", bank: [{ type: "lowpass", frequency: { set: row.cutoff } }] },
5326
+ envelope: [
5327
+ [0, row.gain, "set"],
5328
+ ["1d", 1e-3, "exp", "fixed"]
5329
+ ]
5330
+ }
5331
+ ],
5332
+ falloff: DISTANCE_CURVES.game_sfx,
5333
+ sources: [...GAME_SFX.sources.map((source) => `${source} ${row.team}`)]
5334
+ };
5335
+ }
5336
+ var GAME_SFX_BANK = { id: "game_sfx", sounds: [...GAME_SFX_CLASSES, GAME_SFX_FALLBACK].map((row) => noiseClassRecipe(row)) };
5337
+ var ATTACK = 2e-3;
5338
+ function lengthOf(d, attack) {
5339
+ if (typeof d === "number") return attack + d;
5340
+ if (Array.isArray(d)) return [attack + d[0], attack + d[1]];
5341
+ return { ...d, add: (d.add ?? 0) + attack };
5342
+ }
5343
+ var envelope = (attack) => [
5344
+ [0, 1e-4, "set", "fixed"],
5345
+ [attack, 1, "exp"],
5346
+ ["1d", 1e-4, "exp", "fixed"]
5347
+ ];
5348
+ function sceneNoise(o) {
5349
+ const a = o.a ?? ATTACK;
5350
+ const over = typeof o.d === "number" ? o.d : 0;
5351
+ return {
5352
+ filters: {
5353
+ mode: "serial",
5354
+ 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 }]
5355
+ },
5356
+ duration: lengthOf(o.d, a),
5357
+ gain: o.vol,
5358
+ ...o.delay === void 0 ? {} : { at: o.delay },
5359
+ ...o.wet === void 0 ? {} : { wet: o.wet },
5360
+ source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
5361
+ envelope: envelope(a)
5362
+ };
5363
+ }
5364
+ function sceneTone(o) {
5365
+ return {
5366
+ source: { kind: "osc", wave: o.wave ?? "sine", frequency: { from: o.f, to: o.f2 ?? "from", curve: "exp", over: "duration" } },
5367
+ duration: lengthOf(o.d, ATTACK),
5368
+ gain: o.vol,
5369
+ ...o.delay === void 0 ? {} : { at: o.delay },
5370
+ ...o.wet === void 0 ? {} : { wet: o.wet },
5371
+ envelope: envelope(ATTACK)
5372
+ };
5373
+ }
5374
+ function sceneVoice(o) {
5375
+ return {
5376
+ duration: lengthOf(o.d, 0),
5377
+ gain: o.vol,
5378
+ wet: 0.45,
5379
+ source: {
5380
+ kind: "formant_voice",
5381
+ wave: o.wave ?? "sawtooth",
5382
+ frequency: { from: o.f0, to: o.f1, curve: "linear", over: "duration" },
5383
+ vibrato: { rate: [4, 10], depth: 0.08 },
5384
+ formants: [
5385
+ { frequency: o.fA, q: 4 },
5386
+ { frequency: o.fB, q: 6 }
5387
+ ],
5388
+ breath: { frequency: o.fB, q: 1.1, gain: o.noise },
5389
+ mix: 3.2
5390
+ },
5391
+ envelope: [
5392
+ [0, 1e-4, "set", "fixed"],
5393
+ [0.1, 1, "exp"],
5394
+ ["0.6d", 1, "set"],
5395
+ ["1d", 1e-4, "exp", "fixed"]
5396
+ ]
5397
+ };
5398
+ }
5399
+ function sceneRecipe(id, layers, line, options = {}) {
5400
+ return {
5401
+ id,
5402
+ bus: options.bus ?? "world",
5403
+ pan: true,
5404
+ layers,
5405
+ falloff: DISTANCE_CURVES.art_scene,
5406
+ ...options.vars ? { vars: options.vars } : {},
5407
+ ...options.group ? { group: options.group } : {},
5408
+ sources: [`cena:${line}`]
5409
+ };
5410
+ }
5411
+ var BENCH_BANK = {
5412
+ id: "audio_bench",
5413
+ sounds: [
5414
+ {
5415
+ id: "bench_ping",
5416
+ bus: "ui",
5417
+ pan: true,
5418
+ duration: 0.25,
5419
+ layers: [
5420
+ {
5421
+ gain: 0.5,
5422
+ wet: 0.3,
5423
+ source: { kind: "osc", wave: "sine", frequency: { from: 880 } },
5424
+ envelope: [
5425
+ [0, 1e-4, "set", "fixed"],
5426
+ [2e-3, 1, "exp"],
5427
+ ["1d", 1e-4, "exp", "fixed"]
5428
+ ]
5429
+ }
5430
+ ],
5431
+ falloff: DISTANCE_CURVES.art_scene,
5432
+ sources: ["sdk: bench probe, a 880 Hz ping through the art scene output stage"]
5433
+ },
5434
+ {
5435
+ id: "bench_hiss",
5436
+ bus: "ui",
5437
+ pan: true,
5438
+ duration: [0.3, 0.9],
5439
+ layers: [
5440
+ {
5441
+ gain: 0.4,
5442
+ source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
5443
+ filters: { mode: "serial", bank: [{ type: "highpass", frequency: { set: 3e3 }, q: 0.7 }] },
5444
+ envelope: [
5445
+ [0, 1e-4, "set", "fixed"],
5446
+ [2e-3, 1, "exp"],
5447
+ ["1d", 1e-4, "exp", "fixed"]
5448
+ ]
5449
+ }
5450
+ ],
5451
+ falloff: DISTANCE_CURVES.art_scene,
5452
+ sources: ["sdk: bench probe, a highpass hiss of random length"]
5453
+ }
5454
+ ]
5455
+ };
5456
+
5457
+ // src/audio/recipes.ts
5458
+ var UNDEAD_VOICE_GROUP = "undead_voice";
5459
+ var voiced = { bus: "voices", group: UNDEAD_VOICE_GROUP };
5460
+ var p = (param) => ({ param });
5461
+ var SCREAM_HOARSE = {
5462
+ id: "scream_hoarse",
5463
+ bus: "voices",
5464
+ order: "scream",
5465
+ duration: [1, 1.4],
5466
+ layers: [
5467
+ {
5468
+ source: { kind: "noise", offset: "random" },
5469
+ filters: {
5470
+ mode: "parallel",
5471
+ bank: [
5472
+ { type: "bandpass", q: 6, frequency: { from: 700 * 1.15, to: 700 * 0.8, curve: "linear", over: "duration" }, level: 1 },
5473
+ { type: "bandpass", q: 8, frequency: { from: 1150 * 1.15, to: 1150 * 0.8, curve: "linear", over: "duration" }, level: 0.7 },
5474
+ { type: "bandpass", q: 10, frequency: { from: 2600 * 1.15, to: 2600 * 0.8, curve: "linear", over: "duration" }, level: 0.35 }
5475
+ ]
5476
+ },
5477
+ modulators: [{ target: "gain", wave: "square", rate: [31, 40], depth: 0.35 }],
5478
+ envelope: [
5479
+ [0, 0, "set"],
5480
+ [0.06, 0.9, "linear"],
5481
+ ["0.6d", 0.9, "set"],
5482
+ ["1d", 1e-3, "exp", "fixed"]
5483
+ ]
5484
+ }
5485
+ ],
5486
+ falloff: DISTANCE_CURVES.game_scream,
5487
+ sources: ["jogo:L62128-62142 grito"]
5488
+ };
5489
+ var groanLayer = (base) => ({
5490
+ source: {
5491
+ kind: "osc",
5492
+ wave: "sawtooth",
5493
+ 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" }
5494
+ },
5495
+ filters: { mode: "serial", bank: [{ type: "bandpass", q: 3, frequency: { set: typeof base === "number" ? base * 3 : { ...base, scale: 3 } } }] },
5496
+ envelope: [
5497
+ [0, 0, "set"],
5498
+ [0.15, 0.25, "linear"],
5499
+ ["1d", 1e-3, "exp", "fixed"]
5500
+ ]
5501
+ });
5502
+ var GROAN = {
5503
+ id: "groan",
5504
+ ...voiced,
5505
+ order: "groan",
5506
+ vars: { base: [95, 135], dur: [0.9, 1.5] },
5507
+ duration: p("dur"),
5508
+ layers: [groanLayer(p("base"))],
5509
+ falloff: DISTANCE_CURVES.game_voice,
5510
+ sources: ["jogo:L62143-62155 voz gemido"]
5511
+ };
5512
+ var RUNNER_GROAN = {
5513
+ id: "runner_groan",
5514
+ ...voiced,
5515
+ order: "groan",
5516
+ vars: { dur: [0.9, 1.5] },
5517
+ duration: p("dur"),
5518
+ layers: [groanLayer(300)],
5519
+ falloff: DISTANCE_CURVES.game_voice,
5520
+ sources: ["jogo:L62143-62155 voz corredor"]
5521
+ };
5522
+ var scene = (id, layers, line, options = {}) => sceneRecipe(id, layers, line, options);
5523
+ var groan = (id, vars, voice, line) => scene(id, [sceneVoice(voice)], line, { vars, ...voiced });
5524
+ 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)");
5525
+ 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)");
5526
+ 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)");
5527
+ var SCREAM = scene(
5528
+ "scream",
5529
+ [
5530
+ sceneVoice({ f0: 520, f1: 880, d: 2.6, fA: 1800, fB: 3200, noise: 0.9, vol: 1.6 }),
5531
+ sceneVoice({ f0: 640, f1: 1020, d: 2.4, fA: 2400, fB: 3800, noise: 0.6, vol: 1.1, wave: "square" })
5532
+ ],
5533
+ "L1347 scream",
5534
+ { bus: "voices" }
5535
+ );
5536
+ 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" });
5537
+ 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" });
5538
+ 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" });
5539
+ var BITE = scene(
5540
+ "bite",
5541
+ [
5542
+ sceneNoise({ f: 1400, d: 0.2, vol: 1.1 }),
5543
+ ...[0, 1, 2, 3].map((k) => sceneNoise({ type: "highpass", f: 2500, d: 0.03, vol: 0.5, delay: 0.03 + k * 0.04 })),
5544
+ sceneTone({ wave: "sawtooth", f: 320, f2: 170, d: 0.5, vol: 0.35, delay: 0.05 })
5545
+ ],
5546
+ "L1351 bite",
5547
+ { bus: "voices" }
5548
+ );
5549
+ 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" });
5550
+ 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");
5551
+ 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)");
5552
+ var SEVER = scene(
5553
+ "sever",
5554
+ [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 })],
5555
+ "L1355 sever"
5556
+ );
5557
+ var GORE = scene(
5558
+ "gore",
5559
+ [
5560
+ sceneNoise({ f: 900, f2: 200, d: 0.4, vol: 1.1, wet: 0.3 }),
5561
+ ...[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 }))
5562
+ ],
5563
+ "L1356 gore"
5564
+ );
5565
+ var CHARACTER_SOUNDS = [
5566
+ SCREAM_HOARSE,
5567
+ GROAN,
5568
+ RUNNER_GROAN,
5569
+ GROAN_WALKER,
5570
+ GROAN_BRUTE,
5571
+ GROAN_RUNNER,
5572
+ SCREAM,
5573
+ INHALE,
5574
+ LEAP,
5575
+ GRAB,
5576
+ BITE,
5577
+ HURT,
5578
+ FLESH,
5579
+ FLESH_HEAD,
5580
+ SEVER,
5581
+ GORE
5582
+ ];
5583
+ var CHARACTER_SOUND_BANK = { id: "characters_three", sounds: CHARACTER_SOUNDS };
5584
+
5585
+ // src/audio/voices.json
5586
+ var voices_default = {
5587
+ sources: [
5588
+ "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",
5589
+ "cena:L1238, L1248, L1249 the scream and the inhale belong to the Gritador, the leap to the Saltador",
5590
+ "cena:L1226 the groan when a body wakes (FEIGN to CHASE) and the game L67275/L67328 voz('corredor') when one notices the player",
5591
+ "jogo:L66986 the groan of the game: 'corredor' for the runner category, 'gemido' for the rest; jogo:L66989 screamAt plays 'grito'",
5592
+ "types.json: the voice line of each Bestiario type"
5593
+ ],
5594
+ types: {
5595
+ walker: { origin: "team", groan: "groan_walker", gameGroan: "groan" },
5596
+ runner: { origin: "team", groan: "groan_runner", gameGroan: "runner_groan" },
5597
+ crawler: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
5598
+ brute: { origin: "team", groan: "groan_brute", gameGroan: "groan" },
5599
+ sniffer: {
5600
+ origin: "extra",
5601
+ groan: "groan_runner",
5602
+ gameGroan: "groan",
5603
+ idle: [{ sound: "inhale" }, { sound: "groan_runner", delay: 0.6 }]
5604
+ },
5605
+ leaper: { origin: "team", groan: "groan_runner", gameGroan: "groan", special: [{ sound: "leap" }] },
5606
+ bloater: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
5607
+ stalker: { origin: "rule", groan: "groan_runner", gameGroan: "groan", idle: [] },
5608
+ blind: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
5609
+ screamer: {
5610
+ origin: "team",
5611
+ groan: "groan_walker",
5612
+ gameGroan: "groan",
5613
+ special: [{ sound: "inhale" }, { sound: "scream", delay: 1.5 }],
5614
+ gameSpecial: [{ sound: "scream_hoarse" }]
5615
+ },
5616
+ charred: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
5617
+ drowned: { origin: "rule", groan: "groan_walker", gameGroan: "groan" },
5618
+ devourer: {
5619
+ origin: "extra",
5620
+ groan: "groan_walker",
5621
+ gameGroan: "groan",
5622
+ idle: [{ sound: "groan_walker" }, { sound: "bite", delay: 0.4 }]
5623
+ }
5624
+ }
5625
+ };
5626
+
5627
+ // src/audio/sound.ts
5628
+ var DATA = voices_default;
5629
+ var UNDEAD_VOICES = DATA.types;
5630
+ var UNDEAD_VOICE_SOURCES = DATA.sources;
5631
+ var BODY_SOUNDS = {
5632
+ art_scene: {
5633
+ wound: { sound: "flesh", head: "flesh_head" },
5634
+ severed: { sound: "sever" },
5635
+ destroyed: { sound: "gore" },
5636
+ split: { sound: "gore" }
5637
+ },
5638
+ game: {
5639
+ wound: { sound: "sfx_flesh" }
5640
+ }
5641
+ };
5642
+ var PLAYER_SOUNDS = { grab: "grab", bite: "bite", hurt: "hurt" };
5643
+ var HEAD_PARTS = /* @__PURE__ */ new Set(["head", "jaw"]);
5644
+ function createCharacterSound(sink, options = {}) {
5645
+ sink.register?.(CHARACTER_SOUND_BANK);
5646
+ sink.register?.(GAME_SFX_BANK);
5647
+ const style = options.style ?? "art_scene";
5648
+ const voices = { ...UNDEAD_VOICES, ...options.voices };
5649
+ const rules = { ...BODY_SOUNDS.art_scene, ...style === "game" ? BODY_SOUNDS.game : {}, ...options.body };
5650
+ const queue = [];
5651
+ const voiceOf = (typeId) => {
5652
+ const own = voices[typeId];
5653
+ if (own === null) return void 0;
5654
+ return own ?? (options.fallbackType ? voices[options.fallbackType] ?? void 0 : void 0);
5655
+ };
5656
+ const stepsOf = (typeId, moment) => {
5657
+ const voice = voiceOf(typeId);
5658
+ if (!voice) return [];
5659
+ const groan2 = style === "game" ? voice.gameGroan : voice.groan;
5660
+ if (moment === "special") return (style === "game" ? voice.gameSpecial ?? voice.special : voice.special) ?? [];
5661
+ if (moment === "alert") return voice.alert ?? [{ sound: style === "game" ? "runner_groan" : groan2 }];
5662
+ return voice.idle ?? [{ sound: groan2 }];
5663
+ };
5664
+ const start = (sound, at, volume) => void sink.play({ sound, position: at, volume });
5665
+ return {
5666
+ stepsOf,
5667
+ voice(typeId, moment, position, volume = 1) {
5668
+ const steps = stepsOf(typeId, moment);
5669
+ for (const step of steps) {
5670
+ if (step.delay && step.delay > 0) queue.push({ sound: step.sound, at: position, volume, wait: step.delay });
5671
+ else start(step.sound, position, volume);
5672
+ }
5673
+ return steps.length;
5674
+ },
5675
+ body(event, position, volume = 1) {
5676
+ if (event.type !== "body") return false;
5677
+ const rule = rules[event.change];
5678
+ if (!rule) return false;
5679
+ const head = event.part !== void 0 && HEAD_PARTS.has(event.part);
5680
+ start(head && rule.head ? rule.head : rule.sound, position, volume);
5681
+ return true;
5682
+ },
5683
+ player: (kind, volume = 1) => void sink.play({ sound: PLAYER_SOUNDS[kind], volume }),
5684
+ update(dt) {
5685
+ for (let i = queue.length - 1; i >= 0; i--) {
5686
+ const item = queue[i];
5687
+ item.wait -= dt;
5688
+ if (item.wait <= 0) {
5689
+ queue.splice(i, 1);
5690
+ start(item.sound, item.at, item.volume);
5691
+ }
5692
+ }
5693
+ },
5694
+ pending: () => queue.length,
5695
+ dispose() {
5696
+ queue.length = 0;
5697
+ }
5698
+ };
5699
+ }
5700
+
5214
5701
  // src/test-area/crowd.ts
5215
5702
  var FIELD = { x0: 12, x1: 30, z0: -16, z1: 16 };
5216
5703
  var STRIDE = 1;
@@ -5475,7 +5962,7 @@ var GameCharactersThree = (() => {
5475
5962
  hit_preset: "What the Fire hit button does to the body you look at",
5476
5963
  protection: "Worn by the bodies created from now on"
5477
5964
  },
5478
- 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" },
5965
+ 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)" },
5479
5966
  options: {
5480
5967
  penetration: { physical: "Physical", "team-game": "Team game" },
5481
5968
  death_rule: { "vitality-or-head": "Vitality or head", "head-only": "Head only" },
@@ -5520,7 +6007,7 @@ var GameCharactersThree = (() => {
5520
6007
  hit_preset: "O que o bot\xE3o Disparar faz no corpo para o qual voc\xEA olha",
5521
6008
  protection: "Vestida pelos corpos criados daqui em diante"
5522
6009
  },
5523
- 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" },
6010
+ 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)" },
5524
6011
  options: {
5525
6012
  penetration: { physical: "F\xEDsica", "team-game": "Do jogo" },
5526
6013
  death_rule: { "vitality-or-head": "Vitalidade ou cabe\xE7a", "head-only": "S\xF3 a cabe\xE7a" },
@@ -5575,6 +6062,7 @@ var GameCharactersThree = (() => {
5575
6062
  let best = null;
5576
6063
  let people = null;
5577
6064
  let wounds = null;
6065
+ let sound = null;
5578
6066
  let crowd = null;
5579
6067
  let selected = null;
5580
6068
  let time = 0;
@@ -5667,6 +6155,9 @@ var GameCharactersThree = (() => {
5667
6155
  if (Math.abs(scale - 1) > 0.01) object.scale.setScalar(scale);
5668
6156
  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 };
5669
6157
  entry.character = wrap(entry, character);
6158
+ const age = made.kind === "zombie" ? undead?.traitsOf(character)?.age : void 0;
6159
+ const blood = age !== void 0 ? options.bloodOf?.(age) : void 0;
6160
+ if (entry.body && blood) entry.body.blood = blood;
5670
6161
  entries.set(id, entry);
5671
6162
  selected ?? (selected = entry);
5672
6163
  trackEntry(entry);
@@ -5749,8 +6240,8 @@ var GameCharactersThree = (() => {
5749
6240
  const selectedOrFirst = () => selected ?? entries.values().next().value ?? null;
5750
6241
  const fireOn = (entry) => {
5751
6242
  const preset2 = hitPresetOf(settings.hit);
5752
- const p = entry.root.position;
5753
- entry.character.hit(hitFor(preset2, [p.x, p.y, p.z], 1.3, kit.calibers));
6243
+ const p2 = entry.root.position;
6244
+ entry.character.hit(hitFor(preset2, [p2.x, p2.y, p2.z], 1.3, kit.calibers));
5754
6245
  };
5755
6246
  const ensureCrowd = () => {
5756
6247
  if (crowd) return crowd;
@@ -5785,7 +6276,14 @@ var GameCharactersThree = (() => {
5785
6276
  if (infection.reanimated) target.character = wrap(target, undead.turn(target.character));
5786
6277
  }
5787
6278
  } else if (change.id === "fire_preset" && target) fireOn(target);
5788
- else if (change.id === "spawn_100" || change.id === "spawn_1000") {
6279
+ else if ((change.id === "voice" || change.id === "special_call") && sound) {
6280
+ const speaker = target?.kind === "zombie" ? target : [...entries.values()].find((entry) => entry.kind === "zombie");
6281
+ if (speaker) {
6282
+ const at = speaker.root.getWorldPosition(new host.THREE.Vector3());
6283
+ const hurried = settings.moment === "alert" || settings.moment === "chase" || settings.moment === "sprint";
6284
+ sound.voice(speaker.cardId, change.id === "special_call" ? "special" : hurried ? "alert" : "idle", [at.x, at.y + 1.5, at.z]);
6285
+ }
6286
+ } else if (change.id === "spawn_100" || change.id === "spawn_1000") {
5789
6287
  ensureBuilders(host?.THREE ?? three);
5790
6288
  ensureCrowd().spawn(change.id === "spawn_100" ? 100 : 1e3);
5791
6289
  }
@@ -5890,6 +6388,8 @@ var GameCharactersThree = (() => {
5890
6388
  withHint(choice("hit_preset", HIT_PRESETS.map((preset2) => preset2.id), "9x19"), text.hints.hit_preset),
5891
6389
  withHint(choice("protection", ["none", "vest_police", "vest_military", "helmet_combat", "turnout_gear", "vest_helmet"], "none"), text.hints.protection),
5892
6390
  { kind: "action", id: "fire_preset", name: text.actions.fire_preset },
6391
+ { kind: "action", id: "voice", name: text.actions.voice },
6392
+ { kind: "action", id: "special_call", name: text.actions.special_call },
5893
6393
  { kind: "action", id: "heal", name: text.actions.heal },
5894
6394
  { kind: "action", id: "kill", name: text.actions.kill },
5895
6395
  { kind: "action", id: "infect", name: text.actions.infect },
@@ -5917,9 +6417,10 @@ var GameCharactersThree = (() => {
5917
6417
  attach(lent) {
5918
6418
  host = lent;
5919
6419
  ensureBuilders(lent.THREE);
5920
- const scene = lent.scene;
5921
- const bridge = options.fluids ? createFluidsBridge({ sink: options.fluids }) : void 0;
5922
- wounds = createWounds(lent.THREE, { random, scene, groundAt: (x, z) => lent.groundAt(x, z), ...bridge ? { bridge } : {} });
6420
+ const scene2 = lent.scene;
6421
+ const bridge = options.fluids ? createFluidsBridge({ sink: options.fluids, ...options.film ? { film: options.film } : {} }) : void 0;
6422
+ sound = lent.audio && options.sound !== false ? createCharacterSound(lent.audio, options.sound ?? {}) : null;
6423
+ wounds = createWounds(lent.THREE, { random, scene: scene2, groundAt: (x, z) => lent.groundAt(x, z), ...bridge ? { bridge } : {}, ...sound ? { sound } : {} });
5923
6424
  for (const entry of entries.values()) trackEntry(entry);
5924
6425
  const stopChange = lent.bench?.onChange(onChange);
5925
6426
  const stopFrame = lent.onFrame((dt) => {
@@ -5932,6 +6433,7 @@ var GameCharactersThree = (() => {
5932
6433
  animate(entry, dt);
5933
6434
  }
5934
6435
  wounds?.update(dt);
6436
+ sound?.update(dt);
5935
6437
  crowd?.update(dt, lent.camera);
5936
6438
  });
5937
6439
  return () => {
@@ -5940,6 +6442,8 @@ var GameCharactersThree = (() => {
5940
6442
  clearOverlays();
5941
6443
  wounds?.dispose();
5942
6444
  wounds = null;
6445
+ sound?.dispose();
6446
+ sound = null;
5943
6447
  if (crowd) {
5944
6448
  crowd.group.parent?.remove(crowd.group);
5945
6449
  crowd = null;