@adatechnology/game-test-room 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  // src/room.ts
2
2
  import { createDestruction, JOINT_DEFAULTS as JOINT_DEFAULTS2, resolveStrings as resolveDestructionStrings } from "@adatechnology/game-destruction";
3
- import { createDestructionRenderer, specFromObject3D } from "@adatechnology/game-destruction-three";
3
+ import { createAudioEngine, createAudioSink } from "@adatechnology/game-audio";
4
+ import { createDestructionRenderer, createDestructionSound, specFromObject3D } from "@adatechnology/game-destruction-three";
4
5
  import { createArsenal, DEFAULT_LOADOUT } from "@adatechnology/game-weapons";
5
6
  import { createFilmLcg, createFluids, createItemUse, ITEM_USES, resolveStrings as resolveFluidStrings } from "@adatechnology/game-fluids";
6
7
  import { createFilmBridge, createFilmManager, createFluidRenderer } from "@adatechnology/game-fluids-three";
7
8
  import { createGroundField, createVfx } from "@adatechnology/game-vfx";
8
9
  import { createGroundPatch, createVfxRenderer } from "@adatechnology/game-vfx-three";
9
- import { checkAnimation, checkBench, checkDestructible, checkSpec } from "@adatechnology/game-contracts";
10
+ import { checkAnimation, checkBench, checkDestructible, checkSpec, createEnvironmentBus } from "@adatechnology/game-contracts";
10
11
 
11
12
  // src/items.ts
12
13
  import { buildFrame } from "@adatechnology/game-destruction";
@@ -321,6 +322,16 @@ var en = {
321
322
  "ui.slow": "Slow motion 0.25\xD7",
322
323
  "ui.fast": "Time \xD710",
323
324
  "ui.tap": "Tap",
325
+ "ui.blast.shelter": "Shelter",
326
+ "ui.blast.fragmentHits": "Fragments",
327
+ "ui.blast.confinement": "Confinement",
328
+ "ui.blast.shelter.hint": "Blasts: walls and other parts between the blast and the target take their share",
329
+ "ui.blast.fragmentHits.hint": "Blasts: the pieces of what breaks fly on and hit what is behind",
330
+ "ui.blast.confinement.hint": "Blasts: pressure builds up in an enclosed room",
331
+ "metric.blast_shelter": "Blast reaching (%)",
332
+ "metric.blast_confinement": "Blast pressure (x)",
333
+ "metric.blast_fragments": "Fragment hits",
334
+ "act.blast_report": "Blast: {shelter}% reached the most sheltered target, pressure x{confinement}, {fragments} fragment hits, {n} targets",
324
335
  "ui.quality": "Quality",
325
336
  "ui.quality.low": "low",
326
337
  "ui.quality.medium": "medium",
@@ -646,6 +657,16 @@ var ptBR = {
646
657
  "ui.slow": "C\xE2mera lenta 0,25\xD7",
647
658
  "ui.fast": "Tempo \xD710",
648
659
  "ui.tap": "Torneira",
660
+ "ui.blast.shelter": "Abrigo",
661
+ "ui.blast.fragmentHits": "Estilha\xE7os",
662
+ "ui.blast.confinement": "Confinamento",
663
+ "ui.blast.shelter.hint": "Explos\xF5es: paredes e outras partes entre a explos\xE3o e o alvo ficam com a sua parte",
664
+ "ui.blast.fragmentHits.hint": "Explos\xF5es: os peda\xE7os do que quebra voam e acertam o que est\xE1 atr\xE1s",
665
+ "ui.blast.confinement.hint": "Explos\xF5es: a press\xE3o sobe num ambiente fechado",
666
+ "metric.blast_shelter": "Explos\xE3o que chegou (%)",
667
+ "metric.blast_confinement": "Press\xE3o da explos\xE3o (x)",
668
+ "metric.blast_fragments": "Estilha\xE7os que acertaram",
669
+ "act.blast_report": "Explos\xE3o: {shelter}% chegou ao alvo mais abrigado, press\xE3o x{confinement}, {fragments} estilha\xE7os acertaram, {n} alvos",
649
670
  "ui.quality": "Qualidade",
650
671
  "ui.quality.low": "baixa",
651
672
  "ui.quality.medium": "m\xE9dia",
@@ -1371,6 +1392,16 @@ var segmentTouchesBox = (from, to, min, max) => {
1371
1392
 
1372
1393
  // src/explosives.ts
1373
1394
  var CONE_BLAST_RADIUS = 0.5;
1395
+ var summarizeBlast = (results) => {
1396
+ const reports = results.flatMap((r) => r.blast ? [r.blast] : []);
1397
+ if (reports.length === 0) return void 0;
1398
+ return {
1399
+ shelter: Math.min(...reports.map((r) => r.shelter)),
1400
+ confinement: reports[0].confinement,
1401
+ fragmentHits: reports.reduce((n, r) => n + r.fragmentHits, 0),
1402
+ targets: reports.length
1403
+ };
1404
+ };
1374
1405
  var toArray = (vector) => [vector.x, vector.y, vector.z];
1375
1406
  function createExplosives(context) {
1376
1407
  const { THREE, scene, camera, world, renderer, arsenal, translate, format, action } = context;
@@ -1517,17 +1548,27 @@ function createExplosives(context) {
1517
1548
  if (box2.isEmpty()) box2.expandByPoint(object.position);
1518
1549
  const at = mostExposedPoint(box2, detonation);
1519
1550
  if (!at) continue;
1520
- reached += world.explode(at, detonation.energy * blastEffect(detonation, at).falloff, CONE_BLAST_RADIUS, "player").length;
1551
+ const results = world.explode(at, detonation.energy * blastEffect(detonation, at).falloff, CONE_BLAST_RADIUS, "player", context.blast());
1552
+ reached += results.length;
1553
+ told(results);
1521
1554
  }
1522
1555
  return reached;
1523
1556
  };
1557
+ const told = (results) => {
1558
+ const summary = summarizeBlast(results);
1559
+ if (!summary) return;
1560
+ context.blasted(summary);
1561
+ action("blast_report", translate("act.blast_report", { shelter: format(summary.shelter * 100, 0), confinement: format(summary.confinement, 2), fragments: summary.fragmentHits, n: summary.targets }), { data: { ...summary } });
1562
+ };
1524
1563
  const detonate = (projectile, detonation) => {
1525
1564
  const weaponName = context.toolName(projectile.weapon);
1526
1565
  const reach = Math.max(detonation.injuryRadius, detonation.lethalRadius);
1527
- const reached = detonation.energy <= 0 ? 0 : detonation.cone ? coneBlast(detonation) : world.explode(detonation.point, detonation.energy, reach, "player").length;
1566
+ const results = detonation.energy <= 0 || detonation.cone ? [] : world.explode(detonation.point, detonation.energy, reach, "player", context.blast());
1567
+ const reached = detonation.energy <= 0 ? 0 : detonation.cone ? coneBlast(detonation) : results.length;
1568
+ if (!detonation.cone) told(results);
1528
1569
  if (detonation.energy > 0) {
1529
1570
  const isPlayed = context.effects.explosion(detonation.explosive, detonation.point, { direction: detonation.direction }) !== null;
1530
- renderer.explode(detonation.point, detonation.energy, reach, { visual: !isPlayed });
1571
+ renderer.explode(detonation.point, detonation.energy, reach, { visual: !isPlayed, report: results });
1531
1572
  }
1532
1573
  if (detonation.stunSeconds !== void 0) renderer.emit("flash", detonation.point, [0, 1, 0], 1, 1);
1533
1574
  const onPlayer = blastEffect(detonation, toArray(camera.position));
@@ -1824,7 +1865,7 @@ function createLog(options = {}) {
1824
1865
  }
1825
1866
 
1826
1867
  // src/metrics.ts
1827
- var ROOM_METRICS = ["fps", "frame_ms", "draw_calls", "triangles", "objects", "fragments", "particles", "decals"];
1868
+ var ROOM_METRICS = ["fps", "frame_ms", "draw_calls", "triangles", "objects", "fragments", "particles", "decals", "blast_shelter", "blast_confinement", "blast_fragments"];
1828
1869
  var WINDOW_S = 0.5;
1829
1870
  function createRoomMetrics(sources) {
1830
1871
  const wanted = new Set(sources.ids);
@@ -1861,6 +1902,12 @@ function createRoomMetrics(sources) {
1861
1902
  const render = ownsRender ? { calls, triangles } : gl.info?.render;
1862
1903
  put("draw_calls", render?.calls);
1863
1904
  put("triangles", render?.triangles);
1905
+ const blast = sources.blast?.();
1906
+ if (blast) {
1907
+ put("blast_shelter", blast.shelter * 100);
1908
+ put("blast_confinement", blast.confinement);
1909
+ put("blast_fragments", blast.fragmentHits);
1910
+ }
1864
1911
  if (!["objects", "fragments", "particles", "decals"].some((id) => wanted.has(id))) return out;
1865
1912
  const destruction = sources.destruction.stats();
1866
1913
  const fluids = sources.fluids.stats();
@@ -3543,6 +3590,8 @@ function createUi(o) {
3543
3590
  o.onFast(fast);
3544
3591
  });
3545
3592
  const tapBtn = ctl(t("ui.tap"), () => o.onTap());
3593
+ const blastBtns = ["shelter", "fragmentHits", "confinement"].map((key) => ({ key, b: ctl(t(`ui.blast.${key}`), () => o.onBlast(key)) }));
3594
+ for (const { key, b } of blastBtns) b.title = t(`ui.blast.${key}.hint`);
3546
3595
  const qSel = mk2("select");
3547
3596
  for (const q of ["low", "medium", "high"]) {
3548
3597
  const op = mk2("option", void 0, t(`ui.quality.${q}`));
@@ -3747,6 +3796,7 @@ function createUi(o) {
3747
3796
  }
3748
3797
  slotsEl.forEach((b, i) => b.classList.toggle("on", i === s.slot));
3749
3798
  tapBtn.classList.toggle("on", s.tap);
3799
+ for (const { key: key2, b } of blastBtns) b.classList.toggle("on", s.blast[key2]);
3750
3800
  count.textContent = t("ui.objects", { n: s.objects });
3751
3801
  const ak = JSON.stringify(s.animations);
3752
3802
  if (ak !== lastAnim) {
@@ -3871,6 +3921,13 @@ function createTestRoom(options) {
3871
3921
  let simTime = 0;
3872
3922
  const log = createLog({ exportCancelled: () => t("log.export_cancelled") });
3873
3923
  log.setClock(() => simTime);
3924
+ const ownAudio = options.audio === void 0;
3925
+ const audio = options.audio === false ? null : options.audio ?? createAudioEngine();
3926
+ const destructionSound = audio ? createDestructionSound(audio) : null;
3927
+ const playSound = (key, point, volume) => {
3928
+ destructionSound?.onSound(key, point, volume);
3929
+ options.onSound?.(key, point, volume);
3930
+ };
3874
3931
  const craterField = options.craters ? createGroundField() : void 0;
3875
3932
  const craterPatch = craterField ? createGroundPatch(THREE, { field: craterField, material: new THREE.MeshStandardMaterial({ color: 9342602, roughness: 0.95 }) }) : void 0;
3876
3933
  if (craterPatch) {
@@ -3897,7 +3954,7 @@ function createTestRoom(options) {
3897
3954
  quality,
3898
3955
  groundAt,
3899
3956
  pointScale,
3900
- onSound: options.onSound,
3957
+ onSound: playSound,
3901
3958
  onPiece: (piece) => {
3902
3959
  rendererPieces.set(piece.objectId, piece);
3903
3960
  loose.push(piece);
@@ -4621,6 +4678,12 @@ function createTestRoom(options) {
4621
4678
  bodies.push({ id: "player", box: new THREE.Box3(new THREE.Vector3(x - PLAYER_RADIUS, feet, z - PLAYER_RADIUS), new THREE.Vector3(x + PLAYER_RADIUS, y + 0.1, z + PLAYER_RADIUS)) });
4622
4679
  return bodies;
4623
4680
  }
4681
+ let blastOptions = { ...options.blast };
4682
+ let lastBlast;
4683
+ const blastOn = (key) => Boolean(blastOptions[key]);
4684
+ const toggleBlast = (key) => {
4685
+ blastOptions = { ...blastOptions, [key]: blastOn(key) ? false : true };
4686
+ };
4624
4687
  const explosives = createExplosives({
4625
4688
  THREE,
4626
4689
  scene,
@@ -4635,6 +4698,10 @@ function createTestRoom(options) {
4635
4698
  toolName,
4636
4699
  withoutHeld,
4637
4700
  groundAt,
4701
+ blast: () => blastOptions,
4702
+ blasted: (summary) => {
4703
+ lastBlast = summary;
4704
+ },
4638
4705
  translate: t,
4639
4706
  format: num,
4640
4707
  action,
@@ -5061,7 +5128,7 @@ function createTestRoom(options) {
5061
5128
  let metricsAcc = 0;
5062
5129
  let ownsLoop = false;
5063
5130
  const roomMetricIds = options.metrics === false ? [] : options.metrics ?? ROOM_METRICS;
5064
- const roomMetrics = createRoomMetrics({ gl, destruction: renderer, fluids: fluidView, effects: vfx.world, ids: roomMetricIds });
5131
+ const roomMetrics = createRoomMetrics({ gl, destruction: renderer, fluids: fluidView, effects: vfx.world, ids: roomMetricIds, blast: () => lastBlast });
5065
5132
  let qualityName = quality;
5066
5133
  const syncContainers = (sdt) => {
5067
5134
  for (const e of entries.values()) {
@@ -5120,6 +5187,10 @@ function createTestRoom(options) {
5120
5187
  s.update(dt);
5121
5188
  flushEvents(id, s);
5122
5189
  }
5190
+ if (audio) {
5191
+ const look = camera.getWorldDirection(listenerDirection);
5192
+ audio.setListener({ position: [camera.position.x, camera.position.y, camera.position.z], yaw: Math.atan2(-look.x, -look.z) });
5193
+ }
5123
5194
  for (const update2 of frameListeners) update2(dt);
5124
5195
  for (const tr of tracers) {
5125
5196
  if (tr.life > 0) {
@@ -5176,6 +5247,7 @@ function createTestRoom(options) {
5176
5247
  timeScale,
5177
5248
  fast: fastForward,
5178
5249
  tap: isTapOn(),
5250
+ blast: { shelter: blastOn("shelter"), fragmentHits: blastOn("fragmentHits"), confinement: blastOn("confinement") },
5179
5251
  quality: qualityName,
5180
5252
  objects: [...entries.values()].filter((e) => !e.arena).length
5181
5253
  };
@@ -5229,7 +5301,7 @@ function createTestRoom(options) {
5229
5301
  const ev = evs[pl.fired++];
5230
5302
  action("animation_event", t("act.anim_event", { name: pl.entry.name, clip: pl.clip.id, type: ev.type }), { objectId: pl.entry.so.objectId, itemId: pl.entry.so.itemId, data: { clip: pl.clip.id, type: ev.type, at: ev.t, ...ev.data ?? {} } });
5231
5303
  const key = ev.type === "sound" ? ev.data?.key : void 0;
5232
- if (typeof key === "string") options.onSound?.(key, v3(pl.entry.so.root.getWorldPosition(tmpV)), 1);
5304
+ if (typeof key === "string") playSound(key, v3(pl.entry.so.root.getWorldPosition(tmpV)), 1);
5233
5305
  }
5234
5306
  if (pl.t >= pl.clip.duration) {
5235
5307
  playing.splice(i, 1);
@@ -5322,6 +5394,9 @@ function createTestRoom(options) {
5322
5394
  };
5323
5395
  const hostListeners = /* @__PURE__ */ new Set();
5324
5396
  const frameListeners = /* @__PURE__ */ new Set();
5397
+ const listenerDirection = new THREE.Vector3();
5398
+ const environment = options.environment ?? createEnvironmentBus();
5399
+ const audioSink = audio ? createAudioSink(audio) : void 0;
5325
5400
  function emitHost(event) {
5326
5401
  for (const listener of hostListeners) listener(event);
5327
5402
  }
@@ -5371,6 +5446,8 @@ function createTestRoom(options) {
5371
5446
  scene,
5372
5447
  camera,
5373
5448
  locale,
5449
+ ...audioSink ? { audio: audioSink } : {},
5450
+ environment,
5374
5451
  groundAt,
5375
5452
  effects: vfx,
5376
5453
  tool: () => ({
@@ -5624,6 +5701,8 @@ function createTestRoom(options) {
5624
5701
  arsenal,
5625
5702
  fluids,
5626
5703
  fluidRenderer: fluidView,
5704
+ audio,
5705
+ environment,
5627
5706
  log,
5628
5707
  get tool() {
5629
5708
  return tool;
@@ -5684,6 +5763,10 @@ function createTestRoom(options) {
5684
5763
  heldTilt = rad;
5685
5764
  },
5686
5765
  toggleTap,
5766
+ blast: () => ({ ...blastOptions }),
5767
+ setBlast: (next) => {
5768
+ blastOptions = { ...blastOptions, ...next };
5769
+ },
5687
5770
  pause() {
5688
5771
  paused = true;
5689
5772
  },
@@ -5717,6 +5800,8 @@ function createTestRoom(options) {
5717
5800
  return { room: roomMetrics.sample(), module: moduleMetrics() };
5718
5801
  },
5719
5802
  dispose() {
5803
+ destructionSound?.dispose();
5804
+ if (ownAudio) audio?.dispose();
5720
5805
  for (const detach of detachers.values()) detach();
5721
5806
  detachers.clear();
5722
5807
  for (const store of benches.values()) store.dispose();
@@ -5792,6 +5877,7 @@ function createTestRoom(options) {
5792
5877
  setTool(loadout[index]);
5793
5878
  },
5794
5879
  onTap: () => toggleTap(),
5880
+ onBlast: (key) => toggleBlast(key),
5795
5881
  onPlay: (objectId, clipId) => {
5796
5882
  playAnimation(clipId, objectId);
5797
5883
  },