@adatechnology/game-test-room 0.25.2 → 0.27.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.d.ts +38 -4
- package/dist/index.global.js +3316 -1073
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +399 -116
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// src/room.ts
|
|
2
2
|
import { createDestruction, JOINT_DEFAULTS, resolveStrings as resolveDestructionStrings } from "@adatechnology/game-destruction";
|
|
3
3
|
import { createAudioEngine, createAudioSink, loadKitSamples, loadUserSamples } from "@adatechnology/game-audio";
|
|
4
|
-
import {
|
|
4
|
+
import { registerFittedSounds } from "@adatechnology/game-audio/fitted";
|
|
5
|
+
import { createDestructionRenderer, createDestructionSound, dragSoundKey, impactKey, impactSoundIds, impactSoundKeys, specFromObject3D } from "@adatechnology/game-destruction-three";
|
|
5
6
|
import { bodyMass, KNOCK_CHEST, KNOCK_DOWN_SPEED, knockOf, surfaceOfMaterial } from "@adatechnology/game-contracts";
|
|
6
7
|
|
|
7
8
|
// src/body-sounds.ts
|
|
@@ -79,7 +80,7 @@ function createStrideCounter(stride = STRIDE_METRES) {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
// src/room.ts
|
|
82
|
-
import { createArsenal, DEFAULT_LOADOUT } from "@adatechnology/game-weapons";
|
|
83
|
+
import { createArsenal, DEFAULT_LOADOUT, realLoadout, weaponMenu } from "@adatechnology/game-weapons";
|
|
83
84
|
import { createFilmLcg, createFluids, createItemUse, ITEM_USES, resolveStrings as resolveFluidStrings } from "@adatechnology/game-fluids";
|
|
84
85
|
import { createFilmBridge, createFilmManager, createFluidRenderer } from "@adatechnology/game-fluids-three";
|
|
85
86
|
import { SPRITE_KINDS, createVfx, patchSpriteKinds } from "@adatechnology/game-vfx";
|
|
@@ -189,7 +190,7 @@ function resolveScenery(scenery) {
|
|
|
189
190
|
if (!position) throw new Error(`game-test-room: scenery "${id}" is new, so it needs a position`);
|
|
190
191
|
merged.set(id, { ...obj, position });
|
|
191
192
|
}
|
|
192
|
-
return [...merged.entries()].map(([id, o]) => ({ id, spec: o.spec, position: o.position, rotationY: o.rotationY ?? 0, colors: o.colors, build: o.build, follow: o.follow, label: o.label, top: o.top, spout: o.spout, free: o.free, pushable: o.pushable }));
|
|
193
|
+
return [...merged.entries()].map(([id, o]) => ({ id, spec: o.spec, position: o.position, rotationY: o.rotationY ?? 0, colors: o.colors, build: o.build, follow: o.follow, warmUpProbes: o.warmUpProbes, label: o.label, top: o.top, spout: o.spout, free: o.free, pushable: o.pushable }));
|
|
193
194
|
}
|
|
194
195
|
function createArena(THREE, size, scene, scenery, tapOption) {
|
|
195
196
|
const group = markKitOrigin(new THREE.Group(), { owner: "game-test-room", kind: "room" });
|
|
@@ -352,6 +353,23 @@ var USE_SOUNDS = {
|
|
|
352
353
|
};
|
|
353
354
|
var USE_SOUND_EVERY = 0.55;
|
|
354
355
|
var useSoundOf = (sounds, useId) => sounds?.uses?.[useId] ?? USE_SOUNDS[useId];
|
|
356
|
+
var HAND_RECORDED_IDS = [
|
|
357
|
+
"door_wood_open",
|
|
358
|
+
"door_creak",
|
|
359
|
+
"door_wood_close",
|
|
360
|
+
"door_wood_slam",
|
|
361
|
+
"door_locked_rattle",
|
|
362
|
+
"item_pickup_metal",
|
|
363
|
+
"item_pickup_plastic",
|
|
364
|
+
"item_pickup_glass",
|
|
365
|
+
"item_pickup_wood",
|
|
366
|
+
"item_pickup_fabric",
|
|
367
|
+
"drink_gulp",
|
|
368
|
+
// the old leaf keys still played by a leaf without a set (a vehicle's, whose bank falls back to them)
|
|
369
|
+
"latch",
|
|
370
|
+
"shut_wood",
|
|
371
|
+
"rattle"
|
|
372
|
+
];
|
|
355
373
|
|
|
356
374
|
// src/bench.ts
|
|
357
375
|
var snap = (control, value) => {
|
|
@@ -2223,7 +2241,7 @@ function createPlayer(THREE, o) {
|
|
|
2223
2241
|
if (o.blockers) {
|
|
2224
2242
|
for (const isBlocked of o.blockers) if (isBlocked(x, z, radius)) return true;
|
|
2225
2243
|
}
|
|
2226
|
-
return o.footing ? o.footing(x, z, radius, y).blocked : false;
|
|
2244
|
+
return o.footing ? o.footing(x, z, radius, floorLevel + y).blocked : false;
|
|
2227
2245
|
};
|
|
2228
2246
|
const clearRadius = () => {
|
|
2229
2247
|
for (const share of CLEAR_SHARES) if (!blocked(px.x, px.z, PLAYER_RADIUS * share)) return PLAYER_RADIUS * share;
|
|
@@ -2522,7 +2540,7 @@ function createPlayer(THREE, o) {
|
|
|
2522
2540
|
const run = !crouched && !walkHeld && (keys.has("ShiftLeft") || keys.has("ShiftRight"));
|
|
2523
2541
|
const fromX = px.x;
|
|
2524
2542
|
const fromZ = px.z;
|
|
2525
|
-
const speed = crouched ? CROUCH_WALK : run ? RUN : WALK;
|
|
2543
|
+
const speed = (crouched ? CROUCH_WALK : run ? RUN : WALK) * (o.speedAt?.(px.x, px.z) ?? 1);
|
|
2526
2544
|
const [walkX, walkZ] = walkKeys();
|
|
2527
2545
|
if ((walkX || walkZ || keys.has("Space")) && eye !== EYE) eye = EYE;
|
|
2528
2546
|
if ((walkX || walkZ) && !walkHeld) move(walkX * speed * dt, walkZ * speed * dt);
|
|
@@ -3318,7 +3336,7 @@ function createRoomDoors(options) {
|
|
|
3318
3336
|
|
|
3319
3337
|
// src/room.ts
|
|
3320
3338
|
import { releaseObject3D as releaseObject3D2 } from "@adatechnology/game-shapes-three";
|
|
3321
|
-
import { createTerrain, createTerrainImpl, DEFAULT_PROFILE } from "@adatechnology/game-terrain";
|
|
3339
|
+
import { createFootprints, createTerrain, createTerrainImpl, DEFAULT_PROFILE, waterLift } from "@adatechnology/game-terrain";
|
|
3322
3340
|
import { createTerrainRenderer, terrainProfileName } from "@adatechnology/game-terrain-three";
|
|
3323
3341
|
|
|
3324
3342
|
// src/avatar.ts
|
|
@@ -3618,7 +3636,7 @@ var en = {
|
|
|
3618
3636
|
"hud.throw": "throw",
|
|
3619
3637
|
"hud.tilt": "tilt (hold)",
|
|
3620
3638
|
"hud.equip": "equip {weapon}",
|
|
3621
|
-
"ui.help": "Click: look \xB7 WASD: walk \xB7 Shift: run \xB7 C: crouch (tap stays, hold while pressed) \xB7 Space: jump \xB7 1-0 / wheel: tool \xB7 LMB: use (bare hands: punch) \xB7 E: grab/drop (hold against furniture: push) \xB7 G or RMB: throw \xB7 R: reload \xB7 Q: fire mode \xB7 X: clear a jam \xB7 B: safety \xB7 Y: detonate charges set down \xB7 K: kick \xB7 N: strike combo on/off \xB7 Tab: panels \xB7 Esc: free the mouse",
|
|
3639
|
+
"ui.help": "Click: look \xB7 WASD: walk \xB7 Shift: run \xB7 C: crouch (tap stays, hold while pressed) \xB7 Space: jump \xB7 1-0 / wheel: tool \xB7 \u2019 (key left of 1): every weapon by category \xB7 LMB: use (bare hands: punch) \xB7 E: grab/drop (hold against furniture: push) \xB7 G or RMB: throw \xB7 R: reload \xB7 Q: fire mode \xB7 X: clear a jam \xB7 B: safety \xB7 Y: detonate charges set down \xB7 K: kick \xB7 N: strike combo on/off \xB7 Tab: panels \xB7 Esc: free the mouse",
|
|
3622
3640
|
"ui.help.camera": "V: 1st/3rd person (Shift+V: kick)",
|
|
3623
3641
|
"camera.first": "1st person",
|
|
3624
3642
|
"camera.third": "3rd person",
|
|
@@ -3660,6 +3678,10 @@ var en = {
|
|
|
3660
3678
|
"arena.test_table": "Test table",
|
|
3661
3679
|
"ui.clear_log": "Clear",
|
|
3662
3680
|
"ui.catalog": "Catalogue",
|
|
3681
|
+
"ui.tool_menu": "Weapons",
|
|
3682
|
+
"ui.tool_menu.search": "Search weapons and tools",
|
|
3683
|
+
"ui.tool_menu.close": "Close",
|
|
3684
|
+
"ui.tool_menu.results": "{n} found",
|
|
3663
3685
|
"ui.clear": "Clear",
|
|
3664
3686
|
"ui.reset_all": "Reset all",
|
|
3665
3687
|
"ui.slow": "Slow motion 0.25\xD7",
|
|
@@ -3983,7 +4005,7 @@ var ptBR = {
|
|
|
3983
4005
|
"hud.throw": "arremessar",
|
|
3984
4006
|
"hud.tilt": "inclinar (segurar)",
|
|
3985
4007
|
"hud.equip": "equipar {weapon}",
|
|
3986
|
-
"ui.help": "Clique: olhar \xB7 WASD: andar \xB7 Shift: correr \xB7 C: agachar (toque fixa, segurar s\xF3 enquanto aperta) \xB7 Espa\xE7o: pular \xB7 1-0 / roda: ferramenta \xB7 bot\xE3o esquerdo: usar (m\xE3o livre: soco) \xB7 E: pegar/soltar (segurar encostado no m\xF3vel: empurrar) \xB7 G ou bot\xE3o direito: arremessar \xB7 R: recarregar \xB7 Q: modo de tiro \xB7 X: destravar \xB7 B: trava \xB7 Y: detonar cargas postas \xB7 K: chute \xB7 N: liga/desliga a sequ\xEAncia de golpes \xB7 Tab: pain\xE9is \xB7 Esc: soltar o mouse",
|
|
4008
|
+
"ui.help": "Clique: olhar \xB7 WASD: andar \xB7 Shift: correr \xB7 C: agachar (toque fixa, segurar s\xF3 enquanto aperta) \xB7 Espa\xE7o: pular \xB7 1-0 / roda: ferramenta \xB7 \u2019 (tecla \xE0 esquerda do 1): todas as armas por categoria \xB7 bot\xE3o esquerdo: usar (m\xE3o livre: soco) \xB7 E: pegar/soltar (segurar encostado no m\xF3vel: empurrar) \xB7 G ou bot\xE3o direito: arremessar \xB7 R: recarregar \xB7 Q: modo de tiro \xB7 X: destravar \xB7 B: trava \xB7 Y: detonar cargas postas \xB7 K: chute \xB7 N: liga/desliga a sequ\xEAncia de golpes \xB7 Tab: pain\xE9is \xB7 Esc: soltar o mouse",
|
|
3987
4009
|
"ui.help.camera": "V: 1\xAA/3\xAA pessoa (Shift+V: chute)",
|
|
3988
4010
|
"camera.first": "1\xAA pessoa",
|
|
3989
4011
|
"camera.third": "3\xAA pessoa",
|
|
@@ -4025,6 +4047,10 @@ var ptBR = {
|
|
|
4025
4047
|
"arena.test_table": "Mesa de testes",
|
|
4026
4048
|
"ui.clear_log": "Limpar",
|
|
4027
4049
|
"ui.catalog": "Cat\xE1logo",
|
|
4050
|
+
"ui.tool_menu": "Armas",
|
|
4051
|
+
"ui.tool_menu.search": "Buscar armas e ferramentas",
|
|
4052
|
+
"ui.tool_menu.close": "Fechar",
|
|
4053
|
+
"ui.tool_menu.results": "{n} encontradas",
|
|
4028
4054
|
"ui.clear": "Limpar",
|
|
4029
4055
|
"ui.reset_all": "Consertar tudo",
|
|
4030
4056
|
"ui.slow": "C\xE2mera lenta 0,25\xD7",
|
|
@@ -4446,12 +4472,12 @@ function createTestTablePanel(table, t) {
|
|
|
4446
4472
|
list.appendChild(b);
|
|
4447
4473
|
}
|
|
4448
4474
|
};
|
|
4449
|
-
const
|
|
4475
|
+
const fold3 = (text) => text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
4450
4476
|
search.oninput = () => {
|
|
4451
4477
|
found.textContent = "";
|
|
4452
|
-
const term =
|
|
4478
|
+
const term = fold3(search.value.trim());
|
|
4453
4479
|
if (!term) return;
|
|
4454
|
-
const hits = table.items().filter((item) =>
|
|
4480
|
+
const hits = table.items().filter((item) => fold3(`${item.name} ${item.id}`).includes(term)).slice(0, 8);
|
|
4455
4481
|
for (const item of hits) {
|
|
4456
4482
|
const b = mk2("button", "tr-btn", item.name);
|
|
4457
4483
|
b.type = "button";
|
|
@@ -4483,6 +4509,121 @@ function createTestTablePanel(table, t) {
|
|
|
4483
4509
|
};
|
|
4484
4510
|
}
|
|
4485
4511
|
|
|
4512
|
+
// src/tool-menu.ts
|
|
4513
|
+
var TOOL_MENU_CSS = `
|
|
4514
|
+
.tr-wmenu{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:min(880px,92vw);max-height:78vh;display:none;flex-direction:column;z-index:30;pointer-events:auto}
|
|
4515
|
+
.tr-wmenu.open{display:flex}
|
|
4516
|
+
.tr-wmenu header{display:flex;gap:6px;align-items:center;margin-bottom:6px}.tr-wmenu header h3{margin:0;flex:1}
|
|
4517
|
+
.tr-wmenu .tr-search{margin:0;width:220px}
|
|
4518
|
+
.tr-wmenu-body{overflow:auto;flex:1}
|
|
4519
|
+
.tr-wmenu-sec h4{margin:8px 0 4px;font-size:11px;color:#9aa3b2;text-transform:uppercase;letter-spacing:.04em;font-weight:600}
|
|
4520
|
+
.tr-wmenu-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(128px,1fr));gap:4px}
|
|
4521
|
+
.tr-wmenu-grid .tr-btn{display:flex;flex-direction:column;gap:3px;min-width:0;text-align:left}
|
|
4522
|
+
.tr-wmenu-grid .tr-btn span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
4523
|
+
.tr-wmenu-grid .tr-thumb{height:22px}
|
|
4524
|
+
.tr-menu-btn{margin-left:6px;max-width:none}
|
|
4525
|
+
`;
|
|
4526
|
+
var mk3 = (tag, cls, text) => {
|
|
4527
|
+
const e = document.createElement(tag);
|
|
4528
|
+
if (cls) e.className = cls;
|
|
4529
|
+
if (text !== void 0) e.textContent = text;
|
|
4530
|
+
return e;
|
|
4531
|
+
};
|
|
4532
|
+
var fold = (text) => text.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase();
|
|
4533
|
+
function createToolMenu(o) {
|
|
4534
|
+
const element = mk3("div", "tr-panel tr-wmenu");
|
|
4535
|
+
element.dataset.toolMenu = "";
|
|
4536
|
+
const header = mk3("header");
|
|
4537
|
+
const search = mk3("input", "tr-search");
|
|
4538
|
+
search.type = "search";
|
|
4539
|
+
search.placeholder = o.t("ui.tool_menu.search");
|
|
4540
|
+
const close = mk3("button", "tr-btn", "\xD7");
|
|
4541
|
+
close.type = "button";
|
|
4542
|
+
close.title = o.t("ui.tool_menu.close");
|
|
4543
|
+
header.append(mk3("h3", void 0, o.t("ui.tool_menu")), search, close);
|
|
4544
|
+
const tabs = mk3("div", "tr-tabs");
|
|
4545
|
+
const body = mk3("div", "tr-wmenu-body");
|
|
4546
|
+
element.append(header, tabs, body);
|
|
4547
|
+
let open = false;
|
|
4548
|
+
let activeGroup = "";
|
|
4549
|
+
let groups = [];
|
|
4550
|
+
const toolButton = (tool, group) => {
|
|
4551
|
+
const button = mk3("button", tool.id === o.current() ? "tr-btn on" : "tr-btn");
|
|
4552
|
+
button.type = "button";
|
|
4553
|
+
button.title = tool.name;
|
|
4554
|
+
button.dataset.tool = tool.id;
|
|
4555
|
+
if (tool.thumbnail) button.appendChild(o.thumb(tool.thumbnail, tool.thumbnailCredit));
|
|
4556
|
+
button.appendChild(mk3("span", void 0, tool.name));
|
|
4557
|
+
if (group) button.appendChild(mk3("small", void 0, group));
|
|
4558
|
+
button.onclick = () => {
|
|
4559
|
+
o.onPick(tool.id);
|
|
4560
|
+
toggle(false);
|
|
4561
|
+
};
|
|
4562
|
+
return button;
|
|
4563
|
+
};
|
|
4564
|
+
const section = (name, buttons) => {
|
|
4565
|
+
const wrap = mk3("div", "tr-wmenu-sec");
|
|
4566
|
+
const grid = mk3("div", "tr-wmenu-grid");
|
|
4567
|
+
grid.append(...buttons);
|
|
4568
|
+
wrap.append(mk3("h4", void 0, name), grid);
|
|
4569
|
+
return wrap;
|
|
4570
|
+
};
|
|
4571
|
+
const renderBody = () => {
|
|
4572
|
+
body.textContent = "";
|
|
4573
|
+
const term = fold(search.value.trim());
|
|
4574
|
+
if (term) {
|
|
4575
|
+
const found = groups.flatMap((group2) => group2.sections.flatMap((s) => s.tools.filter((tool) => fold(`${tool.name} ${tool.id} ${tool.keywords ?? ""} ${s.name}`).includes(term)).map((tool) => toolButton(tool, s.name))));
|
|
4576
|
+
body.appendChild(found.length ? section(o.t("ui.tool_menu.results", { n: found.length }), found) : mk3("div", "tr-empty", o.t("ui.no_results")));
|
|
4577
|
+
return;
|
|
4578
|
+
}
|
|
4579
|
+
const group = groups.find((g) => g.id === activeGroup);
|
|
4580
|
+
for (const s of group?.sections ?? []) body.appendChild(section(`${s.name} (${s.tools.length})`, s.tools.map((tool) => toolButton(tool))));
|
|
4581
|
+
};
|
|
4582
|
+
const renderTabs = () => {
|
|
4583
|
+
tabs.textContent = "";
|
|
4584
|
+
if (!groups.some((g) => g.id === activeGroup)) activeGroup = groups.find((g) => g.sections.some((s) => s.tools.some((tool) => tool.id === o.current())))?.id ?? groups[0]?.id ?? "";
|
|
4585
|
+
for (const group of groups) {
|
|
4586
|
+
const count = group.sections.reduce((sum, s) => sum + s.tools.length, 0);
|
|
4587
|
+
const button = mk3("button", group.id === activeGroup ? "tr-btn on" : "tr-btn", `${group.name} (${count})`);
|
|
4588
|
+
button.type = "button";
|
|
4589
|
+
button.dataset.group = group.id;
|
|
4590
|
+
button.onclick = () => {
|
|
4591
|
+
activeGroup = group.id;
|
|
4592
|
+
search.value = "";
|
|
4593
|
+
renderTabs();
|
|
4594
|
+
};
|
|
4595
|
+
tabs.appendChild(button);
|
|
4596
|
+
}
|
|
4597
|
+
renderBody();
|
|
4598
|
+
};
|
|
4599
|
+
search.oninput = renderBody;
|
|
4600
|
+
close.onclick = () => toggle(false);
|
|
4601
|
+
element.addEventListener("keydown", (e) => {
|
|
4602
|
+
if (e.code === "Escape") toggle(false);
|
|
4603
|
+
});
|
|
4604
|
+
function toggle(next) {
|
|
4605
|
+
const want = next ?? !open;
|
|
4606
|
+
if (want === open) return open;
|
|
4607
|
+
open = want;
|
|
4608
|
+
element.classList.toggle("open", open);
|
|
4609
|
+
if (open) {
|
|
4610
|
+
groups = o.groups();
|
|
4611
|
+
activeGroup = "";
|
|
4612
|
+
search.value = "";
|
|
4613
|
+
renderTabs();
|
|
4614
|
+
}
|
|
4615
|
+
o.onOpen?.(open);
|
|
4616
|
+
return open;
|
|
4617
|
+
}
|
|
4618
|
+
return {
|
|
4619
|
+
element,
|
|
4620
|
+
toggle,
|
|
4621
|
+
get open() {
|
|
4622
|
+
return open;
|
|
4623
|
+
}
|
|
4624
|
+
};
|
|
4625
|
+
}
|
|
4626
|
+
|
|
4486
4627
|
// src/ui.ts
|
|
4487
4628
|
var CSS = `
|
|
4488
4629
|
.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}
|
|
@@ -4563,8 +4704,8 @@ var CSS = `
|
|
|
4563
4704
|
.tr-summary{margin-top:6px;color:#9aa3b2}
|
|
4564
4705
|
@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}}
|
|
4565
4706
|
`;
|
|
4566
|
-
var
|
|
4567
|
-
var
|
|
4707
|
+
var fold2 = (text) => text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
4708
|
+
var mk4 = (tag, cls, text) => {
|
|
4568
4709
|
const e = document.createElement(tag);
|
|
4569
4710
|
if (cls) e.className = cls;
|
|
4570
4711
|
if (text !== void 0) e.textContent = text;
|
|
@@ -4575,7 +4716,7 @@ var thumbSource = (thumbnail) => {
|
|
|
4575
4716
|
return source.replace(/["\\\n\r]/g, (char) => encodeURIComponent(char));
|
|
4576
4717
|
};
|
|
4577
4718
|
var thumb = (thumbnail, credit) => {
|
|
4578
|
-
const e =
|
|
4719
|
+
const e = mk4("span", "tr-thumb");
|
|
4579
4720
|
if (credit) e.title = credit;
|
|
4580
4721
|
else e.setAttribute("aria-hidden", "true");
|
|
4581
4722
|
e.style.setProperty("--tr-thumb", `url("${thumbSource(thumbnail)}")`);
|
|
@@ -4596,15 +4737,15 @@ async function toggleFullscreen() {
|
|
|
4596
4737
|
}
|
|
4597
4738
|
function createUi(o) {
|
|
4598
4739
|
const { t } = o;
|
|
4599
|
-
const style =
|
|
4600
|
-
style.textContent = CSS + (o.testTable ? TEST_TABLE_CSS : "");
|
|
4601
|
-
const root =
|
|
4740
|
+
const style = mk4("style");
|
|
4741
|
+
style.textContent = CSS + (o.testTable ? TEST_TABLE_CSS : "") + (o.toolMenu ? TOOL_MENU_CSS : "");
|
|
4742
|
+
const root = mk4("div", "tr-root");
|
|
4602
4743
|
const pos = getComputedStyle?.(o.container)?.position;
|
|
4603
4744
|
if (!pos || pos === "static") o.container.style.position = "relative";
|
|
4604
4745
|
root.appendChild(style);
|
|
4605
4746
|
o.container.appendChild(root);
|
|
4606
|
-
const cross =
|
|
4607
|
-
cross.appendChild(
|
|
4747
|
+
const cross = mk4("div", "tr-cross");
|
|
4748
|
+
cross.appendChild(mk4("i"));
|
|
4608
4749
|
const ring = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
4609
4750
|
ring.setAttribute("class", "tr-ring");
|
|
4610
4751
|
ring.setAttribute("viewBox", "0 0 64 64");
|
|
@@ -4621,35 +4762,45 @@ function createUi(o) {
|
|
|
4621
4762
|
const ringFill = ringCircle("tr-ring-fill");
|
|
4622
4763
|
const RING_LENGTH = 2 * Math.PI * 27;
|
|
4623
4764
|
ringFill.style.strokeDasharray = String(RING_LENGTH);
|
|
4624
|
-
const top =
|
|
4625
|
-
const toolBox =
|
|
4626
|
-
const status =
|
|
4627
|
-
const alertBox =
|
|
4628
|
-
const prog =
|
|
4629
|
-
const progLabel =
|
|
4630
|
-
const progBar =
|
|
4631
|
-
const progFill =
|
|
4765
|
+
const top = mk4("div", "tr-top");
|
|
4766
|
+
const toolBox = mk4("div", "tr-tool");
|
|
4767
|
+
const status = mk4("div", "tr-status");
|
|
4768
|
+
const alertBox = mk4("div", "tr-alert");
|
|
4769
|
+
const prog = mk4("div", "tr-prog");
|
|
4770
|
+
const progLabel = mk4("b");
|
|
4771
|
+
const progBar = mk4("div", "tr-bar");
|
|
4772
|
+
const progFill = mk4("i");
|
|
4632
4773
|
progBar.appendChild(progFill);
|
|
4633
4774
|
prog.append(progLabel, progBar);
|
|
4634
4775
|
top.append(toolBox, alertBox, status, prog);
|
|
4635
|
-
const bottom =
|
|
4636
|
-
const held =
|
|
4637
|
-
const hotbar =
|
|
4776
|
+
const bottom = mk4("div", "tr-bottom");
|
|
4777
|
+
const held = mk4("div", "tr-held");
|
|
4778
|
+
const hotbar = mk4("div", "tr-hotbar");
|
|
4638
4779
|
bottom.append(held, hotbar);
|
|
4639
|
-
|
|
4780
|
+
let currentTool = "";
|
|
4781
|
+
const menuOptions = o.toolMenu;
|
|
4782
|
+
const toolMenu = menuOptions ? createToolMenu({ t, groups: menuOptions.groups, current: () => currentTool, onPick: menuOptions.onPick, ...menuOptions.onOpen ? { onOpen: menuOptions.onOpen } : {}, thumb }) : null;
|
|
4783
|
+
if (toolMenu && menuOptions) {
|
|
4784
|
+
const menuButton = mk4("button", "tr-slot tr-menu-btn");
|
|
4785
|
+
menuButton.type = "button";
|
|
4786
|
+
menuButton.append(mk4("b", void 0, menuOptions.keyLabel), document.createTextNode(t("ui.tool_menu")));
|
|
4787
|
+
menuButton.onclick = () => toolMenu.toggle();
|
|
4788
|
+
bottom.appendChild(menuButton);
|
|
4789
|
+
}
|
|
4790
|
+
const help = mk4("div", "tr-help");
|
|
4640
4791
|
help.textContent = t("ui.help");
|
|
4641
|
-
const left =
|
|
4642
|
-
const insp =
|
|
4643
|
-
const logPanel =
|
|
4792
|
+
const left = mk4("div", "tr-left");
|
|
4793
|
+
const insp = mk4("div", "tr-panel tr-insp");
|
|
4794
|
+
const logPanel = mk4("div", "tr-panel tr-log");
|
|
4644
4795
|
const tablePanel = o.testTable ? createTestTablePanel(o.testTable, t) : null;
|
|
4645
4796
|
left.append(insp, ...tablePanel ? [tablePanel.element] : [], logPanel);
|
|
4646
|
-
const logTitle =
|
|
4647
|
-
const logBar =
|
|
4648
|
-
const logBody =
|
|
4797
|
+
const logTitle = mk4("h3", void 0, t("ui.log"));
|
|
4798
|
+
const logBar = mk4("div", "tr-logbar");
|
|
4799
|
+
const logBody = mk4("div", "tr-logbody");
|
|
4649
4800
|
const filters = { action: true, event: true, check: true };
|
|
4650
4801
|
const filterBtns = [];
|
|
4651
4802
|
for (const ch of ["action", "event", "check"]) {
|
|
4652
|
-
const b =
|
|
4803
|
+
const b = mk4("button", "tr-btn on", t(`ui.filter.${ch}`));
|
|
4653
4804
|
b.type = "button";
|
|
4654
4805
|
b.onclick = () => {
|
|
4655
4806
|
filters[ch] = !filters[ch];
|
|
@@ -4660,8 +4811,8 @@ function createUi(o) {
|
|
|
4660
4811
|
logBar.appendChild(b);
|
|
4661
4812
|
}
|
|
4662
4813
|
let autoscroll = true;
|
|
4663
|
-
const asLabel =
|
|
4664
|
-
const asBox =
|
|
4814
|
+
const asLabel = mk4("label");
|
|
4815
|
+
const asBox = mk4("input");
|
|
4665
4816
|
asBox.type = "checkbox";
|
|
4666
4817
|
asBox.checked = true;
|
|
4667
4818
|
asBox.onchange = () => {
|
|
@@ -4670,7 +4821,7 @@ function createUi(o) {
|
|
|
4670
4821
|
asLabel.append(asBox, document.createTextNode(" " + t("ui.autoscroll")));
|
|
4671
4822
|
logBar.appendChild(asLabel);
|
|
4672
4823
|
const logBtn = (label, fn) => {
|
|
4673
|
-
const b =
|
|
4824
|
+
const b = mk4("button", "tr-btn", label);
|
|
4674
4825
|
b.type = "button";
|
|
4675
4826
|
b.onclick = fn;
|
|
4676
4827
|
logBar.appendChild(b);
|
|
@@ -4678,7 +4829,7 @@ function createUi(o) {
|
|
|
4678
4829
|
logBtn(t("ui.copy_json"), () => {
|
|
4679
4830
|
const text = o.log.toJSON();
|
|
4680
4831
|
const select = () => {
|
|
4681
|
-
const ta =
|
|
4832
|
+
const ta = mk4("textarea");
|
|
4682
4833
|
ta.value = text;
|
|
4683
4834
|
ta.setAttribute("readonly", "");
|
|
4684
4835
|
ta.style.cssText = "position:fixed;left:8px;bottom:8px;width:min(480px,80vw);height:120px;z-index:99999";
|
|
@@ -4711,7 +4862,7 @@ function createUi(o) {
|
|
|
4711
4862
|
const fill = (n, e) => {
|
|
4712
4863
|
n.className = lineClass(e);
|
|
4713
4864
|
n.textContent = "";
|
|
4714
|
-
const tm =
|
|
4865
|
+
const tm = mk4("time", void 0, fmtTime(e.t));
|
|
4715
4866
|
n.append(tm, document.createTextNode(e.text));
|
|
4716
4867
|
};
|
|
4717
4868
|
let pendingAdds = [];
|
|
@@ -4719,7 +4870,7 @@ function createUi(o) {
|
|
|
4719
4870
|
let isLogStale = false;
|
|
4720
4871
|
const isLogVisible = () => !window.matchMedia?.("(max-width:980px)").matches || root.classList.contains("open");
|
|
4721
4872
|
const buildNode = (e) => {
|
|
4722
|
-
const n =
|
|
4873
|
+
const n = mk4("div");
|
|
4723
4874
|
fill(n, e);
|
|
4724
4875
|
nodes.set(e.seq, n);
|
|
4725
4876
|
return n;
|
|
@@ -4775,41 +4926,41 @@ function createUi(o) {
|
|
|
4775
4926
|
if (updated) pendingUpdates.set(e.seq, e);
|
|
4776
4927
|
else pendingAdds.push(e);
|
|
4777
4928
|
});
|
|
4778
|
-
const right =
|
|
4779
|
-
const cat =
|
|
4780
|
-
const anim =
|
|
4781
|
-
const tests =
|
|
4929
|
+
const right = mk4("div", "tr-right");
|
|
4930
|
+
const cat = mk4("div", "tr-panel tr-cat");
|
|
4931
|
+
const anim = mk4("div", "tr-panel tr-anim");
|
|
4932
|
+
const tests = mk4("div", "tr-panel tr-tests");
|
|
4782
4933
|
const benchPanel = createBenchPanel({ t, num: (n, d) => num(n, d), onControl: o.onControl, onLayer: o.onLayer, onAction: o.onAction });
|
|
4783
4934
|
right.append(cat, anim, benchPanel.element, tests);
|
|
4784
|
-
cat.appendChild(
|
|
4785
|
-
const itemSearch =
|
|
4935
|
+
cat.appendChild(mk4("h3", void 0, t("ui.catalog")));
|
|
4936
|
+
const itemSearch = mk4("input", "tr-search");
|
|
4786
4937
|
itemSearch.type = "search";
|
|
4787
4938
|
itemSearch.placeholder = t("ui.search_items");
|
|
4788
|
-
const tabs =
|
|
4789
|
-
const items =
|
|
4939
|
+
const tabs = mk4("div", "tr-tabs");
|
|
4940
|
+
const items = mk4("div", "tr-items");
|
|
4790
4941
|
cat.append(itemSearch, tabs, items);
|
|
4791
4942
|
let activeGroup = "";
|
|
4792
4943
|
const visibleItems = () => {
|
|
4793
|
-
const term =
|
|
4944
|
+
const term = fold2(itemSearch.value.trim());
|
|
4794
4945
|
if (!term) return (o.groups().get(activeGroup) ?? []).map((item) => ({ item, group: activeGroup }));
|
|
4795
|
-
return [...o.groups()].flatMap(([group, list2]) => list2.filter((item) =>
|
|
4946
|
+
return [...o.groups()].flatMap(([group, list2]) => list2.filter((item) => fold2(`${item.name} ${item.id} ${group}`).includes(term)).map((item) => ({ item, group })));
|
|
4796
4947
|
};
|
|
4797
4948
|
const renderItems = () => {
|
|
4798
4949
|
items.textContent = "";
|
|
4799
4950
|
const searching = itemSearch.value.trim() !== "";
|
|
4800
4951
|
const found = visibleItems();
|
|
4801
|
-
if (!found.length) items.appendChild(
|
|
4952
|
+
if (!found.length) items.appendChild(mk4("div", "tr-empty", t("ui.no_results")));
|
|
4802
4953
|
for (const { item: it, group } of found) {
|
|
4803
|
-
const wrap =
|
|
4804
|
-
const b =
|
|
4805
|
-
if (it.thumbnail) b.append(thumb(it.thumbnail),
|
|
4806
|
-
if (searching) b.appendChild(
|
|
4954
|
+
const wrap = mk4("div", "tr-item");
|
|
4955
|
+
const b = mk4("button", it.thumbnail ? "tr-btn has-thumb" : "tr-btn", it.thumbnail ? void 0 : it.name);
|
|
4956
|
+
if (it.thumbnail) b.append(thumb(it.thumbnail), mk4("span", "tr-name", it.name));
|
|
4957
|
+
if (searching) b.appendChild(mk4("small", void 0, group));
|
|
4807
4958
|
b.type = "button";
|
|
4808
4959
|
b.title = it.note ?? it.name;
|
|
4809
4960
|
b.onclick = () => o.onSpawn(it.id);
|
|
4810
4961
|
wrap.appendChild(b);
|
|
4811
4962
|
if (it.weapon) {
|
|
4812
|
-
const w =
|
|
4963
|
+
const w = mk4("button", "tr-btn", "\u2694");
|
|
4813
4964
|
w.type = "button";
|
|
4814
4965
|
w.title = t("ui.equip");
|
|
4815
4966
|
w.onclick = () => o.onEquip(it.id);
|
|
@@ -4823,7 +4974,7 @@ function createUi(o) {
|
|
|
4823
4974
|
const groupNames = [...o.groups().keys()];
|
|
4824
4975
|
if (!groupNames.includes(activeGroup)) activeGroup = groupNames[0] ?? "";
|
|
4825
4976
|
for (const g of groupNames) {
|
|
4826
|
-
const b =
|
|
4977
|
+
const b = mk4("button", g === activeGroup ? "tr-btn on" : "tr-btn", g);
|
|
4827
4978
|
b.type = "button";
|
|
4828
4979
|
b.onclick = () => {
|
|
4829
4980
|
activeGroup = g;
|
|
@@ -4836,9 +4987,9 @@ function createUi(o) {
|
|
|
4836
4987
|
renderTabs();
|
|
4837
4988
|
itemSearch.oninput = renderItems;
|
|
4838
4989
|
for (const box2 of [itemSearch]) box2.addEventListener("keydown", (e) => e.stopPropagation());
|
|
4839
|
-
const controls =
|
|
4990
|
+
const controls = mk4("div", "tr-row");
|
|
4840
4991
|
const ctl = (label, fn) => {
|
|
4841
|
-
const b =
|
|
4992
|
+
const b = mk4("button", "tr-btn", label);
|
|
4842
4993
|
b.type = "button";
|
|
4843
4994
|
b.onclick = () => fn(b);
|
|
4844
4995
|
controls.appendChild(b);
|
|
@@ -4869,34 +5020,34 @@ function createUi(o) {
|
|
|
4869
5020
|
const g = ctl(t("ui.graphics"), () => o.onGraphics());
|
|
4870
5021
|
g.dataset.testid = "graphics-button";
|
|
4871
5022
|
} else {
|
|
4872
|
-
const qSel =
|
|
5023
|
+
const qSel = mk4("select");
|
|
4873
5024
|
for (const q of ["low", "medium", "high"]) {
|
|
4874
|
-
const op =
|
|
5025
|
+
const op = mk4("option", void 0, t(`ui.quality.${q}`));
|
|
4875
5026
|
op.value = q;
|
|
4876
5027
|
qSel.appendChild(op);
|
|
4877
5028
|
}
|
|
4878
5029
|
qSel.value = o.quality;
|
|
4879
5030
|
qSel.onchange = () => o.onQuality(qSel.value);
|
|
4880
|
-
const qLabel =
|
|
5031
|
+
const qLabel = mk4("label", void 0, t("ui.quality") + " ");
|
|
4881
5032
|
qLabel.appendChild(qSel);
|
|
4882
5033
|
controls.appendChild(qLabel);
|
|
4883
5034
|
}
|
|
4884
|
-
const count =
|
|
5035
|
+
const count = mk4("span");
|
|
4885
5036
|
controls.appendChild(count);
|
|
4886
5037
|
cat.appendChild(controls);
|
|
4887
|
-
tests.appendChild(
|
|
4888
|
-
const runAll =
|
|
5038
|
+
tests.appendChild(mk4("h3", void 0, t("ui.tests")));
|
|
5039
|
+
const runAll = mk4("button", "tr-btn", t("ui.run_all"));
|
|
4889
5040
|
runAll.type = "button";
|
|
4890
|
-
const summary =
|
|
4891
|
-
const testSearch =
|
|
5041
|
+
const summary = mk4("div", "tr-summary");
|
|
5042
|
+
const testSearch = mk4("input", "tr-search");
|
|
4892
5043
|
testSearch.type = "search";
|
|
4893
5044
|
testSearch.placeholder = t("ui.search_tests");
|
|
4894
5045
|
testSearch.addEventListener("keydown", (e) => e.stopPropagation());
|
|
4895
|
-
const list =
|
|
5046
|
+
const list = mk4("div", "list");
|
|
4896
5047
|
tests.append(runAll, summary, testSearch, list);
|
|
4897
5048
|
testSearch.oninput = () => {
|
|
4898
|
-
const term =
|
|
4899
|
-
for (const b of scBtns.values()) b.style.display = !term ||
|
|
5049
|
+
const term = fold2(testSearch.value.trim());
|
|
5050
|
+
for (const b of scBtns.values()) b.style.display = !term || fold2(b.textContent ?? "").includes(term) ? "" : "none";
|
|
4900
5051
|
};
|
|
4901
5052
|
const scBtns = /* @__PURE__ */ new Map();
|
|
4902
5053
|
let busy = false;
|
|
@@ -4915,7 +5066,7 @@ function createUi(o) {
|
|
|
4915
5066
|
list.textContent = "";
|
|
4916
5067
|
scBtns.clear();
|
|
4917
5068
|
for (const s of o.scenarios()) {
|
|
4918
|
-
const b =
|
|
5069
|
+
const b = mk4("button", "tr-btn", s.name);
|
|
4919
5070
|
b.type = "button";
|
|
4920
5071
|
b.onclick = async () => {
|
|
4921
5072
|
if (busy) return;
|
|
@@ -4941,7 +5092,7 @@ function createUi(o) {
|
|
|
4941
5092
|
busy = false;
|
|
4942
5093
|
}
|
|
4943
5094
|
};
|
|
4944
|
-
const toggle =
|
|
5095
|
+
const toggle = mk4("button", "tr-btn tr-toggle", t("ui.panel"));
|
|
4945
5096
|
toggle.type = "button";
|
|
4946
5097
|
const togglePanels = () => {
|
|
4947
5098
|
root.classList.toggle("open");
|
|
@@ -4949,17 +5100,17 @@ function createUi(o) {
|
|
|
4949
5100
|
o.onResize();
|
|
4950
5101
|
};
|
|
4951
5102
|
toggle.onclick = togglePanels;
|
|
4952
|
-
root.append(cross, ring, top, bottom, help, left, right, toggle);
|
|
5103
|
+
root.append(cross, ring, top, bottom, help, left, right, toggle, ...toolMenu ? [toolMenu.element] : []);
|
|
4953
5104
|
const bar = (value, max, cls) => {
|
|
4954
|
-
const b =
|
|
4955
|
-
const i =
|
|
5105
|
+
const b = mk4("div", `tr-bar ${cls}`);
|
|
5106
|
+
const i = mk4("i");
|
|
4956
5107
|
i.style.width = `${Math.max(0, Math.min(100, value / Math.max(1e-9, max) * 100))}%`;
|
|
4957
5108
|
b.appendChild(i);
|
|
4958
5109
|
return b;
|
|
4959
5110
|
};
|
|
4960
5111
|
const row = (k, v) => {
|
|
4961
|
-
const r =
|
|
4962
|
-
r.append(
|
|
5112
|
+
const r = mk4("div", "r");
|
|
5113
|
+
r.append(mk4("span", void 0, k), mk4("span", void 0, v));
|
|
4963
5114
|
return r;
|
|
4964
5115
|
};
|
|
4965
5116
|
const num = (n, d = 1) => {
|
|
@@ -4968,14 +5119,14 @@ function createUi(o) {
|
|
|
4968
5119
|
};
|
|
4969
5120
|
const renderInspector = (i) => {
|
|
4970
5121
|
insp.textContent = "";
|
|
4971
|
-
insp.appendChild(
|
|
5122
|
+
insp.appendChild(mk4("h3", void 0, t("ui.inspector")));
|
|
4972
5123
|
if (!i) {
|
|
4973
|
-
insp.appendChild(
|
|
5124
|
+
insp.appendChild(mk4("div", void 0, t("ui.aim_at_something")));
|
|
4974
5125
|
return;
|
|
4975
5126
|
}
|
|
4976
5127
|
if (i.ground) {
|
|
4977
5128
|
const g = i.ground;
|
|
4978
|
-
insp.appendChild(
|
|
5129
|
+
insp.appendChild(mk4("div", "t", `${i.object} \u203A ${g.surface}`));
|
|
4979
5130
|
insp.appendChild(row(t("ins.material"), g.material));
|
|
4980
5131
|
insp.appendChild(row(t("ins.dug"), `${num(g.dug * 100, 0)} cm`));
|
|
4981
5132
|
insp.appendChild(row(t("ins.dig_limit"), `${num(g.remaining, 2)} m`));
|
|
@@ -4983,7 +5134,7 @@ function createUi(o) {
|
|
|
4983
5134
|
return;
|
|
4984
5135
|
}
|
|
4985
5136
|
if (i.thumbnail) insp.appendChild(thumb(i.thumbnail, i.thumbnailCredit));
|
|
4986
|
-
insp.appendChild(
|
|
5137
|
+
insp.appendChild(mk4("div", "t", `${i.object} \u203A ${i.part}`));
|
|
4987
5138
|
insp.appendChild(row(t("ins.material"), i.material));
|
|
4988
5139
|
insp.appendChild(row(t("ins.status"), i.status + (i.burning ? ` \xB7 ${t("ins.burning")}` : "")));
|
|
4989
5140
|
insp.appendChild(row(t("ins.hp"), `${num(i.hp, 0)} / ${num(i.maxHp, 0)}`));
|
|
@@ -5002,30 +5153,30 @@ function createUi(o) {
|
|
|
5002
5153
|
insp.appendChild(bar(Math.min(i.stress, 1.2), 1.2, i.stress > 1 ? "bad" : i.stress > 0.7 ? "warn" : "hp"));
|
|
5003
5154
|
}
|
|
5004
5155
|
if (i.joints.length) {
|
|
5005
|
-
insp.appendChild(
|
|
5156
|
+
insp.appendChild(mk4("div", void 0, t("ins.joints") + ":"));
|
|
5006
5157
|
for (const j of i.joints) {
|
|
5007
5158
|
const tools = j.tools.length ? j.tools.join(", ") : t("tool.none");
|
|
5008
|
-
insp.appendChild(
|
|
5159
|
+
insp.appendChild(mk4("div", void 0, `\u2022 ${j.kind} (${j.id})${j.open ? ` \xB7 ${t("ins.open")}` : ` \u2192 ${tools}`}`));
|
|
5009
5160
|
}
|
|
5010
5161
|
}
|
|
5011
|
-
if (i.note) insp.appendChild(
|
|
5162
|
+
if (i.note) insp.appendChild(mk4("div", void 0, i.note));
|
|
5012
5163
|
};
|
|
5013
5164
|
let lastAnim = "";
|
|
5014
5165
|
const renderAnimations = (a) => {
|
|
5015
5166
|
anim.textContent = "";
|
|
5016
|
-
anim.appendChild(
|
|
5167
|
+
anim.appendChild(mk4("h3", void 0, t("ui.animations")));
|
|
5017
5168
|
if (!a) {
|
|
5018
|
-
anim.appendChild(
|
|
5169
|
+
anim.appendChild(mk4("div", "sub", t("ui.animations_aim")));
|
|
5019
5170
|
return;
|
|
5020
5171
|
}
|
|
5021
|
-
anim.appendChild(
|
|
5172
|
+
anim.appendChild(mk4("div", "sub", a.name));
|
|
5022
5173
|
if (a.clips.length === 0) {
|
|
5023
|
-
anim.appendChild(
|
|
5174
|
+
anim.appendChild(mk4("div", "sub", t("ui.animations_none")));
|
|
5024
5175
|
return;
|
|
5025
5176
|
}
|
|
5026
|
-
const l =
|
|
5177
|
+
const l = mk4("div", "list");
|
|
5027
5178
|
for (const c of a.clips) {
|
|
5028
|
-
const b =
|
|
5179
|
+
const b = mk4("button", a.playing === c.id ? "tr-btn on" : "tr-btn", `\u25B6 ${t(`anim.${c.id}`) === `anim.${c.id}` ? c.id : t(`anim.${c.id}`)} (${num(c.duration, 1)} s)`);
|
|
5029
5180
|
b.type = "button";
|
|
5030
5181
|
b.onclick = () => o.onPlay(a.objectId, c.id);
|
|
5031
5182
|
l.appendChild(b);
|
|
@@ -5045,10 +5196,11 @@ function createUi(o) {
|
|
|
5045
5196
|
},
|
|
5046
5197
|
flushLog,
|
|
5047
5198
|
hud(s) {
|
|
5199
|
+
currentTool = s.toolId;
|
|
5048
5200
|
toolBox.textContent = "";
|
|
5049
5201
|
toolBox.append(document.createTextNode(s.toolName));
|
|
5050
|
-
if (s.ammo) toolBox.append(
|
|
5051
|
-
if (s.camera) toolBox.append(
|
|
5202
|
+
if (s.ammo) toolBox.append(mk4("small", void 0, s.ammo));
|
|
5203
|
+
if (s.camera) toolBox.append(mk4("small", void 0, t(`camera.${s.camera}`)));
|
|
5052
5204
|
const helpText = s.camera ? `${t("ui.help")} \xB7 ${t("ui.help.camera")}` : t("ui.help");
|
|
5053
5205
|
if (help.textContent !== helpText) help.textContent = helpText;
|
|
5054
5206
|
if (s.status) status.textContent = s.status;
|
|
@@ -5071,7 +5223,7 @@ function createUi(o) {
|
|
|
5071
5223
|
];
|
|
5072
5224
|
if (s.held.weapon) keys.push(["H", t("hud.equip", { weapon: s.held.weapon })]);
|
|
5073
5225
|
for (const [k, label] of keys) {
|
|
5074
|
-
held.append(
|
|
5226
|
+
held.append(mk4("kbd", void 0, k), document.createTextNode(` ${label} `));
|
|
5075
5227
|
}
|
|
5076
5228
|
} else held.style.display = "none";
|
|
5077
5229
|
const key2 = s.slots.join("|") + s.page;
|
|
@@ -5081,9 +5233,9 @@ function createUi(o) {
|
|
|
5081
5233
|
slotsEl.length = 0;
|
|
5082
5234
|
hudPage = s.page - 1;
|
|
5083
5235
|
s.slots.forEach((name, i) => {
|
|
5084
|
-
const b =
|
|
5236
|
+
const b = mk4("button", "tr-slot");
|
|
5085
5237
|
b.type = "button";
|
|
5086
|
-
b.append(
|
|
5238
|
+
b.append(mk4("b", void 0, String((i + 1) % 10)), document.createTextNode(name));
|
|
5087
5239
|
b.title = name;
|
|
5088
5240
|
b.onclick = () => o.onTool(hudPage * 10 + i);
|
|
5089
5241
|
hotbar.appendChild(b);
|
|
@@ -5128,6 +5280,7 @@ function createUi(o) {
|
|
|
5128
5280
|
renderTabs();
|
|
5129
5281
|
},
|
|
5130
5282
|
togglePanels,
|
|
5283
|
+
toolMenu: (open) => toolMenu?.toggle(open) ?? false,
|
|
5131
5284
|
bench: benchPanel.show,
|
|
5132
5285
|
metrics: benchPanel.metrics,
|
|
5133
5286
|
get benchVisible() {
|
|
@@ -5274,6 +5427,7 @@ function createModuleViews(THREE, gl) {
|
|
|
5274
5427
|
|
|
5275
5428
|
// src/room.ts
|
|
5276
5429
|
var FIXED_DT = 1 / 60;
|
|
5430
|
+
var TOOL_MENU_KEY_LABELS = { Backquote: "'" };
|
|
5277
5431
|
var EXPLOSIVE_EFFECT_IDS = ["m67", "molotov", "pipe_bomb", "m79_40mm", "m72_66mm", "at4_84mm", "dynamite", "c4", "claymore"];
|
|
5278
5432
|
var DEFAULT_PARTICLE_LIMIT = 900;
|
|
5279
5433
|
var WARMED_FOG_STATES = ["none", "linear", "exp2"];
|
|
@@ -5288,6 +5442,8 @@ var KICK_MASS = 6;
|
|
|
5288
5442
|
var KICK_HEIGHT = 0.6;
|
|
5289
5443
|
var GROUND_ID = "room_ground";
|
|
5290
5444
|
var TERRAIN_CELL = 0.1;
|
|
5445
|
+
var GROUND_SPLASH_MAX_LITRES = 3;
|
|
5446
|
+
var WATER_ENTRY_SPEED = 1.5;
|
|
5291
5447
|
var TERRAIN_MAX_CELLS = 1e3;
|
|
5292
5448
|
var GROUND_BURST_MAX = 28;
|
|
5293
5449
|
var GROUND_BURST_PER_METRE = 30;
|
|
@@ -5379,7 +5535,7 @@ function createTestRoom(options) {
|
|
|
5379
5535
|
const restingTops = [];
|
|
5380
5536
|
const restingTopAt = (x, z) => {
|
|
5381
5537
|
let top = 0;
|
|
5382
|
-
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);
|
|
5538
|
+
for (const c of restingTops) if (x > c.minX && x < c.maxX && z > c.minZ && z < c.maxZ && world.has(c.objectId) && !renderer.piece(c.objectId)) top = Math.max(top, c.top);
|
|
5383
5539
|
return top;
|
|
5384
5540
|
};
|
|
5385
5541
|
const sceneryTopAt = (x, z) => {
|
|
@@ -5422,13 +5578,14 @@ function createTestRoom(options) {
|
|
|
5422
5578
|
const preload = () => {
|
|
5423
5579
|
window.removeEventListener("keydown", preload, true);
|
|
5424
5580
|
window.removeEventListener("pointerdown", preload, true);
|
|
5425
|
-
void audio.loadSamples([
|
|
5581
|
+
void audio.loadSamples([...HAND_RECORDED_IDS, ...impactSoundIds()]).catch(() => void 0);
|
|
5426
5582
|
};
|
|
5427
5583
|
window.addEventListener("keydown", preload, true);
|
|
5428
5584
|
window.addEventListener("pointerdown", preload, true);
|
|
5429
5585
|
});
|
|
5430
5586
|
}
|
|
5431
5587
|
const bodySounds = createBodySounds(audio);
|
|
5588
|
+
if (audio && options.fittedSounds !== false) registerFittedSounds(audio);
|
|
5432
5589
|
const playSound = (key2, point, volume, detail) => {
|
|
5433
5590
|
destructionSound?.onSound(key2, point, volume, detail);
|
|
5434
5591
|
if (detail) options.onSound?.(key2, point, volume, detail);
|
|
@@ -5447,7 +5604,8 @@ function createTestRoom(options) {
|
|
|
5447
5604
|
vfx: createVfx({
|
|
5448
5605
|
profile: quality,
|
|
5449
5606
|
...options.debris?.particles ? { sprites: { kinds: patchSpriteKinds(SPRITE_KINDS, options.debris.particles) } } : {},
|
|
5450
|
-
groundAt
|
|
5607
|
+
groundAt,
|
|
5608
|
+
marklessAt: (x, z) => terrain.waterAt(x, z) !== null
|
|
5451
5609
|
})
|
|
5452
5610
|
});
|
|
5453
5611
|
const renderer = createDestructionRenderer(THREE, {
|
|
@@ -5500,8 +5658,11 @@ function createTestRoom(options) {
|
|
|
5500
5658
|
const material = terrain.material(result.removed[0]?.material ?? result.exposed);
|
|
5501
5659
|
const def = world.materials.get(material.id);
|
|
5502
5660
|
const size = Math.min(GROUND_BURST_MAX, Math.ceil(result.radius * GROUND_BURST_PER_METRE));
|
|
5503
|
-
renderer.emit(material.debris === "clods" ? "dust" : material.debris, result.point, [0, 1, 0], Math.max(2, size), Math.min(4, 1 + result.radius * 4));
|
|
5661
|
+
renderer.emit(material.debris === "clods" ? "dust" : material.debris === "blocks" ? "chips" : material.debris, result.point, [0, 1, 0], Math.max(2, size), Math.min(4, 1 + result.radius * 4));
|
|
5504
5662
|
if (result.radius > GROUND_DUST_RADIUS) renderer.emit("dust", result.point, [0, 1, 0], size, 1.4);
|
|
5663
|
+
const litres = Math.min(GROUND_SPLASH_MAX_LITRES, 0.1 + result.radius * 2);
|
|
5664
|
+
if (result.water) renderer.emit("foam", [result.point[0], result.water.level + 0.05, result.point[2]], [0, 1, 0], Math.max(6, size * 2), 2 + result.radius * 4);
|
|
5665
|
+
else if (material.splash) fluids.splash(material.splash, [result.point[0], result.point[1] + 0.05, result.point[2]], litres, [0, 1.5 + result.radius * 3, 0]);
|
|
5505
5666
|
playSound(impactKey(def), result.point, Math.min(1, 0.25 + result.radius), impactSoundKeys(material.id, def));
|
|
5506
5667
|
};
|
|
5507
5668
|
terrain.onDeform((region) => renderer.wakeRegion(region.x, region.z, region.radius));
|
|
@@ -5535,7 +5696,7 @@ function createTestRoom(options) {
|
|
|
5535
5696
|
const specs = /* @__PURE__ */ new Map();
|
|
5536
5697
|
const loose = [];
|
|
5537
5698
|
const arsenal = options.arsenal ?? createArsenal({ locale, weapons: options.weapons, seed: 1 });
|
|
5538
|
-
const loadout = (options.loadout ?? DEFAULT_LOADOUT).filter((id) => arsenal.weapons.has(id));
|
|
5699
|
+
const loadout = (options.loadout ?? realLoadout(arsenal, DEFAULT_LOADOUT)).filter((id) => arsenal.weapons.has(id));
|
|
5539
5700
|
const tools = /* @__PURE__ */ new Map();
|
|
5540
5701
|
let toolIndex = Math.max(0, loadout.indexOf("hand"));
|
|
5541
5702
|
let toolId = loadout[toolIndex] ?? "hand";
|
|
@@ -5739,7 +5900,8 @@ function createTestRoom(options) {
|
|
|
5739
5900
|
footing: (x, z, radius, feet) => footing(x, z, radius, feet),
|
|
5740
5901
|
blockers,
|
|
5741
5902
|
limit: arenaSize,
|
|
5742
|
-
floorAt: terrain.
|
|
5903
|
+
floorAt: terrain.footAt,
|
|
5904
|
+
speedAt: terrain.walkFactorAt,
|
|
5743
5905
|
start: arena.playerStart,
|
|
5744
5906
|
crouchKeys: options.crouch?.keys,
|
|
5745
5907
|
onSlam: (event) => slamPlayer(event),
|
|
@@ -6336,13 +6498,50 @@ function createTestRoom(options) {
|
|
|
6336
6498
|
const playerMass = options.playerMass ?? bodyMass(1.75);
|
|
6337
6499
|
const SLAM_REACH = PLAYER_RADIUS + 0.35;
|
|
6338
6500
|
const SLAM_SHOULDER = 1.3;
|
|
6501
|
+
const SLAM_PROBE = PLAYER_RADIUS / 3 + 0.03;
|
|
6502
|
+
const SLAM_INSET = 0.05;
|
|
6503
|
+
const SLAM_SWEEP = 0.2;
|
|
6504
|
+
const slamHit = (feet, direction) => {
|
|
6505
|
+
const [x, y, z] = feet;
|
|
6506
|
+
const [dx, , dz] = direction;
|
|
6507
|
+
const height2 = y + ((player.knocked?.lying ?? 0) > 0.5 ? 0.3 : SLAM_SHOULDER);
|
|
6508
|
+
let nearest = null;
|
|
6509
|
+
for (const e of entries.values()) {
|
|
6510
|
+
const box3 = inTheWay(e, x + dx * SLAM_PROBE, z + dz * SLAM_PROBE, PLAYER_RADIUS);
|
|
6511
|
+
if (!box3 || box3.top - box3.base < STEP_OVER) continue;
|
|
6512
|
+
const [enterX, exitX] = slab(x, dx, box3.minX, box3.maxX);
|
|
6513
|
+
const [enterZ, exitZ] = slab(z, dz, box3.minZ, box3.maxZ);
|
|
6514
|
+
const entry = Math.max(0, enterX, enterZ);
|
|
6515
|
+
const exit2 = Math.min(exitX, exitZ);
|
|
6516
|
+
if (exit2 < entry || nearest && entry >= nearest.entry) continue;
|
|
6517
|
+
nearest = { box: box3, exit: exit2, entry };
|
|
6518
|
+
}
|
|
6519
|
+
if (!nearest) return rayWorld([x, height2, z], direction, SLAM_REACH);
|
|
6520
|
+
const { box: box2, exit } = nearest;
|
|
6521
|
+
const reach = Math.max(SLAM_REACH, exit);
|
|
6522
|
+
const low = box2.base + SLAM_INSET;
|
|
6523
|
+
const high = Math.max(low, box2.top - SLAM_INSET);
|
|
6524
|
+
const first = Math.min(Math.max(height2, low), high);
|
|
6525
|
+
const hit = rayWorld([x, first, z], direction, reach);
|
|
6526
|
+
if (hit) return hit;
|
|
6527
|
+
for (let h = low; h <= high; h += SLAM_SWEEP) {
|
|
6528
|
+
const swept = rayWorld([x, h, z], direction, reach);
|
|
6529
|
+
if (swept) return swept;
|
|
6530
|
+
}
|
|
6531
|
+
return null;
|
|
6532
|
+
};
|
|
6533
|
+
const slab = (origin, step2, min, max) => {
|
|
6534
|
+
if (Math.abs(step2) < 1e-9) return origin > min && origin < max ? [-Infinity, Infinity] : [Infinity, -Infinity];
|
|
6535
|
+
const a = (min - origin) / step2;
|
|
6536
|
+
const b = (max - origin) / step2;
|
|
6537
|
+
return a < b ? [a, b] : [b, a];
|
|
6538
|
+
};
|
|
6339
6539
|
const slamPlayer = (event) => {
|
|
6340
6540
|
const [x, feet, z] = player.position;
|
|
6341
6541
|
const energy = 0.5 * playerMass * event.speed * event.speed;
|
|
6342
6542
|
let hit = null;
|
|
6343
6543
|
if (event.against === "obstacle") {
|
|
6344
|
-
|
|
6345
|
-
hit = rayWorld(from, event.direction, SLAM_REACH);
|
|
6544
|
+
hit = slamHit([x, feet, z], event.direction);
|
|
6346
6545
|
if (hit) world.hitPart(hit.object, hit.part, { type: "blunt", point: hit.point, direction: event.direction, energy, radius: 0.2, ...hit.normal ? { normal: hit.normal } : {} });
|
|
6347
6546
|
}
|
|
6348
6547
|
bodySounds.slam(event.speed, [x, feet + 0.5, z]);
|
|
@@ -6477,6 +6676,7 @@ function createTestRoom(options) {
|
|
|
6477
6676
|
camera.updateMatrixWorld(true);
|
|
6478
6677
|
};
|
|
6479
6678
|
let handSource = null;
|
|
6679
|
+
let muzzleSource = null;
|
|
6480
6680
|
let debugView = null;
|
|
6481
6681
|
let lastHand = { stance: "relaxed", triggerFinger: null, targets: null };
|
|
6482
6682
|
const strikeClips = options.strikeClips ?? DEFAULT_STRIKE_CLIPS;
|
|
@@ -6600,6 +6800,10 @@ function createTestRoom(options) {
|
|
|
6600
6800
|
rng = rng * 1664525 + 1013904223 >>> 0;
|
|
6601
6801
|
return rng / 4294967296;
|
|
6602
6802
|
};
|
|
6803
|
+
const muzzlePose = () => {
|
|
6804
|
+
const lent = muzzleSource?.();
|
|
6805
|
+
return lent ? { position: [...lent.position], drawsFlash: lent.drawsFlash } : { position: muzzle(), drawsFlash: false };
|
|
6806
|
+
};
|
|
6603
6807
|
const muzzle = () => {
|
|
6604
6808
|
aim();
|
|
6605
6809
|
const e = camera.matrixWorld.elements;
|
|
@@ -6788,9 +6992,11 @@ function createTestRoom(options) {
|
|
|
6788
6992
|
const fireShots = (shots, recoil) => {
|
|
6789
6993
|
if (shots.length === 0) return;
|
|
6790
6994
|
const w = toolName();
|
|
6791
|
-
const m =
|
|
6792
|
-
|
|
6793
|
-
|
|
6995
|
+
const { position: m, drawsFlash } = muzzlePose();
|
|
6996
|
+
if (!drawsFlash) {
|
|
6997
|
+
renderer.emit("flash", m, [aimD[0], aimD[1], aimD[2]], 1, 0);
|
|
6998
|
+
renderer.emit("sparks", m, [aimD[0], aimD[1], aimD[2]], 4, 6);
|
|
6999
|
+
}
|
|
6794
7000
|
let hits = 0;
|
|
6795
7001
|
let first = null;
|
|
6796
7002
|
let energy = 0;
|
|
@@ -6854,7 +7060,7 @@ function createTestRoom(options) {
|
|
|
6854
7060
|
const cone = u.kind === "flame" ? u.cone : false;
|
|
6855
7061
|
const rays = fireRays({ origin: u.origin, direction: u.direction, range: u.range, cone, radius: u.radius });
|
|
6856
7062
|
let target = null;
|
|
6857
|
-
const m =
|
|
7063
|
+
const m = muzzlePose().position;
|
|
6858
7064
|
withoutHeld(() => {
|
|
6859
7065
|
for (const d of rays) {
|
|
6860
7066
|
const r = world.raycast(u.origin, d, u.range);
|
|
@@ -7036,7 +7242,7 @@ function createTestRoom(options) {
|
|
|
7036
7242
|
const extinguishUse = (u) => {
|
|
7037
7243
|
const w = toolName();
|
|
7038
7244
|
const rays = fireRays({ origin: u.origin, direction: u.direction, range: u.range, cone: true, radius: 0.6 });
|
|
7039
|
-
const m =
|
|
7245
|
+
const m = muzzlePose().position;
|
|
7040
7246
|
renderer.emit("foam", m, [u.direction[0], u.direction[1], u.direction[2]], 8, u.range * 1.2);
|
|
7041
7247
|
const hit = /* @__PURE__ */ new Set();
|
|
7042
7248
|
withoutHeld(() => {
|
|
@@ -7181,6 +7387,24 @@ function createTestRoom(options) {
|
|
|
7181
7387
|
return true;
|
|
7182
7388
|
};
|
|
7183
7389
|
const kickKeys = options.kick?.keys ?? ["KeyK"];
|
|
7390
|
+
const toolMenuKeys = options.toolMenu === false ? [] : options.toolMenu?.keys ?? ["Backquote"];
|
|
7391
|
+
const toolMenuGroups = () => {
|
|
7392
|
+
const thumbnails = /* @__PURE__ */ new Map();
|
|
7393
|
+
for (const item of catalog) if (item.weapon && item.thumbnail && !thumbnails.has(item.weapon)) thumbnails.set(item.weapon, item);
|
|
7394
|
+
return weaponMenu(arsenal, { locale }).map((group) => ({
|
|
7395
|
+
id: group.id,
|
|
7396
|
+
name: group.name,
|
|
7397
|
+
sections: group.sections.map((section) => ({
|
|
7398
|
+
id: section.id,
|
|
7399
|
+
name: section.name,
|
|
7400
|
+
tools: section.weapons.map((weapon) => {
|
|
7401
|
+
const item = thumbnails.get(weapon.id);
|
|
7402
|
+
return { id: weapon.id, name: weapon.name, ...weapon.maker || weapon.tags ? { keywords: [weapon.maker, ...weapon.tags ?? []].filter(Boolean).join(" ") } : {}, ...item?.thumbnail ? { thumbnail: item.thumbnail } : {}, ...item?.thumbnailCredit ? { thumbnailCredit: item.thumbnailCredit } : {} };
|
|
7403
|
+
})
|
|
7404
|
+
}))
|
|
7405
|
+
}));
|
|
7406
|
+
};
|
|
7407
|
+
const toggleToolMenu = (open) => ui?.toolMenu(open) ?? false;
|
|
7184
7408
|
const setTool = (id) => {
|
|
7185
7409
|
if (!arsenal.weapons.has(id)) throw new Error(`game-test-room: unknown weapon "${id}"`);
|
|
7186
7410
|
releaseTrigger();
|
|
@@ -7276,7 +7500,6 @@ function createTestRoom(options) {
|
|
|
7276
7500
|
heldVel[0] = heldVel[1] = heldVel[2] = 0;
|
|
7277
7501
|
playSound(pickupSoundOf(heldEntry?.item?.sounds, world.materials.get(heaviestMaterial(r.object) ?? "")), r.point, Math.min(1, 0.45 + kg * 0.1));
|
|
7278
7502
|
action("grab", t("act.grab", { name: heldName, kg: num(kg, 1) }), { objectId: r.object, itemId: entryOf(r.object)?.so.itemId, data: { massKg: Math.round(kg * 10) / 10 } });
|
|
7279
|
-
playSound(impactKey(world.materials.get(heaviestMaterial(r.object) ?? "")).replace("impact_", "grab_"), v3(piece.root.getWorldPosition(tmpV)), 0.8);
|
|
7280
7503
|
fire("grab", { held: true, objectId: r.object });
|
|
7281
7504
|
return true;
|
|
7282
7505
|
};
|
|
@@ -7581,6 +7804,38 @@ function createTestRoom(options) {
|
|
|
7581
7804
|
land(p);
|
|
7582
7805
|
}
|
|
7583
7806
|
};
|
|
7807
|
+
const inWater = /* @__PURE__ */ new Set();
|
|
7808
|
+
const floatPiece = (piece, dt) => {
|
|
7809
|
+
if (piece.held) return;
|
|
7810
|
+
const box2 = tmpBox.setFromObject(piece.root);
|
|
7811
|
+
const centre = box2.getCenter(tmpV);
|
|
7812
|
+
const here = terrain.waterAt(centre.x, centre.z);
|
|
7813
|
+
if (!here || box2.min.y >= here.level) {
|
|
7814
|
+
inWater.delete(piece.objectId);
|
|
7815
|
+
return;
|
|
7816
|
+
}
|
|
7817
|
+
const size = box2.getSize(tmpV2);
|
|
7818
|
+
const volume = piece.halfExtents[0] * piece.halfExtents[1] * piece.halfExtents[2] * 8;
|
|
7819
|
+
if (!inWater.has(piece.objectId)) {
|
|
7820
|
+
inWater.add(piece.objectId);
|
|
7821
|
+
const speed = -piece.velocity[1];
|
|
7822
|
+
if (speed > WATER_ENTRY_SPEED) {
|
|
7823
|
+
renderer.emit("foam", [centre.x, here.level + 0.05, centre.z], [0, 1, 0], Math.min(GROUND_BURST_MAX, Math.ceil(piece.mass)), speed * 0.5);
|
|
7824
|
+
terrain.ripple(centre.x, centre.z, Math.max(size.x, size.z));
|
|
7825
|
+
}
|
|
7826
|
+
}
|
|
7827
|
+
const lift = waterLift({ mass: piece.mass, volume, submerged: (here.level - box2.min.y) / Math.max(1e-3, size.y) });
|
|
7828
|
+
if (piece.sleeping && !(lift.floats && lift.acceleration > 9.81 * 1.1)) return;
|
|
7829
|
+
piece.sleeping = false;
|
|
7830
|
+
const keep = Math.exp(-lift.drag * dt);
|
|
7831
|
+
piece.velocity = [piece.velocity[0] * keep, (piece.velocity[1] + lift.acceleration * dt) * keep, piece.velocity[2] * keep];
|
|
7832
|
+
piece.angularVelocity = [piece.angularVelocity[0] * keep, piece.angularVelocity[1] * keep, piece.angularVelocity[2] * keep];
|
|
7833
|
+
};
|
|
7834
|
+
const updateWater = (dt) => {
|
|
7835
|
+
for (const e of entries.values()) if (e.piece) floatPiece(e.piece, dt);
|
|
7836
|
+
for (const p of loose) floatPiece(p, dt);
|
|
7837
|
+
};
|
|
7838
|
+
const footprints = createFootprints(terrain);
|
|
7584
7839
|
const prevVy = /* @__PURE__ */ new Map();
|
|
7585
7840
|
const tmpBox = new THREE.Box3();
|
|
7586
7841
|
const onKey = (code, down, e) => {
|
|
@@ -7600,6 +7855,10 @@ function createTestRoom(options) {
|
|
|
7600
7855
|
if (idx < loadout.length) setTool(loadout[idx]);
|
|
7601
7856
|
return;
|
|
7602
7857
|
}
|
|
7858
|
+
if (toolMenuKeys.includes(code) && !e.ctrlKey && !e.metaKey && !e.repeat) {
|
|
7859
|
+
toggleToolMenu();
|
|
7860
|
+
return;
|
|
7861
|
+
}
|
|
7603
7862
|
if (kickKeys.includes(code) && !e.ctrlKey && !e.metaKey) {
|
|
7604
7863
|
kick();
|
|
7605
7864
|
return;
|
|
@@ -7874,6 +8133,8 @@ function createTestRoom(options) {
|
|
|
7874
8133
|
showGroundFire(dt);
|
|
7875
8134
|
vfx.update(dt);
|
|
7876
8135
|
updateImpacts();
|
|
8136
|
+
updateWater(dt);
|
|
8137
|
+
if (player.position[1] <= terrain.footAt(player.position[0], player.position[2]) + 0.15) footprints.update(player.position[0], player.position[2]);
|
|
7877
8138
|
fluidView.update(dt);
|
|
7878
8139
|
filmBridge.update(dt);
|
|
7879
8140
|
film.update(dt);
|
|
@@ -8236,7 +8497,7 @@ function createTestRoom(options) {
|
|
|
8236
8497
|
crouching: false,
|
|
8237
8498
|
speed: player.speed,
|
|
8238
8499
|
running: player.running,
|
|
8239
|
-
cameraMode
|
|
8500
|
+
cameraMode
|
|
8240
8501
|
}),
|
|
8241
8502
|
configureTool(settings) {
|
|
8242
8503
|
const condition = settings[SETTING_IDS.condition];
|
|
@@ -8273,6 +8534,12 @@ function createTestRoom(options) {
|
|
|
8273
8534
|
if (handSource === source) handSource = null;
|
|
8274
8535
|
};
|
|
8275
8536
|
},
|
|
8537
|
+
setMuzzleSource(source) {
|
|
8538
|
+
muzzleSource = source;
|
|
8539
|
+
return () => {
|
|
8540
|
+
if (muzzleSource === source) muzzleSource = null;
|
|
8541
|
+
};
|
|
8542
|
+
},
|
|
8276
8543
|
log(text, data, type) {
|
|
8277
8544
|
action(type ?? "module_note", text, { data });
|
|
8278
8545
|
},
|
|
@@ -8335,6 +8602,10 @@ function createTestRoom(options) {
|
|
|
8335
8602
|
warmup.register(probeOf("vfx", vfx));
|
|
8336
8603
|
warmup.register(probeOf("fluids", fluidView));
|
|
8337
8604
|
warmup.register(spriteProbes(THREE));
|
|
8605
|
+
for (const object of arena.objects) {
|
|
8606
|
+
const probes = object.warmUpProbes;
|
|
8607
|
+
if (probes) warmup.register(probeOf(`scenery:${object.id}`, { warmUpProbes: () => probes(THREE) }));
|
|
8608
|
+
}
|
|
8338
8609
|
const warmedEffect = (id) => {
|
|
8339
8610
|
let handle = null;
|
|
8340
8611
|
return {
|
|
@@ -8543,7 +8814,7 @@ function createTestRoom(options) {
|
|
|
8543
8814
|
const openArea = (area) => {
|
|
8544
8815
|
clear();
|
|
8545
8816
|
const mod = [...modules.values()].find((m) => m.id === area || m.group === area);
|
|
8546
|
-
|
|
8817
|
+
terrain.setLayout(mod?.ground ?? { surface: DEFAULT_PROFILE });
|
|
8547
8818
|
const label = mod?.group ?? S.groups[area] ?? area;
|
|
8548
8819
|
ui?.showGroup(label);
|
|
8549
8820
|
activeBench = mod && benches.has(mod.id) ? mod.id : lastBench();
|
|
@@ -8722,6 +8993,7 @@ function createTestRoom(options) {
|
|
|
8722
8993
|
return combo ? comboOn : null;
|
|
8723
8994
|
},
|
|
8724
8995
|
equip,
|
|
8996
|
+
toggleToolMenu,
|
|
8725
8997
|
terrain,
|
|
8726
8998
|
openArea,
|
|
8727
8999
|
modules: () => [...modules.values()].map((m) => ({ id: m.id, name: m.name, group: m.group, hasArea: typeof m.area === "function" })),
|
|
@@ -8929,7 +9201,18 @@ function createTestRoom(options) {
|
|
|
8929
9201
|
onAction: (id) => {
|
|
8930
9202
|
if (activeBench) slowly(activeBench, id, () => benches.get(activeBench)?.act(id));
|
|
8931
9203
|
},
|
|
8932
|
-
...options.testTable !== false && arena.tops.some((x) => x.id === tableScenery) ? { testTable } : {}
|
|
9204
|
+
...options.testTable !== false && arena.tops.some((x) => x.id === tableScenery) ? { testTable } : {},
|
|
9205
|
+
...options.toolMenu !== false ? {
|
|
9206
|
+
toolMenu: {
|
|
9207
|
+
groups: toolMenuGroups,
|
|
9208
|
+
onPick: equip,
|
|
9209
|
+
// the list takes clicks: the mouse leaves the view while it is open
|
|
9210
|
+
onOpen: (open) => {
|
|
9211
|
+
if (open && document.pointerLockElement) document.exitPointerLock?.();
|
|
9212
|
+
},
|
|
9213
|
+
keyLabel: toolMenuKeys.map((key2) => TOOL_MENU_KEY_LABELS[key2] ?? key2.replace(/^Key/, "")).join("/")
|
|
9214
|
+
}
|
|
9215
|
+
} : {}
|
|
8933
9216
|
});
|
|
8934
9217
|
ui.hud(hudState());
|
|
8935
9218
|
showBench();
|