@adatechnology/game-test-room 0.23.0 → 0.25.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.
@@ -62,8 +62,29 @@ var GameTestRoom = (() => {
62
62
  const length3 = Math.hypot(direction[0], direction[1], direction[2]);
63
63
  return { speed, direction: [direction[0] / length3, direction[1] / length3, direction[2] / length3], distance: distance4, falloff };
64
64
  }
65
+ var DEFAULT_BLAST_IMPULSE = { bodyArea: 0.7, lift: 0.15, minSpeed: 0.3, tntPerJoule: 0.24 / 2e5 };
66
+ var NEAREST = 0.3;
67
+ function sideOnImpulse(tnt, distance4) {
68
+ if (!(tnt > 0)) return 0;
69
+ const w3 = Math.cbrt(tnt);
70
+ const z = Math.max(distance4, NEAREST) / w3;
71
+ const scaled2 = 0.067 * Math.sqrt(1 + (z / 0.23) ** 4) / (z * z * Math.cbrt(1 + (z / 1.55) ** 3));
72
+ return scaled2 * w3 * 100;
73
+ }
74
+ function bodyImpulse(tnt, distance4) {
75
+ if (!(tnt > 0)) return 0;
76
+ const z = Math.max(distance4, NEAREST) / Math.cbrt(tnt);
77
+ return sideOnImpulse(tnt, distance4) * (2 + 4 / (1 + z * z));
78
+ }
79
+ function blastBodySpeed(tnt, distance4, mass, reach = 1, options = {}) {
80
+ const area = options.bodyArea ?? DEFAULT_BLAST_IMPULSE.bodyArea;
81
+ return bodyImpulse(tnt, distance4) * area * Math.max(0, reach) / Math.max(mass, 1);
82
+ }
65
83
  var KNOCK_DOWN_SPEED = 2.5;
66
84
  var LIE_SECONDS = 1.6;
85
+ var WOUNDED_LIE_SECONDS = 6;
86
+ var FRAGMENT_COVER = 0.5;
87
+ var STUN_SECONDS = 4;
67
88
  var STAND_UP_PER_SPEED = 0.25;
68
89
  var FALL_SECONDS = 0.25;
69
90
  var RISE_SECONDS = 0.6;
@@ -72,11 +93,38 @@ var GameTestRoom = (() => {
72
93
  var KNOCK_CHEST = 1.1;
73
94
  var CHEST = KNOCK_CHEST;
74
95
  var bodyMass = (height, build = 1) => 70 * (height / 1.75) ** 2 * build;
75
- function knockOf(blast3, feet, mass, options) {
76
- const hit = blastShove(blast3, { position: [feet[0], feet[1] + CHEST, feet[2]], mass }, options);
77
- if (!hit) return void 0;
78
- const [dx, dy, dz] = hit.direction;
79
- return { velocity: [dx * hit.speed, Math.max(0, dy * hit.speed), dz * hit.speed], airborne: dy > 0, lying: 0, lieLeft: 0, down: hit.speed >= KNOCK_DOWN_SPEED };
96
+ function knockOf(blast3, feet, mass, options, impulse = {}) {
97
+ const o = { ...DEFAULT_BLAST_IMPULSE, ...impulse };
98
+ const chest = [feet[0], feet[1] + CHEST, feet[2]];
99
+ const dx = chest[0] - blast3.point[0];
100
+ const dy = chest[1] - blast3.point[1];
101
+ const dz = chest[2] - blast3.point[2];
102
+ const distance4 = Math.hypot(dx, dy, dz);
103
+ if (!(blast3.radius > 0) || distance4 > blast3.radius) return void 0;
104
+ const reach = Math.max(0, Math.min(1, blast3.reach ?? 1));
105
+ const tnt = blast3.tnt ?? blast3.energy * o.tntPerJoule;
106
+ let speed = blastBodySpeed(tnt, distance4, mass, reach, o);
107
+ const minSpeed = impulse.minSpeed ?? options?.minSpeed ?? o.minSpeed;
108
+ if (speed < minSpeed) speed = 0;
109
+ const struck = reach >= FRAGMENT_COVER;
110
+ const wounded = struck && blast3.lethalRadius !== void 0 && distance4 <= blast3.lethalRadius;
111
+ const edge = 1 - distance4 / blast3.radius;
112
+ const stunSeconds = struck ? STUN_SECONDS * edge * edge * reach : 0;
113
+ if (speed === 0 && !wounded && stunSeconds < 0.25) return void 0;
114
+ const horizontal = Math.hypot(dx, dz);
115
+ const away = horizontal > 1e-4 ? [dx / horizontal, o.lift, dz / horizontal] : [0, 1, 0];
116
+ const length3 = Math.hypot(away[0], away[1], away[2]);
117
+ const velocity = [away[0] / length3 * speed, away[1] / length3 * speed, away[2] / length3 * speed];
118
+ return {
119
+ velocity,
120
+ airborne: velocity[1] > 0.5,
121
+ lying: 0,
122
+ lieLeft: wounded ? WOUNDED_LIE_SECONDS : 0,
123
+ away: horizontal > 1e-4 ? [dx / horizontal, 0, dz / horizontal] : [0, 0, 0],
124
+ down: speed >= KNOCK_DOWN_SPEED || wounded,
125
+ ...wounded ? { wounded } : {},
126
+ ...stunSeconds >= 0.25 ? { stunSeconds } : {}
127
+ };
80
128
  }
81
129
  function stepKnock(knock2, position, dt, groundAt, alive) {
82
130
  const v = knock2.velocity;
@@ -100,14 +148,11 @@ var GameTestRoom = (() => {
100
148
  }
101
149
  const moving = horizontal > 0.05 || knock2.airborne;
102
150
  if (knock2.down) {
103
- if (moving) knock2.lying = Math.min(1, knock2.lying + dt / FALL_SECONDS);
104
- else {
105
- knock2.lying = 1;
106
- if (alive) {
107
- knock2.lieLeft = knock2.lieLeft > 0 ? knock2.lieLeft : LIE_SECONDS + Math.hypot(v[0], v[2]) * STAND_UP_PER_SPEED;
108
- knock2.lieLeft -= dt;
109
- if (knock2.lieLeft <= 0) knock2.down = false;
110
- }
151
+ if (moving || knock2.lying < 1) knock2.lying = Math.min(1, knock2.lying + dt / FALL_SECONDS);
152
+ else if (alive) {
153
+ knock2.lieLeft = knock2.lieLeft > 0 ? knock2.lieLeft : LIE_SECONDS + Math.hypot(v[0], v[2]) * STAND_UP_PER_SPEED;
154
+ knock2.lieLeft -= dt;
155
+ if (knock2.lieLeft <= 0) knock2.down = false;
111
156
  }
112
157
  } else if (knock2.lying > 0) knock2.lying = Math.max(0, knock2.lying - dt / RISE_SECONDS);
113
158
  return !moving && !knock2.down && knock2.lying === 0;
@@ -5020,7 +5065,7 @@ var GameTestRoom = (() => {
5020
5065
  return;
5021
5066
  }
5022
5067
  if (ex) {
5023
- emit2({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
5068
+ emit2({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, ...ex.tnt !== void 0 ? { tnt: ex.tnt } : {}, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
5024
5069
  blasts.push({ point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, source: src });
5025
5070
  }
5026
5071
  partLost(o, p, src);
@@ -6657,7 +6702,7 @@ var GameTestRoom = (() => {
6657
6702
  };
6658
6703
  var resolveStrings = (locale) => locales[locale ?? "en"] ?? locales.en;
6659
6704
 
6660
- // ../game-audio/dist/chunk-7TLFY7VO.js
6705
+ // ../game-audio/dist/chunk-KBTUANNG.js
6661
6706
  var DISTANCE_CURVES = {
6662
6707
  /** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
6663
6708
  game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
@@ -8526,10 +8571,12 @@ var GameTestRoom = (() => {
8526
8571
  noise: () => noiseBuffer(store, audio, mix.noise.seconds),
8527
8572
  time
8528
8573
  });
8574
+ const recipeBank = registry.bankOf(plan.id);
8529
8575
  announce({
8530
8576
  id: plan.id,
8531
8577
  kind: "recipe",
8532
8578
  ...fallbackOf ? { fallbackOf } : {},
8579
+ ...recipeBank ? { bank: recipeBank } : {},
8533
8580
  requestedAt,
8534
8581
  startAt: time,
8535
8582
  outputLatency: latencyOf(audio),
@@ -8631,9 +8678,11 @@ var GameTestRoom = (() => {
8631
8678
  const time = requestedAt + (playOptions.delay ?? 0);
8632
8679
  const chokeList = sample.choke ? choked(sample.id) : void 0;
8633
8680
  const realized = realizeSample(plan, { context: audio, output: sampleOutput(plan.bus), buffer: (url2) => sampleStore.buffer(url2), time });
8681
+ const sampleBank = registry.bankOf(plan.id);
8634
8682
  announce({
8635
8683
  id: plan.id,
8636
8684
  kind: "sample",
8685
+ ...sampleBank ? { bank: sampleBank } : {},
8637
8686
  requestedAt,
8638
8687
  startAt: time,
8639
8688
  outputLatency: latencyOf(audio),
@@ -8954,7 +9003,11 @@ var GameTestRoom = (() => {
8954
9003
  ["casing_drop_terra_pistol", 1],
8955
9004
  ["casing_drop_terra_rifle", 1],
8956
9005
  ["casing_drop_terra_shell", 1],
8957
- ["rocket_fire", 2]
9006
+ ["rocket_fire", 2],
9007
+ ["smg_45_auto", 2, "sfx_pistol"],
9008
+ ["weapon_draw", 2],
9009
+ ["suppressor_attach", 3],
9010
+ ["suppressor_detach", 3]
8958
9011
  ]),
8959
9012
  ...entries("explosoes-fogo", [
8960
9013
  ["explosion_grenade", 1, "boom"],
@@ -8968,7 +9021,11 @@ var GameTestRoom = (() => {
8968
9021
  ["fire_loop_large", 1],
8969
9022
  ["fire_extinguish", 2, "hiss"],
8970
9023
  ["gas_leak_hiss", 2],
8971
- ["structure_collapse", 2, "crumble"]
9024
+ ["structure_collapse", 2, "crumble"],
9025
+ ["grenade_bounce", 1],
9026
+ ["c4_plant", 2],
9027
+ ["c4_beep", 2],
9028
+ ["c4_disarm", 2]
8972
9029
  ]),
8973
9030
  ...entries("personagem", [
8974
9031
  ["footstep_concrete_walk", 1, "sfx_step"],
@@ -8984,7 +9041,19 @@ var GameTestRoom = (() => {
8984
9041
  ["blunt_hit", 1],
8985
9042
  ["bite", 1, "sfx_flesh"],
8986
9043
  ["body_fall", 1],
8987
- ["player_hurt", 2]
9044
+ ["player_hurt", 2],
9045
+ ["footstep_tile_walk", 1, "sfx_step"],
9046
+ ["footstep_water_walk", 1, "sfx_step"],
9047
+ ["footstep_grate_walk", 2, "sfx_step"],
9048
+ ["footstep_duct_walk", 3, "sfx_step"],
9049
+ ["ladder_climb", 2],
9050
+ ["swim", 2],
9051
+ ["zombie_groan", 1],
9052
+ ["zombie_alert", 1],
9053
+ ["zombie_attack", 1],
9054
+ ["zombie_pain", 1],
9055
+ ["zombie_claw_hit", 1, "sfx_flesh"],
9056
+ ["zombie_claw_miss", 2]
8988
9057
  ]),
8989
9058
  ...entries("veiculos", [
8990
9059
  ["engine_loop_car", 1],
@@ -9012,7 +9081,8 @@ var GameTestRoom = (() => {
9012
9081
  ["wind_loop", 2],
9013
9082
  ["thunder_near", 2],
9014
9083
  ["thunder_far", 2],
9015
- ["water_splash", 2]
9084
+ ["water_splash", 2],
9085
+ ["helicopter_loop", 3]
9016
9086
  ])
9017
9087
  ];
9018
9088
  var SFX_MATERIALS = [
@@ -9094,6 +9164,175 @@ var GameTestRoom = (() => {
9094
9164
  burst: 20,
9095
9165
  burstSpacing: 0.06
9096
9166
  };
9167
+ var SFX_TIERS = { light: 0, medium: 0.34, heavy: 0.67 };
9168
+ function splitTier(id) {
9169
+ const match = /^(.*)_(light|medium|heavy)$/.exec(id);
9170
+ return match ? { base: match[1], tier: match[2] } : { base: id };
9171
+ }
9172
+ var refsOf = (entries2, baseUrl) => (entries2 ?? []).map((entry) => ({ url: `${baseUrl}${entry.file}` }));
9173
+ function sampleBankFromManifest(manifest, options) {
9174
+ const baseUrl = options.baseUrl ?? "";
9175
+ const samples = [];
9176
+ const tiersOf = /* @__PURE__ */ new Map();
9177
+ for (const sound of manifest.sounds) {
9178
+ const { base, tier } = splitTier(sound.id);
9179
+ if (tier) tiersOf.set(base, [...tiersOf.get(base) ?? [], sound]);
9180
+ }
9181
+ const bases = /* @__PURE__ */ new Map();
9182
+ for (const sound of manifest.sounds) {
9183
+ if (!splitTier(sound.id).tier) bases.set(sound.id, sound);
9184
+ }
9185
+ for (const [base, tiers] of tiersOf) {
9186
+ if (!bases.has(base)) bases.set(base, { id: base, category: tiers[0]?.category ?? "", layers: { close: tiers[0]?.layers.close ?? [] } });
9187
+ }
9188
+ for (const sound of bases.values()) {
9189
+ const numbers = options.sounds?.[sound.id] ?? options.defaults?.(sound);
9190
+ const close = refsOf(sound.layers.close, baseUrl);
9191
+ if (!numbers || close.length === 0) continue;
9192
+ const tiers = (tiersOf.get(sound.id) ?? []).map((tier) => {
9193
+ const name = splitTier(tier.id).tier;
9194
+ const tierFar = refsOf(tier.layers.far, baseUrl);
9195
+ return { name, from: SFX_TIERS[name] ?? 0, close: refsOf(tier.layers.close, baseUrl), ...tierFar.length > 0 ? { far: tierFar } : {} };
9196
+ }).sort((a, b) => a.from - b.from);
9197
+ const far = refsOf(sound.layers.far, baseUrl);
9198
+ const indoor = refsOf(sound.layers.tail_in, baseUrl);
9199
+ const outdoor = refsOf(sound.layers.tail_out, baseUrl);
9200
+ const files = [sound, ...tiersOf.get(sound.id) ?? []].flatMap((entry) => Object.values(entry.layers).flatMap((entries2) => entries2 ?? []));
9201
+ samples.push({
9202
+ id: sound.id,
9203
+ bus: numbers.bus,
9204
+ close,
9205
+ ...far.length > 0 ? { far } : {},
9206
+ ...tiers.length > 0 ? { tiers } : {},
9207
+ ...numbers.crossover ? { crossover: numbers.crossover } : {},
9208
+ ...indoor.length > 0 || outdoor.length > 0 ? { tail: { ...indoor.length > 0 ? { indoor } : {}, ...outdoor.length > 0 ? { outdoor } : {}, ...numbers.tailGain === void 0 ? {} : { gain: numbers.tailGain } } } : {},
9209
+ ...numbers.pitch === void 0 ? {} : { pitch: numbers.pitch },
9210
+ ...numbers.gain === void 0 ? {} : { gain: numbers.gain },
9211
+ ...numbers.falloff ? { falloff: numbers.falloff } : {},
9212
+ ...numbers.cutoffK === void 0 ? {} : { cutoffK: numbers.cutoffK },
9213
+ ...numbers.group ? { group: numbers.group } : {},
9214
+ ...numbers.priority === void 0 ? {} : { priority: numbers.priority },
9215
+ ...numbers.fallback ? { fallback: numbers.fallback } : {},
9216
+ sources: files.map((file) => `${file.file}: ${file.source} ${file.author} ${file.license}`)
9217
+ });
9218
+ }
9219
+ return { id: options.id, samples };
9220
+ }
9221
+ var KIT_CATEGORY_BUS = {
9222
+ armas: "weapons",
9223
+ impactos: "world",
9224
+ "explosoes-fogo": "world",
9225
+ personagem: "world",
9226
+ veiculos: "world",
9227
+ "itens-portas": "world",
9228
+ ambiente: "ambience"
9229
+ };
9230
+ var VOICE_IDS = /^(player_(hurt|death|effort|breath)|scream|zombie_)/;
9231
+ var KIT_SAMPLE_ALIASES = {
9232
+ // a house door: the latch giving, the hinge, the leaf meeting the frame, a locked one shaken; a drawer on its runners
9233
+ latch: "metal_latch",
9234
+ creak_wood: "door_creak",
9235
+ shut_wood: "door_wood_shut",
9236
+ rattle: "door_locked_rattle",
9237
+ // a drawer stays on its recipe: the recorded runs (1 to 1.9 s) outlast a drawer's pull
9238
+ // an item taken in the hand (game-destruction-three grab_* keys)
9239
+ grab_hard: "item_pickup_generic",
9240
+ grab_soft: "item_pickup_leather"
9241
+ };
9242
+ function kitSampleDefaults(sound) {
9243
+ const bus = VOICE_IDS.test(sound.id) ? "voices" : KIT_CATEGORY_BUS[sound.category];
9244
+ if (!bus) return void 0;
9245
+ return { bus, pitch: sound.category === "ambiente" || /_loop/.test(sound.id) ? 0 : 0.04 };
9246
+ }
9247
+ function withAliases(manifest, aliases) {
9248
+ const byId = new Map(manifest.sounds.map((sound) => [sound.id, sound]));
9249
+ const extra = [];
9250
+ for (const [alias, target] of Object.entries(aliases)) {
9251
+ const sound = byId.get(target);
9252
+ if (sound && !byId.has(alias)) extra.push({ ...sound, id: alias });
9253
+ }
9254
+ return { ...manifest, sounds: [...manifest.sounds, ...extra] };
9255
+ }
9256
+ function kitSampleBank(manifest, options = {}) {
9257
+ return sampleBankFromManifest(withAliases(manifest, options.aliases ?? KIT_SAMPLE_ALIASES), {
9258
+ id: options.id ?? "kit_samples",
9259
+ baseUrl: options.baseUrl ?? "",
9260
+ defaults: kitSampleDefaults
9261
+ });
9262
+ }
9263
+ var isFileUrl = (url2) => {
9264
+ const page = globalThis.location?.href;
9265
+ try {
9266
+ return new URL(url2, page).protocol === "file:";
9267
+ } catch {
9268
+ return false;
9269
+ }
9270
+ };
9271
+ var fetchJson = async (url2) => {
9272
+ if (isFileUrl(url2)) throw new Error(`${url2}: file:// is not fetched`);
9273
+ const response = await fetch(url2);
9274
+ if (!response.ok) throw new Error(`${url2}: ${response.status}`);
9275
+ return await response.json();
9276
+ };
9277
+ async function loadKitSamples(engine, options = {}) {
9278
+ const baseUrl = options.baseUrl ?? "";
9279
+ let manifest;
9280
+ try {
9281
+ manifest = await (options.fetchManifest ?? fetchJson)(`${baseUrl}manifest.json`);
9282
+ } catch {
9283
+ return null;
9284
+ }
9285
+ const bank = kitSampleBank(manifest, options);
9286
+ engine.register(bank);
9287
+ if (options.preload && engine.loadSamples) void engine.loadSamples(options.preload).catch(() => void 0);
9288
+ return bank;
9289
+ }
9290
+ var USER_SAMPLES_BANK = "user_samples";
9291
+ var USER_SOUND_PREFIX = "user:";
9292
+ function userSampleBank(manifest, options = {}) {
9293
+ const playing = [];
9294
+ const behindKit = [];
9295
+ const sounds = [];
9296
+ for (const picked of manifest.sounds) {
9297
+ const sound = { ...picked, id: splitTier(picked.id).base };
9298
+ sounds.push({ ...sound, id: `${USER_SOUND_PREFIX}${sound.id}` });
9299
+ if (sound.role === "fallback" && options.kitHas?.(sound.id)) {
9300
+ behindKit.push(sound.id);
9301
+ continue;
9302
+ }
9303
+ sounds.push(sound);
9304
+ playing.push(sound.id);
9305
+ }
9306
+ const bank = sampleBankFromManifest(
9307
+ { sounds },
9308
+ {
9309
+ id: USER_SAMPLES_BANK,
9310
+ baseUrl: options.baseUrl ?? "",
9311
+ defaults: (sound) => kitSampleDefaults({ id: sound.id.replace(USER_SOUND_PREFIX, ""), category: sound.category })
9312
+ }
9313
+ );
9314
+ return { bank, playing, behindKit };
9315
+ }
9316
+ var fetchManifestJson = async (url2) => {
9317
+ const page = globalThis.location?.href;
9318
+ if (new URL(url2, page ?? "http://localhost/").protocol === "file:") throw new Error(`${url2}: file:// is not fetched`);
9319
+ const response = await fetch(url2);
9320
+ if (!response.ok) throw new Error(`${url2}: ${response.status}`);
9321
+ return await response.json();
9322
+ };
9323
+ async function loadUserSamples(engine, options = {}) {
9324
+ const baseUrl = options.baseUrl ?? "./sfx-local/";
9325
+ let manifest;
9326
+ try {
9327
+ manifest = await (options.fetchManifest ?? fetchManifestJson)(`${baseUrl}manifest.json`);
9328
+ } catch {
9329
+ return null;
9330
+ }
9331
+ const kitHas = options.kitHas ?? ((id) => Boolean(engine.registry?.sample(id)));
9332
+ const result2 = userSampleBank(manifest, { baseUrl, kitHas });
9333
+ engine.register(result2.bank);
9334
+ return result2;
9335
+ }
9097
9336
  var PT = {
9098
9337
  group: "Som",
9099
9338
  name: "Painel de som (arquivos e C1\u2013C10)",
@@ -9101,6 +9340,7 @@ var GameTestRoom = (() => {
9101
9340
  category: "1 \xB7 Categoria",
9102
9341
  sound: "1 \xB7 Som",
9103
9342
  play: "1 \xB7 \u25B6 Tocar",
9343
+ play_user: "1 \xB7 \u25B6 Tocar o seu",
9104
9344
  material: "2 \xB7 Material",
9105
9345
  blow: "2 \xB7 Golpe",
9106
9346
  strike: "2 \xB7 \u25B6 Tocar golpe",
@@ -9122,6 +9362,7 @@ var GameTestRoom = (() => {
9122
9362
  approve: "5 \xB7 Aprovo (C10)"
9123
9363
  },
9124
9364
  hints: {
9365
+ play_user: "Toca a sua grava\xE7\xE3o deste som (dos seus pacotes, fora do git), mesmo quando o SDK tem uma melhor e toca na frente.",
9125
9366
  sound: "A = arquivo, S = sintetizado, F = faltando. O que tem arquivo toca o arquivo; o resto toca o som atual.",
9126
9367
  strike: "Toca impact_<material>_<golpe>: o arquivo se existir, sen\xE3o o som atual daquele material.",
9127
9368
  wall: "P\xF5e a parede de casa entre voc\xEA e a fonte: o som fica abafado (C4).",
@@ -9166,9 +9407,10 @@ var GameTestRoom = (() => {
9166
9407
  strengths: { light: "leve", medium: "m\xE9dio", heavy: "forte" },
9167
9408
  states: { file: "A", synth: "S", missing: "F" },
9168
9409
  criteria: { C1: "C1 Sincronia", C2: "C2 Certo p/ a\xE7\xE3o", C3: "C3 Dist\xE2ncia", C4: "C4 Posi\xE7\xE3o", C5: "C5 Varia\xE7\xE3o", C6: "C6 Volume", C7: "C7 Intensidade", C8: "C8 Cauda", C9: "C9 Rajada", C10: "C10 Escuta" },
9169
- metrics: { chosen: "1 \xB7 Escolhido", matrix: "2 \xB7 Matriz", last: "\xDAltimo tocado", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Prefer\xEAncias", state: "5 \xB7 Medi\xE7\xE3o", count: "1 \xB7 Cat\xE1logo" },
9410
+ metrics: { source: "1 \xB7 Fonte do que tocou", chosen: "1 \xB7 Escolhido", matrix: "2 \xB7 Matriz", last: "\xDAltimo tocado", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Prefer\xEAncias", state: "5 \xB7 Medi\xE7\xE3o", count: "1 \xB7 Cat\xE1logo" },
9170
9411
  run: { idle: "parada", measuring: "medindo\u2026", done: "pronta", failed: "falhou", recording: "gravando\u2026", recorded: "grava\xE7\xE3o baixada" },
9171
9412
  words: { none: "\u2014", hidden: "? (revele)", prefers: "prefere", approved: "aprovado pelo dono" },
9413
+ sources: { sdk: "SDK", user: "seu som", synth: "sintetizado" },
9172
9414
  probe: "Fonte do som",
9173
9415
  wall: "Parede (oclus\xE3o)",
9174
9416
  scenarios: { pipeline: "Arquivo \u2192 decodifica \u2192 toca (round-robin, cauda)", criteria: "Crit\xE9rios C1\u2013C9 no som de calibra\xE7\xE3o", loop: "Loop de arquivo: emenda sem buraco (crossfade)", panel: "Painel: categorias, matriz, A/B e parede" }
@@ -9181,6 +9423,7 @@ var GameTestRoom = (() => {
9181
9423
  category: "1 \xB7 Category",
9182
9424
  sound: "1 \xB7 Sound",
9183
9425
  play: "1 \xB7 \u25B6 Play",
9426
+ play_user: "1 \xB7 \u25B6 Play yours",
9184
9427
  material: "2 \xB7 Material",
9185
9428
  blow: "2 \xB7 Blow",
9186
9429
  strike: "2 \xB7 \u25B6 Play blow",
@@ -9202,6 +9445,7 @@ var GameTestRoom = (() => {
9202
9445
  approve: "5 \xB7 Approve (C10)"
9203
9446
  },
9204
9447
  hints: {
9448
+ play_user: "Plays your own take of this sound (from your packs, kept out of git), even where the SDK has a better one that plays first.",
9205
9449
  sound: "A = file, S = synthesized, F = missing. A file plays the file; the rest plays the current sound.",
9206
9450
  strike: "Plays impact_<material>_<blow>: the file if there is one, else the current sound of that material.",
9207
9451
  wall: "Puts the house wall between you and the source: the sound is muffled (C4).",
@@ -9236,138 +9480,16 @@ var GameTestRoom = (() => {
9236
9480
  environments: { indoor: "indoor", outdoor: "outdoor" },
9237
9481
  strengths: { light: "light", medium: "medium", heavy: "heavy" },
9238
9482
  criteria: { C1: "C1 Sync", C2: "C2 Right sound", C3: "C3 Distance", C4: "C4 Position", C5: "C5 Variation", C6: "C6 Level", C7: "C7 Strength", C8: "C8 Tail", C9: "C9 Burst", C10: "C10 Listening" },
9239
- metrics: { chosen: "1 \xB7 Chosen", matrix: "2 \xB7 Matrix", last: "Last played", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Preferences", state: "5 \xB7 Measurement", count: "1 \xB7 Catalogue" },
9483
+ metrics: { source: "1 \xB7 Source of what played", chosen: "1 \xB7 Chosen", matrix: "2 \xB7 Matrix", last: "Last played", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Preferences", state: "5 \xB7 Measurement", count: "1 \xB7 Catalogue" },
9240
9484
  run: { idle: "idle", measuring: "measuring\u2026", done: "done", failed: "failed", recording: "recording\u2026", recorded: "recording downloaded" },
9241
9485
  words: { none: "\u2014", hidden: "? (reveal)", prefers: "prefers", approved: "approved by the owner" },
9486
+ sources: { sdk: "SDK", user: "your sound", synth: "synthesized" },
9242
9487
  probe: "Sound source",
9243
9488
  wall: "Wall (occlusion)",
9244
9489
  scenarios: { pipeline: "File \u2192 decode \u2192 play (round-robin, tail)", criteria: "Criteria C1\u2013C9 on the calibration sound", loop: "File loop: seamless joins (crossfade)", panel: "Panel: categories, matrix, A/B and wall" }
9245
9490
  };
9246
9491
 
9247
9492
  // ../game-audio/dist/index.js
9248
- var SFX_TIERS = { light: 0, medium: 0.34, heavy: 0.67 };
9249
- function splitTier(id) {
9250
- const match = /^(.*)_(light|medium|heavy)$/.exec(id);
9251
- return match ? { base: match[1], tier: match[2] } : { base: id };
9252
- }
9253
- var refsOf = (entries2, baseUrl) => (entries2 ?? []).map((entry) => ({ url: `${baseUrl}${entry.file}` }));
9254
- function sampleBankFromManifest(manifest, options) {
9255
- const baseUrl = options.baseUrl ?? "";
9256
- const samples = [];
9257
- const tiersOf = /* @__PURE__ */ new Map();
9258
- for (const sound of manifest.sounds) {
9259
- const { base, tier } = splitTier(sound.id);
9260
- if (tier) tiersOf.set(base, [...tiersOf.get(base) ?? [], sound]);
9261
- }
9262
- const bases = /* @__PURE__ */ new Map();
9263
- for (const sound of manifest.sounds) {
9264
- if (!splitTier(sound.id).tier) bases.set(sound.id, sound);
9265
- }
9266
- for (const [base, tiers] of tiersOf) {
9267
- if (!bases.has(base)) bases.set(base, { id: base, category: tiers[0]?.category ?? "", layers: { close: tiers[0]?.layers.close ?? [] } });
9268
- }
9269
- for (const sound of bases.values()) {
9270
- const numbers = options.sounds?.[sound.id] ?? options.defaults?.(sound);
9271
- const close = refsOf(sound.layers.close, baseUrl);
9272
- if (!numbers || close.length === 0) continue;
9273
- const tiers = (tiersOf.get(sound.id) ?? []).map((tier) => {
9274
- const name = splitTier(tier.id).tier;
9275
- const tierFar = refsOf(tier.layers.far, baseUrl);
9276
- return { name, from: SFX_TIERS[name] ?? 0, close: refsOf(tier.layers.close, baseUrl), ...tierFar.length > 0 ? { far: tierFar } : {} };
9277
- }).sort((a, b) => a.from - b.from);
9278
- const far = refsOf(sound.layers.far, baseUrl);
9279
- const indoor = refsOf(sound.layers.tail_in, baseUrl);
9280
- const outdoor = refsOf(sound.layers.tail_out, baseUrl);
9281
- const files = [sound, ...tiersOf.get(sound.id) ?? []].flatMap((entry) => Object.values(entry.layers).flatMap((entries2) => entries2 ?? []));
9282
- samples.push({
9283
- id: sound.id,
9284
- bus: numbers.bus,
9285
- close,
9286
- ...far.length > 0 ? { far } : {},
9287
- ...tiers.length > 0 ? { tiers } : {},
9288
- ...numbers.crossover ? { crossover: numbers.crossover } : {},
9289
- ...indoor.length > 0 || outdoor.length > 0 ? { tail: { ...indoor.length > 0 ? { indoor } : {}, ...outdoor.length > 0 ? { outdoor } : {}, ...numbers.tailGain === void 0 ? {} : { gain: numbers.tailGain } } } : {},
9290
- ...numbers.pitch === void 0 ? {} : { pitch: numbers.pitch },
9291
- ...numbers.gain === void 0 ? {} : { gain: numbers.gain },
9292
- ...numbers.falloff ? { falloff: numbers.falloff } : {},
9293
- ...numbers.cutoffK === void 0 ? {} : { cutoffK: numbers.cutoffK },
9294
- ...numbers.group ? { group: numbers.group } : {},
9295
- ...numbers.priority === void 0 ? {} : { priority: numbers.priority },
9296
- ...numbers.fallback ? { fallback: numbers.fallback } : {},
9297
- sources: files.map((file) => `${file.file}: ${file.source} ${file.author} ${file.license}`)
9298
- });
9299
- }
9300
- return { id: options.id, samples };
9301
- }
9302
- var KIT_CATEGORY_BUS = {
9303
- armas: "weapons",
9304
- impactos: "world",
9305
- "explosoes-fogo": "world",
9306
- personagem: "world",
9307
- veiculos: "world",
9308
- "itens-portas": "world",
9309
- ambiente: "ambience"
9310
- };
9311
- var VOICE_IDS = /^(player_(hurt|death|effort|breath)|scream|zombie_)/;
9312
- var KIT_SAMPLE_ALIASES = {
9313
- // a house door: the latch giving, the hinge, the leaf meeting the frame, a locked one shaken; a drawer on its runners
9314
- latch: "metal_latch",
9315
- creak_wood: "door_creak",
9316
- shut_wood: "door_wood_shut",
9317
- rattle: "door_locked_rattle",
9318
- // a drawer stays on its recipe: the recorded runs (1 to 1.9 s) outlast a drawer's pull
9319
- // an item taken in the hand (game-destruction-three grab_* keys)
9320
- grab_hard: "item_pickup_generic",
9321
- grab_soft: "item_pickup_leather"
9322
- };
9323
- function kitSampleDefaults(sound) {
9324
- const bus = VOICE_IDS.test(sound.id) ? "voices" : KIT_CATEGORY_BUS[sound.category];
9325
- if (!bus) return void 0;
9326
- return { bus, pitch: sound.category === "ambiente" || /_loop/.test(sound.id) ? 0 : 0.04 };
9327
- }
9328
- function withAliases(manifest, aliases) {
9329
- const byId = new Map(manifest.sounds.map((sound) => [sound.id, sound]));
9330
- const extra = [];
9331
- for (const [alias, target] of Object.entries(aliases)) {
9332
- const sound = byId.get(target);
9333
- if (sound && !byId.has(alias)) extra.push({ ...sound, id: alias });
9334
- }
9335
- return { ...manifest, sounds: [...manifest.sounds, ...extra] };
9336
- }
9337
- function kitSampleBank(manifest, options = {}) {
9338
- return sampleBankFromManifest(withAliases(manifest, options.aliases ?? KIT_SAMPLE_ALIASES), {
9339
- id: options.id ?? "kit_samples",
9340
- baseUrl: options.baseUrl ?? "",
9341
- defaults: kitSampleDefaults
9342
- });
9343
- }
9344
- var isFileUrl = (url2) => {
9345
- const page = globalThis.location?.href;
9346
- try {
9347
- return new URL(url2, page).protocol === "file:";
9348
- } catch {
9349
- return false;
9350
- }
9351
- };
9352
- var fetchJson = async (url2) => {
9353
- if (isFileUrl(url2)) throw new Error(`${url2}: file:// is not fetched`);
9354
- const response = await fetch(url2);
9355
- if (!response.ok) throw new Error(`${url2}: ${response.status}`);
9356
- return await response.json();
9357
- };
9358
- async function loadKitSamples(engine, options = {}) {
9359
- const baseUrl = options.baseUrl ?? "";
9360
- let manifest;
9361
- try {
9362
- manifest = await (options.fetchManifest ?? fetchJson)(`${baseUrl}manifest.json`);
9363
- } catch {
9364
- return null;
9365
- }
9366
- const bank = kitSampleBank(manifest, options);
9367
- engine.register(bank);
9368
- if (options.preload && engine.loadSamples) void engine.loadSamples(options.preload).catch(() => void 0);
9369
- return bank;
9370
- }
9371
9493
  var ATTACK = 2e-3;
9372
9494
  function lengthOf(d, attack) {
9373
9495
  if (typeof d === "number") return attack + d;
@@ -22848,7 +22970,6 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
22848
22970
  var rendererCount = 0;
22849
22971
  var COLLAPSE_CLOUD_KG = 1e3;
22850
22972
  var CHIP_DECAL_EDGE = 0.05;
22851
- var OPAQUE_CRACK = 0.1;
22852
22973
  var WOOD_RIM = 1.5;
22853
22974
  var IMPACT_TAGS = ["metal", "glass", "wood"];
22854
22975
  var GLOW_FROM_C = 430;
@@ -23772,14 +23893,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
23772
23893
  const pv = b?.parts.get(ev.part);
23773
23894
  if (!b || !pv) return;
23774
23895
  const def = this.world.materials.get(pv.material);
23775
- const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
23776
- const mv = this.nearestMesh(pv, wp);
23777
- if (mv) {
23778
- const glass2 = !!def?.visual?.transparent;
23779
- const own = this.partColor(pv, def) ?? fallbackColor(pv.material, def);
23780
- const color = glass2 ? 15267071 : new this.THREE.Color(own).multiplyScalar(0.45).getHex();
23781
- const length3 = glass2 ? ev.crack.length : Math.min(ev.crack.length, OPAQUE_CRACK);
23782
- this.visuals.addCrack(mv, wp, this.worldDir(b, ev.crack.normal), ev.crack.rays, length3, color, hashNumbers(ev.time, ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]));
23896
+ if (def?.visual?.transparent) {
23897
+ const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
23898
+ const mv = this.nearestMesh(pv, wp);
23899
+ const seed = hashNumbers(ev.time, ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
23900
+ if (mv) this.visuals.addCrack(mv, wp, this.worldDir(b, ev.crack.normal), ev.crack.rays, ev.crack.length, 15267071, seed);
23783
23901
  }
23784
23902
  this.impactSound(pv.material, ev.worldPoint, 0.5, ev.blow);
23785
23903
  }
@@ -28757,6 +28875,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
28757
28875
  lethalRadius: 5,
28758
28876
  injuryRadius: 15,
28759
28877
  energy: 2e5,
28878
+ tnt: 0.24,
28760
28879
  destructionLevel: 2,
28761
28880
  noise: 500,
28762
28881
  fuse: 4,
@@ -28802,6 +28921,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
28802
28921
  lethalRadius: 3,
28803
28922
  injuryRadius: 10,
28804
28923
  energy: 43200,
28924
+ tnt: 0.15,
28805
28925
  destructionLevel: 2,
28806
28926
  noise: 450,
28807
28927
  fuse: 3,
@@ -28821,6 +28941,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
28821
28941
  lethalRadius: 5,
28822
28942
  injuryRadius: 14,
28823
28943
  energy: 2e5,
28944
+ tnt: 1.5,
28824
28945
  destructionLevel: 2,
28825
28946
  noise: 550,
28826
28947
  fire: {
@@ -28867,6 +28988,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
28867
28988
  lethalRadius: 4,
28868
28989
  injuryRadius: 12,
28869
28990
  energy: 102400,
28991
+ tnt: 0.25,
28870
28992
  destructionLevel: 3,
28871
28993
  noise: 600,
28872
28994
  fuseLength: 0.03,
@@ -28889,6 +29011,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
28889
29011
  lethalRadius: 6,
28890
29012
  injuryRadius: 18,
28891
29013
  energy: 345600,
29014
+ tnt: 0.76,
28892
29015
  destructionLevel: 3,
28893
29016
  noise: 700,
28894
29017
  triggers: [
@@ -28912,6 +29035,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
28912
29035
  length: 50
28913
29036
  },
28914
29037
  energy: 102400,
29038
+ tnt: 0.9,
28915
29039
  destructionLevel: 1,
28916
29040
  noise: 500,
28917
29041
  triggers: [
@@ -28935,6 +29059,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
28935
29059
  lethalRadius: 0,
28936
29060
  injuryRadius: 10,
28937
29061
  energy: 0,
29062
+ tnt: 0.01,
28938
29063
  destructionLevel: 0,
28939
29064
  noise: 400,
28940
29065
  fuseRange: [
@@ -28959,6 +29084,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
28959
29084
  lethalRadius: 4,
28960
29085
  injuryRadius: 12,
28961
29086
  energy: 102400,
29087
+ tnt: 0.045,
28962
29088
  destructionLevel: 2,
28963
29089
  noise: 450,
28964
29090
  triggers: [
@@ -28977,6 +29103,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
28977
29103
  lethalRadius: 5,
28978
29104
  injuryRadius: 15,
28979
29105
  energy: 2e5,
29106
+ tnt: 0.45,
28980
29107
  destructionLevel: 3,
28981
29108
  noise: 700,
28982
29109
  triggers: [
@@ -28995,6 +29122,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
28995
29122
  lethalRadius: 5,
28996
29123
  injuryRadius: 16,
28997
29124
  energy: 2e5,
29125
+ tnt: 0.7,
28998
29126
  destructionLevel: 3,
28999
29127
  noise: 750,
29000
29128
  triggers: [
@@ -29013,6 +29141,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
29013
29141
  lethalRadius: 5,
29014
29142
  injuryRadius: 16,
29015
29143
  energy: 2e5,
29144
+ tnt: 0.45,
29016
29145
  destructionLevel: 3,
29017
29146
  noise: 750,
29018
29147
  triggers: [
@@ -29031,6 +29160,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
29031
29160
  lethalRadius: 5,
29032
29161
  injuryRadius: 16,
29033
29162
  energy: 2e5,
29163
+ tnt: 0.7,
29034
29164
  destructionLevel: 3,
29035
29165
  noise: 750,
29036
29166
  triggers: [
@@ -29049,6 +29179,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
29049
29179
  lethalRadius: 4,
29050
29180
  injuryRadius: 12,
29051
29181
  energy: 102400,
29182
+ tnt: 1,
29052
29183
  destructionLevel: 2,
29053
29184
  noise: 550,
29054
29185
  fire: {
@@ -29082,6 +29213,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
29082
29213
  lethalRadius: 5,
29083
29214
  injuryRadius: 14,
29084
29215
  energy: 2e5,
29216
+ tnt: 0.5,
29085
29217
  destructionLevel: 2,
29086
29218
  noise: 550,
29087
29219
  fire: {
@@ -29119,6 +29251,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
29119
29251
  lethalRadius: 4,
29120
29252
  injuryRadius: 12,
29121
29253
  energy: 102400,
29254
+ tnt: 0.5,
29122
29255
  destructionLevel: 3,
29123
29256
  noise: 700,
29124
29257
  fire: {
@@ -29148,6 +29281,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
29148
29281
  lethalRadius: 12,
29149
29282
  injuryRadius: 30,
29150
29283
  energy: 2764800,
29284
+ tnt: 20,
29151
29285
  destructionLevel: 3,
29152
29286
  noise: 700,
29153
29287
  fire: {
@@ -29175,6 +29309,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
29175
29309
  lethalRadius: 4,
29176
29310
  injuryRadius: 12,
29177
29311
  energy: 102400,
29312
+ tnt: 0.045,
29178
29313
  destructionLevel: 2,
29179
29314
  noise: 450,
29180
29315
  triggers: [
@@ -37893,6 +38028,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
37893
38028
  direction: this.facing,
37894
38029
  at: at3,
37895
38030
  energy: def.energy,
38031
+ ...def.tnt !== void 0 ? { tnt: def.tnt } : {},
37896
38032
  lethalRadius: def.lethalRadius,
37897
38033
  injuryRadius: def.injuryRadius,
37898
38034
  noise: def.noise,
@@ -45695,7 +45831,17 @@ void main(){
45695
45831
  if (onPlayer.zone !== "none") {
45696
45832
  action("blast_player", translate("act.blast_player", { weapon: weaponName, zone: translate(`blast.zone.${onPlayer.zone}`), distance: format(onPlayer.distance, 1), stun: format(onPlayer.stunSeconds, 1) }), { data: { ...onPlayer } });
45697
45833
  }
45698
- context.emitHost({ type: "explosion", source: projectile.weapon, point: detonation.point, energy: detonation.energy, radius: reach, cause: detonation.cause, explosive: detonation.explosive });
45834
+ context.emitHost({
45835
+ type: "explosion",
45836
+ source: projectile.weapon,
45837
+ point: detonation.point,
45838
+ energy: detonation.energy,
45839
+ radius: reach,
45840
+ cause: detonation.cause,
45841
+ explosive: detonation.explosive,
45842
+ lethalRadius: detonation.lethalRadius,
45843
+ ...detonation.tnt !== void 0 ? { tnt: detonation.tnt } : {}
45844
+ });
45699
45845
  if (detonation.fire) context.startFire(projectile.weapon, detonation.point, detonation.fire, detonation.cause === "impact");
45700
45846
  };
45701
45847
  const detonateBare = (projectile, isImpact) => {
@@ -47864,6 +48010,9 @@ void main(){
47864
48010
  var LYING_PITCH = 1.05;
47865
48011
  var LYING_ROLL = 0.35;
47866
48012
  var LYING_PITCH_FACE_DOWN = -1.05;
48013
+ var STUN_PITCH = 0.05;
48014
+ var STUN_ROLL = 0.08;
48015
+ var STUN_FULL = 2;
47867
48016
  function createPlayer(THREE, o) {
47868
48017
  const cam = o.camera;
47869
48018
  let yaw = 0;
@@ -47903,11 +48052,16 @@ void main(){
47903
48052
  };
47904
48053
  let knocked = null;
47905
48054
  let knockedForward = false;
48055
+ let stunLeft = 0;
48056
+ let stunPhase = 0;
47906
48057
  let rollSide = 1;
47907
48058
  const apply = () => {
47908
48059
  const lying = knocked?.lying ?? 0;
47909
48060
  const fallen = lying * lying * (3 - 2 * lying);
47910
- euler.set(pitch + ((knockedForward ? LYING_PITCH_FACE_DOWN : LYING_PITCH) - pitch) * fallen, yaw, LYING_ROLL * rollSide * fallen, "YXZ");
48061
+ const sway = Math.min(1, stunLeft / STUN_FULL);
48062
+ const swayPitch = STUN_PITCH * sway * Math.sin(stunPhase * 1.3);
48063
+ const swayRoll = STUN_ROLL * sway * Math.sin(stunPhase * 0.9 + 1);
48064
+ euler.set(pitch + ((knockedForward ? LYING_PITCH_FACE_DOWN : LYING_PITCH) - pitch) * fallen + swayPitch, yaw, LYING_ROLL * rollSide * fallen + swayRoll, "YXZ");
47911
48065
  cam.quaternion.setFromEuler(euler);
47912
48066
  const standing = eye - CROUCH_DROP * crouch - bend;
47913
48067
  cam.position.set(px.x, floorLevel + y + standing + (LYING_EYE - standing) * fallen, px.z);
@@ -48181,11 +48335,15 @@ void main(){
48181
48335
  knocked.velocity = [knocked.velocity[0] + next.velocity[0], knocked.velocity[1] + next.velocity[1], knocked.velocity[2] + next.velocity[2]];
48182
48336
  knocked.airborne || (knocked.airborne = next.airborne);
48183
48337
  knocked.down || (knocked.down = next.down);
48338
+ knocked.wounded || (knocked.wounded = next.wounded);
48339
+ knocked.lieLeft = Math.max(knocked.lieLeft, next.lieLeft);
48184
48340
  } else knocked = { ...next, velocity: [...next.velocity] };
48341
+ stunLeft = Math.max(stunLeft, next.stunSeconds ?? 0);
48185
48342
  if (next.down) {
48186
- const right = Math.cos(yaw) * next.velocity[0] - Math.sin(yaw) * next.velocity[2];
48343
+ const away = next.away && (next.away[0] !== 0 || next.away[2] !== 0) ? next.away : next.velocity;
48344
+ const right = Math.cos(yaw) * away[0] - Math.sin(yaw) * away[2];
48187
48345
  rollSide = right >= 0 ? -1 : 1;
48188
- knockedForward = -Math.sin(yaw) * next.velocity[0] - Math.cos(yaw) * next.velocity[2] > 0;
48346
+ knockedForward = -Math.sin(yaw) * away[0] - Math.cos(yaw) * away[2] > 0;
48189
48347
  crouchLatched = false;
48190
48348
  }
48191
48349
  if (knocked.airborne) vy2 = Math.max(vy2, knocked.velocity[1]);
@@ -48196,7 +48354,14 @@ void main(){
48196
48354
  get knockedForward() {
48197
48355
  return knockedForward;
48198
48356
  },
48357
+ get stunned() {
48358
+ return stunLeft;
48359
+ },
48199
48360
  update(dt) {
48361
+ if (stunLeft > 0) {
48362
+ stunLeft = Math.max(0, stunLeft - dt);
48363
+ stunPhase += dt * 2.4;
48364
+ }
48200
48365
  if (knocked) {
48201
48366
  stepKnocked(dt);
48202
48367
  return;
@@ -51345,6 +51510,9 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
51345
51510
  "act.player_slammed": "The player was thrown into {what} at {speed} m/s",
51346
51511
  "act.player_fell": "The player hit the floor at {speed} m/s",
51347
51512
  "act.player_shoved": "The blast pushed the player at {speed} m/s ({reach}% got through the cover)",
51513
+ "act.player_wounded": "The blast felled the player where they stood, {distance} m from it: badly wounded, stunned for {stun} s ({reach}% got through the cover)",
51514
+ "act.player_thrown_wounded": "The blast threw the player at {speed} m/s, {distance} m from it: badly wounded, stunned for {stun} s ({reach}% got through the cover)",
51515
+ "act.player_stunned": "The blast stunned the player for {stun} s, {distance} m from it: dizzy, ears ringing",
51348
51516
  "act.came_apart": "{name} came apart: what stayed is loose and falls",
51349
51517
  "act.shoved": "{name} stands free: the shots moved it",
51350
51518
  "act.impact": "{name} hit {where} at {speed} m/s: {energy} J of blunt damage",
@@ -51707,6 +51875,9 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
51707
51875
  "act.player_slammed": "O jogador foi arremessado contra {what} a {speed} m/s",
51708
51876
  "act.player_fell": "O jogador bateu no ch\xE3o a {speed} m/s",
51709
51877
  "act.player_shoved": "A explos\xE3o empurrou o jogador a {speed} m/s ({reach}% passou pela cobertura)",
51878
+ "act.player_wounded": "A explos\xE3o derrubou o jogador onde estava, a {distance} m dela: ferido gravemente, atordoado por {stun} s ({reach}% passou pela cobertura)",
51879
+ "act.player_thrown_wounded": "A explos\xE3o arremessou o jogador a {speed} m/s, a {distance} m dela: ferido gravemente, atordoado por {stun} s ({reach}% passou pela cobertura)",
51880
+ "act.player_stunned": "A explos\xE3o atordoou o jogador por {stun} s, a {distance} m dela: tonto, com os ouvidos zumbindo",
51710
51881
  "act.came_apart": "{name} se desmontou: o que ficou est\xE1 solto e cai",
51711
51882
  "act.shoved": "{name} estava solto no ch\xE3o: os tiros o moveram",
51712
51883
  "act.impact": "{name} bateu em {where} a {speed} m/s: {energy} J de dano de impacto",
@@ -53203,7 +53374,11 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
53203
53374
  const audio = options.audio === false ? null : options.audio ?? createAudioEngine({ deferredStart: {} });
53204
53375
  const destructionSound = audio ? createDestructionSound(audio) : null;
53205
53376
  if (audio && options.samplesBaseUrl !== null) {
53206
- void loadKitSamples(audio, { baseUrl: options.samplesBaseUrl ?? "./sfx/" }).then((bank) => {
53377
+ void loadKitSamples(audio, { baseUrl: options.samplesBaseUrl ?? "./sfx/" }).then(async (bank) => {
53378
+ if (options.userSamplesBaseUrl !== null) {
53379
+ const own = await loadUserSamples(audio, { baseUrl: options.userSamplesBaseUrl ?? "./sfx-local/" });
53380
+ if (own) log.add({ channel: "event", type: "user_sounds", text: `sons do dono: ${own.playing.length} tocando, ${own.behindKit.length} atr\xE1s do SDK`, data: { playing: own.playing, behindKit: own.behindKit } });
53381
+ }
53207
53382
  if (!bank || typeof window === "undefined") return;
53208
53383
  const preload = () => {
53209
53384
  window.removeEventListener("keydown", preload, true);
@@ -53432,7 +53607,19 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
53432
53607
  for (const ev of batch) {
53433
53608
  if (ev.type === "leak") puncture(ev);
53434
53609
  else if (ev.type === "ignite") igniteNear(ev.point, 0.9);
53435
- else if (ev.type === "explode") emitHost({ type: "explosion", source: ev.object, point: ev.point, energy: ev.energy, radius: ev.radius, cause: "part", ...ev.effect !== void 0 ? { explosive: ev.effect } : {} });
53610
+ else if (ev.type === "explode") {
53611
+ emitHost({
53612
+ type: "explosion",
53613
+ source: ev.object,
53614
+ point: ev.point,
53615
+ energy: ev.energy,
53616
+ radius: ev.radius,
53617
+ cause: "part",
53618
+ ...ev.effect !== void 0 ? { explosive: ev.effect } : {},
53619
+ ...ev.tnt !== void 0 ? { tnt: ev.tnt } : {},
53620
+ ...ev.lethalRadius !== void 0 ? { lethalRadius: ev.lethalRadius } : {}
53621
+ });
53622
+ }
53436
53623
  fire2("event", ev);
53437
53624
  options.onEvent?.(ev);
53438
53625
  }
@@ -54128,14 +54315,27 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
54128
54315
  const knockPlayer = (event) => {
54129
54316
  if (seating.seat) return;
54130
54317
  const feet = [player.x, player.feet, player.z];
54131
- const reach = world.blastReach(event.point, [feet[0], feet[1] + KNOCK_CHEST, feet[2]], event.source ? { ignore: [event.source] } : {});
54132
- const knock2 = knockOf({ point: event.point, energy: event.energy, radius: event.radius, reach }, feet, playerMass, options.blastShove);
54318
+ const chest = [feet[0], feet[1] + KNOCK_CHEST, feet[2]];
54319
+ const reach = world.blastReach(event.point, chest, event.source ? { ignore: [event.source] } : {});
54320
+ const blast3 = {
54321
+ point: event.point,
54322
+ energy: event.energy,
54323
+ radius: event.radius,
54324
+ reach,
54325
+ ...event.tnt !== void 0 ? { tnt: event.tnt } : {},
54326
+ ...event.lethalRadius !== void 0 ? { lethalRadius: event.lethalRadius } : {}
54327
+ };
54328
+ const knock2 = knockOf(blast3, feet, playerMass, options.blastShove, options.blastImpulse);
54133
54329
  if (!knock2) return;
54134
54330
  player.knock(knock2);
54135
54331
  const speed = Math.hypot(knock2.velocity[0], knock2.velocity[1], knock2.velocity[2]);
54136
- action(knock2.down ? "player_knocked_down" : "player_shoved", t(knock2.down ? "act.knocked_down" : "act.player_shoved", { speed: num(speed, 1), reach: num(reach * 100, 0) }), {
54137
- data: { speed, reach, down: knock2.down, source: event.source }
54138
- });
54332
+ const distance4 = Math.hypot(chest[0] - event.point[0], chest[1] - event.point[1], chest[2] - event.point[2]);
54333
+ const data = { speed, reach, distance: distance4, down: knock2.down, wounded: Boolean(knock2.wounded), stunSeconds: knock2.stunSeconds ?? 0, source: event.source };
54334
+ const values = { speed: num(speed, 1), reach: num(reach * 100, 0), distance: num(distance4, 1), stun: num(knock2.stunSeconds ?? 0, 1) };
54335
+ if (knock2.wounded) action("player_wounded", t(speed >= KNOCK_DOWN_SPEED ? "act.player_thrown_wounded" : "act.player_wounded", values), { data });
54336
+ else if (knock2.down) action("player_knocked_down", t("act.knocked_down", values), { data });
54337
+ else if (speed > 0) action("player_shoved", t("act.player_shoved", values), { data });
54338
+ else action("player_stunned", t("act.player_stunned", values), { data });
54139
54339
  };
54140
54340
  const avatarState = { speed: 0, running: false, crouch: false, down: false, armed: false, pitch: 0 };
54141
54341
  let feetPrev = null;