@adatechnology/game-test-room 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.
package/dist/index.js CHANGED
@@ -291,6 +291,9 @@ var en = {
291
291
  "ui.quality.medium": "medium",
292
292
  "ui.quality.high": "high",
293
293
  "ui.equip": "Equip as a tool",
294
+ "ui.search_items": "Search items (all groups)",
295
+ "ui.search_tests": "Search tests",
296
+ "ui.no_results": "Nothing found",
294
297
  "ui.objects": "{n} objects",
295
298
  "ui.tests": "Tests",
296
299
  "ui.run_all": "Run all",
@@ -580,6 +583,9 @@ var ptBR = {
580
583
  "ui.quality.medium": "m\xE9dia",
581
584
  "ui.quality.high": "alta",
582
585
  "ui.equip": "Equipar como ferramenta",
586
+ "ui.search_items": "Buscar item (todas as abas)",
587
+ "ui.search_tests": "Buscar teste",
588
+ "ui.no_results": "Nada encontrado",
583
589
  "ui.objects": "{n} objetos",
584
590
  "ui.tests": "Testes",
585
591
  "ui.run_all": "Rodar todos",
@@ -2318,7 +2324,11 @@ var SCENARIOS = scenariosFor("en");
2318
2324
  var CSS = `
2319
2325
  .tr-root{position:absolute;inset:0;pointer-events:none;font:12px/1.35 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;color:#e8eaee;overflow:hidden}
2320
2326
  .tr-root *{box-sizing:border-box}
2321
- .tr-root button,.tr-root select,.tr-root label{pointer-events:auto}
2327
+ .tr-root button,.tr-root select,.tr-root label,.tr-root input{pointer-events:auto}
2328
+ .tr-search{width:100%;margin:0 0 6px;padding:5px 8px;border-radius:5px;border:1px solid #3a404c;background:#0f1116;color:#e8eaee;font:inherit}
2329
+ .tr-search::placeholder{color:#7d8594}
2330
+ .tr-item small{color:#7d8594;margin-left:4px}
2331
+ .tr-empty{color:#9aa3b2;padding:4px 2px;grid-column:1/-1}
2322
2332
  .tr-cross{position:absolute;left:50%;top:50%;width:18px;height:18px;margin:-9px 0 0 -9px}
2323
2333
  .tr-cross:before,.tr-cross:after{content:"";position:absolute;background:#fff;opacity:.85;box-shadow:0 0 2px #000}
2324
2334
  .tr-cross:before{left:8px;top:0;width:2px;height:18px}.tr-cross:after{top:8px;left:0;height:2px;width:18px}
@@ -2369,6 +2379,7 @@ var CSS = `
2369
2379
  .tr-summary{margin-top:6px;color:#9aa3b2}
2370
2380
  @media (max-width:980px){.tr-left,.tr-right{display:none}.tr-root.open .tr-left,.tr-root.open .tr-right{display:flex}.tr-toggle{display:block;pointer-events:auto}.tr-root.open .tr-toggle{right:min(310px,34vw)}.tr-left{width:min(400px,46vw)}.tr-help{display:none}}
2371
2381
  `;
2382
+ var fold = (text) => text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
2372
2383
  var mk = (tag, cls, text) => {
2373
2384
  const e = document.createElement(tag);
2374
2385
  if (cls) e.className = cls;
@@ -2508,15 +2519,27 @@ function createUi(o) {
2508
2519
  const tests = mk("div", "tr-panel tr-tests");
2509
2520
  right.append(cat, anim, tests);
2510
2521
  cat.appendChild(mk("h3", void 0, t("ui.catalog")));
2522
+ const itemSearch = mk("input", "tr-search");
2523
+ itemSearch.type = "search";
2524
+ itemSearch.placeholder = t("ui.search_items");
2511
2525
  const tabs = mk("div", "tr-tabs");
2512
2526
  const items = mk("div", "tr-items");
2513
- cat.append(tabs, items);
2527
+ cat.append(itemSearch, tabs, items);
2514
2528
  let activeGroup = "";
2529
+ const visibleItems = () => {
2530
+ const term = fold(itemSearch.value.trim());
2531
+ if (!term) return (o.groups().get(activeGroup) ?? []).map((item) => ({ item, group: activeGroup }));
2532
+ return [...o.groups()].flatMap(([group, list2]) => list2.filter((item) => fold(`${item.name} ${item.id} ${group}`).includes(term)).map((item) => ({ item, group })));
2533
+ };
2515
2534
  const renderItems = () => {
2516
2535
  items.textContent = "";
2517
- for (const it of o.groups().get(activeGroup) ?? []) {
2536
+ const searching = itemSearch.value.trim() !== "";
2537
+ const found = visibleItems();
2538
+ if (!found.length) items.appendChild(mk("div", "tr-empty", t("ui.no_results")));
2539
+ for (const { item: it, group } of found) {
2518
2540
  const wrap = mk("div", "tr-item");
2519
2541
  const b = mk("button", "tr-btn", it.name);
2542
+ if (searching) b.appendChild(mk("small", void 0, group));
2520
2543
  b.type = "button";
2521
2544
  b.title = it.note ?? it.name;
2522
2545
  b.onclick = () => o.onSpawn(it.id);
@@ -2547,6 +2570,8 @@ function createUi(o) {
2547
2570
  renderItems();
2548
2571
  };
2549
2572
  renderTabs();
2573
+ itemSearch.oninput = renderItems;
2574
+ for (const box2 of [itemSearch]) box2.addEventListener("keydown", (e) => e.stopPropagation());
2550
2575
  const controls = mk("div", "tr-row");
2551
2576
  const ctl = (label, fn) => {
2552
2577
  const b = mk("button", "tr-btn", label);
@@ -2588,8 +2613,16 @@ function createUi(o) {
2588
2613
  const runAll = mk("button", "tr-btn", t("ui.run_all"));
2589
2614
  runAll.type = "button";
2590
2615
  const summary = mk("div", "tr-summary");
2616
+ const testSearch = mk("input", "tr-search");
2617
+ testSearch.type = "search";
2618
+ testSearch.placeholder = t("ui.search_tests");
2619
+ testSearch.addEventListener("keydown", (e) => e.stopPropagation());
2591
2620
  const list = mk("div", "list");
2592
- tests.append(runAll, summary, list);
2621
+ tests.append(runAll, summary, testSearch, list);
2622
+ testSearch.oninput = () => {
2623
+ const term = fold(testSearch.value.trim());
2624
+ for (const b of scBtns.values()) b.style.display = !term || fold(b.textContent ?? "").includes(term) ? "" : "none";
2625
+ };
2593
2626
  const scBtns = /* @__PURE__ */ new Map();
2594
2627
  let busy = false;
2595
2628
  const mark = (rep) => {
@@ -2788,6 +2821,11 @@ function createUi(o) {
2788
2821
  renderTabs();
2789
2822
  renderScenarios();
2790
2823
  },
2824
+ showGroup(group) {
2825
+ activeGroup = group;
2826
+ itemSearch.value = "";
2827
+ renderTabs();
2828
+ },
2791
2829
  togglePanels,
2792
2830
  dispose() {
2793
2831
  offLog();
@@ -2858,7 +2896,13 @@ function createTestRoom(options) {
2858
2896
  const p = world.part(arena.table.id, arena.table.topPart);
2859
2897
  return !!p && p.status !== "broken" && p.status !== "melted" && p.status !== "ashes";
2860
2898
  };
2861
- const groundAt = (x, z) => x > arena.table.minX && x < arena.table.maxX && z > arena.table.minZ && z < arena.table.maxZ && tableOk() ? arena.table.top : 0;
2899
+ const restingTops = [];
2900
+ const restingTopAt = (x, z) => {
2901
+ let top = 0;
2902
+ for (const c of restingTops) if (x > c.minX && x < c.maxX && z > c.minZ && z < c.maxZ && world.has(c.objectId)) top = Math.max(top, c.top);
2903
+ return top;
2904
+ };
2905
+ const groundAt = (x, z) => Math.max(x > arena.table.minX && x < arena.table.maxX && z > arena.table.minZ && z < arena.table.maxZ && tableOk() ? arena.table.top : 0, restingTopAt(x, z));
2862
2906
  const ownFluids = !options.fluids;
2863
2907
  const fluids = options.fluids ?? createFluids({ seed: 1, locale, liquids: options.liquids, groundAt });
2864
2908
  const pristineFluids = ownFluids ? fluids.serialize() : null;
@@ -2991,16 +3035,16 @@ function createTestRoom(options) {
2991
3035
  const puncture = (ev) => {
2992
3036
  const e = entryOf(ev.object);
2993
3037
  if (!e?.containerId || e.fluidPart !== ev.part) return;
2994
- const host = hostOf(e);
2995
- host.root.updateMatrixWorld(true);
2996
- tmpM.copy(host.root.matrixWorld).invert();
3038
+ const host2 = hostOf(e);
3039
+ host2.root.updateMatrixWorld(true);
3040
+ tmpM.copy(host2.root.matrixWorld).invert();
2997
3041
  const local = tmpV.set(ev.point[0], ev.point[1], ev.point[2]).applyMatrix4(tmpM);
2998
3042
  local.x -= e.fluidOffset[0];
2999
3043
  local.y -= e.fluidOffset[1];
3000
3044
  local.z -= e.fluidOffset[2];
3001
- host.root.getWorldQuaternion(tmpQ).invert();
3045
+ host2.root.getWorldQuaternion(tmpQ).invert();
3002
3046
  const n = tmpV2.set(ev.direction[0], ev.direction[1], ev.direction[2]).applyQuaternion(tmpQ);
3003
- const part = world.part(host.objectId, ev.part);
3047
+ const part = world.part(host2.objectId, ev.part);
3004
3048
  const hole = part?.holes[part.holes.length - 1];
3005
3049
  fluids.puncture(e.containerId, [local.x, local.y, local.z], [n.x, n.y, n.z], hole?.diameter ?? 9e-3);
3006
3050
  };
@@ -3206,6 +3250,7 @@ function createTestRoom(options) {
3206
3250
  };
3207
3251
  const clear = () => {
3208
3252
  clearEverything();
3253
+ restingTops.length = 0;
3209
3254
  pending = [];
3210
3255
  action("clear", t("act.clear"));
3211
3256
  };
@@ -3425,6 +3470,7 @@ function createTestRoom(options) {
3425
3470
  }
3426
3471
  });
3427
3472
  player.kick(recoil[0], recoil[1]);
3473
+ emitHost({ type: "shot", weapon: toolId, shots: shots.length, recoil });
3428
3474
  const f = first;
3429
3475
  const d = f ? num(f.distance, 1) : "";
3430
3476
  let text;
@@ -3664,7 +3710,8 @@ function createTestRoom(options) {
3664
3710
  if (cat === "hand") return;
3665
3711
  const res = tool.trigger([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]], dt, held, "player");
3666
3712
  if (res.status === "empty") {
3667
- if (!tool.reloading && tool.reserve > 0) tool.reload();
3713
+ emitHost({ type: "empty", weapon: toolId });
3714
+ if (!tool.reloading && tool.reserve > 0) startReload();
3668
3715
  say(t(tool.reloading ? "hud.reloading" : "hud.empty"));
3669
3716
  return;
3670
3717
  }
@@ -3716,6 +3763,7 @@ function createTestRoom(options) {
3716
3763
  progress = null;
3717
3764
  action("tool", t("act.tool", { weapon: toolName(id) }), { data: { weapon: id } });
3718
3765
  fire("tool", id);
3766
+ emitHost({ type: "tool", weapon: id });
3719
3767
  };
3720
3768
  const cycleTool = (dir) => {
3721
3769
  if (!loadout.length) return;
@@ -3943,7 +3991,7 @@ function createTestRoom(options) {
3943
3991
  case "KeyR":
3944
3992
  if (heldPiece) heldYaw += Math.PI / 8;
3945
3993
  else {
3946
- tool.reload();
3994
+ startReload();
3947
3995
  say(t("hud.reloading"));
3948
3996
  }
3949
3997
  break;
@@ -4041,10 +4089,10 @@ function createTestRoom(options) {
4041
4089
  const syncContainers = (sdt) => {
4042
4090
  for (const e of entries.values()) {
4043
4091
  if (!e.containerId) continue;
4044
- const host = hostOf(e);
4045
- host.root.updateMatrixWorld(true);
4046
- const q = host.root.getWorldQuaternion(tmpQ);
4047
- const p = tmpV.set(e.fluidOffset[0], e.fluidOffset[1], e.fluidOffset[2]).applyMatrix4(host.root.matrixWorld);
4092
+ const host2 = hostOf(e);
4093
+ host2.root.updateMatrixWorld(true);
4094
+ const q = host2.root.getWorldQuaternion(tmpQ);
4095
+ const p = tmpV.set(e.fluidOffset[0], e.fluidOffset[1], e.fluidOffset[2]).applyMatrix4(host2.root.matrixWorld);
4048
4096
  const vel = sdt > 0 ? [(p.x - e.last[0]) / sdt, (p.y - e.last[1]) / sdt, (p.z - e.last[2]) / sdt] : void 0;
4049
4097
  e.last = [p.x, p.y, p.z];
4050
4098
  fluids.setTransform(e.containerId, [p.x, p.y, p.z], [q.x, q.y, q.z, q.w], vel);
@@ -4055,9 +4103,9 @@ function createTestRoom(options) {
4055
4103
  if (!e.containerId || !e.fluidPart) continue;
4056
4104
  const c = fluids.container(e.containerId);
4057
4105
  if (!c) continue;
4058
- const host = hostOf(e);
4059
- const part = world.part(host.objectId, e.fluidPart);
4060
- if (part && part.liters !== void 0 && Math.abs(part.liters - c.liters) > 1e-4) world.setLiters(host.objectId, e.fluidPart, c.liters);
4106
+ const host2 = hostOf(e);
4107
+ const part = world.part(host2.objectId, e.fluidPart);
4108
+ if (part && part.liters !== void 0 && Math.abs(part.liters - c.liters) > 1e-4) world.setLiters(host2.objectId, e.fluidPart, c.liters);
4061
4109
  }
4062
4110
  };
4063
4111
  let paused = false;
@@ -4088,6 +4136,9 @@ function createTestRoom(options) {
4088
4136
  updateImpacts();
4089
4137
  fluidView.update(dt);
4090
4138
  for (const s of tools.values()) s.update(dt);
4139
+ if (reloadingSeen && !tool.reloading) emitHost({ type: "reload", weapon: toolId, phase: "end" });
4140
+ reloadingSeen = tool.reloading;
4141
+ for (const update2 of frameListeners) update2(dt);
4091
4142
  for (const tr of tracers) {
4092
4143
  if (tr.life > 0) {
4093
4144
  tr.life -= dt;
@@ -4229,6 +4280,36 @@ function createTestRoom(options) {
4229
4280
  const moduleCheck = (ok, text, data, itemId) => {
4230
4281
  log.add({ channel: "check", type: ok ? "pass" : "fail", text: `${ok ? "\u2713" : "\u2717"} ${text}`, itemId, data: { ok, ...data } });
4231
4282
  };
4283
+ const modules = /* @__PURE__ */ new Map();
4284
+ const detachers = [];
4285
+ const hostListeners = /* @__PURE__ */ new Set();
4286
+ const frameListeners = /* @__PURE__ */ new Set();
4287
+ let reloadingSeen = false;
4288
+ function emitHost(event) {
4289
+ for (const listener of hostListeners) listener(event);
4290
+ }
4291
+ function startReload() {
4292
+ if (tool.reload()) emitHost({ type: "reload", weapon: toolId, phase: "start" });
4293
+ }
4294
+ const host = {
4295
+ THREE,
4296
+ scene,
4297
+ camera,
4298
+ locale,
4299
+ groundAt,
4300
+ tool: () => ({ weapon: toolId, category: tool.def.category, ammo: tool.ammo, capacity: tool.capacity, reserve: tool.reserve, reloading: tool.reloading }),
4301
+ on(listener) {
4302
+ hostListeners.add(listener);
4303
+ return () => hostListeners.delete(listener);
4304
+ },
4305
+ onFrame(update2) {
4306
+ frameListeners.add(update2);
4307
+ return () => frameListeners.delete(update2);
4308
+ },
4309
+ log(text, data) {
4310
+ action("module_note", text, { data });
4311
+ }
4312
+ };
4232
4313
  const loadModule = (mod) => {
4233
4314
  const report = { module: mod.id, items: [], scenarios: [], ok: true, problems: [] };
4234
4315
  const fail = (problem) => {
@@ -4293,6 +4374,8 @@ function createTestRoom(options) {
4293
4374
  report.scenarios.push(sc.id);
4294
4375
  }
4295
4376
  action("module", t("act.module_loaded", { name: mod.name, items: report.items.length, scenarios: report.scenarios.length, result: t(report.ok ? "mod.all_ok" : "mod.some_failed") }), { data: { module: mod.id, items: report.items, scenarios: report.scenarios, ok: report.ok } });
4377
+ modules.set(mod.id, mod);
4378
+ if (mod.attach) detachers.push(mod.attach(host));
4296
4379
  ui?.refresh();
4297
4380
  return report;
4298
4381
  };
@@ -4348,6 +4431,37 @@ function createTestRoom(options) {
4348
4431
  }
4349
4432
  return report;
4350
4433
  };
4434
+ const layArea = (layout) => {
4435
+ const holders = layout.placements.filter((p) => p.surface);
4436
+ const rest = layout.placements.filter((p) => !p.surface);
4437
+ const placed = [...holders, ...rest].map((p) => {
4438
+ const so = spawn(p.item, p.position, p.rotationY ?? 0);
4439
+ if (p.surface) {
4440
+ const [hx, hz] = p.surface.halfSize;
4441
+ restingTops.push({ objectId: so.objectId, minX: p.position[0] - hx, maxX: p.position[0] + hx, minZ: p.position[2] - hz, maxZ: p.position[2] + hz, top: p.surface.height });
4442
+ }
4443
+ return { so, asleep: p.asleep === true };
4444
+ });
4445
+ for (const { so, asleep } of placed) {
4446
+ const piece = asleep ? rendererPieces.get(so.objectId) : void 0;
4447
+ if (piece) piece.sleeping = true;
4448
+ }
4449
+ if (layout.view) room.look(layout.view.eye, layout.view.target);
4450
+ return placed.map(({ so }) => so);
4451
+ };
4452
+ const openArea = (area) => {
4453
+ clear();
4454
+ const mod = [...modules.values()].find((m) => m.id === area || m.group === area);
4455
+ const label = mod?.group ?? S.groups[area] ?? area;
4456
+ ui?.showGroup(label);
4457
+ const layout = mod?.area?.(locale);
4458
+ if (layout) return layArea(layout);
4459
+ const items = catalog.filter((item) => item.group === label);
4460
+ const front = items.filter((item) => item.movable !== false);
4461
+ const back = items.filter((item) => item.movable === false);
4462
+ const row = (list, gap, z) => list.map((item, index) => spawn(item.id, [(index - (list.length - 1) / 2) * gap, 0, z], 0));
4463
+ return [...row(front, 1.6, 1.5), ...row(back, 4.5, -4)];
4464
+ };
4351
4465
  const room = {
4352
4466
  world,
4353
4467
  renderer,
@@ -4427,10 +4541,12 @@ function createTestRoom(options) {
4427
4541
  fastForward = on;
4428
4542
  },
4429
4543
  equip,
4544
+ openArea,
4430
4545
  loadModule,
4431
4546
  animations: animationsOf,
4432
4547
  playAnimation,
4433
4548
  dispose() {
4549
+ for (const detach of detachers.splice(0)) detach();
4434
4550
  cancelAnimationFrame?.(raf);
4435
4551
  window.removeEventListener("resize", onResize);
4436
4552
  player.dispose();
@@ -4520,6 +4636,7 @@ function createTestRoom(options) {
4520
4636
  if (options.initial) {
4521
4637
  for (const [i, id] of options.initial.entries()) spawn(id, [(i - (options.initial.length - 1) / 2) * 1.3, 0, 0.5]);
4522
4638
  }
4639
+ if (options.area) openArea(options.area);
4523
4640
  return room;
4524
4641
  }
4525
4642
  function structuredCloneSpec(spec) {