@adatechnology/game-test-room 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -0
- package/dist/index.global.js +136 -19
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +136 -19
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -179,6 +179,8 @@ interface TestRoomOptions {
|
|
|
179
179
|
arenaSize?: number;
|
|
180
180
|
/** Spawn these catalogue ids on start, in a row in front of the player. */
|
|
181
181
|
initial?: string[];
|
|
182
|
+
/** Open on one catalogue group (`furniture`, `structure`, `weapons`…): see `TestRoom.openArea`. */
|
|
183
|
+
area?: string;
|
|
182
184
|
/** Called for every destruction event (to log, play sounds, sync over LAN). Fluid events reach the log too. */
|
|
183
185
|
onEvent?: (event: DestructionEvent) => void;
|
|
184
186
|
onSound?: (key: string, point: Vec3, volume: number) => void;
|
|
@@ -245,6 +247,11 @@ interface TestRoom {
|
|
|
245
247
|
setFastForward(on: boolean): void;
|
|
246
248
|
/** Equip a weapon by id, adding it to the hotbar when it is not there. Same as selecting a catalogue item with `TestRoomItem.weapon`. */
|
|
247
249
|
equip(weaponId: string): void;
|
|
250
|
+
/**
|
|
251
|
+
* Set the room up to test one catalogue group or loaded module (by its group or id): clear it and open the catalogue on that tab. A module
|
|
252
|
+
* that brings an `area` is laid out by it; any other group is laid in rows (small items in front, fixed big ones behind). Unknown: nothing.
|
|
253
|
+
*/
|
|
254
|
+
openArea(group: string): SpawnedObject[];
|
|
248
255
|
/** The scenarios the panel offers: the built-in `SCENARIOS` in the room locale plus `TestRoomOptions.scenarios`. */
|
|
249
256
|
readonly scenarios: TestRoomScenario[];
|
|
250
257
|
/** The liquid renderer of `@adatechnology/game-fluids-three` (containers, streams, stains). */
|
package/dist/index.global.js
CHANGED
|
@@ -11242,6 +11242,9 @@ void main() {
|
|
|
11242
11242
|
"ui.quality.medium": "medium",
|
|
11243
11243
|
"ui.quality.high": "high",
|
|
11244
11244
|
"ui.equip": "Equip as a tool",
|
|
11245
|
+
"ui.search_items": "Search items (all groups)",
|
|
11246
|
+
"ui.search_tests": "Search tests",
|
|
11247
|
+
"ui.no_results": "Nothing found",
|
|
11245
11248
|
"ui.objects": "{n} objects",
|
|
11246
11249
|
"ui.tests": "Tests",
|
|
11247
11250
|
"ui.run_all": "Run all",
|
|
@@ -11531,6 +11534,9 @@ void main() {
|
|
|
11531
11534
|
"ui.quality.medium": "m\xE9dia",
|
|
11532
11535
|
"ui.quality.high": "alta",
|
|
11533
11536
|
"ui.equip": "Equipar como ferramenta",
|
|
11537
|
+
"ui.search_items": "Buscar item (todas as abas)",
|
|
11538
|
+
"ui.search_tests": "Buscar teste",
|
|
11539
|
+
"ui.no_results": "Nada encontrado",
|
|
11534
11540
|
"ui.objects": "{n} objetos",
|
|
11535
11541
|
"ui.tests": "Testes",
|
|
11536
11542
|
"ui.run_all": "Rodar todos",
|
|
@@ -13268,7 +13274,11 @@ void main() {
|
|
|
13268
13274
|
var CSS = `
|
|
13269
13275
|
.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}
|
|
13270
13276
|
.tr-root *{box-sizing:border-box}
|
|
13271
|
-
.tr-root button,.tr-root select,.tr-root label{pointer-events:auto}
|
|
13277
|
+
.tr-root button,.tr-root select,.tr-root label,.tr-root input{pointer-events:auto}
|
|
13278
|
+
.tr-search{width:100%;margin:0 0 6px;padding:5px 8px;border-radius:5px;border:1px solid #3a404c;background:#0f1116;color:#e8eaee;font:inherit}
|
|
13279
|
+
.tr-search::placeholder{color:#7d8594}
|
|
13280
|
+
.tr-item small{color:#7d8594;margin-left:4px}
|
|
13281
|
+
.tr-empty{color:#9aa3b2;padding:4px 2px;grid-column:1/-1}
|
|
13272
13282
|
.tr-cross{position:absolute;left:50%;top:50%;width:18px;height:18px;margin:-9px 0 0 -9px}
|
|
13273
13283
|
.tr-cross:before,.tr-cross:after{content:"";position:absolute;background:#fff;opacity:.85;box-shadow:0 0 2px #000}
|
|
13274
13284
|
.tr-cross:before{left:8px;top:0;width:2px;height:18px}.tr-cross:after{top:8px;left:0;height:2px;width:18px}
|
|
@@ -13319,6 +13329,7 @@ void main() {
|
|
|
13319
13329
|
.tr-summary{margin-top:6px;color:#9aa3b2}
|
|
13320
13330
|
@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}}
|
|
13321
13331
|
`;
|
|
13332
|
+
var fold = (text) => text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
13322
13333
|
var mk = (tag, cls, text) => {
|
|
13323
13334
|
const e = document.createElement(tag);
|
|
13324
13335
|
if (cls) e.className = cls;
|
|
@@ -13458,15 +13469,27 @@ void main() {
|
|
|
13458
13469
|
const tests = mk("div", "tr-panel tr-tests");
|
|
13459
13470
|
right.append(cat, anim, tests);
|
|
13460
13471
|
cat.appendChild(mk("h3", void 0, t("ui.catalog")));
|
|
13472
|
+
const itemSearch = mk("input", "tr-search");
|
|
13473
|
+
itemSearch.type = "search";
|
|
13474
|
+
itemSearch.placeholder = t("ui.search_items");
|
|
13461
13475
|
const tabs = mk("div", "tr-tabs");
|
|
13462
13476
|
const items = mk("div", "tr-items");
|
|
13463
|
-
cat.append(tabs, items);
|
|
13477
|
+
cat.append(itemSearch, tabs, items);
|
|
13464
13478
|
let activeGroup = "";
|
|
13479
|
+
const visibleItems = () => {
|
|
13480
|
+
const term = fold(itemSearch.value.trim());
|
|
13481
|
+
if (!term) return (o.groups().get(activeGroup) ?? []).map((item) => ({ item, group: activeGroup }));
|
|
13482
|
+
return [...o.groups()].flatMap(([group, list2]) => list2.filter((item) => fold(`${item.name} ${item.id} ${group}`).includes(term)).map((item) => ({ item, group })));
|
|
13483
|
+
};
|
|
13465
13484
|
const renderItems = () => {
|
|
13466
13485
|
items.textContent = "";
|
|
13467
|
-
|
|
13486
|
+
const searching = itemSearch.value.trim() !== "";
|
|
13487
|
+
const found = visibleItems();
|
|
13488
|
+
if (!found.length) items.appendChild(mk("div", "tr-empty", t("ui.no_results")));
|
|
13489
|
+
for (const { item: it, group } of found) {
|
|
13468
13490
|
const wrap = mk("div", "tr-item");
|
|
13469
13491
|
const b = mk("button", "tr-btn", it.name);
|
|
13492
|
+
if (searching) b.appendChild(mk("small", void 0, group));
|
|
13470
13493
|
b.type = "button";
|
|
13471
13494
|
b.title = it.note ?? it.name;
|
|
13472
13495
|
b.onclick = () => o.onSpawn(it.id);
|
|
@@ -13497,6 +13520,8 @@ void main() {
|
|
|
13497
13520
|
renderItems();
|
|
13498
13521
|
};
|
|
13499
13522
|
renderTabs();
|
|
13523
|
+
itemSearch.oninput = renderItems;
|
|
13524
|
+
for (const box2 of [itemSearch]) box2.addEventListener("keydown", (e) => e.stopPropagation());
|
|
13500
13525
|
const controls = mk("div", "tr-row");
|
|
13501
13526
|
const ctl = (label, fn) => {
|
|
13502
13527
|
const b = mk("button", "tr-btn", label);
|
|
@@ -13538,8 +13563,16 @@ void main() {
|
|
|
13538
13563
|
const runAll = mk("button", "tr-btn", t("ui.run_all"));
|
|
13539
13564
|
runAll.type = "button";
|
|
13540
13565
|
const summary = mk("div", "tr-summary");
|
|
13566
|
+
const testSearch = mk("input", "tr-search");
|
|
13567
|
+
testSearch.type = "search";
|
|
13568
|
+
testSearch.placeholder = t("ui.search_tests");
|
|
13569
|
+
testSearch.addEventListener("keydown", (e) => e.stopPropagation());
|
|
13541
13570
|
const list = mk("div", "list");
|
|
13542
|
-
tests.append(runAll, summary, list);
|
|
13571
|
+
tests.append(runAll, summary, testSearch, list);
|
|
13572
|
+
testSearch.oninput = () => {
|
|
13573
|
+
const term = fold(testSearch.value.trim());
|
|
13574
|
+
for (const b of scBtns.values()) b.style.display = !term || fold(b.textContent ?? "").includes(term) ? "" : "none";
|
|
13575
|
+
};
|
|
13543
13576
|
const scBtns = /* @__PURE__ */ new Map();
|
|
13544
13577
|
let busy = false;
|
|
13545
13578
|
const mark = (rep) => {
|
|
@@ -13738,6 +13771,11 @@ void main() {
|
|
|
13738
13771
|
renderTabs();
|
|
13739
13772
|
renderScenarios();
|
|
13740
13773
|
},
|
|
13774
|
+
showGroup(group) {
|
|
13775
|
+
activeGroup = group;
|
|
13776
|
+
itemSearch.value = "";
|
|
13777
|
+
renderTabs();
|
|
13778
|
+
},
|
|
13741
13779
|
togglePanels,
|
|
13742
13780
|
dispose() {
|
|
13743
13781
|
offLog();
|
|
@@ -13808,7 +13846,13 @@ void main() {
|
|
|
13808
13846
|
const p = world.part(arena.table.id, arena.table.topPart);
|
|
13809
13847
|
return !!p && p.status !== "broken" && p.status !== "melted" && p.status !== "ashes";
|
|
13810
13848
|
};
|
|
13811
|
-
const
|
|
13849
|
+
const restingTops = [];
|
|
13850
|
+
const restingTopAt = (x, z) => {
|
|
13851
|
+
let top = 0;
|
|
13852
|
+
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);
|
|
13853
|
+
return top;
|
|
13854
|
+
};
|
|
13855
|
+
const groundAt = (x, z) => Math.max(x > arena.table.minX && x < arena.table.maxX && z > arena.table.minZ && z < arena.table.maxZ && tableOk() ? arena.table.top : 0, restingTopAt(x, z));
|
|
13812
13856
|
const ownFluids = !options.fluids;
|
|
13813
13857
|
const fluids = options.fluids ?? createFluids({ seed: 1, locale, liquids: options.liquids, groundAt });
|
|
13814
13858
|
const pristineFluids = ownFluids ? fluids.serialize() : null;
|
|
@@ -13941,16 +13985,16 @@ void main() {
|
|
|
13941
13985
|
const puncture = (ev) => {
|
|
13942
13986
|
const e = entryOf(ev.object);
|
|
13943
13987
|
if (!e?.containerId || e.fluidPart !== ev.part) return;
|
|
13944
|
-
const
|
|
13945
|
-
|
|
13946
|
-
tmpM.copy(
|
|
13988
|
+
const host2 = hostOf(e);
|
|
13989
|
+
host2.root.updateMatrixWorld(true);
|
|
13990
|
+
tmpM.copy(host2.root.matrixWorld).invert();
|
|
13947
13991
|
const local = tmpV2.set(ev.point[0], ev.point[1], ev.point[2]).applyMatrix4(tmpM);
|
|
13948
13992
|
local.x -= e.fluidOffset[0];
|
|
13949
13993
|
local.y -= e.fluidOffset[1];
|
|
13950
13994
|
local.z -= e.fluidOffset[2];
|
|
13951
|
-
|
|
13995
|
+
host2.root.getWorldQuaternion(tmpQ).invert();
|
|
13952
13996
|
const n = tmpV22.set(ev.direction[0], ev.direction[1], ev.direction[2]).applyQuaternion(tmpQ);
|
|
13953
|
-
const part = world.part(
|
|
13997
|
+
const part = world.part(host2.objectId, ev.part);
|
|
13954
13998
|
const hole = part?.holes[part.holes.length - 1];
|
|
13955
13999
|
fluids.puncture(e.containerId, [local.x, local.y, local.z], [n.x, n.y, n.z], hole?.diameter ?? 9e-3);
|
|
13956
14000
|
};
|
|
@@ -14156,6 +14200,7 @@ void main() {
|
|
|
14156
14200
|
};
|
|
14157
14201
|
const clear = () => {
|
|
14158
14202
|
clearEverything();
|
|
14203
|
+
restingTops.length = 0;
|
|
14159
14204
|
pending = [];
|
|
14160
14205
|
action("clear", t("act.clear"));
|
|
14161
14206
|
};
|
|
@@ -14375,6 +14420,7 @@ void main() {
|
|
|
14375
14420
|
}
|
|
14376
14421
|
});
|
|
14377
14422
|
player.kick(recoil[0], recoil[1]);
|
|
14423
|
+
emitHost({ type: "shot", weapon: toolId, shots: shots.length, recoil });
|
|
14378
14424
|
const f = first;
|
|
14379
14425
|
const d = f ? num(f.distance, 1) : "";
|
|
14380
14426
|
let text;
|
|
@@ -14614,7 +14660,8 @@ void main() {
|
|
|
14614
14660
|
if (cat === "hand") return;
|
|
14615
14661
|
const res = tool.trigger([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]], dt, held, "player");
|
|
14616
14662
|
if (res.status === "empty") {
|
|
14617
|
-
|
|
14663
|
+
emitHost({ type: "empty", weapon: toolId });
|
|
14664
|
+
if (!tool.reloading && tool.reserve > 0) startReload();
|
|
14618
14665
|
say(t(tool.reloading ? "hud.reloading" : "hud.empty"));
|
|
14619
14666
|
return;
|
|
14620
14667
|
}
|
|
@@ -14666,6 +14713,7 @@ void main() {
|
|
|
14666
14713
|
progress = null;
|
|
14667
14714
|
action("tool", t("act.tool", { weapon: toolName(id) }), { data: { weapon: id } });
|
|
14668
14715
|
fire("tool", id);
|
|
14716
|
+
emitHost({ type: "tool", weapon: id });
|
|
14669
14717
|
};
|
|
14670
14718
|
const cycleTool = (dir) => {
|
|
14671
14719
|
if (!loadout.length) return;
|
|
@@ -14893,7 +14941,7 @@ void main() {
|
|
|
14893
14941
|
case "KeyR":
|
|
14894
14942
|
if (heldPiece) heldYaw += Math.PI / 8;
|
|
14895
14943
|
else {
|
|
14896
|
-
|
|
14944
|
+
startReload();
|
|
14897
14945
|
say(t("hud.reloading"));
|
|
14898
14946
|
}
|
|
14899
14947
|
break;
|
|
@@ -14991,10 +15039,10 @@ void main() {
|
|
|
14991
15039
|
const syncContainers = (sdt) => {
|
|
14992
15040
|
for (const e of entries.values()) {
|
|
14993
15041
|
if (!e.containerId) continue;
|
|
14994
|
-
const
|
|
14995
|
-
|
|
14996
|
-
const q =
|
|
14997
|
-
const p = tmpV2.set(e.fluidOffset[0], e.fluidOffset[1], e.fluidOffset[2]).applyMatrix4(
|
|
15042
|
+
const host2 = hostOf(e);
|
|
15043
|
+
host2.root.updateMatrixWorld(true);
|
|
15044
|
+
const q = host2.root.getWorldQuaternion(tmpQ);
|
|
15045
|
+
const p = tmpV2.set(e.fluidOffset[0], e.fluidOffset[1], e.fluidOffset[2]).applyMatrix4(host2.root.matrixWorld);
|
|
14998
15046
|
const vel = sdt > 0 ? [(p.x - e.last[0]) / sdt, (p.y - e.last[1]) / sdt, (p.z - e.last[2]) / sdt] : void 0;
|
|
14999
15047
|
e.last = [p.x, p.y, p.z];
|
|
15000
15048
|
fluids.setTransform(e.containerId, [p.x, p.y, p.z], [q.x, q.y, q.z, q.w], vel);
|
|
@@ -15005,9 +15053,9 @@ void main() {
|
|
|
15005
15053
|
if (!e.containerId || !e.fluidPart) continue;
|
|
15006
15054
|
const c = fluids.container(e.containerId);
|
|
15007
15055
|
if (!c) continue;
|
|
15008
|
-
const
|
|
15009
|
-
const part = world.part(
|
|
15010
|
-
if (part && part.liters !== void 0 && Math.abs(part.liters - c.liters) > 1e-4) world.setLiters(
|
|
15056
|
+
const host2 = hostOf(e);
|
|
15057
|
+
const part = world.part(host2.objectId, e.fluidPart);
|
|
15058
|
+
if (part && part.liters !== void 0 && Math.abs(part.liters - c.liters) > 1e-4) world.setLiters(host2.objectId, e.fluidPart, c.liters);
|
|
15011
15059
|
}
|
|
15012
15060
|
};
|
|
15013
15061
|
let paused = false;
|
|
@@ -15038,6 +15086,9 @@ void main() {
|
|
|
15038
15086
|
updateImpacts();
|
|
15039
15087
|
fluidView.update(dt);
|
|
15040
15088
|
for (const s of tools.values()) s.update(dt);
|
|
15089
|
+
if (reloadingSeen && !tool.reloading) emitHost({ type: "reload", weapon: toolId, phase: "end" });
|
|
15090
|
+
reloadingSeen = tool.reloading;
|
|
15091
|
+
for (const update2 of frameListeners) update2(dt);
|
|
15041
15092
|
for (const tr of tracers) {
|
|
15042
15093
|
if (tr.life > 0) {
|
|
15043
15094
|
tr.life -= dt;
|
|
@@ -15179,6 +15230,36 @@ void main() {
|
|
|
15179
15230
|
const moduleCheck = (ok, text, data, itemId) => {
|
|
15180
15231
|
log.add({ channel: "check", type: ok ? "pass" : "fail", text: `${ok ? "\u2713" : "\u2717"} ${text}`, itemId, data: { ok, ...data } });
|
|
15181
15232
|
};
|
|
15233
|
+
const modules = /* @__PURE__ */ new Map();
|
|
15234
|
+
const detachers = [];
|
|
15235
|
+
const hostListeners = /* @__PURE__ */ new Set();
|
|
15236
|
+
const frameListeners = /* @__PURE__ */ new Set();
|
|
15237
|
+
let reloadingSeen = false;
|
|
15238
|
+
function emitHost(event) {
|
|
15239
|
+
for (const listener of hostListeners) listener(event);
|
|
15240
|
+
}
|
|
15241
|
+
function startReload() {
|
|
15242
|
+
if (tool.reload()) emitHost({ type: "reload", weapon: toolId, phase: "start" });
|
|
15243
|
+
}
|
|
15244
|
+
const host = {
|
|
15245
|
+
THREE,
|
|
15246
|
+
scene,
|
|
15247
|
+
camera,
|
|
15248
|
+
locale,
|
|
15249
|
+
groundAt,
|
|
15250
|
+
tool: () => ({ weapon: toolId, category: tool.def.category, ammo: tool.ammo, capacity: tool.capacity, reserve: tool.reserve, reloading: tool.reloading }),
|
|
15251
|
+
on(listener) {
|
|
15252
|
+
hostListeners.add(listener);
|
|
15253
|
+
return () => hostListeners.delete(listener);
|
|
15254
|
+
},
|
|
15255
|
+
onFrame(update2) {
|
|
15256
|
+
frameListeners.add(update2);
|
|
15257
|
+
return () => frameListeners.delete(update2);
|
|
15258
|
+
},
|
|
15259
|
+
log(text, data) {
|
|
15260
|
+
action("module_note", text, { data });
|
|
15261
|
+
}
|
|
15262
|
+
};
|
|
15182
15263
|
const loadModule = (mod) => {
|
|
15183
15264
|
const report = { module: mod.id, items: [], scenarios: [], ok: true, problems: [] };
|
|
15184
15265
|
const fail = (problem) => {
|
|
@@ -15243,6 +15324,8 @@ void main() {
|
|
|
15243
15324
|
report.scenarios.push(sc.id);
|
|
15244
15325
|
}
|
|
15245
15326
|
action("module", t("act.module_loaded", { name: mod.name, items: report.items.length, scenarios: report.scenarios.length, result: t(report.ok ? "mod.all_ok" : "mod.some_failed") }), { data: { module: mod.id, items: report.items, scenarios: report.scenarios, ok: report.ok } });
|
|
15327
|
+
modules.set(mod.id, mod);
|
|
15328
|
+
if (mod.attach) detachers.push(mod.attach(host));
|
|
15246
15329
|
ui?.refresh();
|
|
15247
15330
|
return report;
|
|
15248
15331
|
};
|
|
@@ -15298,6 +15381,37 @@ void main() {
|
|
|
15298
15381
|
}
|
|
15299
15382
|
return report;
|
|
15300
15383
|
};
|
|
15384
|
+
const layArea = (layout) => {
|
|
15385
|
+
const holders = layout.placements.filter((p) => p.surface);
|
|
15386
|
+
const rest = layout.placements.filter((p) => !p.surface);
|
|
15387
|
+
const placed = [...holders, ...rest].map((p) => {
|
|
15388
|
+
const so = spawn(p.item, p.position, p.rotationY ?? 0);
|
|
15389
|
+
if (p.surface) {
|
|
15390
|
+
const [hx, hz] = p.surface.halfSize;
|
|
15391
|
+
restingTops.push({ objectId: so.objectId, minX: p.position[0] - hx, maxX: p.position[0] + hx, minZ: p.position[2] - hz, maxZ: p.position[2] + hz, top: p.surface.height });
|
|
15392
|
+
}
|
|
15393
|
+
return { so, asleep: p.asleep === true };
|
|
15394
|
+
});
|
|
15395
|
+
for (const { so, asleep } of placed) {
|
|
15396
|
+
const piece = asleep ? rendererPieces.get(so.objectId) : void 0;
|
|
15397
|
+
if (piece) piece.sleeping = true;
|
|
15398
|
+
}
|
|
15399
|
+
if (layout.view) room.look(layout.view.eye, layout.view.target);
|
|
15400
|
+
return placed.map(({ so }) => so);
|
|
15401
|
+
};
|
|
15402
|
+
const openArea = (area) => {
|
|
15403
|
+
clear();
|
|
15404
|
+
const mod = [...modules.values()].find((m) => m.id === area || m.group === area);
|
|
15405
|
+
const label = mod?.group ?? S.groups[area] ?? area;
|
|
15406
|
+
ui?.showGroup(label);
|
|
15407
|
+
const layout = mod?.area?.(locale);
|
|
15408
|
+
if (layout) return layArea(layout);
|
|
15409
|
+
const items = catalog.filter((item) => item.group === label);
|
|
15410
|
+
const front = items.filter((item) => item.movable !== false);
|
|
15411
|
+
const back = items.filter((item) => item.movable === false);
|
|
15412
|
+
const row = (list, gap, z) => list.map((item, index) => spawn(item.id, [(index - (list.length - 1) / 2) * gap, 0, z], 0));
|
|
15413
|
+
return [...row(front, 1.6, 1.5), ...row(back, 4.5, -4)];
|
|
15414
|
+
};
|
|
15301
15415
|
const room = {
|
|
15302
15416
|
world,
|
|
15303
15417
|
renderer,
|
|
@@ -15377,10 +15491,12 @@ void main() {
|
|
|
15377
15491
|
fastForward = on;
|
|
15378
15492
|
},
|
|
15379
15493
|
equip,
|
|
15494
|
+
openArea,
|
|
15380
15495
|
loadModule,
|
|
15381
15496
|
animations: animationsOf,
|
|
15382
15497
|
playAnimation,
|
|
15383
15498
|
dispose() {
|
|
15499
|
+
for (const detach of detachers.splice(0)) detach();
|
|
15384
15500
|
cancelAnimationFrame?.(raf);
|
|
15385
15501
|
window.removeEventListener("resize", onResize);
|
|
15386
15502
|
player.dispose();
|
|
@@ -15470,6 +15586,7 @@ void main() {
|
|
|
15470
15586
|
if (options.initial) {
|
|
15471
15587
|
for (const [i, id] of options.initial.entries()) spawn(id, [(i - (options.initial.length - 1) / 2) * 1.3, 0, 0.5]);
|
|
15472
15588
|
}
|
|
15589
|
+
if (options.area) openArea(options.area);
|
|
15473
15590
|
return room;
|
|
15474
15591
|
}
|
|
15475
15592
|
function structuredCloneSpec(spec) {
|