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