@adatechnology/game-test-room 0.11.1 → 0.13.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/README.md +80 -7
- package/dist/index.d.ts +304 -29
- package/dist/index.global.js +5064 -2023
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1678 -1656
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/room.ts
|
|
2
|
-
import { createDestruction, JOINT_DEFAULTS
|
|
2
|
+
import { createDestruction, JOINT_DEFAULTS, resolveStrings as resolveDestructionStrings } from "@adatechnology/game-destruction";
|
|
3
3
|
import { createAudioEngine, createAudioSink } from "@adatechnology/game-audio";
|
|
4
4
|
import { createDestructionRenderer, createDestructionSound, specFromObject3D } from "@adatechnology/game-destruction-three";
|
|
5
5
|
import { createArsenal, DEFAULT_LOADOUT } from "@adatechnology/game-weapons";
|
|
@@ -7,1256 +7,283 @@ import { createFilmLcg, createFluids, createItemUse, ITEM_USES, resolveStrings a
|
|
|
7
7
|
import { createFilmBridge, createFilmManager, createFluidRenderer } from "@adatechnology/game-fluids-three";
|
|
8
8
|
import { createGroundField, createVfx } from "@adatechnology/game-vfx";
|
|
9
9
|
import { createGroundPatch, createVfxRenderer } from "@adatechnology/game-vfx-three";
|
|
10
|
-
import { checkAnimation, checkBench, checkDestructible, checkSpec, createEnvironmentBus } from "@adatechnology/game-contracts";
|
|
10
|
+
import { checkAnimation, checkBench, checkDestructible, checkSpec, createEnvironmentBus, shotExtras, useToHit } from "@adatechnology/game-contracts";
|
|
11
11
|
|
|
12
12
|
// src/items.ts
|
|
13
|
-
|
|
13
|
+
var box = (id, material, size, position, extra = {}) => ({ id, material, shape: { kind: "box", size }, position, ...extra });
|
|
14
|
+
var joint = (a, b, kind, extra = {}) => ({ a, b, kind, ...extra });
|
|
15
|
+
function brickWallSpec(cols = 8, rows = 6, panel = [1.25, 0.5, 0.2]) {
|
|
16
|
+
const parts = [];
|
|
17
|
+
const joints = [];
|
|
18
|
+
const id = (c, r) => `brick_${r}_${c}`;
|
|
19
|
+
for (let r = 0; r < rows; r++) {
|
|
20
|
+
for (let c = 0; c < cols; c++) {
|
|
21
|
+
parts.push(box(id(c, r), "brick", panel, [(c - (cols - 1) / 2) * panel[0], panel[1] / 2 + r * panel[1], 0], { anchored: r === 0 }));
|
|
22
|
+
if (c > 0) joints.push(joint(id(c - 1, r), id(c, r), "mortar"));
|
|
23
|
+
if (r > 0) joints.push(joint(id(c, r - 1), id(c, r), "mortar"));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { name: "brick wall", parts, joints, tags: ["structure"] };
|
|
27
|
+
}
|
|
14
28
|
|
|
15
|
-
// src/
|
|
16
|
-
var
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
29
|
+
// src/arena.ts
|
|
30
|
+
var sizeOf = (shape) => shape.kind === "box" ? shape.size : shape.kind === "cylinder" ? [shape.radius * 2, shape.height, shape.radius * 2] : [shape.radius * 2, shape.radius * 2, shape.radius * 2];
|
|
31
|
+
function specBounds(THREE, spec, position, rotationY, options = {}) {
|
|
32
|
+
const out = { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity, minZ: Infinity, maxZ: -Infinity };
|
|
33
|
+
const q = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, rotationY, 0));
|
|
34
|
+
const v = new THREE.Vector3();
|
|
35
|
+
const e = new THREE.Euler();
|
|
36
|
+
const pq = new THREE.Quaternion();
|
|
37
|
+
let any = false;
|
|
38
|
+
for (const p of spec.parts) {
|
|
39
|
+
if (options.onlyPart !== void 0 && p.id !== options.onlyPart) continue;
|
|
40
|
+
const [sx, sy, sz] = sizeOf(p.shape);
|
|
41
|
+
pq.setFromEuler(e.set(p.rotation?.[0] ?? 0, p.rotation?.[1] ?? 0, p.rotation?.[2] ?? 0, "XYZ"));
|
|
42
|
+
const corners = [];
|
|
43
|
+
let top = -Infinity;
|
|
44
|
+
for (const cx of [-0.5, 0.5]) for (const cy of [-0.5, 0.5]) for (const cz of [-0.5, 0.5]) {
|
|
45
|
+
v.set(cx * sx, cy * sy, cz * sz).applyQuaternion(pq);
|
|
46
|
+
const c = [v.x + p.position[0], v.y + p.position[1], v.z + p.position[2]];
|
|
47
|
+
top = Math.max(top, c[1]);
|
|
48
|
+
corners.push(c);
|
|
49
|
+
}
|
|
50
|
+
if (top < (options.minHeight ?? 0)) continue;
|
|
51
|
+
any = true;
|
|
52
|
+
for (const c of corners) {
|
|
53
|
+
v.set(c[0], c[1], c[2]).applyQuaternion(q);
|
|
54
|
+
out.minX = Math.min(out.minX, v.x + position[0]);
|
|
55
|
+
out.maxX = Math.max(out.maxX, v.x + position[0]);
|
|
56
|
+
out.minY = Math.min(out.minY, v.y + position[1]);
|
|
57
|
+
out.maxY = Math.max(out.maxY, v.y + position[1]);
|
|
58
|
+
out.minZ = Math.min(out.minZ, v.z + position[2]);
|
|
59
|
+
out.maxZ = Math.max(out.maxZ, v.z + position[2]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return any ? out : null;
|
|
63
|
+
}
|
|
64
|
+
var tableSpec = () => {
|
|
65
|
+
const parts = [{ id: "top", material: "wood_hard", shape: { kind: "box", size: [1.6, 0.05, 0.8] }, position: [0, 0.755, 0] }];
|
|
66
|
+
const joints = [];
|
|
67
|
+
for (const [i, [x, z]] of [
|
|
68
|
+
[-0.72, -0.32],
|
|
69
|
+
[0.72, -0.32],
|
|
70
|
+
[-0.72, 0.32],
|
|
71
|
+
[0.72, 0.32]
|
|
72
|
+
].entries()) {
|
|
73
|
+
parts.push({ id: `leg_${i + 1}`, material: "wood_hard", shape: { kind: "box", size: [0.06, 0.73, 0.06] }, position: [x, 0.365, z], anchored: true });
|
|
74
|
+
joints.push({ a: `leg_${i + 1}`, b: "top", kind: "screw" });
|
|
75
|
+
}
|
|
76
|
+
return { name: "table", parts, joints, tags: ["furniture"] };
|
|
44
77
|
};
|
|
45
|
-
var
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
78
|
+
var shelfSpec = () => {
|
|
79
|
+
const parts = [];
|
|
80
|
+
const joints = [];
|
|
81
|
+
for (const [i, x] of [-0.6, 0.6].entries()) {
|
|
82
|
+
parts.push({ id: `upright_${i + 1}`, material: "wood_hard", shape: { kind: "box", size: [0.04, 1.7, 0.4] }, position: [x, 0.85, 0], anchored: true });
|
|
83
|
+
}
|
|
84
|
+
for (const [i, y] of [0.4, 0.9, 1.4].entries()) {
|
|
85
|
+
parts.push({ id: `board_${i + 1}`, material: "wood_soft", shape: { kind: "box", size: [1.2, 0.03, 0.4] }, position: [0, y, 0] });
|
|
86
|
+
joints.push({ a: `board_${i + 1}`, b: "upright_1", kind: "screw" }, { a: `board_${i + 1}`, b: "upright_2", kind: "screw" });
|
|
87
|
+
}
|
|
88
|
+
return { name: "shelf", parts, joints, tags: ["furniture"] };
|
|
89
|
+
};
|
|
90
|
+
var defaultScenery = (wallZ) => ({
|
|
91
|
+
target_wall: { spec: brickWallSpec(), position: [0, 0, wallZ], colors: { brick: 10831662 } },
|
|
92
|
+
table: { spec: tableSpec(), position: [3.5, 0, 0], top: { part: "top", y: 0.78 } },
|
|
93
|
+
shelf: { spec: shelfSpec(), position: [-3.5, 0, 0], colors: { wood_soft: 13081186 } }
|
|
94
|
+
});
|
|
95
|
+
var DEFAULT_WALL_Z = -11;
|
|
96
|
+
var WALL_ID = "target_wall";
|
|
97
|
+
var TABLE_ID = "table";
|
|
98
|
+
function resolveScenery(scenery) {
|
|
99
|
+
const merged = new Map(Object.entries(defaultScenery(DEFAULT_WALL_Z)));
|
|
100
|
+
for (const [id, obj] of Object.entries(scenery ?? {})) {
|
|
101
|
+
if (obj === null) {
|
|
102
|
+
merged.delete(id);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const old = merged.get(id);
|
|
106
|
+
const position = obj.position ?? old?.position;
|
|
107
|
+
if (!position) throw new Error(`game-test-room: scenery "${id}" is new, so it needs a position`);
|
|
108
|
+
merged.set(id, { ...obj, position });
|
|
109
|
+
}
|
|
110
|
+
return [...merged.entries()].map(([id, o]) => ({ id, spec: o.spec, position: o.position, rotationY: o.rotationY ?? 0, colors: o.colors, build: o.build, label: o.label, top: o.top, spout: o.spout }));
|
|
111
|
+
}
|
|
112
|
+
function createArena(THREE, size, scene, scenery, tapOption) {
|
|
113
|
+
const group = new THREE.Group();
|
|
114
|
+
group.name = "test-room-arena";
|
|
115
|
+
scene.background = new THREE.Color(10466504);
|
|
116
|
+
group.add(new THREE.HemisphereLight(13624319, 7037527, 0.75));
|
|
117
|
+
const sun = new THREE.DirectionalLight(16777215, 2.1);
|
|
118
|
+
sun.position.set(7, 13, 8);
|
|
119
|
+
sun.castShadow = true;
|
|
120
|
+
sun.shadow.mapSize.set(1024, 1024);
|
|
121
|
+
const sc = sun.shadow.camera;
|
|
122
|
+
sc.left = -16;
|
|
123
|
+
sc.right = 16;
|
|
124
|
+
sc.top = 16;
|
|
125
|
+
sc.bottom = -16;
|
|
126
|
+
sc.near = 1;
|
|
127
|
+
sc.far = 40;
|
|
128
|
+
sun.shadow.bias = -6e-4;
|
|
129
|
+
group.add(sun);
|
|
130
|
+
const floor = new THREE.Mesh(new THREE.PlaneGeometry(size * 2, size * 2), new THREE.MeshStandardMaterial({ color: 9342602, roughness: 0.95 }));
|
|
131
|
+
floor.rotation.x = -Math.PI / 2;
|
|
132
|
+
floor.receiveShadow = true;
|
|
133
|
+
floor.name = "floor";
|
|
134
|
+
group.add(floor);
|
|
135
|
+
const grid = new THREE.GridHelper(size * 2, size * 2, 5000274, 7829371);
|
|
136
|
+
grid.position.y = 4e-3;
|
|
137
|
+
group.add(grid);
|
|
138
|
+
const sceneryObjects = resolveScenery(scenery);
|
|
139
|
+
const wallObject = sceneryObjects.find((o) => o.id === WALL_ID);
|
|
140
|
+
const wallZ = wallObject ? wallObject.position[2] : DEFAULT_WALL_Z;
|
|
141
|
+
const playerStart = [0, 1.7, 5];
|
|
142
|
+
const pts = [];
|
|
143
|
+
const rx = -1.6;
|
|
144
|
+
for (let z = Math.round(playerStart[2]); z >= wallZ; z--) {
|
|
145
|
+
const d = playerStart[2] - z;
|
|
146
|
+
const len = d % 5 === 0 ? 0.5 : 0.2;
|
|
147
|
+
pts.push(rx, 0.01, z, rx + len, 0.01, z);
|
|
148
|
+
}
|
|
149
|
+
pts.push(rx, 0.01, playerStart[2], rx, 0.01, wallZ);
|
|
150
|
+
const rg = new THREE.BufferGeometry();
|
|
151
|
+
rg.setAttribute("position", new THREE.Float32BufferAttribute(pts, 3));
|
|
152
|
+
group.add(new THREE.LineSegments(rg, new THREE.LineBasicMaterial({ color: 16765503 })));
|
|
153
|
+
for (let d = 5; playerStart[2] - d >= wallZ; d += 5) {
|
|
154
|
+
try {
|
|
155
|
+
const canvas = document.createElement("canvas");
|
|
156
|
+
canvas.width = 128;
|
|
157
|
+
canvas.height = 64;
|
|
158
|
+
const ctx = canvas.getContext("2d");
|
|
159
|
+
if (!ctx) break;
|
|
160
|
+
ctx.fillStyle = "#ffd23f";
|
|
161
|
+
ctx.font = "bold 44px sans-serif";
|
|
162
|
+
ctx.textAlign = "center";
|
|
163
|
+
ctx.fillText(`${d} m`, 64, 48);
|
|
164
|
+
const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: new THREE.CanvasTexture(canvas), transparent: true, depthWrite: false }));
|
|
165
|
+
sprite.scale.set(1.2, 0.6, 1);
|
|
166
|
+
sprite.position.set(rx - 0.8, 0.45, playerStart[2] - d);
|
|
167
|
+
group.add(sprite);
|
|
168
|
+
} catch {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const tops = [];
|
|
173
|
+
const surfaces = [{ id: "floor", point: [0, 0, 0], normal: [0, 1, 0], halfSize: [size, size], porosity: 0.05 }];
|
|
174
|
+
for (const o of sceneryObjects) {
|
|
175
|
+
if (!o.top) continue;
|
|
176
|
+
const b = specBounds(THREE, o.spec, o.position, o.rotationY, { onlyPart: o.top.part });
|
|
177
|
+
if (!b) continue;
|
|
178
|
+
const top = o.position[1] + o.top.y;
|
|
179
|
+
tops.push({ id: o.id, topPart: o.top.part, minX: b.minX, maxX: b.maxX, minZ: b.minZ, maxZ: b.maxZ, top });
|
|
180
|
+
surfaces.push({ id: o.id, point: [(b.minX + b.maxX) / 2, top, (b.minZ + b.maxZ) / 2], normal: [0, 1, 0], halfSize: [(b.maxX - b.minX) / 2, (b.maxZ - b.minZ) / 2], porosity: 0.3 });
|
|
181
|
+
}
|
|
182
|
+
const colliders = [];
|
|
183
|
+
for (const o of sceneryObjects) {
|
|
184
|
+
const b = specBounds(THREE, o.spec, o.position, o.rotationY, { minHeight: 0.1 });
|
|
185
|
+
if (!b) continue;
|
|
186
|
+
colliders.push(o.id === WALL_ID ? [-size, b.minZ - 1, size, b.maxZ + 0.1] : [b.minX, b.minZ, b.maxX, b.maxZ]);
|
|
187
|
+
}
|
|
188
|
+
let tapSpout = null;
|
|
189
|
+
const tableTop = tops.find((t) => t.id === TABLE_ID);
|
|
190
|
+
const spouting = sceneryObjects.find((o) => o.spout);
|
|
191
|
+
if (spouting?.spout && tapOption !== null) {
|
|
192
|
+
const s = new THREE.Vector3(spouting.spout[0], spouting.spout[1], spouting.spout[2]).applyAxisAngle(new THREE.Vector3(0, 1, 0), spouting.rotationY);
|
|
193
|
+
tapSpout = [spouting.position[0] + s.x, spouting.position[1] + s.y, spouting.position[2] + s.z];
|
|
194
|
+
} else if (tableTop && tapOption !== null) {
|
|
195
|
+
const cx = (tableTop.minX + tableTop.maxX) / 2;
|
|
196
|
+
const cz = (tableTop.minZ + tableTop.maxZ) / 2;
|
|
197
|
+
const y = tableTop.top;
|
|
198
|
+
if (tapOption) {
|
|
199
|
+
const rot = tapOption.rotationY ?? 0;
|
|
200
|
+
const sp = new THREE.Vector3(tapOption.spout[0], tapOption.spout[1], tapOption.spout[2]).applyAxisAngle(new THREE.Vector3(0, 1, 0), rot);
|
|
201
|
+
const at = tapOption.position ?? [cx + 0.3 - sp.x, y, cz - sp.z];
|
|
202
|
+
const model = tapOption.build(THREE);
|
|
203
|
+
model.position.set(at[0], at[1], at[2]);
|
|
204
|
+
model.rotation.y = rot;
|
|
205
|
+
model.traverse((m) => {
|
|
206
|
+
m.castShadow = true;
|
|
66
207
|
});
|
|
67
|
-
|
|
208
|
+
group.add(model);
|
|
209
|
+
tapSpout = [at[0] + sp.x, at[1] + sp.y, at[2] + sp.z];
|
|
210
|
+
} else {
|
|
211
|
+
const postX = tableTop.maxX + 0.35;
|
|
212
|
+
const tapMat = new THREE.MeshStandardMaterial({ color: 12172996, metalness: 0.8, roughness: 0.35 });
|
|
213
|
+
const post = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 1.4, 10), tapMat);
|
|
214
|
+
post.position.set(postX, 0.7, cz);
|
|
215
|
+
const arm = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, postX - (cx + 0.3), 10), tapMat);
|
|
216
|
+
arm.rotation.z = Math.PI / 2;
|
|
217
|
+
arm.position.set((postX + cx + 0.3) / 2, y + 0.6, cz);
|
|
218
|
+
const spoutMesh = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 0.1, 10), tapMat);
|
|
219
|
+
spoutMesh.position.set(cx + 0.3, y + 0.55, cz);
|
|
220
|
+
for (const m of [post, arm, spoutMesh]) {
|
|
221
|
+
m.castShadow = true;
|
|
222
|
+
group.add(m);
|
|
223
|
+
}
|
|
224
|
+
tapSpout = [cx + 0.3, y + 0.5, cz];
|
|
225
|
+
colliders.push([postX - 0.05, cz - 0.1, postX + 0.05, cz + 0.1]);
|
|
68
226
|
}
|
|
69
|
-
return m;
|
|
70
|
-
};
|
|
71
|
-
for (const p of spec.parts) {
|
|
72
|
-
let geo;
|
|
73
|
-
const s = p.shape;
|
|
74
|
-
if (s.kind === "box") geo = new THREE.BoxGeometry(s.size[0], s.size[1], s.size[2]);
|
|
75
|
-
else if (s.kind === "cylinder") geo = new THREE.CylinderGeometry(s.radius, s.radius, s.height, seg);
|
|
76
|
-
else geo = new THREE.SphereGeometry(s.radius, seg, Math.max(6, seg - 4));
|
|
77
|
-
const mesh = new THREE.Mesh(geo, materialFor(p));
|
|
78
|
-
mesh.name = p.id;
|
|
79
|
-
mesh.userData.destructionPart = p.id;
|
|
80
|
-
mesh.userData.destructionMaterial = p.material;
|
|
81
|
-
mesh.position.set(p.position[0], p.position[1], p.position[2]);
|
|
82
|
-
if (p.rotation) mesh.rotation.set(p.rotation[0], p.rotation[1], p.rotation[2], "XYZ");
|
|
83
|
-
mesh.castShadow = shadows && !TRANSPARENT[p.material];
|
|
84
|
-
mesh.receiveShadow = shadows;
|
|
85
|
-
root.add(mesh);
|
|
86
227
|
}
|
|
87
|
-
return
|
|
228
|
+
return {
|
|
229
|
+
group,
|
|
230
|
+
objects: sceneryObjects,
|
|
231
|
+
surfaces,
|
|
232
|
+
colliders,
|
|
233
|
+
playerStart,
|
|
234
|
+
wallZ,
|
|
235
|
+
tops,
|
|
236
|
+
tapSpout,
|
|
237
|
+
sun
|
|
238
|
+
};
|
|
88
239
|
}
|
|
89
240
|
|
|
90
|
-
// src/
|
|
91
|
-
var
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
old_tv: "Old TV",
|
|
97
|
-
steel_sheet: "Steel sheet on a stand",
|
|
98
|
-
glass_pane: "Glass pane",
|
|
99
|
-
concrete_block: "Concrete block",
|
|
100
|
-
brick_column: "Brick column",
|
|
101
|
-
building_frame: "Building frame",
|
|
102
|
-
car_door: "Car door",
|
|
103
|
-
small_tree: "Small tree",
|
|
104
|
-
potted_plant: "Potted plant",
|
|
105
|
-
coffee_cup: "Cup of coffee",
|
|
106
|
-
glass_bottle: "Glass bottle",
|
|
107
|
-
wooden_crate: "Wooden crate",
|
|
108
|
-
steel_drum: "Steel drum",
|
|
109
|
-
plastic_bucket: "Plastic bucket",
|
|
110
|
-
jerrycan: "Jerrycan",
|
|
111
|
-
gas_bottle: "Gas bottle"
|
|
112
|
-
},
|
|
113
|
-
groups: { furniture: "Furniture", structure: "Structure", vehicles: "Vehicles", plants: "Plants", items: "Items", containers: "Containers" },
|
|
114
|
-
text: {
|
|
115
|
-
"locale.decimal": ".",
|
|
116
|
-
yes: "yes",
|
|
117
|
-
no: "no",
|
|
118
|
-
piece: "piece",
|
|
119
|
-
coords: "x {x} \xB7 z {z}",
|
|
120
|
-
"count.piece.one": "1 piece",
|
|
121
|
-
"count.piece.other": "{n} pieces",
|
|
122
|
-
"count.joint.one": "1 joint",
|
|
123
|
-
"count.joint.other": "{n} joints",
|
|
124
|
-
"count.dent.one": "1 dent",
|
|
125
|
-
"count.dent.other": "{n} dents",
|
|
126
|
-
"count.hole.one": "1 hole",
|
|
127
|
-
"count.hole.other": "{n} holes",
|
|
128
|
-
"count.crack.one": "1 crack",
|
|
129
|
-
"count.crack.other": "{n} cracks",
|
|
130
|
-
"joint.generic": "joint",
|
|
131
|
-
"lost.broken": "broke",
|
|
132
|
-
"lost.melted": "melted away",
|
|
133
|
-
"lost.ashes": "burned to ash",
|
|
134
|
-
"tool.none": "by hand only",
|
|
135
|
-
"arena.target_wall": "Target wall",
|
|
136
|
-
"arena.table": "Table",
|
|
137
|
-
"arena.shelf": "Shelf",
|
|
138
|
-
"surface.floor": "on the floor",
|
|
139
|
-
"surface.ground": "on the floor",
|
|
140
|
-
"surface.table": "on the table",
|
|
141
|
-
"by.break": "broken loose",
|
|
142
|
-
"by.tool": "undone with a tool",
|
|
143
|
-
"by.part_lost": "the part next to it was lost",
|
|
144
|
-
"dmg.ballistic": "ballistic",
|
|
145
|
-
"dmg.blunt": "blunt",
|
|
146
|
-
"dmg.slash": "slash",
|
|
147
|
-
"dmg.explosive": "blast",
|
|
148
|
-
"dmg.fire": "fire",
|
|
149
|
-
"dmg.heat": "heat",
|
|
150
|
-
"effect.caffeine": "caffeine",
|
|
151
|
-
"effect.alcohol": "alcohol",
|
|
152
|
-
"effect.poison": "poison",
|
|
153
|
-
"effect.disgust": "disgust",
|
|
154
|
-
"ev.dent": "{where}: dent of {mm} mm, {hp}",
|
|
155
|
-
"ev.hole.through": "{where}: {mm} mm hole went through, {hp}",
|
|
156
|
-
"ev.hole.stuck": "{where}: {mm} mm hole, did not go through, {hp}",
|
|
157
|
-
"ev.dents": "{where}: {n} dents, deepest {mm} mm, {hp}",
|
|
158
|
-
"ev.holes": "{where}: {n} holes of {mm} mm, {thr} went through, {hp}",
|
|
159
|
-
"ev.leak.tail": ", leaking {liquid} at {rate} l/s",
|
|
160
|
-
"ev.leak.alone": "{where}: leaking {liquid} at {rate} l/s",
|
|
161
|
-
"ev.crack": "{where}: cracked, {rays} rays up to {cm} cm, {hp}",
|
|
162
|
-
"ev.ricochet": "{where}: bullet ricocheted, {energy} J left",
|
|
163
|
-
"ev.fracture.shatter": "{where}: shattered into {n} shards",
|
|
164
|
-
"ev.fracture.crumble": "{where}: crumbled into {n} chunks",
|
|
165
|
-
"ev.fracture.splinter": "{where}: splintered into {n} pieces",
|
|
166
|
-
"ev.fracture.tear": "{where}: torn into {n} scraps",
|
|
167
|
-
"ev.fracture.snap": "{where}: snapped into {n} pieces",
|
|
168
|
-
"ev.fracture.none": "{where}: broke into {n} pieces",
|
|
169
|
-
"ev.detach": "{obj}: {a} came off {b} ({by}); the object now has {pieces}",
|
|
170
|
-
"ev.detach.many.break": "{obj}: {joints} broke loose; the object now has {pieces}",
|
|
171
|
-
"ev.detach.many.tool": "{obj}: {joints} undone with a tool; the object now has {pieces}",
|
|
172
|
-
"ev.lost.tail": " and came loose from {joints}; the object now has {pieces}",
|
|
173
|
-
"ev.lost.alone": "{where}: {verb} and came loose from {joints}; the object now has {pieces}",
|
|
174
|
-
"ev.cracks": "{where}: cracking under the load, {n} cracks so far, longest {cm} cm, {hp}",
|
|
175
|
-
"ev.disassemble": "{obj}: undoing the {kind} between {a} and {b} with {tool}: {pct}%",
|
|
176
|
-
"ev.ignite": "{where}: caught fire at {temp} \xB0C",
|
|
177
|
-
"ev.ignite.many": "{obj}: {n} parts caught fire at {temp} \xB0C ({parts})",
|
|
178
|
-
"ev.extinguish": "{where}: fire put out",
|
|
179
|
-
"ev.extinguish.many": "{obj}: fire put out on {n} parts ({parts})",
|
|
180
|
-
"ev.burnout": "{where}: burned to ash",
|
|
181
|
-
"ev.melt": "{where}: melting, {pct}% melted away",
|
|
182
|
-
"ev.drip": "{where}: dripping molten material",
|
|
183
|
-
"ev.explode": "{where}: exploded, {energy} J within {radius} m",
|
|
184
|
-
"ev.destroyed": "{obj}: destroyed",
|
|
185
|
-
"ev.damage": "{where}: {type} damage {amount}, {hp}",
|
|
186
|
-
"ev.damage.overload": "{where}: crushed by the weight it carries, lost {amount} hp so far, {hp}",
|
|
187
|
-
"ev.stress": "{where}: overloaded at {stress}% of its capacity, carrying {load} kg",
|
|
188
|
-
"ev.collapse": "{obj}: collapsed, {pieces} fell ({mass} kg): {parts}",
|
|
189
|
-
"ev.stain_ignite": "The {liquid} stain at x {x} \xB7 z {z} caught fire and will burn for about {seconds} s",
|
|
190
|
-
"ev.stain_burned": "The burning stain burned itself out",
|
|
191
|
-
"ev.fire_dries": "Fire dried the wet stains nearby",
|
|
192
|
-
"fl.fill": "{who}: filled with {liquid} (+{liters} l)",
|
|
193
|
-
"fl.pour": "{who}: pouring {liquid} at {rate} l/s",
|
|
194
|
-
"fl.spill": "{who}: spilled {liquid} over the lip at {rate} l/s",
|
|
195
|
-
"fl.leak": "{who}: leaking {liquid} at {rate} l/s",
|
|
196
|
-
"fl.drink": "{who}: drank {liters} l, thirst +{thirst}",
|
|
197
|
-
"fl.burn": "too hot: burned the mouth",
|
|
198
|
-
"fl.empty": "{who}: empty",
|
|
199
|
-
"fl.splash": "Splash of {liquid}",
|
|
200
|
-
"fl.stain": "Stain of {liquid} {surface} at {where}",
|
|
201
|
-
"fl.stains": "{n} stains of {liquid} {surface}, the latest at {where}",
|
|
202
|
-
"fl.dried": "A stain dried up",
|
|
203
|
-
"fl.dried.mark": "A stain dried and left a mark",
|
|
204
|
-
"fl.dried.known": "The {liquid} stain {surface} at {where} dried up",
|
|
205
|
-
"fl.dried.known.mark": "The {liquid} stain {surface} at {where} dried and left a mark",
|
|
206
|
-
"fl.dried.many": "{n} {liquid} stains dried up",
|
|
207
|
-
"fl.dried.many.mark": "{n} {liquid} stains dried and left marks",
|
|
208
|
-
"fl.mix": "{who}: {from} mixed into {into}",
|
|
209
|
-
"act.anim_start": '{name}: animation "{clip}" ({seconds} s)',
|
|
210
|
-
"act.anim_event": '{name}: "{clip}" event {type}',
|
|
211
|
-
"act.anim_end": '{name}: animation "{clip}" finished',
|
|
212
|
-
"act.module_loaded": 'Module "{name}" loaded: {items} item(s), {scenarios} scenario(s), contract checks: {result}',
|
|
213
|
-
"mod.all_ok": "all passed",
|
|
214
|
-
"mod.some_failed": "some FAILED",
|
|
215
|
-
"mod.destructible_ok": "{name}: destructible contract ok ({parts} parts, hit and state well-formed)",
|
|
216
|
-
"mod.destructible_fail": "{name}: destructible contract broken: {problems}",
|
|
217
|
-
"mod.animation_ok": '{name}: animation "{clip}" ok ({seconds} s)',
|
|
218
|
-
"mod.spec_ok": "{name}: spec ok ({parts} parts)",
|
|
219
|
-
"mod.spec_fail": "{name}: spec broken: {problems}",
|
|
220
|
-
"mod.spec_warn": "{name}: spec warning: {warning}",
|
|
221
|
-
"mod.animation_fail": '{name}: animation "{clip}" broken: {problems}',
|
|
222
|
-
"ui.animations": "Animations",
|
|
223
|
-
"ui.animations_aim": "Aim at an item or hold it to see its animations.",
|
|
224
|
-
"ui.animations_none": "This item brings no animations.",
|
|
225
|
-
"mod.bench_ok": "{name}: bench ok ({controls} controls, {layers} layers, {metrics} metrics)",
|
|
226
|
-
"mod.bench_fail": "{name}: bench broken: {problems}",
|
|
227
|
-
"mod.metric_failed": '{name}: metric "{metric}" threw and shows n/a: {error}',
|
|
228
|
-
"mod.listener_failed": "{name}: a bench listener threw: {error}",
|
|
229
|
-
"ui.bench": "Bench",
|
|
230
|
-
"ui.layers": "Layers",
|
|
231
|
-
"ui.metrics": "Counters",
|
|
232
|
-
"metric.fps": "FPS",
|
|
233
|
-
"metric.frame_ms": "ms/frame",
|
|
234
|
-
"metric.draw_calls": "Draw calls",
|
|
235
|
-
"metric.triangles": "Triangles",
|
|
236
|
-
"metric.objects": "Objects",
|
|
237
|
-
"metric.fragments": "Fragments",
|
|
238
|
-
"metric.particles": "Particles",
|
|
239
|
-
"metric.decals": "Decals",
|
|
240
|
-
"act.spawn": "Spawned {name} at x {x} \xB7 z {z}",
|
|
241
|
-
"act.clear": "Cleared the room",
|
|
242
|
-
"act.reset": "Repaired every object",
|
|
243
|
-
"act.tool": "Equipped {weapon}",
|
|
244
|
-
"act.shoot.hit": "{weapon}: shot, {energy} J, hit {where} at {dist} m",
|
|
245
|
-
"act.shoot.miss": "{weapon}: shot, {energy} J, hit nothing",
|
|
246
|
-
"act.shoot.pellets": "{weapon}: shot, {n} projectiles of up to {energy} J, {hits} hit {where} at {dist} m",
|
|
247
|
-
"act.melee.hit": "{weapon}: strike, {energy} J, hit {where} at {dist} m",
|
|
248
|
-
"act.melee.miss": "{weapon}: struck the air",
|
|
249
|
-
"act.flame.hit": "{weapon}: flame at {temp} \xB0C on {where}",
|
|
250
|
-
"act.flame.air": "{weapon}: flame, nothing in range",
|
|
251
|
-
"act.heat.hit": "{weapon}: heating {where} at {temp} \xB0C",
|
|
252
|
-
"act.heat.air": "{weapon}: nothing in range",
|
|
253
|
-
"act.throw.grenade": "{weapon}: thrown, fuse {fuse} s",
|
|
254
|
-
"act.throw.molotov": "{weapon}: thrown, breaks on impact",
|
|
255
|
-
"act.launch": "{weapon}: launched, arms after {arm} m",
|
|
256
|
-
"act.place": "{weapon}: charge set down, waits for {triggers}",
|
|
257
|
-
"act.tripwire": "{weapon}: {who} crossed the trip wire",
|
|
258
|
-
"act.dud": "{weapon}: hit before arming ({distance} m), did not explode",
|
|
259
|
-
"act.backblast": "{weapon}: blast behind the tube, {radius} m, {n} objects in the cone, {player} on the shooter",
|
|
260
|
-
"act.blast_player": "{weapon}: you are in the {zone} zone, {distance} m away (stun {stun} s)",
|
|
261
|
-
"hud.no_charges": "No charge set down waits for the remote detonator",
|
|
262
|
-
"blast.zone.lethal": "lethal",
|
|
263
|
-
"blast.zone.injury": "injury",
|
|
264
|
-
"act.explode": "{weapon}: exploded, {energy} J within {radius} m, {n} objects in range",
|
|
265
|
-
"act.molotov_fire": "{weapon}: fire on the ground, radius {radius} m for {seconds} s",
|
|
266
|
-
"act.tool.none": "{weapon}: nothing in reach",
|
|
267
|
-
"act.tool.nojoint": "{weapon}: no joint near {where}",
|
|
268
|
-
"act.tool.refused": "{weapon} cannot undo the {kind} of {obj}; it needs: {need}",
|
|
269
|
-
"act.extinguish.hit": "{weapon}: foam on {what}",
|
|
270
|
-
"act.extinguish.air": "{weapon}: foam, nothing in range",
|
|
271
|
-
"act.grab": "Grabbed {name} ({kg} kg)",
|
|
272
|
-
"act.drop": "Dropped {name}",
|
|
273
|
-
"act.throw": "Threw {name} at {speed} m/s",
|
|
274
|
-
"act.landing": "{name} landed at {speed} m/s: {energy} J",
|
|
275
|
-
"act.impact": "{name} hit {where} at {speed} m/s: {energy} J of blunt damage",
|
|
276
|
-
"act.use_item": "{name}: {use}",
|
|
277
|
-
"act.tap.on": "Tap opened",
|
|
278
|
-
"act.tap.off": "Tap closed",
|
|
279
|
-
"use.drink": "drink",
|
|
280
|
-
"use.sip": "sip",
|
|
281
|
-
"use.chug": "chug",
|
|
282
|
-
"use.pour": "pour",
|
|
283
|
-
"use.pour_out": "pour out",
|
|
284
|
-
"use.open": "open",
|
|
285
|
-
"use.close": "close",
|
|
286
|
-
"hud.reloading": "Reloading...",
|
|
287
|
-
"hud.empty": "Empty",
|
|
288
|
-
"hud.no_fuel": "Out of fuel",
|
|
289
|
-
"hud.fault": "Jammed ({fault}): press X to clear",
|
|
290
|
-
"hud.safe": "Safety on: press B",
|
|
291
|
-
"hud.clearing": "Clearing the jam...",
|
|
292
|
-
"hud.safety_on": "Safety on",
|
|
293
|
-
"hud.safety_off": "Safety off",
|
|
294
|
-
"fault.misfire": "misfire",
|
|
295
|
-
"fault.stovepipe": "stovepipe",
|
|
296
|
-
"fault.double_feed": "double feed",
|
|
297
|
-
"fault.failure_to_extract": "failure to extract",
|
|
298
|
-
"hud.nothing_to_grab": "Nothing to grab here",
|
|
299
|
-
"hud.cannot_grab": "{name} cannot be picked up",
|
|
300
|
-
"hud.too_heavy": "{name} is too heavy ({kg} kg)",
|
|
301
|
-
"hud.empty_container": "{name} is empty",
|
|
302
|
-
"hud.refused": "Wrong tool for the {kind}",
|
|
303
|
-
"hud.drop": "drop",
|
|
304
|
-
"hud.rotate": "rotate",
|
|
305
|
-
"hud.throw": "throw",
|
|
306
|
-
"hud.tilt": "tilt (hold)",
|
|
307
|
-
"hud.equip": "equip {weapon}",
|
|
308
|
-
"ui.help": "Click: look \xB7 WASD: walk \xB7 Shift: run \xB7 Space: jump \xB7 1-0 / wheel: tool \xB7 LMB: use \xB7 E: grab/drop \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 Tab: panels \xB7 Esc: free the mouse",
|
|
309
|
-
"ui.log": "Behaviour log",
|
|
310
|
-
"ui.filter.action": "actions",
|
|
311
|
-
"ui.filter.event": "events",
|
|
312
|
-
"ui.filter.check": "checks",
|
|
313
|
-
"ui.autoscroll": "autoscroll",
|
|
314
|
-
"log.export_cancelled": "export cancelled",
|
|
315
|
-
"ui.copy_json": "Copy JSON",
|
|
316
|
-
"ui.download_ndjson": "Download NDJSON",
|
|
317
|
-
"ui.download_txt": "Download TXT",
|
|
318
|
-
"ui.clear_log": "Clear",
|
|
319
|
-
"ui.catalog": "Catalogue",
|
|
320
|
-
"ui.clear": "Clear",
|
|
321
|
-
"ui.reset_all": "Reset all",
|
|
322
|
-
"ui.slow": "Slow motion 0.25\xD7",
|
|
323
|
-
"ui.fast": "Time \xD710",
|
|
324
|
-
"ui.tap": "Tap",
|
|
325
|
-
"ui.blast.shelter": "Shelter",
|
|
326
|
-
"ui.blast.fragmentHits": "Fragments",
|
|
327
|
-
"ui.blast.confinement": "Confinement",
|
|
328
|
-
"ui.blast.shelter.hint": "Blasts: walls and other parts between the blast and the target take their share",
|
|
329
|
-
"ui.blast.fragmentHits.hint": "Blasts: the pieces of what breaks fly on and hit what is behind",
|
|
330
|
-
"ui.blast.confinement.hint": "Blasts: pressure builds up in an enclosed room",
|
|
331
|
-
"metric.blast_shelter": "Blast reaching (%)",
|
|
332
|
-
"metric.blast_confinement": "Blast pressure (x)",
|
|
333
|
-
"metric.blast_fragments": "Fragment hits",
|
|
334
|
-
"act.blast_report": "Blast: {shelter}% reached the most sheltered target, pressure x{confinement}, {fragments} fragment hits, {n} targets",
|
|
335
|
-
"ui.quality": "Quality",
|
|
336
|
-
"ui.quality.low": "low",
|
|
337
|
-
"ui.quality.medium": "medium",
|
|
338
|
-
"ui.quality.high": "high",
|
|
339
|
-
"ui.equip": "Equip as a tool",
|
|
340
|
-
"ui.search_items": "Search items (all groups)",
|
|
341
|
-
"ui.search_tests": "Search tests",
|
|
342
|
-
"ui.no_results": "Nothing found",
|
|
343
|
-
"ui.objects": "{n} objects",
|
|
344
|
-
"ui.tests": "Tests",
|
|
345
|
-
"ui.run_all": "Run all",
|
|
346
|
-
"ui.running": "Running...",
|
|
347
|
-
"ui.report": "{passed} passed, {failed} failed",
|
|
348
|
-
"ui.panel": "Panels",
|
|
349
|
-
"ui.inspector": "Inspector",
|
|
350
|
-
"ui.aim_at_something": "Aim at an object",
|
|
351
|
-
"ins.material": "Material",
|
|
352
|
-
"ins.status": "Status",
|
|
353
|
-
"ins.burning": "burning",
|
|
354
|
-
"ins.hp": "Hit points",
|
|
355
|
-
"ins.temperature": "Temperature",
|
|
356
|
-
"ins.char": "Charred",
|
|
357
|
-
"ins.melt": "Melted",
|
|
358
|
-
"ins.liters": "Liquid",
|
|
359
|
-
"ins.damage": "Marks",
|
|
360
|
-
"ins.damage_counts": "{dents}, {holes}, {cracks}",
|
|
361
|
-
"ins.mass": "Mass",
|
|
362
|
-
"ins.load": "Load",
|
|
363
|
-
"ins.stress": "Stress",
|
|
364
|
-
"ins.joints": "Joints",
|
|
365
|
-
"ins.open": "open",
|
|
366
|
-
"sc.start": "Scenario: {name}",
|
|
367
|
-
"sc.pass": "Scenario passed: {name}",
|
|
368
|
-
"sc.fail": "Scenario FAILED: {name}",
|
|
369
|
-
"sc.error": "the scenario threw an error: {msg}",
|
|
370
|
-
"leg.still": "leg_1 still attached",
|
|
371
|
-
"leg.gone": "leg_1 came off",
|
|
372
|
-
"sc.steel_through_hole": "9 mm into a steel sheet makes a through hole",
|
|
373
|
-
"sc.steel_through_hole.check": "9 mm into 3 mm steel: {holes} hole(s) of {mm} mm, went through: {through}, sheet {hp} hp (expected: through)",
|
|
374
|
-
"sc.concrete_stops_bullet": "9 mm into a concrete block does not go through",
|
|
375
|
-
"sc.concrete_stops_bullet.check": "9 mm into 40 cm of concrete: went through: {through}, {dents}, {holes}, {cracks}, block {hp} hp (expected: stopped, wall behind untouched)",
|
|
376
|
-
"sc.hammer_dents_drum": "Hammer dents a steel drum",
|
|
377
|
-
"sc.hammer_dents_drum.check": "Hammer on the drum: {swings} swing(s), {dents} dent(s), first {depth} mm deep, body {hp} hp (expected: dented, not broken)",
|
|
378
|
-
"sc.glass_shatters": "Glass pane shatters on the first 9 mm shot",
|
|
379
|
-
"sc.glass_shatters.check": "Glass pane: broken after {shots} shot(s) into {n} shards (expected: broken on the first shot, at least 4 shards)",
|
|
380
|
-
"sc.shotgun_splinters_crate": "Shotgun splinters a wooden crate",
|
|
381
|
-
"sc.shotgun_splinters_crate.check": "12 ga on the crate: {shots} shot(s), {splinters} board(s) splintered, {holes} holes (expected: at least one splintered)",
|
|
382
|
-
"sc.kick_chair_leg": "A kick takes a chair leg off",
|
|
383
|
-
"sc.kick_chair_leg.check": "Chair: {kicks} kick(s), {groups} group(s), loose pieces {before} \u2192 {after}, {leg} (expected: a loose piece appears)",
|
|
384
|
-
"sc.kick_detaches_leg_via_tool": "The kick tool (hotbar) takes a chair leg off",
|
|
385
|
-
"sc.kick_detaches_leg_via_tool.check": "Chair, kick tool: {kicks} kick(s), loose pieces {before} \u2192 {after}, {leg} (expected: a loose piece appears)",
|
|
386
|
-
"sc.screwdriver_shelf_board": "Screwdriver takes a screwed shelf board off in about 2.5 s",
|
|
387
|
-
"sc.screwdriver_shelf_board.check": "Screwdriver on the shelf board: took {secs} s, expected about {want} s, the shelf is now in {pieces}",
|
|
388
|
-
"sc.wrong_tool_refused": "The wrong tool is refused",
|
|
389
|
-
"sc.wrong_tool_refused.check": "Screwdriver on a weld: refused {refused} time(s), joints opened {opened} (expected: refused, none opened)",
|
|
390
|
-
"sc.lighter_burns_crate": "A lighter ignites a wooden crate and it ends as ash",
|
|
391
|
-
"sc.lighter_burns_crate.lit": "Lighter on the crate: burning: {lit} (expected: yes)",
|
|
392
|
-
"sc.lighter_burns_crate.ash": "After burning for 500 s: {ash} of {total} boards are ash, most charred {char}% (expected: all ash or charred)",
|
|
393
|
-
"sc.torch_melts_steel": "Wood fire does not melt a steel drum, the cutting torch does",
|
|
394
|
-
"sc.torch_melts_steel.wood": "Burning crate next to the drum: drum peaked at {peak} \xB0C, melted {melt}%, crate still burning: {crate} (expected: no melt)",
|
|
395
|
-
"sc.torch_melts_steel.torch": "Cutting torch on the drum: {temp} \xB0C, melted {melt}% (expected: melting)",
|
|
396
|
-
"sc.bucket_melts_drips": "A plastic bucket melts and drips near fire",
|
|
397
|
-
"sc.bucket_melts_drips.check": "Bucket in flames: melted {melt}%, {temp} \xB0C, {drips} drip line(s) (expected: melting)",
|
|
398
|
-
"sc.gas_bottle_explodes": "A gas bottle in a fire explodes and pushes a crate",
|
|
399
|
-
"sc.gas_bottle_explodes.explode": "Gas bottle in fire: exploded after {seconds} s (expected: explodes)",
|
|
400
|
-
"sc.gas_bottle_explodes.push": "Crate 1.3 m away: moved {moved} m, lost {lost} (expected: pushed or broken)",
|
|
401
|
-
"sc.bottle_drop_shatters": "A glass bottle dropped from 1.5 m shatters",
|
|
402
|
-
"sc.bottle_drop_shatters.check": "Bottle dropped from 1.5 m: status {status}, {cracks} cracks, water stain: {stain} (expected: broken, water on the floor)",
|
|
403
|
-
"sc.throw_chair_at_wall": "A chair thrown at the wall deals blunt damage",
|
|
404
|
-
"sc.throw_chair_at_wall.check": "Chair thrown at the wall: {impacts} impact(s), first {energy} J, {wall} damage line(s) on the wall (expected: impact and damage)",
|
|
405
|
-
"sc.coffee_three_sips": "Three sips of coffee lower the level and log caffeine",
|
|
406
|
-
"sc.coffee_three_sips.check": "Coffee: {sips} sip(s), level {before} \u2192 {after} l, caffeine logged: {caffeine}, burned the mouth: {burn} (expected: 3 sips, caffeine)",
|
|
407
|
-
"sc.tilt_cup_spills": "Tilting the cup spills coffee and leaves a stain",
|
|
408
|
-
"sc.tilt_cup_spills.check": "Cup tilted: level {before} \u2192 {after} l, {stains} coffee stain(s) (expected: spilled with a stain)",
|
|
409
|
-
"sc.jerrycan_leaks_fuel": "Shooting the jerrycan makes a leak and a fuel stain",
|
|
410
|
-
"sc.jerrycan_leaks_fuel.check": "Jerrycan shot: {streams} leak stream(s), fuel stain: {stain}, {left} l left of 20, hole line mentions the leak: {leak} (expected: leaking, stain)",
|
|
411
|
-
"sc.fuel_stain_burns": "A lit fuel stain burns",
|
|
412
|
-
"sc.fuel_stain_burns.stain": "Fuel stain on the floor after the leak: {stain} (expected: yes)",
|
|
413
|
-
"sc.fuel_stain_burns.ignite": "Lighter on the stain: {ignited} ignition(s), fire reached nearby objects: {near} (expected: ignited)",
|
|
414
|
-
"sc.fuel_stain_burns.out": "After 30 s the stain burned out: {burned} (expected: yes)",
|
|
415
|
-
"roof.standing": "roof standing",
|
|
416
|
-
"roof.fell": "roof fell",
|
|
417
|
-
"sc.one_column_roof_stands": "One column destroyed: the roof stays up",
|
|
418
|
-
"sc.one_column_roof_stands.check": "Frame with one column gone: {roof}, heaviest collapse {heavy} kg, peak stress {stress}% (expected: standing, no big collapse)",
|
|
419
|
-
"sc.two_columns_roof_collapses": "Two columns destroyed: the roof collapses",
|
|
420
|
-
"sc.two_columns_roof_collapses.check": "Frame with two columns gone: the others at {stress}% right away, then heavy loose pieces: {pieces} ({kg} kg), collapse event logged: {logged} (expected: the roof comes down)",
|
|
421
|
-
"sc.extinguisher_puts_out_fire": "The extinguisher puts out a burning crate",
|
|
422
|
-
"sc.extinguisher_puts_out_fire.burning": "Crate set on fire: burning: {burning} (expected: yes)",
|
|
423
|
-
"sc.extinguisher_puts_out_fire.out": "After the extinguisher: still burning: {burning}, {events} extinguish event(s) (expected: out)"
|
|
424
|
-
}
|
|
241
|
+
// src/bench.ts
|
|
242
|
+
var snap = (control, value) => {
|
|
243
|
+
const clamped = Math.min(control.max, Math.max(control.min, value));
|
|
244
|
+
if (!control.step) return clamped;
|
|
245
|
+
const stepped = control.min + Math.round((clamped - control.min) / control.step) * control.step;
|
|
246
|
+
return Number(Math.min(control.max, stepped).toFixed(10));
|
|
425
247
|
};
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
building_frame: "Estrutura de pr\xE9dio",
|
|
437
|
-
car_door: "Porta de carro",
|
|
438
|
-
small_tree: "\xC1rvore pequena",
|
|
439
|
-
potted_plant: "Vaso de planta",
|
|
440
|
-
coffee_cup: "Copo de caf\xE9",
|
|
441
|
-
glass_bottle: "Garrafa de vidro",
|
|
442
|
-
wooden_crate: "Caixote de madeira",
|
|
443
|
-
steel_drum: "Tambor de a\xE7o",
|
|
444
|
-
plastic_bucket: "Balde de pl\xE1stico",
|
|
445
|
-
jerrycan: "Gal\xE3o",
|
|
446
|
-
gas_bottle: "Botij\xE3o de g\xE1s"
|
|
447
|
-
},
|
|
448
|
-
groups: { furniture: "M\xF3veis", structure: "Estrutura", vehicles: "Ve\xEDculos", plants: "Plantas", items: "Itens", containers: "Recipientes" },
|
|
449
|
-
text: {
|
|
450
|
-
"locale.decimal": ",",
|
|
451
|
-
yes: "sim",
|
|
452
|
-
no: "n\xE3o",
|
|
453
|
-
piece: "pe\xE7a",
|
|
454
|
-
coords: "x {x} \xB7 z {z}",
|
|
455
|
-
"count.piece.one": "1 pe\xE7a",
|
|
456
|
-
"count.piece.other": "{n} pe\xE7as",
|
|
457
|
-
"count.joint.one": "1 encaixe",
|
|
458
|
-
"count.joint.other": "{n} encaixes",
|
|
459
|
-
"count.dent.one": "1 amassado",
|
|
460
|
-
"count.dent.other": "{n} amassados",
|
|
461
|
-
"count.hole.one": "1 furo",
|
|
462
|
-
"count.hole.other": "{n} furos",
|
|
463
|
-
"count.crack.one": "1 trinca",
|
|
464
|
-
"count.crack.other": "{n} trincas",
|
|
465
|
-
"joint.generic": "junta",
|
|
466
|
-
"lost.broken": "quebrou",
|
|
467
|
-
"lost.melted": "derreteu",
|
|
468
|
-
"lost.ashes": "virou cinza",
|
|
469
|
-
"tool.none": "s\xF3 com a m\xE3o",
|
|
470
|
-
"arena.target_wall": "Parede de alvo",
|
|
471
|
-
"arena.table": "Mesa",
|
|
472
|
-
"arena.shelf": "Estante",
|
|
473
|
-
"surface.floor": "no ch\xE3o",
|
|
474
|
-
"surface.ground": "no ch\xE3o",
|
|
475
|
-
"surface.table": "na mesa",
|
|
476
|
-
"by.break": "arrebentou",
|
|
477
|
-
"by.tool": "desmontado com ferramenta",
|
|
478
|
-
"by.part_lost": "a pe\xE7a ao lado se perdeu",
|
|
479
|
-
"dmg.ballistic": "de tiro",
|
|
480
|
-
"dmg.blunt": "de impacto",
|
|
481
|
-
"dmg.slash": "de corte",
|
|
482
|
-
"dmg.explosive": "de explos\xE3o",
|
|
483
|
-
"dmg.fire": "de fogo",
|
|
484
|
-
"dmg.heat": "de calor",
|
|
485
|
-
"effect.caffeine": "cafe\xEDna",
|
|
486
|
-
"effect.alcohol": "\xE1lcool",
|
|
487
|
-
"effect.poison": "veneno",
|
|
488
|
-
"effect.disgust": "nojo",
|
|
489
|
-
"ev.dent": "{where}: amassado de {mm} mm, {hp}",
|
|
490
|
-
"ev.hole.through": "{where}: furo de {mm} mm atravessou, {hp}",
|
|
491
|
-
"ev.hole.stuck": "{where}: furo de {mm} mm n\xE3o atravessou, {hp}",
|
|
492
|
-
"ev.dents": "{where}: {n} amassados, o mais fundo com {mm} mm, {hp}",
|
|
493
|
-
"ev.holes": "{where}: {n} furos de {mm} mm, {thr} atravessaram, {hp}",
|
|
494
|
-
"ev.leak.tail": ", vazando {liquid} a {rate} l/s",
|
|
495
|
-
"ev.leak.alone": "{where}: vazando {liquid} a {rate} l/s",
|
|
496
|
-
"ev.crack": "{where}: trincou, {rays} raios de at\xE9 {cm} cm, {hp}",
|
|
497
|
-
"ev.ricochet": "{where}: a bala ricocheteou, sobraram {energy} J",
|
|
498
|
-
"ev.fracture.shatter": "{where}: estilha\xE7ou em {n} cacos",
|
|
499
|
-
"ev.fracture.crumble": "{where}: esfarelou em {n} peda\xE7os",
|
|
500
|
-
"ev.fracture.splinter": "{where}: lascou em {n} peda\xE7os",
|
|
501
|
-
"ev.fracture.tear": "{where}: rasgou em {n} retalhos",
|
|
502
|
-
"ev.fracture.snap": "{where}: partiu em {n} peda\xE7os",
|
|
503
|
-
"ev.fracture.none": "{where}: quebrou em {n} peda\xE7os",
|
|
504
|
-
"ev.detach": "{obj}: {a} soltou de {b} ({by}); o objeto agora tem {pieces}",
|
|
505
|
-
"ev.detach.many.break": "{obj}: {joints} arrebentaram; o objeto agora tem {pieces}",
|
|
506
|
-
"ev.detach.many.tool": "{obj}: {joints} soltos com ferramenta; o objeto agora tem {pieces}",
|
|
507
|
-
"ev.lost.tail": " e se soltou de {joints}; o objeto agora tem {pieces}",
|
|
508
|
-
"ev.lost.alone": "{where}: {verb} e se soltou de {joints}; o objeto agora tem {pieces}",
|
|
509
|
-
"ev.cracks": "{where}: rachando sob o peso, {n} trincas at\xE9 agora, a maior com {cm} cm, {hp}",
|
|
510
|
-
"ev.disassemble": "{obj}: soltando {kind} entre {a} e {b} com {tool}: {pct}%",
|
|
511
|
-
"ev.ignite": "{where}: pegou fogo a {temp} \xB0C",
|
|
512
|
-
"ev.ignite.many": "{obj}: {n} pe\xE7as pegaram fogo a {temp} \xB0C ({parts})",
|
|
513
|
-
"ev.extinguish": "{where}: fogo apagado",
|
|
514
|
-
"ev.extinguish.many": "{obj}: fogo apagado em {n} pe\xE7as ({parts})",
|
|
515
|
-
"ev.burnout": "{where}: virou cinza",
|
|
516
|
-
"ev.melt": "{where}: derretendo, {pct}% j\xE1 derreteu",
|
|
517
|
-
"ev.drip": "{where}: pingando material derretido",
|
|
518
|
-
"ev.explode": "{where}: explodiu, {energy} J em {radius} m",
|
|
519
|
-
"ev.destroyed": "{obj}: destru\xEDdo",
|
|
520
|
-
"ev.damage": "{where}: dano {type} de {amount}, {hp}",
|
|
521
|
-
"ev.damage.overload": "{where}: esmagado pelo peso que sustenta, perdeu {amount} hp at\xE9 agora, {hp}",
|
|
522
|
-
"ev.stress": "{where}: sobrecarregado a {stress}% da capacidade, sustentando {load} kg",
|
|
523
|
-
"ev.collapse": "{obj}: desabou, ca\xEDram {pieces} ({mass} kg): {parts}",
|
|
524
|
-
"ev.stain_ignite": "A mancha de {liquid} em x {x} \xB7 z {z} pegou fogo e vai queimar por uns {seconds} s",
|
|
525
|
-
"ev.stain_burned": "A mancha em chamas se consumiu",
|
|
526
|
-
"ev.fire_dries": "O fogo secou as manchas molhadas por perto",
|
|
527
|
-
"fl.fill": "{who}: encheu de {liquid} (+{liters} l)",
|
|
528
|
-
"fl.pour": "{who}: despejando {liquid} a {rate} l/s",
|
|
529
|
-
"fl.spill": "{who}: derramou {liquid} pela borda a {rate} l/s",
|
|
530
|
-
"fl.leak": "{who}: vazando {liquid} a {rate} l/s",
|
|
531
|
-
"fl.drink": "{who}: bebeu {liters} l, sede +{thirst}",
|
|
532
|
-
"fl.burn": "quente demais: queimou a boca",
|
|
533
|
-
"fl.empty": "{who}: vazio",
|
|
534
|
-
"fl.splash": "Respingo de {liquid}",
|
|
535
|
-
"fl.stain": "Mancha de {liquid} {surface} em {where}",
|
|
536
|
-
"fl.stains": "{n} manchas de {liquid} {surface}, a \xFAltima em {where}",
|
|
537
|
-
"fl.dried": "Uma mancha secou",
|
|
538
|
-
"fl.dried.mark": "Uma mancha secou e deixou marca",
|
|
539
|
-
"fl.dried.known": "A mancha de {liquid} {surface} em {where} secou",
|
|
540
|
-
"fl.dried.known.mark": "A mancha de {liquid} {surface} em {where} secou e deixou marca",
|
|
541
|
-
"fl.dried.many": "{n} manchas de {liquid} secaram",
|
|
542
|
-
"fl.dried.many.mark": "{n} manchas de {liquid} secaram e deixaram marca",
|
|
543
|
-
"fl.mix": "{who}: {from} misturou em {into}",
|
|
544
|
-
"act.anim_start": '{name}: anima\xE7\xE3o "{clip}" ({seconds} s)',
|
|
545
|
-
"act.anim_event": '{name}: "{clip}" evento {type}',
|
|
546
|
-
"act.anim_end": '{name}: anima\xE7\xE3o "{clip}" terminou',
|
|
547
|
-
"act.module_loaded": 'M\xF3dulo "{name}" carregado: {items} item(ns), {scenarios} cen\xE1rio(s), verifica\xE7\xF5es do contrato: {result}',
|
|
548
|
-
"mod.all_ok": "todas passaram",
|
|
549
|
-
"mod.some_failed": "algumas FALHARAM",
|
|
550
|
-
"mod.destructible_ok": "{name}: contrato de destrui\xE7\xE3o ok ({parts} pe\xE7as, golpe e estado bem formados)",
|
|
551
|
-
"mod.destructible_fail": "{name}: contrato de destrui\xE7\xE3o quebrado: {problems}",
|
|
552
|
-
"mod.animation_ok": '{name}: anima\xE7\xE3o "{clip}" ok ({seconds} s)',
|
|
553
|
-
"mod.spec_ok": "{name}: spec ok ({parts} pe\xE7as)",
|
|
554
|
-
"mod.spec_fail": "{name}: spec quebrada: {problems}",
|
|
555
|
-
"mod.spec_warn": "{name}: aviso na spec: {warning}",
|
|
556
|
-
"mod.animation_fail": '{name}: anima\xE7\xE3o "{clip}" quebrada: {problems}',
|
|
557
|
-
"ui.animations": "Anima\xE7\xF5es",
|
|
558
|
-
"ui.animations_aim": "Mire num item ou segure-o para ver as anima\xE7\xF5es.",
|
|
559
|
-
"ui.animations_none": "Este item n\xE3o traz anima\xE7\xF5es.",
|
|
560
|
-
"mod.bench_ok": "{name}: bancada ok ({controls} controles, {layers} camadas, {metrics} contadores)",
|
|
561
|
-
"mod.bench_fail": "{name}: bancada quebrada: {problems}",
|
|
562
|
-
"mod.metric_failed": '{name}: o contador "{metric}" lan\xE7ou erro e mostra n/a: {error}',
|
|
563
|
-
"mod.listener_failed": "{name}: um ouvinte da bancada lan\xE7ou erro: {error}",
|
|
564
|
-
"ui.bench": "Bancada",
|
|
565
|
-
"ui.layers": "Camadas",
|
|
566
|
-
"ui.metrics": "Contadores",
|
|
567
|
-
"metric.fps": "FPS",
|
|
568
|
-
"metric.frame_ms": "ms/quadro",
|
|
569
|
-
"metric.draw_calls": "Chamadas de desenho",
|
|
570
|
-
"metric.triangles": "Tri\xE2ngulos",
|
|
571
|
-
"metric.objects": "Objetos",
|
|
572
|
-
"metric.fragments": "Fragmentos",
|
|
573
|
-
"metric.particles": "Part\xEDculas",
|
|
574
|
-
"metric.decals": "Decalques",
|
|
575
|
-
"act.spawn": "Criou {name} em x {x} \xB7 z {z}",
|
|
576
|
-
"act.clear": "Limpou a sala",
|
|
577
|
-
"act.reset": "Consertou todos os objetos",
|
|
578
|
-
"act.tool": "Equipou {weapon}",
|
|
579
|
-
"act.shoot.hit": "{weapon}: disparo, {energy} J, acertou {where} a {dist} m",
|
|
580
|
-
"act.shoot.miss": "{weapon}: disparo, {energy} J, n\xE3o acertou nada",
|
|
581
|
-
"act.shoot.pellets": "{weapon}: disparo, {n} proj\xE9teis de at\xE9 {energy} J, {hits} acertaram {where} a {dist} m",
|
|
582
|
-
"act.melee.hit": "{weapon}: golpe, {energy} J, acertou {where} a {dist} m",
|
|
583
|
-
"act.melee.miss": "{weapon}: golpe no ar",
|
|
584
|
-
"act.flame.hit": "{weapon}: chama a {temp} \xB0C em {where}",
|
|
585
|
-
"act.flame.air": "{weapon}: chama, nada ao alcance",
|
|
586
|
-
"act.heat.hit": "{weapon}: esquentando {where} a {temp} \xB0C",
|
|
587
|
-
"act.heat.air": "{weapon}: nada ao alcance",
|
|
588
|
-
"act.throw.grenade": "{weapon}: arremessada, pavio de {fuse} s",
|
|
589
|
-
"act.throw.molotov": "{weapon}: arremessado, quebra no impacto",
|
|
590
|
-
"act.launch": "{weapon}: disparado, arma depois de {arm} m",
|
|
591
|
-
"act.place": "{weapon}: carga posta, espera {triggers}",
|
|
592
|
-
"act.tripwire": "{weapon}: {who} cruzou o fio",
|
|
593
|
-
"act.dud": "{weapon}: bateu antes de armar ({distance} m), n\xE3o explodiu",
|
|
594
|
-
"act.backblast": "{weapon}: sopro atr\xE1s do tubo, {radius} m, {n} objetos no cone, {player} no atirador",
|
|
595
|
-
"act.blast_player": "{weapon}: voc\xEA est\xE1 na zona {zone}, a {distance} m (atordoado {stun} s)",
|
|
596
|
-
"hud.no_charges": "Nenhuma carga posta espera o detonador",
|
|
597
|
-
"blast.zone.lethal": "letal",
|
|
598
|
-
"blast.zone.injury": "de ferimento",
|
|
599
|
-
"act.explode": "{weapon}: explodiu, {energy} J em {radius} m, {n} objetos ao alcance",
|
|
600
|
-
"act.molotov_fire": "{weapon}: fogo no ch\xE3o, raio de {radius} m por {seconds} s",
|
|
601
|
-
"act.tool.none": "{weapon}: nada ao alcance",
|
|
602
|
-
"act.tool.nojoint": "{weapon}: nenhuma junta perto de {where}",
|
|
603
|
-
"act.tool.refused": "{weapon} n\xE3o solta {kind} de {obj}; precisa de: {need}",
|
|
604
|
-
"act.extinguish.hit": "{weapon}: espuma em {what}",
|
|
605
|
-
"act.extinguish.air": "{weapon}: espuma, nada ao alcance",
|
|
606
|
-
"act.grab": "Pegou {name} ({kg} kg)",
|
|
607
|
-
"act.drop": "Soltou {name}",
|
|
608
|
-
"act.throw": "Arremessou {name} a {speed} m/s",
|
|
609
|
-
"act.landing": "{name} caiu a {speed} m/s: {energy} J",
|
|
610
|
-
"act.impact": "{name} bateu em {where} a {speed} m/s: {energy} J de dano de impacto",
|
|
611
|
-
"act.use_item": "{name}: {use}",
|
|
612
|
-
"act.tap.on": "Abriu a torneira",
|
|
613
|
-
"act.tap.off": "Fechou a torneira",
|
|
614
|
-
"use.drink": "beber",
|
|
615
|
-
"use.sip": "golinho",
|
|
616
|
-
"use.chug": "virar",
|
|
617
|
-
"use.pour": "despejar",
|
|
618
|
-
"use.pour_out": "esvaziar",
|
|
619
|
-
"use.open": "abrir",
|
|
620
|
-
"use.close": "fechar",
|
|
621
|
-
"hud.reloading": "Recarregando...",
|
|
622
|
-
"hud.empty": "Sem muni\xE7\xE3o",
|
|
623
|
-
"hud.no_fuel": "Sem combust\xEDvel",
|
|
624
|
-
"hud.fault": "Travou ({fault}): aperte X para destravar",
|
|
625
|
-
"hud.safe": "Trava ligada: aperte B",
|
|
626
|
-
"hud.clearing": "Destravando...",
|
|
627
|
-
"hud.safety_on": "Trava ligada",
|
|
628
|
-
"hud.safety_off": "Trava desligada",
|
|
629
|
-
"fault.misfire": "nega",
|
|
630
|
-
"fault.stovepipe": "chamin\xE9",
|
|
631
|
-
"fault.double_feed": "dupla alimenta\xE7\xE3o",
|
|
632
|
-
"fault.failure_to_extract": "falha de extra\xE7\xE3o",
|
|
633
|
-
"hud.nothing_to_grab": "Nada para pegar aqui",
|
|
634
|
-
"hud.cannot_grab": "{name} n\xE3o d\xE1 para pegar",
|
|
635
|
-
"hud.too_heavy": "{name} \xE9 pesado demais ({kg} kg)",
|
|
636
|
-
"hud.empty_container": "{name} est\xE1 vazio",
|
|
637
|
-
"hud.refused": "Ferramenta errada para {kind}",
|
|
638
|
-
"hud.drop": "soltar",
|
|
639
|
-
"hud.rotate": "girar",
|
|
640
|
-
"hud.throw": "arremessar",
|
|
641
|
-
"hud.tilt": "inclinar (segurar)",
|
|
642
|
-
"hud.equip": "equipar {weapon}",
|
|
643
|
-
"ui.help": "Clique: olhar \xB7 WASD: andar \xB7 Shift: correr \xB7 Espa\xE7o: pular \xB7 1-0 / roda: ferramenta \xB7 bot\xE3o esquerdo: usar \xB7 E: pegar/soltar \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 Tab: pain\xE9is \xB7 Esc: soltar o mouse",
|
|
644
|
-
"ui.log": "Registro de comportamento",
|
|
645
|
-
"ui.filter.action": "a\xE7\xF5es",
|
|
646
|
-
"ui.filter.event": "eventos",
|
|
647
|
-
"ui.filter.check": "testes",
|
|
648
|
-
"ui.autoscroll": "rolar sozinho",
|
|
649
|
-
"log.export_cancelled": "exporta\xE7\xE3o cancelada",
|
|
650
|
-
"ui.copy_json": "Copiar JSON",
|
|
651
|
-
"ui.download_ndjson": "Baixar NDJSON",
|
|
652
|
-
"ui.download_txt": "Baixar TXT",
|
|
653
|
-
"ui.clear_log": "Limpar",
|
|
654
|
-
"ui.catalog": "Cat\xE1logo",
|
|
655
|
-
"ui.clear": "Limpar",
|
|
656
|
-
"ui.reset_all": "Consertar tudo",
|
|
657
|
-
"ui.slow": "C\xE2mera lenta 0,25\xD7",
|
|
658
|
-
"ui.fast": "Tempo \xD710",
|
|
659
|
-
"ui.tap": "Torneira",
|
|
660
|
-
"ui.blast.shelter": "Abrigo",
|
|
661
|
-
"ui.blast.fragmentHits": "Estilha\xE7os",
|
|
662
|
-
"ui.blast.confinement": "Confinamento",
|
|
663
|
-
"ui.blast.shelter.hint": "Explos\xF5es: paredes e outras partes entre a explos\xE3o e o alvo ficam com a sua parte",
|
|
664
|
-
"ui.blast.fragmentHits.hint": "Explos\xF5es: os peda\xE7os do que quebra voam e acertam o que est\xE1 atr\xE1s",
|
|
665
|
-
"ui.blast.confinement.hint": "Explos\xF5es: a press\xE3o sobe num ambiente fechado",
|
|
666
|
-
"metric.blast_shelter": "Explos\xE3o que chegou (%)",
|
|
667
|
-
"metric.blast_confinement": "Press\xE3o da explos\xE3o (x)",
|
|
668
|
-
"metric.blast_fragments": "Estilha\xE7os que acertaram",
|
|
669
|
-
"act.blast_report": "Explos\xE3o: {shelter}% chegou ao alvo mais abrigado, press\xE3o x{confinement}, {fragments} estilha\xE7os acertaram, {n} alvos",
|
|
670
|
-
"ui.quality": "Qualidade",
|
|
671
|
-
"ui.quality.low": "baixa",
|
|
672
|
-
"ui.quality.medium": "m\xE9dia",
|
|
673
|
-
"ui.quality.high": "alta",
|
|
674
|
-
"ui.equip": "Equipar como ferramenta",
|
|
675
|
-
"ui.search_items": "Buscar item (todas as abas)",
|
|
676
|
-
"ui.search_tests": "Buscar teste",
|
|
677
|
-
"ui.no_results": "Nada encontrado",
|
|
678
|
-
"ui.objects": "{n} objetos",
|
|
679
|
-
"ui.tests": "Testes",
|
|
680
|
-
"ui.run_all": "Rodar todos",
|
|
681
|
-
"ui.running": "Rodando...",
|
|
682
|
-
"ui.report": "{passed} passaram, {failed} falharam",
|
|
683
|
-
"ui.panel": "Pain\xE9is",
|
|
684
|
-
"ui.inspector": "Inspetor",
|
|
685
|
-
"ui.aim_at_something": "Mire em um objeto",
|
|
686
|
-
"ins.material": "Material",
|
|
687
|
-
"ins.status": "Estado",
|
|
688
|
-
"ins.burning": "em chamas",
|
|
689
|
-
"ins.hp": "Pontos de vida",
|
|
690
|
-
"ins.temperature": "Temperatura",
|
|
691
|
-
"ins.char": "Carbonizado",
|
|
692
|
-
"ins.melt": "Derretido",
|
|
693
|
-
"ins.liters": "L\xEDquido",
|
|
694
|
-
"ins.damage": "Marcas",
|
|
695
|
-
"ins.damage_counts": "{dents}, {holes}, {cracks}",
|
|
696
|
-
"ins.mass": "Massa",
|
|
697
|
-
"ins.load": "Carga",
|
|
698
|
-
"ins.stress": "Tens\xE3o",
|
|
699
|
-
"ins.joints": "Juntas",
|
|
700
|
-
"ins.open": "aberta",
|
|
701
|
-
"sc.start": "Cen\xE1rio: {name}",
|
|
702
|
-
"sc.pass": "Cen\xE1rio passou: {name}",
|
|
703
|
-
"sc.fail": "Cen\xE1rio FALHOU: {name}",
|
|
704
|
-
"sc.error": "o cen\xE1rio lan\xE7ou um erro: {msg}",
|
|
705
|
-
"leg.still": "leg_1 continua presa",
|
|
706
|
-
"leg.gone": "leg_1 soltou",
|
|
707
|
-
"sc.steel_through_hole": "Tiro de 9 mm em chapa de a\xE7o faz furo que atravessa",
|
|
708
|
-
"sc.steel_through_hole.check": "9 mm em a\xE7o de 3 mm: {holes} furo(s) de {mm} mm, atravessou: {through}, chapa com {hp} hp (esperado: atravessar)",
|
|
709
|
-
"sc.concrete_stops_bullet": "Tiro de 9 mm em bloco de concreto n\xE3o atravessa",
|
|
710
|
-
"sc.concrete_stops_bullet.check": "9 mm em 40 cm de concreto: atravessou: {through}, {dents}, {holes}, {cracks}, bloco com {hp} hp (esperado: parou, parede atr\xE1s intacta)",
|
|
711
|
-
"sc.hammer_dents_drum": "Martelo amassa um tambor de a\xE7o",
|
|
712
|
-
"sc.hammer_dents_drum.check": "Martelo no tambor: {swings} golpe(s), {dents} amassado(s), o primeiro com {depth} mm, corpo com {hp} hp (esperado: amassado, sem quebrar)",
|
|
713
|
-
"sc.glass_shatters": "Vidro estilha\xE7a no primeiro tiro de 9 mm",
|
|
714
|
-
"sc.glass_shatters.check": "Vidro: quebrou depois de {shots} tiro(s) em {n} cacos (esperado: quebrado no primeiro tiro, pelo menos 4 cacos)",
|
|
715
|
-
"sc.shotgun_splinters_crate": "Escopeta lasca um caixote de madeira",
|
|
716
|
-
"sc.shotgun_splinters_crate.check": "Calibre 12 no caixote: {shots} tiro(s), {splinters} t\xE1bua(s) lascada(s), {holes} furos (esperado: pelo menos uma lascada)",
|
|
717
|
-
"sc.kick_chair_leg": "Um chute solta a perna da cadeira",
|
|
718
|
-
"sc.kick_chair_leg.check": "Cadeira: {kicks} chute(s), {groups} grupo(s), pe\xE7as soltas {before} \u2192 {after}, {leg} (esperado: aparece uma pe\xE7a solta)",
|
|
719
|
-
"sc.kick_detaches_leg_via_tool": "A ferramenta chute (barra de atalhos) solta a perna da cadeira",
|
|
720
|
-
"sc.kick_detaches_leg_via_tool.check": "Cadeira, ferramenta chute: {kicks} chute(s), pe\xE7as soltas {before} \u2192 {after}, {leg} (esperado: aparece uma pe\xE7a solta)",
|
|
721
|
-
"sc.screwdriver_shelf_board": "Chave de fenda solta a prateleira parafusada em cerca de 2,5 s",
|
|
722
|
-
"sc.screwdriver_shelf_board.check": "Chave de fenda na prateleira: levou {secs} s, esperado cerca de {want} s, a estante agora est\xE1 em {pieces}",
|
|
723
|
-
"sc.wrong_tool_refused": "A ferramenta errada \xE9 recusada",
|
|
724
|
-
"sc.wrong_tool_refused.check": "Chave de fenda numa solda: recusou {refused} vez(es), juntas abertas {opened} (esperado: recusar, nenhuma aberta)",
|
|
725
|
-
"sc.lighter_burns_crate": "Isqueiro acende o caixote de madeira e ele vira cinza",
|
|
726
|
-
"sc.lighter_burns_crate.lit": "Isqueiro no caixote: pegou fogo: {lit} (esperado: sim)",
|
|
727
|
-
"sc.lighter_burns_crate.ash": "Depois de queimar por 500 s: {ash} de {total} t\xE1buas viraram cinza, a mais queimada {char}% (esperado: tudo cinza ou carbonizado)",
|
|
728
|
-
"sc.torch_melts_steel": "Fogo de madeira n\xE3o derrete o tambor de a\xE7o, o ma\xE7arico derrete",
|
|
729
|
-
"sc.torch_melts_steel.wood": "Caixote em chamas ao lado do tambor: o tambor chegou a {peak} \xB0C, derreteu {melt}%, caixote ainda queimando: {crate} (esperado: sem derreter)",
|
|
730
|
-
"sc.torch_melts_steel.torch": "Ma\xE7arico no tambor: {temp} \xB0C, derreteu {melt}% (esperado: derretendo)",
|
|
731
|
-
"sc.bucket_melts_drips": "Balde de pl\xE1stico derrete e pinga perto do fogo",
|
|
732
|
-
"sc.bucket_melts_drips.check": "Balde nas chamas: derreteu {melt}%, {temp} \xB0C, {drips} linha(s) de pingos (esperado: derretendo)",
|
|
733
|
-
"sc.gas_bottle_explodes": "Botij\xE3o de g\xE1s no fogo explode e empurra um caixote",
|
|
734
|
-
"sc.gas_bottle_explodes.explode": "Botij\xE3o no fogo: explodiu depois de {seconds} s (esperado: explode)",
|
|
735
|
-
"sc.gas_bottle_explodes.push": "Caixote a 1,3 m: andou {moved} m, perdeu {lost} (esperado: empurrado ou quebrado)",
|
|
736
|
-
"sc.bottle_drop_shatters": "Garrafa de vidro solta de 1,5 m estilha\xE7a",
|
|
737
|
-
"sc.bottle_drop_shatters.check": "Garrafa solta de 1,5 m: estado {status}, {cracks} trincas, mancha de \xE1gua: {stain} (esperado: quebrada, \xE1gua no ch\xE3o)",
|
|
738
|
-
"sc.throw_chair_at_wall": "Cadeira arremessada na parede causa dano de impacto",
|
|
739
|
-
"sc.throw_chair_at_wall.check": "Cadeira na parede: {impacts} impacto(s), o primeiro com {energy} J, {wall} linha(s) de dano na parede (esperado: impacto e dano)",
|
|
740
|
-
"sc.coffee_three_sips": "Tr\xEAs goles de caf\xE9 baixam o n\xEDvel e registram cafe\xEDna",
|
|
741
|
-
"sc.coffee_three_sips.check": "Caf\xE9: {sips} gole(s), n\xEDvel {before} \u2192 {after} l, cafe\xEDna registrada: {caffeine}, queimou a boca: {burn} (esperado: 3 goles, cafe\xEDna)",
|
|
742
|
-
"sc.tilt_cup_spills": "Inclinar o copo derrama caf\xE9 e deixa mancha",
|
|
743
|
-
"sc.tilt_cup_spills.check": "Copo inclinado: n\xEDvel {before} \u2192 {after} l, {stains} mancha(s) de caf\xE9 (esperado: derramou com mancha)",
|
|
744
|
-
"sc.jerrycan_leaks_fuel": "Tiro no gal\xE3o faz vazamento e mancha de combust\xEDvel",
|
|
745
|
-
"sc.jerrycan_leaks_fuel.check": "Gal\xE3o baleado: {streams} jato(s) de vazamento, mancha de combust\xEDvel: {stain}, restam {left} l de 20, a linha do furo cita o vazamento: {leak} (esperado: vazando, mancha)",
|
|
746
|
-
"sc.fuel_stain_burns": "Mancha de combust\xEDvel acesa queima",
|
|
747
|
-
"sc.fuel_stain_burns.stain": "Mancha de combust\xEDvel no ch\xE3o depois do vazamento: {stain} (esperado: sim)",
|
|
748
|
-
"sc.fuel_stain_burns.ignite": "Isqueiro na mancha: {ignited} igni\xE7\xE3o(\xF5es), o fogo alcan\xE7ou objetos por perto: {near} (esperado: acendeu)",
|
|
749
|
-
"sc.fuel_stain_burns.out": "Depois de 30 s a mancha se consumiu: {burned} (esperado: sim)",
|
|
750
|
-
"roof.standing": "laje de p\xE9",
|
|
751
|
-
"roof.fell": "laje caiu",
|
|
752
|
-
"sc.one_column_roof_stands": "Pilar destru\xEDdo: a laje fica de p\xE9",
|
|
753
|
-
"sc.one_column_roof_stands.check": "Estrutura sem um pilar: {roof}, maior desabamento {heavy} kg, tens\xE3o m\xE1xima {stress}% (esperado: de p\xE9, sem desabamento grande)",
|
|
754
|
-
"sc.two_columns_roof_collapses": "Dois pilares destru\xEDdos: a laje desaba",
|
|
755
|
-
"sc.two_columns_roof_collapses.check": "Estrutura sem dois pilares: os outros logo a {stress}%, depois pe\xE7as pesadas soltas: {pieces} ({kg} kg), evento de desabamento registrado: {logged} (esperado: a laje desaba)",
|
|
756
|
-
"sc.extinguisher_puts_out_fire": "Extintor apaga um caixote em chamas",
|
|
757
|
-
"sc.extinguisher_puts_out_fire.burning": "Caixote incendiado: queimando: {burning} (esperado: sim)",
|
|
758
|
-
"sc.extinguisher_puts_out_fire.out": "Depois do extintor: ainda queimando: {burning}, {events} evento(s) de apagar (esperado: apagado)"
|
|
759
|
-
}
|
|
248
|
+
function fitValue(control, value) {
|
|
249
|
+
if (control.kind === "number") return typeof value === "number" && Number.isFinite(value) ? snap(control, value) : void 0;
|
|
250
|
+
if (control.kind === "boolean") return typeof value === "boolean" ? value : void 0;
|
|
251
|
+
if (control.kind === "choice") return typeof value === "string" && control.options.some((option) => option.id === value) ? value : void 0;
|
|
252
|
+
return void 0;
|
|
253
|
+
}
|
|
254
|
+
var fromText = (control, text) => {
|
|
255
|
+
if (control.kind === "number") return text.trim() === "" ? Number.NaN : Number(text);
|
|
256
|
+
if (control.kind === "boolean") return text === "1" ? true : text === "0" ? false : void 0;
|
|
257
|
+
return text;
|
|
760
258
|
};
|
|
761
|
-
var
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
if (
|
|
784
|
-
|
|
259
|
+
var toText = (value) => typeof value === "boolean" ? value ? "1" : "0" : String(value);
|
|
260
|
+
function encodeBenchState(controls, layers) {
|
|
261
|
+
const pairs = [];
|
|
262
|
+
for (const control of controls) if (control.value !== void 0) pairs.push(`${control.id}=${encodeURIComponent(toText(control.value))}`);
|
|
263
|
+
for (const layer of layers) pairs.push(`${layer.id}=${layer.on ? "1" : "0"}`);
|
|
264
|
+
return pairs.join(";");
|
|
265
|
+
}
|
|
266
|
+
function decodeBenchState(encoded, controls, layers) {
|
|
267
|
+
const out = /* @__PURE__ */ new Map();
|
|
268
|
+
for (const pair of encoded.split(";")) {
|
|
269
|
+
const at = pair.indexOf("=");
|
|
270
|
+
if (at <= 0) continue;
|
|
271
|
+
const id = pair.slice(0, at);
|
|
272
|
+
let text;
|
|
273
|
+
try {
|
|
274
|
+
text = decodeURIComponent(pair.slice(at + 1));
|
|
275
|
+
} catch {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const control = controls.find((c) => c.id === id);
|
|
279
|
+
if (control) {
|
|
280
|
+
const value = fitValue(control, fromText(control, text));
|
|
281
|
+
if (value !== void 0) out.set(id, value);
|
|
282
|
+
continue;
|
|
785
283
|
}
|
|
284
|
+
if (layers.some((layer) => layer.id === id) && (text === "1" || text === "0")) out.set(id, text === "1");
|
|
786
285
|
}
|
|
787
|
-
return
|
|
788
|
-
}
|
|
789
|
-
var chair = () => {
|
|
790
|
-
const legs = [
|
|
791
|
-
[-0.18, -0.18],
|
|
792
|
-
[0.18, -0.18],
|
|
793
|
-
[-0.18, 0.18],
|
|
794
|
-
[0.18, 0.18]
|
|
795
|
-
];
|
|
796
|
-
return {
|
|
797
|
-
name: "chair",
|
|
798
|
-
parts: [
|
|
799
|
-
box("seat", "wood_soft", [0.42, 0.04, 0.42], [0, 0.45, 0]),
|
|
800
|
-
...legs.map(([x, z], i) => box(`leg_${i + 1}`, "wood_soft", [0.04, 0.43, 0.04], [x, 0.215, z])),
|
|
801
|
-
box("backrest", "wood_soft", [0.4, 0.3, 0.03], [0, 0.64, -0.195])
|
|
802
|
-
],
|
|
803
|
-
joints: [...legs.map((_, i) => joint(`leg_${i + 1}`, "seat", "screw")), joint("backrest", "seat", "screw")],
|
|
804
|
-
tags: ["furniture"]
|
|
805
|
-
};
|
|
806
|
-
};
|
|
807
|
-
var crate = () => ({
|
|
808
|
-
name: "wooden crate",
|
|
809
|
-
parts: [
|
|
810
|
-
box("front", "wood_soft", [0.5, 0.5, 0.02], [0, 0.25, 0.24], { hp: 70 }),
|
|
811
|
-
box("back", "wood_soft", [0.5, 0.5, 0.02], [0, 0.25, -0.24], { hp: 70 }),
|
|
812
|
-
box("left", "wood_soft", [0.02, 0.5, 0.46], [-0.24, 0.25, 0], { hp: 70 }),
|
|
813
|
-
box("right", "wood_soft", [0.02, 0.5, 0.46], [0.24, 0.25, 0], { hp: 70 }),
|
|
814
|
-
box("bottom", "wood_soft", [0.46, 0.02, 0.46], [0, 0.01, 0], { hp: 70 })
|
|
815
|
-
],
|
|
816
|
-
joints: [
|
|
817
|
-
joint("front", "left", "nail"),
|
|
818
|
-
joint("front", "right", "nail"),
|
|
819
|
-
joint("back", "left", "nail"),
|
|
820
|
-
joint("back", "right", "nail"),
|
|
821
|
-
joint("front", "bottom", "nail"),
|
|
822
|
-
joint("back", "bottom", "nail"),
|
|
823
|
-
joint("left", "bottom", "nail"),
|
|
824
|
-
joint("right", "bottom", "nail")
|
|
825
|
-
],
|
|
826
|
-
tags: ["container"]
|
|
827
|
-
});
|
|
828
|
-
var drum = () => ({
|
|
829
|
-
name: "steel drum",
|
|
830
|
-
parts: [
|
|
831
|
-
cyl("drum_body", "steel", 0.29, 0.86, [0, 0.45, 0], { mass: 12, thickness: 12e-4 }),
|
|
832
|
-
cyl("drum_top", "steel", 0.29, 0.02, [0, 0.89, 0], { mass: 1.5, thickness: 12e-4 }),
|
|
833
|
-
cyl("drum_bottom", "steel", 0.29, 0.02, [0, 0.01, 0], { mass: 1.5, thickness: 12e-4 })
|
|
834
|
-
],
|
|
835
|
-
joints: [joint("drum_body", "drum_top", "weld"), joint("drum_body", "drum_bottom", "weld")],
|
|
836
|
-
tags: ["container"]
|
|
837
|
-
});
|
|
838
|
-
var steelSheet = () => ({
|
|
839
|
-
name: "steel sheet",
|
|
840
|
-
parts: [
|
|
841
|
-
box("sheet", "steel", [1, 1, 3e-3], [0, 1.1, 0]),
|
|
842
|
-
box("post_l", "steel", [0.04, 0.54, 0.04], [-0.35, 0.33, 0], { mass: 4 }),
|
|
843
|
-
box("post_r", "steel", [0.04, 0.54, 0.04], [0.35, 0.33, 0], { mass: 4 }),
|
|
844
|
-
box("foot_l", "steel", [0.06, 0.06, 0.5], [-0.35, 0.03, 0], { mass: 6 }),
|
|
845
|
-
box("foot_r", "steel", [0.06, 0.06, 0.5], [0.35, 0.03, 0], { mass: 6 })
|
|
846
|
-
],
|
|
847
|
-
joints: [joint("sheet", "post_l", "weld"), joint("sheet", "post_r", "weld"), joint("post_l", "foot_l", "weld"), joint("post_r", "foot_r", "weld")],
|
|
848
|
-
tags: ["structure"]
|
|
849
|
-
});
|
|
850
|
-
var glassPane = () => ({
|
|
851
|
-
name: "glass pane",
|
|
852
|
-
parts: [
|
|
853
|
-
box("pane", "glass", [0.8, 1, 6e-3], [0, 1.05, 0], { hp: 40 }),
|
|
854
|
-
box("frame_top", "aluminium", [0.88, 0.04, 0.04], [0, 1.57, 0], { mass: 1 }),
|
|
855
|
-
box("frame_bottom", "aluminium", [0.88, 0.04, 0.04], [0, 0.53, 0], { mass: 1 }),
|
|
856
|
-
box("frame_l", "aluminium", [0.04, 1.04, 0.04], [-0.42, 1.05, 0], { mass: 1 }),
|
|
857
|
-
box("frame_r", "aluminium", [0.04, 1.04, 0.04], [0.42, 1.05, 0], { mass: 1 }),
|
|
858
|
-
box("foot_l", "steel", [0.06, 0.5, 0.4], [-0.35, 0.25, 0], { mass: 5 }),
|
|
859
|
-
box("foot_r", "steel", [0.06, 0.5, 0.4], [0.35, 0.25, 0], { mass: 5 })
|
|
860
|
-
],
|
|
861
|
-
joints: [
|
|
862
|
-
joint("pane", "frame_top", "clip"),
|
|
863
|
-
joint("pane", "frame_bottom", "clip"),
|
|
864
|
-
joint("pane", "frame_l", "clip"),
|
|
865
|
-
joint("pane", "frame_r", "clip"),
|
|
866
|
-
joint("frame_bottom", "foot_l", "bolt"),
|
|
867
|
-
joint("frame_bottom", "foot_r", "bolt"),
|
|
868
|
-
joint("frame_l", "frame_top", "weld"),
|
|
869
|
-
joint("frame_r", "frame_top", "weld"),
|
|
870
|
-
joint("frame_l", "frame_bottom", "weld"),
|
|
871
|
-
joint("frame_r", "frame_bottom", "weld")
|
|
872
|
-
],
|
|
873
|
-
tags: ["structure"]
|
|
874
|
-
});
|
|
875
|
-
var concreteBlock = () => ({
|
|
876
|
-
name: "concrete block",
|
|
877
|
-
parts: [box("block", "concrete", [0.4, 0.4, 0.4], [0, 0.2, 0])],
|
|
878
|
-
tags: ["structure"]
|
|
879
|
-
});
|
|
880
|
-
var brickColumn = () => {
|
|
881
|
-
const parts = [];
|
|
882
|
-
const joints = [];
|
|
883
|
-
const layers = 8;
|
|
884
|
-
const id = (l, x, z) => `brick_${l}_${x}${z}`;
|
|
885
|
-
for (let l = 0; l < layers; l++) {
|
|
886
|
-
for (let x = 0; x < 2; x++) {
|
|
887
|
-
for (let z = 0; z < 2; z++) {
|
|
888
|
-
parts.push(box(id(l, x, z), "brick", [0.2, 0.2, 0.2], [(x - 0.5) * 0.2, 0.1 + l * 0.2, (z - 0.5) * 0.2], { anchored: l === 0 }));
|
|
889
|
-
if (x > 0) joints.push(joint(id(l, x - 1, z), id(l, x, z), "mortar"));
|
|
890
|
-
if (z > 0) joints.push(joint(id(l, x, z - 1), id(l, x, z), "mortar"));
|
|
891
|
-
if (l > 0) joints.push(joint(id(l - 1, x, z), id(l, x, z), "mortar"));
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
return { name: "brick column", parts, joints, tags: ["structure"] };
|
|
896
|
-
};
|
|
897
|
-
var frame = () => ({ ...buildFrame({ spanX: 4, spanZ: 4, columnsX: 2, columnsZ: 2, height: 2.8, floors: 1, walls: false, roof: true, columnCapacity: 4400 }), name: "building frame" });
|
|
898
|
-
var bucket = () => ({
|
|
899
|
-
name: "plastic bucket",
|
|
900
|
-
parts: [
|
|
901
|
-
cyl("bucket_body", "plastic", 0.14, 0.28, [0, 0.14, 0], { mass: 0.5, thickness: 2e-3 }),
|
|
902
|
-
box("handle", "plastic", [0.3, 0.012, 0.012], [0, 0.3, 0], { mass: 0.05 })
|
|
903
|
-
],
|
|
904
|
-
joints: [joint("handle", "bucket_body", "clip")],
|
|
905
|
-
tags: ["container"]
|
|
906
|
-
});
|
|
907
|
-
var bottle = () => ({
|
|
908
|
-
name: "glass bottle",
|
|
909
|
-
parts: [
|
|
910
|
-
cyl("bottle_body", "glass", 0.037, 0.17, [0, 0.085, 0], { mass: 0.3, thickness: 3e-3, contains: { fluid: "water", liters: 0.5 } }),
|
|
911
|
-
cyl("bottle_neck", "glass", 0.015, 0.09, [0, 0.215, 0], { mass: 0.08, thickness: 2e-3 })
|
|
912
|
-
],
|
|
913
|
-
joints: [joint("bottle_neck", "bottle_body", "fused")],
|
|
914
|
-
tags: ["item"]
|
|
915
|
-
});
|
|
916
|
-
var cup = () => ({
|
|
917
|
-
name: "coffee cup",
|
|
918
|
-
parts: [
|
|
919
|
-
cyl("cup", "ceramic", 0.05, 0.11, [0, 0.055, 0], { mass: 0.3, thickness: 4e-3, contains: { fluid: "coffee", liters: 0.25 } }),
|
|
920
|
-
box("cup_handle", "ceramic", [0.02, 0.06, 0.014], [0.068, 0.06, 0], { mass: 0.04 })
|
|
921
|
-
],
|
|
922
|
-
joints: [joint("cup_handle", "cup", "fused")],
|
|
923
|
-
tags: ["item"]
|
|
924
|
-
});
|
|
925
|
-
var jerrycan = () => ({
|
|
926
|
-
name: "jerrycan",
|
|
927
|
-
parts: [
|
|
928
|
-
box("tank", "plastic", [0.34, 0.45, 0.14], [0, 0.225, 0], { mass: 1.2, thickness: 3e-3, contains: { fluid: "fuel", liters: 20 } }),
|
|
929
|
-
cyl("cap", "plastic", 0.025, 0.03, [0.08, 0.465, 0], { mass: 0.05 }),
|
|
930
|
-
box("handle", "plastic", [0.1, 0.03, 0.03], [-0.07, 0.465, 0], { mass: 0.1 })
|
|
931
|
-
],
|
|
932
|
-
joints: [joint("cap", "tank", "screw"), joint("handle", "tank", "fused")],
|
|
933
|
-
tags: ["container"]
|
|
934
|
-
});
|
|
935
|
-
var gasBottle = () => ({
|
|
936
|
-
name: "gas bottle",
|
|
937
|
-
parts: [
|
|
938
|
-
cyl("cylinder", "steel", 0.15, 0.7, [0, 0.4, 0], {
|
|
939
|
-
mass: 11,
|
|
940
|
-
thickness: 3e-3,
|
|
941
|
-
contains: { fluid: "gas", liters: 13, pressurized: true },
|
|
942
|
-
explodes: { energy: 12e4, radius: 5, atC: 300 }
|
|
943
|
-
}),
|
|
944
|
-
cyl("valve", "steel", 0.03, 0.1, [0, 0.8, 0], { mass: 0.5 }),
|
|
945
|
-
cyl("foot_ring", "steel", 0.14, 0.05, [0, 0.025, 0], { mass: 1 })
|
|
946
|
-
],
|
|
947
|
-
joints: [joint("valve", "cylinder", "weld"), joint("foot_ring", "cylinder", "weld")],
|
|
948
|
-
tags: ["container"]
|
|
949
|
-
});
|
|
950
|
-
var tree = () => ({
|
|
951
|
-
name: "small tree",
|
|
952
|
-
parts: [
|
|
953
|
-
cyl("trunk", "green_wood", 0.08, 2.2, [0, 1.1, 0], { anchored: true }),
|
|
954
|
-
cyl("branch_1", "green_wood", 0.03, 0.8, [0.34, 1.7, 0], { rotation: [0, 0, -0.9] }),
|
|
955
|
-
cyl("branch_2", "green_wood", 0.03, 0.8, [-0.3, 1.9, 0.12], { rotation: [0, 0, 0.8] }),
|
|
956
|
-
cyl("branch_3", "green_wood", 0.025, 0.7, [0.05, 2, -0.28], { rotation: [0.8, 0, 0] }),
|
|
957
|
-
sph("crown_top", "foliage", 0.55, [0, 2.55, 0], { mass: 2 }),
|
|
958
|
-
sph("crown_1", "foliage", 0.38, [0.62, 2, 0], { mass: 1 }),
|
|
959
|
-
sph("crown_2", "foliage", 0.38, [-0.58, 2.2, 0.15], { mass: 1 }),
|
|
960
|
-
sph("crown_3", "foliage", 0.32, [0.05, 2.3, -0.55], { mass: 1 })
|
|
961
|
-
],
|
|
962
|
-
joints: [
|
|
963
|
-
joint("branch_1", "trunk", "stem"),
|
|
964
|
-
joint("branch_2", "trunk", "stem"),
|
|
965
|
-
joint("branch_3", "trunk", "stem"),
|
|
966
|
-
joint("crown_top", "trunk", "stem"),
|
|
967
|
-
joint("crown_1", "branch_1", "stem"),
|
|
968
|
-
joint("crown_2", "branch_2", "stem"),
|
|
969
|
-
joint("crown_3", "branch_3", "stem")
|
|
970
|
-
],
|
|
971
|
-
tags: ["plant"]
|
|
972
|
-
});
|
|
973
|
-
var pottedPlant = () => ({
|
|
974
|
-
name: "potted plant",
|
|
975
|
-
parts: [
|
|
976
|
-
cyl("pot", "ceramic", 0.15, 0.25, [0, 0.125, 0], { mass: 2, thickness: 8e-3 }),
|
|
977
|
-
cyl("soil", "soil", 0.14, 0.06, [0, 0.26, 0], { mass: 1.5 }),
|
|
978
|
-
sph("leaves_1", "foliage", 0.16, [0, 0.46, 0], { mass: 0.3 }),
|
|
979
|
-
sph("leaves_2", "foliage", 0.12, [0.12, 0.38, 0.04], { mass: 0.2 }),
|
|
980
|
-
sph("leaves_3", "foliage", 0.12, [-0.1, 0.4, -0.06], { mass: 0.2 })
|
|
981
|
-
],
|
|
982
|
-
joints: [joint("soil", "pot", "fused"), joint("leaves_1", "soil", "stem"), joint("leaves_2", "soil", "stem"), joint("leaves_3", "soil", "stem")],
|
|
983
|
-
tags: ["plant"]
|
|
984
|
-
});
|
|
985
|
-
var oldTv = () => ({
|
|
986
|
-
name: "old TV",
|
|
987
|
-
parts: [
|
|
988
|
-
box("stand", "wood_soft", [0.6, 0.35, 0.42], [0, 0.175, 0]),
|
|
989
|
-
box("shell", "plastic", [0.58, 0.44, 0.42], [0, 0.57, 0], { mass: 3, thickness: 4e-3 }),
|
|
990
|
-
box("screen", "glass", [0.42, 0.32, 0.012], [0, 0.57, 0.212], { mass: 1.5 }),
|
|
991
|
-
box("coil", "copper", [0.2, 0.1, 0.2], [0, 0.55, -0.05], { mass: 1.5 })
|
|
992
|
-
],
|
|
993
|
-
joints: [joint("shell", "stand", "screw"), joint("screen", "shell", "clip"), joint("coil", "shell", "screw")],
|
|
994
|
-
tags: ["furniture"]
|
|
995
|
-
});
|
|
996
|
-
var carDoor = () => ({
|
|
997
|
-
name: "car door",
|
|
998
|
-
parts: [
|
|
999
|
-
box("post", "steel", [0.1, 1.3, 0.1], [-0.58, 0.65, 0], { mass: 12, anchored: true }),
|
|
1000
|
-
box("skin", "steel", [1, 0.8, 0.05], [0, 0.75, 0], { mass: 14, thickness: 8e-4 }),
|
|
1001
|
-
box("window", "glass", [0.78, 0.32, 0.01], [0.04, 1.31, 0.01], { mass: 3 }),
|
|
1002
|
-
box("seal", "rubber", [0.84, 0.03, 0.03], [0.04, 1.14, 0.01], { mass: 0.3 })
|
|
1003
|
-
],
|
|
1004
|
-
joints: [
|
|
1005
|
-
joint("skin", "post", "hinge", { id: "hinge_upper" }),
|
|
1006
|
-
joint("skin", "post", "hinge", { id: "hinge_lower" }),
|
|
1007
|
-
joint("window", "seal", "clip"),
|
|
1008
|
-
joint("seal", "skin", "clip")
|
|
1009
|
-
],
|
|
1010
|
-
tags: ["vehicle"]
|
|
1011
|
-
});
|
|
1012
|
-
var sofa = () => ({
|
|
1013
|
-
name: "sofa",
|
|
1014
|
-
parts: [
|
|
1015
|
-
box("frame", "wood_soft", [1.8, 0.3, 0.8], [0, 0.15, 0], { mass: 14 }),
|
|
1016
|
-
box("seat_cushion", "foam", [1.5, 0.18, 0.62], [0, 0.39, 0.06], { mass: 4 }),
|
|
1017
|
-
box("back_cushion", "foam", [1.5, 0.5, 0.16], [0, 0.65, -0.3], { mass: 4 }),
|
|
1018
|
-
box("arm_l", "fabric", [0.15, 0.4, 0.8], [-0.82, 0.5, 0], { mass: 3 }),
|
|
1019
|
-
box("arm_r", "fabric", [0.15, 0.4, 0.8], [0.82, 0.5, 0], { mass: 3 })
|
|
1020
|
-
],
|
|
1021
|
-
joints: [
|
|
1022
|
-
joint("seat_cushion", "frame", "clip"),
|
|
1023
|
-
joint("back_cushion", "frame", "clip"),
|
|
1024
|
-
joint("arm_l", "frame", "screw"),
|
|
1025
|
-
joint("arm_r", "frame", "screw")
|
|
1026
|
-
],
|
|
1027
|
-
tags: ["furniture"]
|
|
1028
|
-
});
|
|
1029
|
-
var shelf = () => ({
|
|
1030
|
-
name: "shelf",
|
|
1031
|
-
parts: [
|
|
1032
|
-
box("back", "plywood", [0.84, 1.5, 0.012], [0, 0.75, -0.14], { anchored: true }),
|
|
1033
|
-
box("board_1", "wood_soft", [0.8, 0.025, 0.28], [0, 0.35, 0]),
|
|
1034
|
-
box("board_2", "wood_soft", [0.8, 0.025, 0.28], [0, 0.75, 0]),
|
|
1035
|
-
box("board_3", "wood_soft", [0.8, 0.025, 0.28], [0, 1.15, 0]),
|
|
1036
|
-
box("board_4", "wood_soft", [0.8, 0.025, 0.28], [0, 1.5, 0])
|
|
1037
|
-
],
|
|
1038
|
-
joints: [joint("board_1", "back", "screw"), joint("board_2", "back", "screw"), joint("board_3", "back", "screw"), joint("board_4", "back", "screw")],
|
|
1039
|
-
tags: ["furniture"]
|
|
1040
|
-
});
|
|
1041
|
-
var DEFS = [
|
|
1042
|
-
{ id: "chair", group: "furniture", spec: chair, colors: { wood_soft: 11038778 } },
|
|
1043
|
-
{ id: "sofa", group: "furniture", spec: sofa, colors: { frame: 7029798, seat_cushion: 8207180, back_cushion: 8207180, arm_l: 6958912, arm_r: 6958912 } },
|
|
1044
|
-
{ id: "shelf", group: "furniture", spec: shelf, colors: { back: 14272931 }, movable: false },
|
|
1045
|
-
{ id: "old_tv", group: "furniture", spec: oldTv, colors: { shell: 7166526, stand: 9067056, screen: 5205864 } },
|
|
1046
|
-
{ id: "steel_sheet", group: "structure", spec: steelSheet, movable: false, colors: { sheet: 11910340 } },
|
|
1047
|
-
{ id: "glass_pane", group: "structure", spec: glassPane, movable: false },
|
|
1048
|
-
{ id: "concrete_block", group: "structure", spec: concreteBlock },
|
|
1049
|
-
{ id: "brick_column", group: "structure", spec: brickColumn, movable: false },
|
|
1050
|
-
{ id: "building_frame", group: "structure", spec: frame, movable: false, colors: { roof: 8224121 } },
|
|
1051
|
-
{ id: "car_door", group: "vehicles", spec: carDoor, movable: false, colors: { skin: 12597547, window: 10278630 } },
|
|
1052
|
-
{ id: "small_tree", group: "plants", spec: tree, movable: false },
|
|
1053
|
-
{ id: "potted_plant", group: "plants", spec: pottedPlant, colors: { pot: 12871482 } },
|
|
1054
|
-
{ id: "coffee_cup", group: "items", spec: cup, colors: { cup: 15986662 }, fluid: { container: { capacity: 0.6, liquid: "coffee", liters: 0.25, temperature: 80, shape: { kind: "cylinder", radius: 0.045, height: 0.1 }, opening: 0.045 }, part: "cup", uses: ["drink", "sip", "pour"], offset: [0, 0.01, 0] } },
|
|
1055
|
-
{
|
|
1056
|
-
id: "glass_bottle",
|
|
1057
|
-
group: "items",
|
|
1058
|
-
spec: bottle,
|
|
1059
|
-
colors: { bottle_body: 8378032, bottle_neck: 8378032 },
|
|
1060
|
-
fluid: { container: { capacity: 0.5, liquid: "water", liters: 0.5, temperature: 18, shape: { kind: "cylinder", radius: 0.034, height: 0.14 }, opening: 0.012 }, part: "bottle_body", uses: ["drink", "sip", "pour"], offset: [0, 0.015, 0] }
|
|
1061
|
-
},
|
|
1062
|
-
{ id: "wooden_crate", group: "containers", spec: crate },
|
|
1063
|
-
{ id: "steel_drum", group: "containers", spec: drum, colors: { drum_body: 2842526, drum_top: 2842526, drum_bottom: 2842526 } },
|
|
1064
|
-
{ id: "plastic_bucket", group: "containers", spec: bucket, colors: { bucket_body: 14239787, handle: 9079434 } },
|
|
1065
|
-
{
|
|
1066
|
-
id: "jerrycan",
|
|
1067
|
-
group: "containers",
|
|
1068
|
-
spec: jerrycan,
|
|
1069
|
-
colors: { tank: 12729122, cap: 15909424, handle: 12729122 },
|
|
1070
|
-
fluid: { container: { capacity: 20, liquid: "fuel", liters: 20, temperature: 20, shape: { kind: "box", size: [0.33, 0.44, 0.13] }, opening: 0.025, closed: true }, part: "tank", uses: ["pour", "open", "close"], offset: [0, 8e-3, 0] }
|
|
1071
|
-
},
|
|
1072
|
-
{ id: "gas_bottle", group: "containers", spec: gasBottle, colors: { cylinder: 14211280, valve: 7829367, foot_ring: 5592405 } }
|
|
1073
|
-
];
|
|
1074
|
-
function demoItems(locale = "en", extra = []) {
|
|
1075
|
-
const s = resolveStrings(locale);
|
|
1076
|
-
const out = DEFS.map((d) => ({
|
|
1077
|
-
id: d.id,
|
|
1078
|
-
name: s.items[d.id] ?? d.id,
|
|
1079
|
-
group: s.groups[d.group] ?? d.group,
|
|
1080
|
-
build: (THREE) => buildFromSpec(THREE, d.spec(), { colors: d.colors }),
|
|
1081
|
-
spec: d.spec,
|
|
1082
|
-
movable: d.movable,
|
|
1083
|
-
fluid: d.fluid
|
|
1084
|
-
}));
|
|
1085
|
-
return [...out, ...extra];
|
|
1086
|
-
}
|
|
1087
|
-
var DEMO_ITEMS = demoItems("en");
|
|
1088
|
-
|
|
1089
|
-
// src/arena.ts
|
|
1090
|
-
var tableSpec = () => {
|
|
1091
|
-
const parts = [{ id: "top", material: "wood_hard", shape: { kind: "box", size: [1.6, 0.05, 0.8] }, position: [0, 0.755, 0] }];
|
|
1092
|
-
const joints = [];
|
|
1093
|
-
for (const [i, [x, z]] of [
|
|
1094
|
-
[-0.72, -0.32],
|
|
1095
|
-
[0.72, -0.32],
|
|
1096
|
-
[-0.72, 0.32],
|
|
1097
|
-
[0.72, 0.32]
|
|
1098
|
-
].entries()) {
|
|
1099
|
-
parts.push({ id: `leg_${i + 1}`, material: "wood_hard", shape: { kind: "box", size: [0.06, 0.73, 0.06] }, position: [x, 0.365, z], anchored: true });
|
|
1100
|
-
joints.push({ a: `leg_${i + 1}`, b: "top", kind: "screw" });
|
|
1101
|
-
}
|
|
1102
|
-
return { name: "table", parts, joints, tags: ["furniture"] };
|
|
1103
|
-
};
|
|
1104
|
-
var shelfSpec = () => {
|
|
1105
|
-
const parts = [];
|
|
1106
|
-
const joints = [];
|
|
1107
|
-
for (const [i, x] of [-0.6, 0.6].entries()) {
|
|
1108
|
-
parts.push({ id: `upright_${i + 1}`, material: "wood_hard", shape: { kind: "box", size: [0.04, 1.7, 0.4] }, position: [x, 0.85, 0], anchored: true });
|
|
1109
|
-
}
|
|
1110
|
-
for (const [i, y] of [0.4, 0.9, 1.4].entries()) {
|
|
1111
|
-
parts.push({ id: `board_${i + 1}`, material: "wood_soft", shape: { kind: "box", size: [1.2, 0.03, 0.4] }, position: [0, y, 0] });
|
|
1112
|
-
joints.push({ a: `board_${i + 1}`, b: "upright_1", kind: "screw" }, { a: `board_${i + 1}`, b: "upright_2", kind: "screw" });
|
|
1113
|
-
}
|
|
1114
|
-
return { name: "shelf", parts, joints, tags: ["furniture"] };
|
|
1115
|
-
};
|
|
1116
|
-
function createArena(THREE, size, scene) {
|
|
1117
|
-
const group = new THREE.Group();
|
|
1118
|
-
group.name = "test-room-arena";
|
|
1119
|
-
scene.background = new THREE.Color(10466504);
|
|
1120
|
-
group.add(new THREE.HemisphereLight(13624319, 7037527, 0.75));
|
|
1121
|
-
const sun = new THREE.DirectionalLight(16777215, 2.1);
|
|
1122
|
-
sun.position.set(7, 13, 8);
|
|
1123
|
-
sun.castShadow = true;
|
|
1124
|
-
sun.shadow.mapSize.set(1024, 1024);
|
|
1125
|
-
const sc = sun.shadow.camera;
|
|
1126
|
-
sc.left = -16;
|
|
1127
|
-
sc.right = 16;
|
|
1128
|
-
sc.top = 16;
|
|
1129
|
-
sc.bottom = -16;
|
|
1130
|
-
sc.near = 1;
|
|
1131
|
-
sc.far = 40;
|
|
1132
|
-
sun.shadow.bias = -6e-4;
|
|
1133
|
-
group.add(sun);
|
|
1134
|
-
const floor = new THREE.Mesh(new THREE.PlaneGeometry(size * 2, size * 2), new THREE.MeshStandardMaterial({ color: 9342602, roughness: 0.95 }));
|
|
1135
|
-
floor.rotation.x = -Math.PI / 2;
|
|
1136
|
-
floor.receiveShadow = true;
|
|
1137
|
-
floor.name = "floor";
|
|
1138
|
-
group.add(floor);
|
|
1139
|
-
const grid = new THREE.GridHelper(size * 2, size * 2, 5000274, 7829371);
|
|
1140
|
-
grid.position.y = 4e-3;
|
|
1141
|
-
group.add(grid);
|
|
1142
|
-
const wallZ = -11;
|
|
1143
|
-
const playerStart = [0, 1.7, 5];
|
|
1144
|
-
const pts = [];
|
|
1145
|
-
const rx = -1.6;
|
|
1146
|
-
for (let z = Math.round(playerStart[2]); z >= wallZ; z--) {
|
|
1147
|
-
const d = playerStart[2] - z;
|
|
1148
|
-
const len = d % 5 === 0 ? 0.5 : 0.2;
|
|
1149
|
-
pts.push(rx, 0.01, z, rx + len, 0.01, z);
|
|
1150
|
-
}
|
|
1151
|
-
pts.push(rx, 0.01, playerStart[2], rx, 0.01, wallZ);
|
|
1152
|
-
const rg = new THREE.BufferGeometry();
|
|
1153
|
-
rg.setAttribute("position", new THREE.Float32BufferAttribute(pts, 3));
|
|
1154
|
-
group.add(new THREE.LineSegments(rg, new THREE.LineBasicMaterial({ color: 16765503 })));
|
|
1155
|
-
for (let d = 5; playerStart[2] - d >= wallZ; d += 5) {
|
|
1156
|
-
try {
|
|
1157
|
-
const canvas = document.createElement("canvas");
|
|
1158
|
-
canvas.width = 128;
|
|
1159
|
-
canvas.height = 64;
|
|
1160
|
-
const ctx = canvas.getContext("2d");
|
|
1161
|
-
if (!ctx) break;
|
|
1162
|
-
ctx.fillStyle = "#ffd23f";
|
|
1163
|
-
ctx.font = "bold 44px sans-serif";
|
|
1164
|
-
ctx.textAlign = "center";
|
|
1165
|
-
ctx.fillText(`${d} m`, 64, 48);
|
|
1166
|
-
const sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: new THREE.CanvasTexture(canvas), transparent: true, depthWrite: false }));
|
|
1167
|
-
sprite.scale.set(1.2, 0.6, 1);
|
|
1168
|
-
sprite.position.set(rx - 0.8, 0.45, playerStart[2] - d);
|
|
1169
|
-
group.add(sprite);
|
|
1170
|
-
} catch {
|
|
1171
|
-
break;
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
const tableX = 3.5;
|
|
1175
|
-
const tapMat = new THREE.MeshStandardMaterial({ color: 12172996, metalness: 0.8, roughness: 0.35 });
|
|
1176
|
-
const post = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 1.4, 10), tapMat);
|
|
1177
|
-
post.position.set(tableX + 1.15, 0.7, 0);
|
|
1178
|
-
const arm = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 1.15 - 0.3, 10), tapMat);
|
|
1179
|
-
arm.rotation.z = Math.PI / 2;
|
|
1180
|
-
arm.position.set(tableX + (1.15 + 0.3) / 2, 1.38, 0);
|
|
1181
|
-
const spoutMesh = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 0.1, 10), tapMat);
|
|
1182
|
-
spoutMesh.position.set(tableX + 0.3, 1.33, 0);
|
|
1183
|
-
for (const m of [post, arm, spoutMesh]) {
|
|
1184
|
-
m.castShadow = true;
|
|
1185
|
-
group.add(m);
|
|
1186
|
-
}
|
|
1187
|
-
const tapSpout = [tableX + 0.3, 1.28, 0];
|
|
1188
|
-
const objects = [
|
|
1189
|
-
{ id: "target_wall", spec: brickWallSpec(), position: [0, 0, wallZ], colors: { brick: 10831662 } },
|
|
1190
|
-
{ id: "table", spec: tableSpec(), position: [tableX, 0, 0] },
|
|
1191
|
-
{ id: "shelf", spec: shelfSpec(), position: [-3.5, 0, 0], colors: { wood_soft: 13081186 } }
|
|
1192
|
-
];
|
|
1193
|
-
return {
|
|
1194
|
-
group,
|
|
1195
|
-
objects,
|
|
1196
|
-
surfaces: [
|
|
1197
|
-
{ id: "floor", point: [0, 0, 0], normal: [0, 1, 0], halfSize: [size, size], porosity: 0.05 },
|
|
1198
|
-
{ id: "table", point: [tableX, 0.78, 0], normal: [0, 1, 0], halfSize: [0.8, 0.4], porosity: 0.3 }
|
|
1199
|
-
],
|
|
1200
|
-
colliders: [
|
|
1201
|
-
[tableX - 0.8, -0.4, tableX + 0.8, 0.4],
|
|
1202
|
-
[tableX + 1.1, -0.1, tableX + 1.2, 0.1],
|
|
1203
|
-
[-3.5 - 0.62, -0.2, -3.5 + 0.62, 0.2],
|
|
1204
|
-
[-size, wallZ - 1, size, wallZ + 0.2]
|
|
1205
|
-
],
|
|
1206
|
-
playerStart,
|
|
1207
|
-
wallZ,
|
|
1208
|
-
table: { minX: tableX - 0.8, maxX: tableX + 0.8, minZ: -0.4, maxZ: 0.4, top: 0.78, id: "table", topPart: "top" },
|
|
1209
|
-
tapSpout,
|
|
1210
|
-
sun
|
|
1211
|
-
};
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// src/bench.ts
|
|
1215
|
-
var snap = (control, value) => {
|
|
1216
|
-
const clamped = Math.min(control.max, Math.max(control.min, value));
|
|
1217
|
-
if (!control.step) return clamped;
|
|
1218
|
-
const stepped = control.min + Math.round((clamped - control.min) / control.step) * control.step;
|
|
1219
|
-
return Number(Math.min(control.max, stepped).toFixed(10));
|
|
1220
|
-
};
|
|
1221
|
-
function fitValue(control, value) {
|
|
1222
|
-
if (control.kind === "number") return typeof value === "number" && Number.isFinite(value) ? snap(control, value) : void 0;
|
|
1223
|
-
if (control.kind === "boolean") return typeof value === "boolean" ? value : void 0;
|
|
1224
|
-
if (control.kind === "choice") return typeof value === "string" && control.options.some((option) => option.id === value) ? value : void 0;
|
|
1225
|
-
return void 0;
|
|
1226
|
-
}
|
|
1227
|
-
var fromText = (control, text) => {
|
|
1228
|
-
if (control.kind === "number") return text.trim() === "" ? Number.NaN : Number(text);
|
|
1229
|
-
if (control.kind === "boolean") return text === "1" ? true : text === "0" ? false : void 0;
|
|
1230
|
-
return text;
|
|
1231
|
-
};
|
|
1232
|
-
var toText = (value) => typeof value === "boolean" ? value ? "1" : "0" : String(value);
|
|
1233
|
-
function encodeBenchState(controls, layers) {
|
|
1234
|
-
const pairs = [];
|
|
1235
|
-
for (const control of controls) if (control.value !== void 0) pairs.push(`${control.id}=${encodeURIComponent(toText(control.value))}`);
|
|
1236
|
-
for (const layer of layers) pairs.push(`${layer.id}=${layer.on ? "1" : "0"}`);
|
|
1237
|
-
return pairs.join(";");
|
|
1238
|
-
}
|
|
1239
|
-
function decodeBenchState(encoded, controls, layers) {
|
|
1240
|
-
const out = /* @__PURE__ */ new Map();
|
|
1241
|
-
for (const pair of encoded.split(";")) {
|
|
1242
|
-
const at = pair.indexOf("=");
|
|
1243
|
-
if (at <= 0) continue;
|
|
1244
|
-
const id = pair.slice(0, at);
|
|
1245
|
-
let text;
|
|
1246
|
-
try {
|
|
1247
|
-
text = decodeURIComponent(pair.slice(at + 1));
|
|
1248
|
-
} catch {
|
|
1249
|
-
continue;
|
|
1250
|
-
}
|
|
1251
|
-
const control = controls.find((c) => c.id === id);
|
|
1252
|
-
if (control) {
|
|
1253
|
-
const value = fitValue(control, fromText(control, text));
|
|
1254
|
-
if (value !== void 0) out.set(id, value);
|
|
1255
|
-
continue;
|
|
1256
|
-
}
|
|
1257
|
-
if (layers.some((layer) => layer.id === id) && (text === "1" || text === "0")) out.set(id, text === "1");
|
|
1258
|
-
}
|
|
1259
|
-
return out;
|
|
286
|
+
return out;
|
|
1260
287
|
}
|
|
1261
288
|
function createBenchStore(module, locale, onListenerError) {
|
|
1262
289
|
const declared = { controls: module.controls?.(locale) ?? [], layers: module.layers?.(locale) ?? [], metrics: module.metrics?.(locale) ?? [] };
|
|
@@ -1365,6 +392,81 @@ function createBenchStore(module, locale, onListenerError) {
|
|
|
1365
392
|
return store;
|
|
1366
393
|
}
|
|
1367
394
|
|
|
395
|
+
// src/build-from-spec.ts
|
|
396
|
+
var MATERIAL_COLORS = {
|
|
397
|
+
steel: 9278620,
|
|
398
|
+
cast_iron: 4869458,
|
|
399
|
+
aluminium: 12896718,
|
|
400
|
+
copper: 12088122,
|
|
401
|
+
wood_soft: 12158287,
|
|
402
|
+
wood_hard: 8014374,
|
|
403
|
+
plywood: 13214826,
|
|
404
|
+
mdf: 11045732,
|
|
405
|
+
glass: 10278630,
|
|
406
|
+
tempered_glass: 10278630,
|
|
407
|
+
ceramic: 15328474,
|
|
408
|
+
concrete: 10132118,
|
|
409
|
+
brick: 10831662,
|
|
410
|
+
plaster: 15131092,
|
|
411
|
+
stone: 7828591,
|
|
412
|
+
plastic: 3897540,
|
|
413
|
+
rubber: 2039586,
|
|
414
|
+
fabric: 9063262,
|
|
415
|
+
foam: 14926936,
|
|
416
|
+
leather: 5978662,
|
|
417
|
+
paper: 15657695,
|
|
418
|
+
foliage: 4165434,
|
|
419
|
+
green_wood: 7031339,
|
|
420
|
+
cactus: 4095306,
|
|
421
|
+
flesh_fruit: 13781810,
|
|
422
|
+
wax: 15721141,
|
|
423
|
+
soil: 4862754
|
|
424
|
+
};
|
|
425
|
+
var TRANSPARENT = { glass: 0.38, tempered_glass: 0.38 };
|
|
426
|
+
function buildFromSpec(THREE, spec, options = {}) {
|
|
427
|
+
const root = new THREE.Group();
|
|
428
|
+
root.name = spec.name ?? "object";
|
|
429
|
+
const seg = options.segments ?? 12;
|
|
430
|
+
const shadows = options.shadows !== false;
|
|
431
|
+
const mats = /* @__PURE__ */ new Map();
|
|
432
|
+
const materialFor = (p) => {
|
|
433
|
+
const color = options.colors?.[p.id] ?? options.colors?.[p.material] ?? MATERIAL_COLORS[p.material] ?? 8947848;
|
|
434
|
+
const key = `${p.material}:${color}`;
|
|
435
|
+
let m = mats.get(key);
|
|
436
|
+
if (!m) {
|
|
437
|
+
const alpha = TRANSPARENT[p.material];
|
|
438
|
+
const metal = p.material === "steel" || p.material === "aluminium" || p.material === "copper" || p.material === "cast_iron";
|
|
439
|
+
m = new THREE.MeshStandardMaterial({
|
|
440
|
+
color,
|
|
441
|
+
roughness: alpha ? 0.1 : metal ? 0.45 : 0.85,
|
|
442
|
+
metalness: metal ? 0.7 : 0,
|
|
443
|
+
transparent: !!alpha,
|
|
444
|
+
opacity: alpha ?? 1,
|
|
445
|
+
depthWrite: !alpha
|
|
446
|
+
});
|
|
447
|
+
mats.set(key, m);
|
|
448
|
+
}
|
|
449
|
+
return m;
|
|
450
|
+
};
|
|
451
|
+
for (const p of spec.parts) {
|
|
452
|
+
let geo;
|
|
453
|
+
const s = p.shape;
|
|
454
|
+
if (s.kind === "box") geo = new THREE.BoxGeometry(s.size[0], s.size[1], s.size[2]);
|
|
455
|
+
else if (s.kind === "cylinder") geo = new THREE.CylinderGeometry(s.radius, s.radius, s.height, seg);
|
|
456
|
+
else geo = new THREE.SphereGeometry(s.radius, seg, Math.max(6, seg - 4));
|
|
457
|
+
const mesh = new THREE.Mesh(geo, materialFor(p));
|
|
458
|
+
mesh.name = p.id;
|
|
459
|
+
mesh.userData.destructionPart = p.id;
|
|
460
|
+
mesh.userData.destructionMaterial = p.material;
|
|
461
|
+
mesh.position.set(p.position[0], p.position[1], p.position[2]);
|
|
462
|
+
if (p.rotation) mesh.rotation.set(p.rotation[0], p.rotation[1], p.rotation[2], "XYZ");
|
|
463
|
+
mesh.castShadow = shadows && !TRANSPARENT[p.material];
|
|
464
|
+
mesh.receiveShadow = shadows;
|
|
465
|
+
root.add(mesh);
|
|
466
|
+
}
|
|
467
|
+
return root;
|
|
468
|
+
}
|
|
469
|
+
|
|
1368
470
|
// src/explosives.ts
|
|
1369
471
|
import { backblastEffect, blastEffect, createExplosiveBody, tripwireSegment } from "@adatechnology/game-weapons";
|
|
1370
472
|
|
|
@@ -1421,6 +523,17 @@ function createExplosives(context) {
|
|
|
1421
523
|
meshes.push(mesh);
|
|
1422
524
|
return mesh;
|
|
1423
525
|
};
|
|
526
|
+
const addModel = (query, at) => {
|
|
527
|
+
const custom = context.projectileModel?.(query);
|
|
528
|
+
if (!custom) return void 0;
|
|
529
|
+
custom.model.position.set(at[0], at[1], at[2]);
|
|
530
|
+
custom.model.traverse((child) => {
|
|
531
|
+
child.castShadow = true;
|
|
532
|
+
});
|
|
533
|
+
scene.add(custom.model);
|
|
534
|
+
meshes.push(custom.model);
|
|
535
|
+
return { mesh: custom.model, ...custom.axis ? { axis: new THREE.Vector3(custom.axis[0], custom.axis[1], custom.axis[2]).normalize() } : {} };
|
|
536
|
+
};
|
|
1424
537
|
const remove = (projectile) => {
|
|
1425
538
|
for (const extra of projectile.attached ?? []) {
|
|
1426
539
|
extra.removeFromParent();
|
|
@@ -1436,10 +549,11 @@ function createExplosives(context) {
|
|
|
1436
549
|
const throwUse = (use) => {
|
|
1437
550
|
const isMolotov = !!use.fire;
|
|
1438
551
|
const isRocket = use.trigger === "impact" && (use.armDistance ?? 0) > 0;
|
|
1439
|
-
const
|
|
1440
|
-
const mesh = addMesh(
|
|
552
|
+
const modeled = addModel({ weapon: context.toolId(), ...use.explosive !== void 0 ? { explosive: use.explosive } : {}, kind: isRocket ? "rocket" : isMolotov ? "molotov" : "grenade" }, use.origin);
|
|
553
|
+
const mesh = modeled?.mesh ?? addMesh(isRocket ? new THREE.CylinderGeometry(0.04, 0.04, 0.5, 8) : isMolotov ? new THREE.CylinderGeometry(0.035, 0.04, 0.2, 8) : new THREE.SphereGeometry(0.045, 8, 6), isMolotov ? 3897915 : isRocket ? 7041116 : 4936480, isMolotov ? 0 : 0.4, use.origin);
|
|
1441
554
|
projectiles.push({
|
|
1442
555
|
mesh,
|
|
556
|
+
...modeled?.axis ? { axis: modeled.axis } : {},
|
|
1443
557
|
pos: new THREE.Vector3(use.origin[0], use.origin[1], use.origin[2]),
|
|
1444
558
|
vel: new THREE.Vector3(use.velocity[0], use.velocity[1], use.velocity[2]),
|
|
1445
559
|
weapon: context.toolId(),
|
|
@@ -1490,7 +604,8 @@ function createExplosives(context) {
|
|
|
1490
604
|
const placed = { ...use, origin: at };
|
|
1491
605
|
const body = createExplosiveBody(placed, arsenal.explosives, arsenal.rules.explosives);
|
|
1492
606
|
if (!body) return;
|
|
1493
|
-
const
|
|
607
|
+
const modeled = addModel({ weapon: context.toolId(), explosive: use.explosive, kind: "charge" }, at);
|
|
608
|
+
const mesh = modeled?.mesh ?? addMesh(new THREE.BoxGeometry(0.18, 0.06, 0.1), use.explosive === "claymore" ? 5597999 : 12757112, 0.1, at);
|
|
1494
609
|
mesh.rotation.y = Math.atan2(use.direction[0], use.direction[2]);
|
|
1495
610
|
const attached = [markerAt(at)];
|
|
1496
611
|
const segment = tripwireSegment(placed);
|
|
@@ -1614,7 +729,7 @@ function createExplosives(context) {
|
|
|
1614
729
|
projectile.pos.set(body.position[0], body.position[1], body.position[2]);
|
|
1615
730
|
projectile.mesh.position.copy(projectile.pos);
|
|
1616
731
|
if (projectile.aligned && body.velocity[0] ** 2 + body.velocity[1] ** 2 + body.velocity[2] ** 2 > 1e-6) {
|
|
1617
|
-
projectile.mesh.quaternion.setFromUnitVectors(up, new THREE.Vector3(body.velocity[0], body.velocity[1], body.velocity[2]).normalize());
|
|
732
|
+
projectile.mesh.quaternion.setFromUnitVectors(projectile.axis ?? up, new THREE.Vector3(body.velocity[0], body.velocity[1], body.velocity[2]).normalize());
|
|
1618
733
|
} else if (!projectile.placed) projectile.mesh.rotation.x += dt * 8;
|
|
1619
734
|
for (const event of events) {
|
|
1620
735
|
if (event.type === "dud") action("dud", translate("act.dud", { weapon: context.toolName(projectile.weapon), distance: format(body.travelled, 1) }), { data: { weapon: projectile.weapon, point: event.point } });
|
|
@@ -1736,16 +851,171 @@ var toHostEvent = (weapon, event, options = {}) => {
|
|
|
1736
851
|
}
|
|
1737
852
|
};
|
|
1738
853
|
|
|
1739
|
-
// src/
|
|
1740
|
-
var
|
|
1741
|
-
var
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
854
|
+
// src/loading-view.ts
|
|
855
|
+
var LOADING_CLASS = "tr-loading";
|
|
856
|
+
var LOADING_CSS = [
|
|
857
|
+
'.tr-loading{position:absolute;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;background:rgba(16,19,26,.72);color:#e8eaee;font:15px/1.4 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;transition:opacity .25s;pointer-events:auto}',
|
|
858
|
+
".tr-loading.tr-loading-fixed{position:fixed}",
|
|
859
|
+
".tr-loading.tr-loading-solid{background:#10131a}",
|
|
860
|
+
".tr-loading.tr-loading-hidden{opacity:0;pointer-events:none}",
|
|
861
|
+
".tr-loading-card{width:min(440px,86vw);text-align:center}",
|
|
862
|
+
".tr-loading-title{font-size:18px;font-weight:600;margin-bottom:18px}",
|
|
863
|
+
".tr-loading-spin{display:inline-block;width:16px;height:16px;margin-right:10px;vertical-align:-2px;border:2px solid #3a3f4a;border-top-color:#ffd23f;border-radius:50%;animation:tr-loading-turn .8s linear infinite}",
|
|
864
|
+
".tr-loading-bar{position:relative;height:8px;border-radius:4px;background:#2a2e36;overflow:hidden}",
|
|
865
|
+
".tr-loading-bar i{display:block;height:100%;width:0;background:#ffd23f;transition:width .15s}",
|
|
866
|
+
".tr-loading-bar.tr-loading-indeterminate i{position:absolute;width:35%;animation:tr-loading-sweep 1.1s ease-in-out infinite}",
|
|
867
|
+
".tr-loading-step{margin-top:12px;color:#9aa3b2;min-height:1.4em}",
|
|
868
|
+
"@keyframes tr-loading-turn{to{transform:rotate(360deg)}}",
|
|
869
|
+
"@keyframes tr-loading-sweep{0%{left:-35%}100%{left:100%}}",
|
|
870
|
+
"@media (prefers-reduced-motion:reduce){.tr-loading-spin,.tr-loading-bar.tr-loading-indeterminate i{animation-duration:3s}}"
|
|
871
|
+
].join("");
|
|
872
|
+
var escape = (text) => text.replace(/[&<>"]/g, (c) => c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """);
|
|
873
|
+
function loadingMarkup(options) {
|
|
874
|
+
const classes = [LOADING_CLASS, options.solid ? "tr-loading-solid" : "", options.fixed ? "tr-loading-fixed" : ""].filter(Boolean).join(" ");
|
|
875
|
+
const id = options.id ? ` id="${escape(options.id)}"` : "";
|
|
876
|
+
return `<div${id} class="${classes}" role="status" aria-live="polite"><div class="tr-loading-card"><div class="tr-loading-title"><span class="tr-loading-spin"></span><span class="tr-loading-label">${escape(options.title)}</span></div><div class="tr-loading-bar tr-loading-indeterminate"><i></i></div><div class="tr-loading-step">${escape(options.step ?? "")}</div></div></div>`;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// src/loading.ts
|
|
880
|
+
var PAINT_FALLBACK_MS = 100;
|
|
881
|
+
var styled = false;
|
|
882
|
+
var ensureStyle = (document2) => {
|
|
883
|
+
if (styled && document2.getElementById("tr-loading-style")) return;
|
|
884
|
+
const style = document2.createElement("style");
|
|
885
|
+
style.id = "tr-loading-style";
|
|
886
|
+
style.textContent = LOADING_CSS;
|
|
887
|
+
document2.head.appendChild(style);
|
|
888
|
+
styled = true;
|
|
1746
889
|
};
|
|
1747
|
-
|
|
1748
|
-
|
|
890
|
+
var nextPaint = () => new Promise((resolve) => {
|
|
891
|
+
let done = false;
|
|
892
|
+
const finish = () => {
|
|
893
|
+
if (!done) {
|
|
894
|
+
done = true;
|
|
895
|
+
resolve();
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
if (typeof requestAnimationFrame === "function") requestAnimationFrame(() => setTimeout(finish, 0));
|
|
899
|
+
setTimeout(finish, PAINT_FALLBACK_MS);
|
|
900
|
+
});
|
|
901
|
+
function createLoading(options) {
|
|
902
|
+
const draw = options.draw !== false && typeof document !== "undefined";
|
|
903
|
+
const tasks = [];
|
|
904
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
905
|
+
let element;
|
|
906
|
+
let labelNode = null;
|
|
907
|
+
let stepNode = null;
|
|
908
|
+
let barNode = null;
|
|
909
|
+
let fillNode = null;
|
|
910
|
+
let hideTimer;
|
|
911
|
+
const idlers = [];
|
|
912
|
+
const current = () => tasks[tasks.length - 1];
|
|
913
|
+
const state = () => {
|
|
914
|
+
const task = current();
|
|
915
|
+
return task ? { label: task.label, text: task.text, done: task.done, total: task.total, opaque: task.opaque, depth: tasks.length } : void 0;
|
|
916
|
+
};
|
|
917
|
+
const build = () => {
|
|
918
|
+
ensureStyle(options.container.ownerDocument);
|
|
919
|
+
const root = document.createElement("div");
|
|
920
|
+
root.className = `${LOADING_CLASS} tr-loading-hidden`;
|
|
921
|
+
root.setAttribute("role", "status");
|
|
922
|
+
root.setAttribute("aria-live", "polite");
|
|
923
|
+
root.innerHTML = '<div class="tr-loading-card"><div class="tr-loading-title"><span class="tr-loading-spin"></span><span class="tr-loading-label"></span></div><div class="tr-loading-bar tr-loading-indeterminate"><i></i></div><div class="tr-loading-step"></div></div>';
|
|
924
|
+
labelNode = root.querySelector(".tr-loading-label");
|
|
925
|
+
stepNode = root.querySelector(".tr-loading-step");
|
|
926
|
+
barNode = root.querySelector(".tr-loading-bar");
|
|
927
|
+
fillNode = root.querySelector(".tr-loading-bar i");
|
|
928
|
+
options.container.appendChild(root);
|
|
929
|
+
return root;
|
|
930
|
+
};
|
|
931
|
+
const render = () => {
|
|
932
|
+
const now = state();
|
|
933
|
+
for (const listener of listeners) listener(now);
|
|
934
|
+
if (!now) for (const wake of idlers.splice(0)) wake();
|
|
935
|
+
if (!draw) return;
|
|
936
|
+
if (!now) {
|
|
937
|
+
if (element) {
|
|
938
|
+
element.classList.add("tr-loading-hidden");
|
|
939
|
+
hideTimer = setTimeout(() => element?.style.setProperty("display", "none"), 300);
|
|
940
|
+
}
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
if (hideTimer !== void 0) clearTimeout(hideTimer);
|
|
944
|
+
element ?? (element = build());
|
|
945
|
+
element.style.removeProperty("display");
|
|
946
|
+
element.classList.remove("tr-loading-hidden");
|
|
947
|
+
element.classList.toggle("tr-loading-solid", now.opaque);
|
|
948
|
+
if (labelNode) labelNode.textContent = now.label;
|
|
949
|
+
if (stepNode) stepNode.textContent = now.text;
|
|
950
|
+
const determinate = now.total !== void 0 && now.total > 0;
|
|
951
|
+
barNode?.classList.toggle("tr-loading-indeterminate", !determinate);
|
|
952
|
+
if (fillNode) fillNode.style.width = determinate ? `${Math.max(0, Math.min(1, now.done / now.total)) * 100}%` : "";
|
|
953
|
+
};
|
|
954
|
+
const begin = (label, taskOptions = {}) => {
|
|
955
|
+
const task = { label, text: options.working, done: 0, total: taskOptions.total, opaque: taskOptions.opaque === true, ended: false };
|
|
956
|
+
tasks.push(task);
|
|
957
|
+
render();
|
|
958
|
+
return {
|
|
959
|
+
step(text, done) {
|
|
960
|
+
if (task.ended) return;
|
|
961
|
+
if (text !== void 0) task.text = text;
|
|
962
|
+
if (done !== void 0) task.done = done;
|
|
963
|
+
render();
|
|
964
|
+
},
|
|
965
|
+
paint: nextPaint,
|
|
966
|
+
end() {
|
|
967
|
+
if (task.ended) return;
|
|
968
|
+
task.ended = true;
|
|
969
|
+
const at = tasks.indexOf(task);
|
|
970
|
+
if (at >= 0) tasks.splice(at, 1);
|
|
971
|
+
render();
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
};
|
|
975
|
+
return {
|
|
976
|
+
get active() {
|
|
977
|
+
return tasks.length > 0;
|
|
978
|
+
},
|
|
979
|
+
get element() {
|
|
980
|
+
return element;
|
|
981
|
+
},
|
|
982
|
+
state,
|
|
983
|
+
idle: () => tasks.length === 0 ? Promise.resolve() : new Promise((resolve) => idlers.push(resolve)),
|
|
984
|
+
on(listener) {
|
|
985
|
+
listeners.add(listener);
|
|
986
|
+
return () => listeners.delete(listener);
|
|
987
|
+
},
|
|
988
|
+
begin,
|
|
989
|
+
async busy(label, work, taskOptions) {
|
|
990
|
+
const task = begin(label, taskOptions);
|
|
991
|
+
try {
|
|
992
|
+
await task.paint();
|
|
993
|
+
return await work(task);
|
|
994
|
+
} finally {
|
|
995
|
+
task.end();
|
|
996
|
+
}
|
|
997
|
+
},
|
|
998
|
+
dispose() {
|
|
999
|
+
if (hideTimer !== void 0) clearTimeout(hideTimer);
|
|
1000
|
+
tasks.length = 0;
|
|
1001
|
+
for (const wake of idlers.splice(0)) wake();
|
|
1002
|
+
listeners.clear();
|
|
1003
|
+
element?.remove();
|
|
1004
|
+
element = void 0;
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/log.ts
|
|
1010
|
+
var MAX_ENTRIES = 5e3;
|
|
1011
|
+
var pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
1012
|
+
var formatTime = (t) => {
|
|
1013
|
+
const m = Math.floor(t / 60);
|
|
1014
|
+
const s = t - m * 60;
|
|
1015
|
+
return `${pad(m)}:${s.toFixed(2).padStart(5, "0")}`;
|
|
1016
|
+
};
|
|
1017
|
+
function createLog(options = {}) {
|
|
1018
|
+
const host = globalThis.claude;
|
|
1749
1019
|
let downloadsP = null;
|
|
1750
1020
|
if (host && typeof host.use === "function") {
|
|
1751
1021
|
try {
|
|
@@ -2436,6 +1706,9 @@ function createNarrator(c) {
|
|
|
2436
1706
|
var EYE = 1.7;
|
|
2437
1707
|
var PLAYER_RADIUS = 0.3;
|
|
2438
1708
|
var WALK = 3.4;
|
|
1709
|
+
var CROUCH_DROP = 0.55;
|
|
1710
|
+
var CROUCH_WALK = 1.7;
|
|
1711
|
+
var CROUCH_RATE = 9;
|
|
2439
1712
|
var RUN = 6.2;
|
|
2440
1713
|
var JUMP = 4.6;
|
|
2441
1714
|
var GRAVITY = 13;
|
|
@@ -2446,15 +1719,20 @@ function createPlayer(THREE, o) {
|
|
|
2446
1719
|
let pitch = 0;
|
|
2447
1720
|
let y = 0;
|
|
2448
1721
|
let eye = EYE;
|
|
1722
|
+
let crouch = 0;
|
|
2449
1723
|
let vy = 0;
|
|
1724
|
+
let walked = 0;
|
|
1725
|
+
let running = false;
|
|
2450
1726
|
const px = { x: o.start[0], z: o.start[2] };
|
|
2451
1727
|
let locked = false;
|
|
2452
1728
|
const keys = /* @__PURE__ */ new Set();
|
|
1729
|
+
const crouchKeys = o.crouchKeys ?? ["KeyC"];
|
|
1730
|
+
const isCrouchHeld = () => crouchKeys.some((code) => keys.has(code));
|
|
2453
1731
|
const euler = new THREE.Euler(0, 0, 0, "YXZ");
|
|
2454
1732
|
const apply = () => {
|
|
2455
1733
|
euler.set(pitch, yaw, 0, "YXZ");
|
|
2456
1734
|
cam.quaternion.setFromEuler(euler);
|
|
2457
|
-
cam.position.set(px.x, eye + y, px.z);
|
|
1735
|
+
cam.position.set(px.x, eye + y - CROUCH_DROP * crouch, px.z);
|
|
2458
1736
|
cam.updateMatrixWorld(true);
|
|
2459
1737
|
};
|
|
2460
1738
|
apply();
|
|
@@ -2481,6 +1759,20 @@ function createPlayer(THREE, o) {
|
|
|
2481
1759
|
};
|
|
2482
1760
|
const onLockChange = () => {
|
|
2483
1761
|
locked = document.pointerLockElement === o.dom;
|
|
1762
|
+
if (locked) {
|
|
1763
|
+
try {
|
|
1764
|
+
const nav = typeof navigator !== "undefined" ? navigator : null;
|
|
1765
|
+
nav?.keyboard?.lock?.().catch(() => {
|
|
1766
|
+
});
|
|
1767
|
+
} catch {
|
|
1768
|
+
}
|
|
1769
|
+
} else {
|
|
1770
|
+
try {
|
|
1771
|
+
const nav = typeof navigator !== "undefined" ? navigator : null;
|
|
1772
|
+
nav?.keyboard?.unlock?.();
|
|
1773
|
+
} catch {
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
2484
1776
|
};
|
|
2485
1777
|
const onMouseDown = (e) => {
|
|
2486
1778
|
o.dom.focus?.();
|
|
@@ -2550,6 +1842,10 @@ function createPlayer(THREE, o) {
|
|
|
2550
1842
|
const onKeyDown = (e) => {
|
|
2551
1843
|
if (typing(e)) return;
|
|
2552
1844
|
if (e.code === "Space" || e.code === "Tab") e.preventDefault();
|
|
1845
|
+
const isControlHeld = e.ctrlKey || e.metaKey || keys.has("ControlLeft") || keys.has("ControlRight");
|
|
1846
|
+
if (isControlHeld || e.altKey && locked) {
|
|
1847
|
+
if (!/^F\d+$/.test(e.code)) e.preventDefault();
|
|
1848
|
+
}
|
|
2553
1849
|
if (!e.repeat) keys.add(e.code);
|
|
2554
1850
|
o.hooks.key(e.code, true, e);
|
|
2555
1851
|
};
|
|
@@ -2560,6 +1856,11 @@ function createPlayer(THREE, o) {
|
|
|
2560
1856
|
};
|
|
2561
1857
|
const onContext = (e) => e.preventDefault();
|
|
2562
1858
|
const onBlur = () => keys.clear();
|
|
1859
|
+
const onBeforeUnload = (e) => {
|
|
1860
|
+
if (!locked && !keys.has("ControlLeft") && !keys.has("ControlRight")) return;
|
|
1861
|
+
e.preventDefault();
|
|
1862
|
+
e.returnValue = "";
|
|
1863
|
+
};
|
|
2563
1864
|
o.dom.addEventListener("mousedown", onMouseDown);
|
|
2564
1865
|
window.addEventListener("mouseup", onMouseUp);
|
|
2565
1866
|
window.addEventListener("mousemove", onMouseMove);
|
|
@@ -2569,6 +1870,7 @@ function createPlayer(THREE, o) {
|
|
|
2569
1870
|
window.addEventListener("keydown", onKeyDown);
|
|
2570
1871
|
window.addEventListener("keyup", onKeyUp);
|
|
2571
1872
|
window.addEventListener("blur", onBlur);
|
|
1873
|
+
window.addEventListener("beforeunload", onBeforeUnload);
|
|
2572
1874
|
return {
|
|
2573
1875
|
get yaw() {
|
|
2574
1876
|
return yaw;
|
|
@@ -2579,10 +1881,36 @@ function createPlayer(THREE, o) {
|
|
|
2579
1881
|
get locked() {
|
|
2580
1882
|
return locked;
|
|
2581
1883
|
},
|
|
1884
|
+
get x() {
|
|
1885
|
+
return px.x;
|
|
1886
|
+
},
|
|
1887
|
+
get z() {
|
|
1888
|
+
return px.z;
|
|
1889
|
+
},
|
|
1890
|
+
get feet() {
|
|
1891
|
+
return y;
|
|
1892
|
+
},
|
|
1893
|
+
get position() {
|
|
1894
|
+
return [px.x, y, px.z];
|
|
1895
|
+
},
|
|
1896
|
+
get speed() {
|
|
1897
|
+
return walked;
|
|
1898
|
+
},
|
|
1899
|
+
get running() {
|
|
1900
|
+
return running;
|
|
1901
|
+
},
|
|
1902
|
+
get crouching() {
|
|
1903
|
+
return isCrouchHeld();
|
|
1904
|
+
},
|
|
2582
1905
|
keys,
|
|
2583
1906
|
update(dt) {
|
|
2584
|
-
const
|
|
2585
|
-
|
|
1907
|
+
const crouched = isCrouchHeld();
|
|
1908
|
+
crouch += ((crouched ? 1 : 0) - crouch) * Math.min(1, dt * CROUCH_RATE);
|
|
1909
|
+
if (!crouched && crouch < 1e-3) crouch = 0;
|
|
1910
|
+
const run = !crouched && (keys.has("ShiftLeft") || keys.has("ShiftRight"));
|
|
1911
|
+
const fromX = px.x;
|
|
1912
|
+
const fromZ = px.z;
|
|
1913
|
+
const speed = crouched ? CROUCH_WALK : run ? RUN : WALK;
|
|
2586
1914
|
let f = 0;
|
|
2587
1915
|
let r = 0;
|
|
2588
1916
|
if (keys.has("KeyW") || keys.has("ArrowUp")) f += 1;
|
|
@@ -2598,6 +1926,8 @@ function createPlayer(THREE, o) {
|
|
|
2598
1926
|
const rz = -Math.sin(yaw);
|
|
2599
1927
|
move((fx * f + rx * r) / len * speed * dt, (fz * f + rz * r) / len * speed * dt);
|
|
2600
1928
|
}
|
|
1929
|
+
walked = dt > 0 ? Math.hypot(px.x - fromX, px.z - fromZ) / dt : 0;
|
|
1930
|
+
running = run && walked > 0;
|
|
2601
1931
|
if (y <= 0 && keys.has("Space")) vy = JUMP;
|
|
2602
1932
|
if (y > 0 || vy > 0) {
|
|
2603
1933
|
vy -= GRAVITY * dt;
|
|
@@ -2656,358 +1986,611 @@ function createPlayer(THREE, o) {
|
|
|
2656
1986
|
window.removeEventListener("keydown", onKeyDown);
|
|
2657
1987
|
window.removeEventListener("keyup", onKeyUp);
|
|
2658
1988
|
window.removeEventListener("blur", onBlur);
|
|
1989
|
+
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
2659
1990
|
if (document.pointerLockElement === o.dom) document.exitPointerLock?.();
|
|
2660
1991
|
}
|
|
2661
1992
|
};
|
|
2662
1993
|
}
|
|
2663
1994
|
|
|
2664
|
-
// src/
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
const
|
|
2680
|
-
const
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
const
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
t("sc.steel_through_hole.check", { mm: num((hole?.diameter ?? 0) * 1e3, 0), through: yn(!!hole?.through), holes: p?.holes.length ?? 0, hp: `${num(p?.hp ?? 0, 0)}/${num(p?.maxHp ?? 0, 0)}` }),
|
|
2697
|
-
{ holes: p?.holes.length, through: hole?.through }
|
|
2698
|
-
);
|
|
2699
|
-
}),
|
|
2700
|
-
sc("concrete_stops_bullet", ({ room, step, expect }) => {
|
|
2701
|
-
const id = at(room, "concrete_block", 0, -4);
|
|
2702
|
-
room.setTool("pistol_9mm");
|
|
2703
|
-
room.waitUntilReady();
|
|
2704
|
-
look(room, [0, 0.5, -1.5], [0, 0.2, -3.8]);
|
|
2705
|
-
room.use();
|
|
2706
|
-
step(0.2);
|
|
2707
|
-
const p = part(room, id, "block");
|
|
2708
|
-
const wall = part(room, "target_wall", "brick_1_4");
|
|
2709
|
-
const through = p?.holes.some((h) => h.through) ?? true;
|
|
2710
|
-
expect(
|
|
2711
|
-
!through && (p?.holes.length ?? 0) + (p?.dents.length ?? 0) + (p?.cracks.length ?? 0) > 0 && (wall?.holes.length ?? 0) === 0,
|
|
2712
|
-
t("sc.concrete_stops_bullet.check", { through: yn(through), dents: cn("dent", p?.dents.length ?? 0), holes: cn("hole", p?.holes.length ?? 0), cracks: cn("crack", p?.cracks.length ?? 0), hp: `${num(p?.hp ?? 0, 0)}/${num(p?.maxHp ?? 0, 0)}` }),
|
|
2713
|
-
{ through }
|
|
2714
|
-
);
|
|
2715
|
-
}),
|
|
2716
|
-
sc("hammer_dents_drum", ({ room, step, expect }) => {
|
|
2717
|
-
const id = at(room, "steel_drum", 0, -4);
|
|
2718
|
-
room.setTool("hammer");
|
|
2719
|
-
room.waitUntilReady();
|
|
2720
|
-
look(room, [0, 0.6, -3.35], [0, 0.5, -4]);
|
|
2721
|
-
let swings = 0;
|
|
2722
|
-
for (; swings < 8 && (part(room, id, "drum_body")?.dents.length ?? 0) === 0; swings++) {
|
|
2723
|
-
room.use();
|
|
2724
|
-
step(1);
|
|
2725
|
-
}
|
|
2726
|
-
const p = part(room, id, "drum_body");
|
|
2727
|
-
expect(
|
|
2728
|
-
(p?.dents.length ?? 0) > 0 && p?.status !== "broken",
|
|
2729
|
-
t("sc.hammer_dents_drum.check", { swings, dents: p?.dents.length ?? 0, depth: num((p?.dents[0]?.depth ?? 0) * 1e3, 1), hp: `${num(p?.hp ?? 0, 0)}/${num(p?.maxHp ?? 0, 0)}` }),
|
|
2730
|
-
{ swings }
|
|
2731
|
-
);
|
|
2732
|
-
}),
|
|
2733
|
-
sc("glass_shatters", ({ room, step, expect }) => {
|
|
2734
|
-
const id = at(room, "glass_pane", 0, -4);
|
|
2735
|
-
room.setTool("pistol_9mm");
|
|
2736
|
-
room.waitUntilReady();
|
|
2737
|
-
look(room, [0, 1.3, -1.5], [0, 1.05, -4]);
|
|
2738
|
-
let shots = 0;
|
|
2739
|
-
for (; shots < 10 && part(room, id, "pane")?.status !== "broken"; shots++) {
|
|
2740
|
-
room.use();
|
|
2741
|
-
step(0.25);
|
|
2742
|
-
}
|
|
2743
|
-
step(0.5);
|
|
2744
|
-
const fr = room.log.entries({ type: "fracture", objectId: id }).find((e) => e.part === "pane");
|
|
2745
|
-
const n = Number(fr?.data?.fragments ?? 0);
|
|
2746
|
-
expect(part(room, id, "pane")?.status === "broken" && shots === 1 && n >= 4, t("sc.glass_shatters.check", { shots, n }), { shots, fragments: n });
|
|
2747
|
-
}),
|
|
2748
|
-
sc("shotgun_splinters_crate", ({ room, step, expect }) => {
|
|
2749
|
-
const id = at(room, "wooden_crate", 0, -4);
|
|
2750
|
-
room.setTool("shotgun_pump_12");
|
|
2751
|
-
room.waitUntilReady();
|
|
2752
|
-
look(room, [0, 0.6, -1.8], [0, 0.25, -3.77]);
|
|
2753
|
-
let shots = 0;
|
|
2754
|
-
const splinters = () => room.log.entries({ type: "fracture", objectId: id }).filter((e) => e.data?.style === "splinter").length;
|
|
2755
|
-
for (; shots < 4 && splinters() === 0; shots++) {
|
|
2756
|
-
room.use();
|
|
2757
|
-
step(1.1);
|
|
2758
|
-
}
|
|
2759
|
-
const holes = (room.world.state(id)?.parts ?? []).reduce((n, p) => n + p.holes.length, 0);
|
|
2760
|
-
expect(splinters() > 0, t("sc.shotgun_splinters_crate.check", { shots, splinters: splinters(), holes }), { shots });
|
|
2761
|
-
}),
|
|
2762
|
-
sc("kick_chair_leg", ({ room, step, expect }) => {
|
|
2763
|
-
const id = at(room, "chair", 0, -4);
|
|
2764
|
-
const before = room.renderer.pieces().length;
|
|
2765
|
-
const leg = part(room, id, "leg_1");
|
|
2766
|
-
void leg;
|
|
2767
|
-
let kicks = 0;
|
|
2768
|
-
for (; kicks < 6 && room.renderer.pieces().length <= before && part(room, id, "leg_1"); kicks++) {
|
|
2769
|
-
room.world.hitPart(id, "leg_1", { type: "blunt", point: [-0.18, 0.2, -4.18], direction: [1, 0, 0], energy: 220, radius: 0.05, source: "kick" });
|
|
2770
|
-
step(0.3);
|
|
2771
|
-
}
|
|
2772
|
-
step(0.5);
|
|
2773
|
-
const after = room.renderer.pieces().length;
|
|
2774
|
-
const groups = room.world.state(id)?.groups.length ?? 1;
|
|
2775
|
-
expect(
|
|
2776
|
-
after > before,
|
|
2777
|
-
t("sc.kick_chair_leg.check", { kicks, groups, before, after, leg: part(room, id, "leg_1") ? t("leg.still") : t("leg.gone") }),
|
|
2778
|
-
{ kicks, before, after }
|
|
2779
|
-
);
|
|
2780
|
-
}),
|
|
2781
|
-
sc("kick_detaches_leg_via_tool", ({ room, step, expect }) => {
|
|
2782
|
-
const id = at(room, "chair", 0, -4);
|
|
2783
|
-
const before = room.renderer.pieces().length;
|
|
2784
|
-
room.setTool("kick");
|
|
2785
|
-
room.waitUntilReady();
|
|
2786
|
-
let kicks = 0;
|
|
2787
|
-
for (; kicks < 8 && room.renderer.pieces().length <= before && part(room, id, "leg_1"); kicks++) {
|
|
2788
|
-
const st = room.world.state(id);
|
|
2789
|
-
const leg = toWorld(st.position, st.rotation, [-0.18, 0.2, -0.18]);
|
|
2790
|
-
const side = rotateVec(st.rotation, [-0.8, 0, 0]);
|
|
2791
|
-
look(room, [leg[0] + side[0], leg[1] + side[1], leg[2] + side[2]], leg);
|
|
2792
|
-
room.use();
|
|
2793
|
-
step(1.05);
|
|
2794
|
-
}
|
|
2795
|
-
step(0.5);
|
|
2796
|
-
const after = room.renderer.pieces().length;
|
|
2797
|
-
expect(after > before, t("sc.kick_detaches_leg_via_tool.check", { kicks, before, after, leg: part(room, id, "leg_1") ? t("leg.still") : t("leg.gone") }), { kicks, before, after });
|
|
2798
|
-
}),
|
|
2799
|
-
sc("screwdriver_shelf_board", ({ room, step, expect }) => {
|
|
2800
|
-
const id = at(room, "shelf", 0, -4);
|
|
2801
|
-
room.setTool("screwdriver");
|
|
2802
|
-
room.waitUntilReady();
|
|
2803
|
-
look(room, [0, 0.8, -3.5], [0, 0.75, -3.86]);
|
|
2804
|
-
const t0 = room.log.entries().length ? room.log.entries().slice(-1)[0].t : 0;
|
|
2805
|
-
room.use(5);
|
|
2806
|
-
const done = room.log.entries({ type: "detach", objectId: id })[0];
|
|
2807
|
-
const elapsed = done ? done.t - t0 : 5;
|
|
2808
|
-
const want = JOINT_DEFAULTS.screw.seconds;
|
|
2809
|
-
const groups = room.renderer.pieces().length;
|
|
2810
|
-
expect(!!done && Math.abs(elapsed - want) < 0.6 && groups >= 1, t("sc.screwdriver_shelf_board.check", { secs: num(elapsed, 1), want: num(want, 1), pieces: pc(groups) }), { elapsed, want });
|
|
2811
|
-
step(0.2);
|
|
2812
|
-
}),
|
|
2813
|
-
sc("wrong_tool_refused", ({ room, expect }) => {
|
|
2814
|
-
const id = at(room, "steel_sheet", 0, -4);
|
|
2815
|
-
room.setTool("screwdriver");
|
|
2816
|
-
room.waitUntilReady();
|
|
2817
|
-
look(room, [0.2, 1.05, -3.6], [0.34, 1, -4]);
|
|
2818
|
-
room.use(0.5);
|
|
2819
|
-
const refused = count(room, "tool_refused");
|
|
2820
|
-
const opened = (room.world.state(id)?.joints ?? []).filter((j) => j.open).length;
|
|
2821
|
-
expect(refused > 0 && opened === 0, t("sc.wrong_tool_refused.check", { refused, opened }), { refused, opened });
|
|
2822
|
-
}),
|
|
2823
|
-
sc("lighter_burns_crate", ({ room, step, expect }) => {
|
|
2824
|
-
const id = at(room, "wooden_crate", 0, -4);
|
|
2825
|
-
room.setTool("lighter");
|
|
2826
|
-
room.waitUntilReady();
|
|
2827
|
-
look(room, [0, 0.3, -3.65], [0, 0.25, -3.76]);
|
|
2828
|
-
room.use(5);
|
|
2829
|
-
const lit = anyBurning(room, id);
|
|
2830
|
-
expect(lit, t("sc.lighter_burns_crate.lit", { lit: yn(lit) }));
|
|
2831
|
-
step(500);
|
|
2832
|
-
const parts = room.world.state(id)?.parts ?? [];
|
|
2833
|
-
const ash = parts.filter((p) => p.status === "ashes").length;
|
|
2834
|
-
const charred = Math.max(...parts.map((p) => p.char));
|
|
2835
|
-
expect(ash === parts.length || ash > 0 && parts.every((p) => p.status === "ashes" || p.status === "broken" || p.char > 0.8), t("sc.lighter_burns_crate.ash", { ash, total: parts.length, char: num(charred * 100, 0) }), { ash, total: parts.length });
|
|
2836
|
-
}),
|
|
2837
|
-
sc("torch_melts_steel", ({ room, step, expect }) => {
|
|
2838
|
-
const drum2 = at(room, "steel_drum", 0, -4);
|
|
2839
|
-
const crate2 = at(room, "wooden_crate", 0.7, -4);
|
|
2840
|
-
igniteHit(room, crate2, [0.7, 0.25, -3.8], 900, 2);
|
|
2841
|
-
let peak = 0;
|
|
2842
|
-
for (let i = 0; i < 40; i++) {
|
|
2843
|
-
step(1);
|
|
2844
|
-
peak = Math.max(peak, part(room, drum2, "drum_body")?.temperature ?? 0);
|
|
2845
|
-
}
|
|
2846
|
-
const melt0 = part(room, drum2, "drum_body")?.melt ?? 0;
|
|
2847
|
-
expect(melt0 === 0 && peak < 1450, t("sc.torch_melts_steel.wood", { peak: num(peak, 0), melt: num(melt0 * 100, 0), crate: yn(anyBurning(room, crate2)) }), { peak });
|
|
2848
|
-
room.setTool("cutting_torch");
|
|
2849
|
-
room.waitUntilReady();
|
|
2850
|
-
look(room, [-0.15, 0.55, -3.55], [-0.15, 0.5, -3.72]);
|
|
2851
|
-
room.use(20);
|
|
2852
|
-
const p = part(room, drum2, "drum_body");
|
|
2853
|
-
expect((p?.melt ?? 0) > 0.02 || p?.status === "melted" || (p?.holes.length ?? 0) > 0, t("sc.torch_melts_steel.torch", { temp: num(p?.temperature ?? 0, 0), melt: num((p?.melt ?? 0) * 100, 0) }), { temperature: p?.temperature, melt: p?.melt });
|
|
2854
|
-
}),
|
|
2855
|
-
sc("bucket_melts_drips", ({ room, step, expect }) => {
|
|
2856
|
-
const id = at(room, "plastic_bucket", 0, -4);
|
|
2857
|
-
room.setTool("flamethrower");
|
|
2858
|
-
room.waitUntilReady();
|
|
2859
|
-
look(room, [0, 0.5, -2.4], [0, 0.15, -4]);
|
|
2860
|
-
room.use(2.5);
|
|
2861
|
-
step(10);
|
|
2862
|
-
const p = part(room, id, "bucket_body");
|
|
2863
|
-
const drips = count(room, "drip", id);
|
|
2864
|
-
expect((p?.melt ?? 0) > 0.05 || p?.status === "melted", t("sc.bucket_melts_drips.check", { melt: num((p?.melt ?? 0) * 100, 0), drips, temp: num(p?.temperature ?? 0, 0) }), { melt: p?.melt, drips });
|
|
2865
|
-
}),
|
|
2866
|
-
sc("gas_bottle_explodes", ({ room, step, expect }) => {
|
|
2867
|
-
const bottle2 = at(room, "gas_bottle", 0, -4);
|
|
2868
|
-
const crate2 = at(room, "wooden_crate", 1.3, -4);
|
|
2869
|
-
const c0 = room.spawned().find((s) => s.objectId === crate2)?.root.position.clone();
|
|
2870
|
-
let seconds = 0;
|
|
2871
|
-
for (; seconds < 60 && count(room, "explode", bottle2) === 0; seconds++) {
|
|
2872
|
-
igniteHit(room, bottle2, [0, 0.4, -3.9], 1e3, 1);
|
|
2873
|
-
step(1);
|
|
2874
|
-
}
|
|
2875
|
-
step(1.5);
|
|
2876
|
-
const c1 = room.spawned().find((s) => s.objectId === crate2)?.root.position;
|
|
2877
|
-
const moved = c0 && c1 ? c0.distanceTo(c1) : 0;
|
|
2878
|
-
const state = room.world.state(crate2);
|
|
2879
|
-
const lost = (state?.parts ?? []).filter((p) => p.status === "broken").length + (state && state.groups.length > 1 ? 1 : 0);
|
|
2880
|
-
expect(count(room, "explode", bottle2) > 0, t("sc.gas_bottle_explodes.explode", { seconds }), { seconds });
|
|
2881
|
-
expect(moved > 0.3 || lost > 0, t("sc.gas_bottle_explodes.push", { moved: num(moved, 1), lost: pc(lost) }), { moved, lost });
|
|
2882
|
-
}),
|
|
2883
|
-
sc("bottle_drop_shatters", ({ room, step, expect }) => {
|
|
2884
|
-
const id = at(room, "glass_bottle", 0, -3, 1.5);
|
|
2885
|
-
step(2);
|
|
2886
|
-
const p = part(room, id, "bottle_body");
|
|
2887
|
-
const stain = room.fluids.stains().find((s) => s.liquid === "water");
|
|
2888
|
-
expect(p?.status === "broken", t("sc.bottle_drop_shatters.check", { status: p?.status ?? "?", cracks: p?.cracks.length ?? 0, stain: yn(!!stain) }), { status: p?.status });
|
|
2889
|
-
}),
|
|
2890
|
-
sc("throw_chair_at_wall", ({ room, step, expect }) => {
|
|
2891
|
-
const id = at(room, "chair", 0, -5.2);
|
|
2892
|
-
look(room, [0, 1.7, -4.2], [0, 0.35, -5.2]);
|
|
2893
|
-
room.grab();
|
|
2894
|
-
look(room, [0, 1.7, -4.2], [0, 2.3, -11]);
|
|
2895
|
-
step(0.3);
|
|
2896
|
-
room.throwHeld(16);
|
|
2897
|
-
step(2.5);
|
|
2898
|
-
const hits = room.log.entries({ type: "impact" });
|
|
2899
|
-
const wallDamage = room.log.entries().filter((e) => e.objectId === "target_wall" && (e.type === "dent" || e.type === "damage" || e.type === "crack" || e.type === "fracture")).length;
|
|
2900
|
-
expect(hits.length > 0 && wallDamage > 0, t("sc.throw_chair_at_wall.check", { impacts: hits.length, wall: wallDamage, energy: num(Number(hits[0]?.data?.energyJ ?? 0), 0) }), { id });
|
|
2901
|
-
}),
|
|
2902
|
-
sc("coffee_three_sips", ({ room, step, expect }) => {
|
|
2903
|
-
const id = at(room, "coffee_cup", 0, -3);
|
|
2904
|
-
look(room, [0, 1, -1.8], [0, 0.05, -3]);
|
|
2905
|
-
const cid = `${id}_fluid`;
|
|
2906
|
-
const before = room.fluids.container(cid)?.liters ?? 0;
|
|
2907
|
-
room.grab();
|
|
2908
|
-
for (let i = 0; i < 3; i++) {
|
|
2909
|
-
room.useItem("sip");
|
|
2910
|
-
step(1.4);
|
|
2911
|
-
}
|
|
2912
|
-
const after = room.fluids.container(cid)?.liters ?? 0;
|
|
2913
|
-
const drinks = room.log.entries().filter((e) => e.type === "fluid.drink");
|
|
2914
|
-
const caffeine = drinks.some((e) => Array.isArray(e.data?.effects) && (e.data?.effects).includes("caffeine"));
|
|
2915
|
-
expect(drinks.length === 3 && before - after > 0.06 && caffeine, t("sc.coffee_three_sips.check", { sips: drinks.length, before: num(before, 2), after: num(after, 2), caffeine: yn(caffeine), burn: yn(drinks.some((e) => !!e.data?.burn)) }), { before, after });
|
|
2916
|
-
}),
|
|
2917
|
-
sc("tilt_cup_spills", ({ room, step, expect }) => {
|
|
2918
|
-
const id = at(room, "coffee_cup", 0, -3);
|
|
2919
|
-
look(room, [0, 1, -1.8], [0, 0.05, -3]);
|
|
2920
|
-
const cid = `${id}_fluid`;
|
|
2921
|
-
const before = room.fluids.container(cid)?.liters ?? 0;
|
|
2922
|
-
room.grab();
|
|
2923
|
-
step(0.6);
|
|
2924
|
-
room.tilt(2);
|
|
2925
|
-
step(2.5);
|
|
2926
|
-
const after = room.fluids.container(cid)?.liters ?? 0;
|
|
2927
|
-
const stains = room.fluids.stains().filter((s) => s.liquid === "coffee").length;
|
|
2928
|
-
expect(before - after > 0.03 && stains > 0, t("sc.tilt_cup_spills.check", { before: num(before, 2), after: num(after, 2), stains }), { before, after });
|
|
2929
|
-
}),
|
|
2930
|
-
sc("jerrycan_leaks_fuel", ({ room, step, expect }) => {
|
|
2931
|
-
const id = at(room, "jerrycan", 0, -3.5);
|
|
2932
|
-
room.setTool("pistol_9mm");
|
|
2933
|
-
room.waitUntilReady();
|
|
2934
|
-
look(room, [0, 0.4, -1.5], [0, 0.12, -3.43]);
|
|
2935
|
-
room.use();
|
|
2936
|
-
step(1.5);
|
|
2937
|
-
const streams = room.fluids.streams().filter((s) => s.cause === "leak" && s.liquid === "fuel");
|
|
2938
|
-
step(5);
|
|
2939
|
-
const stain = room.fluids.stains().find((s) => s.liquid === "fuel");
|
|
2940
|
-
const left = room.fluids.container(`${id}_fluid`)?.liters ?? 20;
|
|
2941
|
-
const hole = room.log.entries({ type: "hole", objectId: id })[0];
|
|
2942
|
-
expect(streams.length > 0 && !!stain && left < 20, t("sc.jerrycan_leaks_fuel.check", { streams: streams.length, stain: yn(!!stain), left: num(left, 2), leak: hole?.data?.leak ? yn(true) : yn(false) }), { left });
|
|
2943
|
-
}),
|
|
2944
|
-
sc("fuel_stain_burns", ({ room, step, expect }) => {
|
|
2945
|
-
const id = at(room, "jerrycan", 0, -3.5);
|
|
2946
|
-
const crate2 = at(room, "wooden_crate", 0.9, -3.2);
|
|
2947
|
-
room.setTool("pistol_9mm");
|
|
2948
|
-
room.waitUntilReady();
|
|
2949
|
-
look(room, [0, 0.4, -1.5], [0, 0.12, -3.43]);
|
|
2950
|
-
room.use();
|
|
2951
|
-
step(6);
|
|
2952
|
-
const stain = room.fluids.stains().find((s) => s.liquid === "fuel");
|
|
2953
|
-
expect(!!stain, t("sc.fuel_stain_burns.stain", { stain: yn(!!stain) }));
|
|
2954
|
-
if (!stain) return;
|
|
2955
|
-
room.setTool("lighter");
|
|
2956
|
-
room.waitUntilReady();
|
|
2957
|
-
look(room, [stain.center[0], 0.1, stain.center[2] + 0.06], [stain.center[0], 0, stain.center[2]]);
|
|
2958
|
-
room.use(1);
|
|
2959
|
-
step(4);
|
|
2960
|
-
const ignited = count(room, "stain_ignite");
|
|
2961
|
-
const fireHits = (room.world.state(id)?.parts ?? []).some((p) => p.burning || p.temperature > 100) || anyBurning(room, crate2);
|
|
2962
|
-
expect(ignited > 0, t("sc.fuel_stain_burns.ignite", { ignited, near: yn(fireHits) }), { ignited });
|
|
2963
|
-
step(30);
|
|
2964
|
-
expect(count(room, "stain_burned") > 0 || room.fluids.stains().filter((s) => s.liquid === "fuel" && s.wetness > 0.05).length === 0, t("sc.fuel_stain_burns.out", { burned: count(room, "stain_burned") }));
|
|
2965
|
-
}),
|
|
2966
|
-
sc("one_column_roof_stands", ({ room, step, expect }) => {
|
|
2967
|
-
const id = at(room, "building_frame", 0, -6);
|
|
2968
|
-
killColumns(room, id, ["column_0_0_0"]);
|
|
2969
|
-
step(40);
|
|
2970
|
-
const roof = part(room, id, "roof");
|
|
2971
|
-
const neighbour = part(room, id, "column_0_0_1")?.stress ?? 0;
|
|
2972
|
-
const heavy = Math.max(heavyCollapse(room), ...room.renderer.pieces().map((p) => p.mass));
|
|
2973
|
-
expect(!!roof && roof.status !== "broken" && heavy < 2e3, t("sc.one_column_roof_stands.check", { roof: roof ? t("roof.standing") : t("roof.fell"), heavy: num(heavy, 0), stress: num(neighbour * 100, 0) }), { heavy, neighbour });
|
|
2974
|
-
}),
|
|
2975
|
-
sc("two_columns_roof_collapses", ({ room, step, expect }) => {
|
|
2976
|
-
const id = at(room, "building_frame", 0, -6);
|
|
2977
|
-
killColumns(room, id, ["column_0_0_0", "column_0_1_0"]);
|
|
2978
|
-
const stress0 = room.world.part(id, "column_0_0_1")?.stress ?? 0;
|
|
2979
|
-
step(40);
|
|
2980
|
-
const pieces = room.renderer.pieces().filter((p) => p.mass >= 2e3);
|
|
2981
|
-
const logged = heavyCollapse(room) >= 2e3;
|
|
2982
|
-
const kg = pieces.reduce((m, p) => m + p.mass, 0);
|
|
2983
|
-
expect(!part(room, id, "roof") && pieces.length > 0, t("sc.two_columns_roof_collapses.check", { stress: num(stress0 * 100, 0), pieces: pc(pieces.length), kg: num(kg, 0), logged: yn(logged) }), { kg, logged });
|
|
2984
|
-
}),
|
|
2985
|
-
sc("extinguisher_puts_out_fire", ({ room, step, expect }) => {
|
|
2986
|
-
const id = at(room, "wooden_crate", 0, -4);
|
|
2987
|
-
igniteHit(room, id, [0, 0.25, -3.8], 900, 4);
|
|
2988
|
-
step(6);
|
|
2989
|
-
const before = anyBurning(room, id);
|
|
2990
|
-
expect(before, t("sc.extinguisher_puts_out_fire.burning", { burning: yn(before) }));
|
|
2991
|
-
room.setTool("extinguisher");
|
|
2992
|
-
room.waitUntilReady();
|
|
2993
|
-
look(room, [0, 0.9, -2.2], [0, 0.25, -4]);
|
|
2994
|
-
room.use(2.5);
|
|
2995
|
-
step(1);
|
|
2996
|
-
const after = anyBurning(room, id);
|
|
2997
|
-
expect(!after, t("sc.extinguisher_puts_out_fire.out", { burning: yn(after), events: count(room, "extinguish", id) }), { events: count(room, "extinguish", id) });
|
|
2998
|
-
})
|
|
2999
|
-
];
|
|
1995
|
+
// src/avatar.ts
|
|
1996
|
+
var DEFAULT_STRIKE_CLIPS = { hand: "punch_right", fists: "punch_right", shove: "shove_two_hands", kick: "front_kick" };
|
|
1997
|
+
var DEFAULT_HAND_STANCES = { fists: "fists" };
|
|
1998
|
+
var THIRD_PERSON_DEFAULTS = {
|
|
1999
|
+
back: 3.8,
|
|
2000
|
+
side: 0.9,
|
|
2001
|
+
up: 1.95,
|
|
2002
|
+
crouchUp: 1.4,
|
|
2003
|
+
lift: 0.1,
|
|
2004
|
+
margin: 0.3,
|
|
2005
|
+
minDistance: 0.2,
|
|
2006
|
+
probes: [0, 0.2, -0.2],
|
|
2007
|
+
recover: 4
|
|
2008
|
+
};
|
|
2009
|
+
function thirdPersonPose(input, options, probe) {
|
|
2010
|
+
const cy = Math.cos(input.pitch);
|
|
2011
|
+
const d = [-Math.sin(input.yaw) * cy, Math.sin(input.pitch), -Math.cos(input.yaw) * cy];
|
|
2012
|
+
const rx = Math.cos(input.yaw);
|
|
2013
|
+
const rz = -Math.sin(input.yaw);
|
|
2014
|
+
const pivot = [input.x + rx * options.side, input.feet + (input.crouch ? options.crouchUp : options.up), input.z + rz * options.side];
|
|
2015
|
+
const dx = -d[0] * options.back;
|
|
2016
|
+
const dy = -d[1] * options.back + options.lift;
|
|
2017
|
+
const dz = -d[2] * options.back;
|
|
2018
|
+
const wanted = Math.hypot(dx, dy, dz);
|
|
2019
|
+
const dir = [dx / wanted, dy / wanted, dz / wanted];
|
|
2020
|
+
let free = wanted;
|
|
2021
|
+
for (const o of options.probes) {
|
|
2022
|
+
const hit = probe([pivot[0] + rx * o, pivot[1], pivot[2] + rz * o], dir, wanted);
|
|
2023
|
+
if (hit !== null) free = Math.min(free, Math.max(options.minDistance, hit - options.margin));
|
|
2024
|
+
}
|
|
2025
|
+
const at = (distance) => [pivot[0] + dir[0] * distance, Math.max(0.1, pivot[1] + dir[1] * distance), pivot[2] + dir[2] * distance];
|
|
2026
|
+
return { position: at(free), at, pivot, direction: d, wanted, free };
|
|
3000
2027
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
2028
|
+
|
|
2029
|
+
// src/locale.ts
|
|
2030
|
+
var en = {
|
|
2031
|
+
items: {},
|
|
2032
|
+
groups: { furniture: "Furniture", structure: "Structure", vehicles: "Vehicles", plants: "Plants", items: "Items", containers: "Containers" },
|
|
2033
|
+
text: {
|
|
2034
|
+
"locale.decimal": ".",
|
|
2035
|
+
yes: "yes",
|
|
2036
|
+
no: "no",
|
|
2037
|
+
piece: "piece",
|
|
2038
|
+
coords: "x {x} \xB7 z {z}",
|
|
2039
|
+
"count.piece.one": "1 piece",
|
|
2040
|
+
"count.piece.other": "{n} pieces",
|
|
2041
|
+
"count.joint.one": "1 joint",
|
|
2042
|
+
"count.joint.other": "{n} joints",
|
|
2043
|
+
"count.dent.one": "1 dent",
|
|
2044
|
+
"count.dent.other": "{n} dents",
|
|
2045
|
+
"count.hole.one": "1 hole",
|
|
2046
|
+
"count.hole.other": "{n} holes",
|
|
2047
|
+
"count.crack.one": "1 crack",
|
|
2048
|
+
"count.crack.other": "{n} cracks",
|
|
2049
|
+
"joint.generic": "joint",
|
|
2050
|
+
"lost.broken": "broke",
|
|
2051
|
+
"lost.melted": "melted away",
|
|
2052
|
+
"lost.ashes": "burned to ash",
|
|
2053
|
+
"tool.none": "by hand only",
|
|
2054
|
+
"arena.target_wall": "Target wall",
|
|
2055
|
+
"arena.table": "Table",
|
|
2056
|
+
"arena.shelf": "Shelf",
|
|
2057
|
+
"surface.floor": "on the floor",
|
|
2058
|
+
"surface.ground": "on the floor",
|
|
2059
|
+
"surface.table": "on the table",
|
|
2060
|
+
"by.break": "broken loose",
|
|
2061
|
+
"by.tool": "undone with a tool",
|
|
2062
|
+
"by.part_lost": "the part next to it was lost",
|
|
2063
|
+
"dmg.ballistic": "ballistic",
|
|
2064
|
+
"dmg.blunt": "blunt",
|
|
2065
|
+
"dmg.slash": "slash",
|
|
2066
|
+
"dmg.explosive": "blast",
|
|
2067
|
+
"dmg.fire": "fire",
|
|
2068
|
+
"dmg.heat": "heat",
|
|
2069
|
+
"effect.caffeine": "caffeine",
|
|
2070
|
+
"effect.alcohol": "alcohol",
|
|
2071
|
+
"effect.poison": "poison",
|
|
2072
|
+
"effect.disgust": "disgust",
|
|
2073
|
+
"ev.dent": "{where}: dent of {mm} mm, {hp}",
|
|
2074
|
+
"ev.hole.through": "{where}: {mm} mm hole went through, {hp}",
|
|
2075
|
+
"ev.hole.stuck": "{where}: {mm} mm hole, did not go through, {hp}",
|
|
2076
|
+
"ev.dents": "{where}: {n} dents, deepest {mm} mm, {hp}",
|
|
2077
|
+
"ev.holes": "{where}: {n} holes of {mm} mm, {thr} went through, {hp}",
|
|
2078
|
+
"ev.leak.tail": ", leaking {liquid} at {rate} l/s",
|
|
2079
|
+
"ev.leak.alone": "{where}: leaking {liquid} at {rate} l/s",
|
|
2080
|
+
"ev.crack": "{where}: cracked, {rays} rays up to {cm} cm, {hp}",
|
|
2081
|
+
"ev.ricochet": "{where}: bullet ricocheted, {energy} J left",
|
|
2082
|
+
"ev.fracture.shatter": "{where}: shattered into {n} shards",
|
|
2083
|
+
"ev.fracture.crumble": "{where}: crumbled into {n} chunks",
|
|
2084
|
+
"ev.fracture.splinter": "{where}: splintered into {n} pieces",
|
|
2085
|
+
"ev.fracture.tear": "{where}: torn into {n} scraps",
|
|
2086
|
+
"ev.fracture.snap": "{where}: snapped into {n} pieces",
|
|
2087
|
+
"ev.fracture.none": "{where}: broke into {n} pieces",
|
|
2088
|
+
"ev.detach": "{obj}: {a} came off {b} ({by}); the object now has {pieces}",
|
|
2089
|
+
"ev.detach.many.break": "{obj}: {joints} broke loose; the object now has {pieces}",
|
|
2090
|
+
"ev.detach.many.tool": "{obj}: {joints} undone with a tool; the object now has {pieces}",
|
|
2091
|
+
"ev.lost.tail": " and came loose from {joints}; the object now has {pieces}",
|
|
2092
|
+
"ev.lost.alone": "{where}: {verb} and came loose from {joints}; the object now has {pieces}",
|
|
2093
|
+
"ev.cracks": "{where}: cracking under the load, {n} cracks so far, longest {cm} cm, {hp}",
|
|
2094
|
+
"ev.disassemble": "{obj}: undoing the {kind} between {a} and {b} with {tool}: {pct}%",
|
|
2095
|
+
"ev.ignite": "{where}: caught fire at {temp} \xB0C",
|
|
2096
|
+
"ev.ignite.many": "{obj}: {n} parts caught fire at {temp} \xB0C ({parts})",
|
|
2097
|
+
"ev.extinguish": "{where}: fire put out",
|
|
2098
|
+
"ev.extinguish.many": "{obj}: fire put out on {n} parts ({parts})",
|
|
2099
|
+
"ev.burnout": "{where}: burned to ash",
|
|
2100
|
+
"ev.melt": "{where}: melting, {pct}% melted away",
|
|
2101
|
+
"ev.drip": "{where}: dripping molten material",
|
|
2102
|
+
"ev.explode": "{where}: exploded, {energy} J within {radius} m",
|
|
2103
|
+
"ev.destroyed": "{obj}: destroyed",
|
|
2104
|
+
"ev.damage": "{where}: {type} damage {amount}, {hp}",
|
|
2105
|
+
"ev.damage.overload": "{where}: crushed by the weight it carries, lost {amount} hp so far, {hp}",
|
|
2106
|
+
"ev.stress": "{where}: overloaded at {stress}% of its capacity, carrying {load} kg",
|
|
2107
|
+
"ev.collapse": "{obj}: collapsed, {pieces} fell ({mass} kg): {parts}",
|
|
2108
|
+
"ev.stain_ignite": "The {liquid} stain at x {x} \xB7 z {z} caught fire and will burn for about {seconds} s",
|
|
2109
|
+
"ev.stain_burned": "The burning stain burned itself out",
|
|
2110
|
+
"ev.fire_dries": "Fire dried the wet stains nearby",
|
|
2111
|
+
"fl.fill": "{who}: filled with {liquid} (+{liters} l)",
|
|
2112
|
+
"fl.pour": "{who}: pouring {liquid} at {rate} l/s",
|
|
2113
|
+
"fl.spill": "{who}: spilled {liquid} over the lip at {rate} l/s",
|
|
2114
|
+
"fl.leak": "{who}: leaking {liquid} at {rate} l/s",
|
|
2115
|
+
"fl.drink": "{who}: drank {liters} l, thirst +{thirst}",
|
|
2116
|
+
"fl.burn": "too hot: burned the mouth",
|
|
2117
|
+
"fl.empty": "{who}: empty",
|
|
2118
|
+
"fl.splash": "Splash of {liquid}",
|
|
2119
|
+
"fl.stain": "Stain of {liquid} {surface} at {where}",
|
|
2120
|
+
"fl.stains": "{n} stains of {liquid} {surface}, the latest at {where}",
|
|
2121
|
+
"fl.dried": "A stain dried up",
|
|
2122
|
+
"fl.dried.mark": "A stain dried and left a mark",
|
|
2123
|
+
"fl.dried.known": "The {liquid} stain {surface} at {where} dried up",
|
|
2124
|
+
"fl.dried.known.mark": "The {liquid} stain {surface} at {where} dried and left a mark",
|
|
2125
|
+
"fl.dried.many": "{n} {liquid} stains dried up",
|
|
2126
|
+
"fl.dried.many.mark": "{n} {liquid} stains dried and left marks",
|
|
2127
|
+
"fl.mix": "{who}: {from} mixed into {into}",
|
|
2128
|
+
"act.anim_start": '{name}: animation "{clip}" ({seconds} s)',
|
|
2129
|
+
"act.anim_event": '{name}: "{clip}" event {type}',
|
|
2130
|
+
"act.anim_end": '{name}: animation "{clip}" finished',
|
|
2131
|
+
"act.area_skip": 'Area item "{item}" is not loaded in this room: left out',
|
|
2132
|
+
"act.module_loaded": 'Module "{name}" loaded: {items} item(s), {scenarios} scenario(s), contract checks: {result}',
|
|
2133
|
+
"mod.all_ok": "all passed",
|
|
2134
|
+
"mod.some_failed": "some FAILED",
|
|
2135
|
+
"mod.destructible_ok": "{name}: destructible contract ok ({parts} parts, hit and state well-formed)",
|
|
2136
|
+
"mod.destructible_fail": "{name}: destructible contract broken: {problems}",
|
|
2137
|
+
"mod.animation_ok": '{name}: animation "{clip}" ok ({seconds} s)',
|
|
2138
|
+
"mod.spec_ok": "{name}: spec ok ({parts} parts)",
|
|
2139
|
+
"mod.spec_fail": "{name}: spec broken: {problems}",
|
|
2140
|
+
"mod.spec_warn": "{name}: spec warning: {warning}",
|
|
2141
|
+
"mod.animation_fail": '{name}: animation "{clip}" broken: {problems}',
|
|
2142
|
+
"ui.animations": "Animations",
|
|
2143
|
+
"ui.animations_aim": "Aim at an item or hold it to see its animations.",
|
|
2144
|
+
"ui.animations_none": "This item brings no animations.",
|
|
2145
|
+
"mod.bench_ok": "{name}: bench ok ({controls} controls, {layers} layers, {metrics} metrics)",
|
|
2146
|
+
"mod.bench_fail": "{name}: bench broken: {problems}",
|
|
2147
|
+
"mod.metric_failed": '{name}: metric "{metric}" threw and shows n/a: {error}',
|
|
2148
|
+
"mod.listener_failed": "{name}: a bench listener threw: {error}",
|
|
2149
|
+
"ui.bench": "Bench",
|
|
2150
|
+
"ui.layers": "Layers",
|
|
2151
|
+
"ui.metrics": "Counters",
|
|
2152
|
+
"metric.fps": "FPS",
|
|
2153
|
+
"metric.frame_ms": "ms/frame",
|
|
2154
|
+
"metric.draw_calls": "Draw calls",
|
|
2155
|
+
"metric.triangles": "Triangles",
|
|
2156
|
+
"metric.objects": "Objects",
|
|
2157
|
+
"metric.fragments": "Fragments",
|
|
2158
|
+
"metric.particles": "Particles",
|
|
2159
|
+
"metric.decals": "Decals",
|
|
2160
|
+
"act.spawn": "Spawned {name} at x {x} \xB7 z {z}",
|
|
2161
|
+
"act.clear": "Cleared the room",
|
|
2162
|
+
"act.reset": "Repaired every object",
|
|
2163
|
+
"act.tool": "Equipped {weapon}",
|
|
2164
|
+
"act.shoot.hit": "{weapon}: shot, {energy} J, hit {where} at {dist} m",
|
|
2165
|
+
"act.shoot.miss": "{weapon}: shot, {energy} J, hit nothing",
|
|
2166
|
+
"act.shoot.pellets": "{weapon}: shot, {n} projectiles of up to {energy} J, {hits} hit {where} at {dist} m",
|
|
2167
|
+
"act.melee.hit": "{weapon}: strike, {energy} J, hit {where} at {dist} m",
|
|
2168
|
+
"act.melee.miss": "{weapon}: struck the air",
|
|
2169
|
+
"act.strike": "{weapon}: {clip} begins, impact in {seconds} s",
|
|
2170
|
+
"act.strike.leg": "{weapon}: {clip} with the {leg} leg begins, impact in {seconds} s",
|
|
2171
|
+
"leg.left": "left",
|
|
2172
|
+
"leg.right": "right",
|
|
2173
|
+
"act.flame.hit": "{weapon}: flame at {temp} \xB0C on {where}",
|
|
2174
|
+
"act.flame.air": "{weapon}: flame, nothing in range",
|
|
2175
|
+
"act.heat.hit": "{weapon}: heating {where} at {temp} \xB0C",
|
|
2176
|
+
"act.heat.air": "{weapon}: nothing in range",
|
|
2177
|
+
"act.throw.grenade": "{weapon}: thrown, fuse {fuse} s",
|
|
2178
|
+
"act.throw.molotov": "{weapon}: thrown, breaks on impact",
|
|
2179
|
+
"act.launch": "{weapon}: launched, arms after {arm} m",
|
|
2180
|
+
"act.place": "{weapon}: charge set down, waits for {triggers}",
|
|
2181
|
+
"act.tripwire": "{weapon}: {who} crossed the trip wire",
|
|
2182
|
+
"act.dud": "{weapon}: hit before arming ({distance} m), did not explode",
|
|
2183
|
+
"act.backblast": "{weapon}: blast behind the tube, {radius} m, {n} objects in the cone, {player} on the shooter",
|
|
2184
|
+
"act.blast_player": "{weapon}: you are in the {zone} zone, {distance} m away (stun {stun} s)",
|
|
2185
|
+
"hud.no_charges": "No charge set down waits for the remote detonator",
|
|
2186
|
+
"blast.zone.lethal": "lethal",
|
|
2187
|
+
"blast.zone.injury": "injury",
|
|
2188
|
+
"act.explode": "{weapon}: exploded, {energy} J within {radius} m, {n} objects in range",
|
|
2189
|
+
"act.molotov_fire": "{weapon}: fire on the ground, radius {radius} m for {seconds} s",
|
|
2190
|
+
"act.tool.none": "{weapon}: nothing in reach",
|
|
2191
|
+
"act.tool.nojoint": "{weapon}: no joint near {where}",
|
|
2192
|
+
"act.tool.refused": "{weapon} cannot undo the {kind} of {obj}; it needs: {need}",
|
|
2193
|
+
"act.extinguish.hit": "{weapon}: foam on {what}",
|
|
2194
|
+
"act.extinguish.air": "{weapon}: foam, nothing in range",
|
|
2195
|
+
"act.grab": "Grabbed {name} ({kg} kg)",
|
|
2196
|
+
"act.drop": "Dropped {name}",
|
|
2197
|
+
"act.throw": "Threw {name} at {speed} m/s",
|
|
2198
|
+
"act.landing": "{name} landed at {speed} m/s: {energy} J",
|
|
2199
|
+
"act.bench_failed": "{name} failed: {error}",
|
|
2200
|
+
"loading.working": "Working\u2026",
|
|
2201
|
+
"loading.open_area": "Opening {area}\u2026",
|
|
2202
|
+
"loading.spawn": "Building {name}\u2026",
|
|
2203
|
+
"act.loosened": "{name} was heavy but free: the blast moved it",
|
|
2204
|
+
"act.impact": "{name} hit {where} at {speed} m/s: {energy} J of blunt damage",
|
|
2205
|
+
"act.use_item": "{name}: {use}",
|
|
2206
|
+
"act.tap.on": "Tap opened",
|
|
2207
|
+
"act.tap.off": "Tap closed",
|
|
2208
|
+
"use.drink": "drink",
|
|
2209
|
+
"use.sip": "sip",
|
|
2210
|
+
"use.chug": "chug",
|
|
2211
|
+
"use.pour": "pour",
|
|
2212
|
+
"use.pour_out": "pour out",
|
|
2213
|
+
"use.open": "open",
|
|
2214
|
+
"use.close": "close",
|
|
2215
|
+
"hud.reloading": "Reloading...",
|
|
2216
|
+
"hud.empty": "Empty",
|
|
2217
|
+
"hud.no_fuel": "Out of fuel",
|
|
2218
|
+
"hud.fault": "Jammed ({fault}): press X to clear",
|
|
2219
|
+
"hud.safe": "Safety on: press B",
|
|
2220
|
+
"hud.clearing": "Clearing the jam...",
|
|
2221
|
+
"hud.safety_on": "Safety on",
|
|
2222
|
+
"hud.safety_off": "Safety off",
|
|
2223
|
+
"fault.misfire": "misfire",
|
|
2224
|
+
"fault.stovepipe": "stovepipe",
|
|
2225
|
+
"fault.double_feed": "double feed",
|
|
2226
|
+
"fault.failure_to_extract": "failure to extract",
|
|
2227
|
+
"hud.nothing_to_grab": "Nothing to grab here",
|
|
2228
|
+
"hud.cannot_grab": "{name} cannot be picked up",
|
|
2229
|
+
"hud.too_heavy": "{name} is too heavy ({kg} kg)",
|
|
2230
|
+
"hud.empty_container": "{name} is empty",
|
|
2231
|
+
"hud.refused": "Wrong tool for the {kind}",
|
|
2232
|
+
"hud.drop": "drop",
|
|
2233
|
+
"hud.rotate": "rotate",
|
|
2234
|
+
"hud.throw": "throw",
|
|
2235
|
+
"hud.tilt": "tilt (hold)",
|
|
2236
|
+
"hud.equip": "equip {weapon}",
|
|
2237
|
+
"ui.help": "Click: look \xB7 WASD: walk \xB7 Shift: run \xB7 C: crouch \xB7 Space: jump \xB7 1-0 / wheel: tool \xB7 LMB: use \xB7 E: grab/drop \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 Tab: panels \xB7 Esc: free the mouse",
|
|
2238
|
+
"ui.help.camera": "V: 1st/3rd person (Shift+V: kick)",
|
|
2239
|
+
"camera.first": "1st person",
|
|
2240
|
+
"camera.third": "3rd person",
|
|
2241
|
+
"act.camera": "View: {mode}",
|
|
2242
|
+
"ui.log": "Behaviour log",
|
|
2243
|
+
"ui.filter.action": "actions",
|
|
2244
|
+
"ui.filter.event": "events",
|
|
2245
|
+
"ui.filter.check": "checks",
|
|
2246
|
+
"ui.autoscroll": "autoscroll",
|
|
2247
|
+
"log.export_cancelled": "export cancelled",
|
|
2248
|
+
"ui.copy_json": "Copy JSON",
|
|
2249
|
+
"ui.download_ndjson": "Download NDJSON",
|
|
2250
|
+
"ui.download_txt": "Download TXT",
|
|
2251
|
+
"ui.clear_log": "Clear",
|
|
2252
|
+
"ui.catalog": "Catalogue",
|
|
2253
|
+
"ui.clear": "Clear",
|
|
2254
|
+
"ui.reset_all": "Reset all",
|
|
2255
|
+
"ui.slow": "Slow motion 0.25\xD7",
|
|
2256
|
+
"ui.fast": "Time \xD710",
|
|
2257
|
+
"ui.tap": "Tap",
|
|
2258
|
+
"ui.fullscreen": "Full screen",
|
|
2259
|
+
"ui.fullscreen.hint": "Full screen opened from here keeps the whole keyboard in the game, Ctrl+W included (F11 does not)",
|
|
2260
|
+
"ui.blast.shelter": "Shelter",
|
|
2261
|
+
"ui.blast.fragmentHits": "Fragments",
|
|
2262
|
+
"ui.blast.confinement": "Confinement",
|
|
2263
|
+
"ui.blast.shelter.hint": "Blasts: walls and other parts between the blast and the target take their share",
|
|
2264
|
+
"ui.blast.fragmentHits.hint": "Blasts: the pieces of what breaks fly on and hit what is behind",
|
|
2265
|
+
"ui.blast.confinement.hint": "Blasts: pressure builds up in an enclosed room",
|
|
2266
|
+
"metric.blast_shelter": "Blast reaching (%)",
|
|
2267
|
+
"metric.blast_confinement": "Blast pressure (x)",
|
|
2268
|
+
"metric.blast_fragments": "Fragment hits",
|
|
2269
|
+
"act.blast_report": "Blast: {shelter}% reached the most sheltered target, pressure x{confinement}, {fragments} fragment hits, {n} targets",
|
|
2270
|
+
"ui.quality": "Quality",
|
|
2271
|
+
"ui.quality.low": "low",
|
|
2272
|
+
"ui.quality.medium": "medium",
|
|
2273
|
+
"ui.quality.high": "high",
|
|
2274
|
+
"ui.equip": "Equip as a tool",
|
|
2275
|
+
"ui.search_items": "Search items (all groups)",
|
|
2276
|
+
"ui.search_tests": "Search tests",
|
|
2277
|
+
"ui.no_results": "Nothing found",
|
|
2278
|
+
"ui.objects": "{n} objects",
|
|
2279
|
+
"ui.tests": "Tests",
|
|
2280
|
+
"ui.run_all": "Run all",
|
|
2281
|
+
"ui.running": "Running...",
|
|
2282
|
+
"ui.report": "{passed} passed, {failed} failed",
|
|
2283
|
+
"ui.panel": "Panels",
|
|
2284
|
+
"ui.inspector": "Inspector",
|
|
2285
|
+
"ui.aim_at_something": "Aim at an object",
|
|
2286
|
+
"ins.material": "Material",
|
|
2287
|
+
"ins.status": "Status",
|
|
2288
|
+
"ins.burning": "burning",
|
|
2289
|
+
"ins.hp": "Hit points",
|
|
2290
|
+
"ins.temperature": "Temperature",
|
|
2291
|
+
"ins.char": "Charred",
|
|
2292
|
+
"ins.melt": "Melted",
|
|
2293
|
+
"ins.liters": "Liquid",
|
|
2294
|
+
"ins.damage": "Marks",
|
|
2295
|
+
"ins.damage_counts": "{dents}, {holes}, {cracks}",
|
|
2296
|
+
"ins.mass": "Mass",
|
|
2297
|
+
"ins.load": "Load",
|
|
2298
|
+
"ins.stress": "Stress",
|
|
2299
|
+
"ins.joints": "Joints",
|
|
2300
|
+
"ins.open": "open",
|
|
2301
|
+
"sc.start": "Scenario: {name}",
|
|
2302
|
+
"sc.pass": "Scenario passed: {name}",
|
|
2303
|
+
"sc.fail": "Scenario FAILED: {name}",
|
|
2304
|
+
"sc.error": "the scenario threw an error: {msg}",
|
|
2305
|
+
"leg.still": "leg_1 still attached",
|
|
2306
|
+
"leg.gone": "leg_1 came off",
|
|
2307
|
+
"roof.standing": "roof standing",
|
|
2308
|
+
"roof.fell": "roof fell"
|
|
2309
|
+
}
|
|
2310
|
+
};
|
|
2311
|
+
var ptBR = {
|
|
2312
|
+
items: {},
|
|
2313
|
+
groups: { furniture: "M\xF3veis", structure: "Estrutura", vehicles: "Ve\xEDculos", plants: "Plantas", items: "Itens", containers: "Recipientes" },
|
|
2314
|
+
text: {
|
|
2315
|
+
"locale.decimal": ",",
|
|
2316
|
+
yes: "sim",
|
|
2317
|
+
no: "n\xE3o",
|
|
2318
|
+
piece: "pe\xE7a",
|
|
2319
|
+
coords: "x {x} \xB7 z {z}",
|
|
2320
|
+
"count.piece.one": "1 pe\xE7a",
|
|
2321
|
+
"count.piece.other": "{n} pe\xE7as",
|
|
2322
|
+
"count.joint.one": "1 encaixe",
|
|
2323
|
+
"count.joint.other": "{n} encaixes",
|
|
2324
|
+
"count.dent.one": "1 amassado",
|
|
2325
|
+
"count.dent.other": "{n} amassados",
|
|
2326
|
+
"count.hole.one": "1 furo",
|
|
2327
|
+
"count.hole.other": "{n} furos",
|
|
2328
|
+
"count.crack.one": "1 trinca",
|
|
2329
|
+
"count.crack.other": "{n} trincas",
|
|
2330
|
+
"joint.generic": "junta",
|
|
2331
|
+
"lost.broken": "quebrou",
|
|
2332
|
+
"lost.melted": "derreteu",
|
|
2333
|
+
"lost.ashes": "virou cinza",
|
|
2334
|
+
"tool.none": "s\xF3 com a m\xE3o",
|
|
2335
|
+
"arena.target_wall": "Parede de alvo",
|
|
2336
|
+
"arena.table": "Mesa",
|
|
2337
|
+
"arena.shelf": "Estante",
|
|
2338
|
+
"surface.floor": "no ch\xE3o",
|
|
2339
|
+
"surface.ground": "no ch\xE3o",
|
|
2340
|
+
"surface.table": "na mesa",
|
|
2341
|
+
"by.break": "arrebentou",
|
|
2342
|
+
"by.tool": "desmontado com ferramenta",
|
|
2343
|
+
"by.part_lost": "a pe\xE7a ao lado se perdeu",
|
|
2344
|
+
"dmg.ballistic": "de tiro",
|
|
2345
|
+
"dmg.blunt": "de impacto",
|
|
2346
|
+
"dmg.slash": "de corte",
|
|
2347
|
+
"dmg.explosive": "de explos\xE3o",
|
|
2348
|
+
"dmg.fire": "de fogo",
|
|
2349
|
+
"dmg.heat": "de calor",
|
|
2350
|
+
"effect.caffeine": "cafe\xEDna",
|
|
2351
|
+
"effect.alcohol": "\xE1lcool",
|
|
2352
|
+
"effect.poison": "veneno",
|
|
2353
|
+
"effect.disgust": "nojo",
|
|
2354
|
+
"ev.dent": "{where}: amassado de {mm} mm, {hp}",
|
|
2355
|
+
"ev.hole.through": "{where}: furo de {mm} mm atravessou, {hp}",
|
|
2356
|
+
"ev.hole.stuck": "{where}: furo de {mm} mm n\xE3o atravessou, {hp}",
|
|
2357
|
+
"ev.dents": "{where}: {n} amassados, o mais fundo com {mm} mm, {hp}",
|
|
2358
|
+
"ev.holes": "{where}: {n} furos de {mm} mm, {thr} atravessaram, {hp}",
|
|
2359
|
+
"ev.leak.tail": ", vazando {liquid} a {rate} l/s",
|
|
2360
|
+
"ev.leak.alone": "{where}: vazando {liquid} a {rate} l/s",
|
|
2361
|
+
"ev.crack": "{where}: trincou, {rays} raios de at\xE9 {cm} cm, {hp}",
|
|
2362
|
+
"ev.ricochet": "{where}: a bala ricocheteou, sobraram {energy} J",
|
|
2363
|
+
"ev.fracture.shatter": "{where}: estilha\xE7ou em {n} cacos",
|
|
2364
|
+
"ev.fracture.crumble": "{where}: esfarelou em {n} peda\xE7os",
|
|
2365
|
+
"ev.fracture.splinter": "{where}: lascou em {n} peda\xE7os",
|
|
2366
|
+
"ev.fracture.tear": "{where}: rasgou em {n} retalhos",
|
|
2367
|
+
"ev.fracture.snap": "{where}: partiu em {n} peda\xE7os",
|
|
2368
|
+
"ev.fracture.none": "{where}: quebrou em {n} peda\xE7os",
|
|
2369
|
+
"ev.detach": "{obj}: {a} soltou de {b} ({by}); o objeto agora tem {pieces}",
|
|
2370
|
+
"ev.detach.many.break": "{obj}: {joints} arrebentaram; o objeto agora tem {pieces}",
|
|
2371
|
+
"ev.detach.many.tool": "{obj}: {joints} soltos com ferramenta; o objeto agora tem {pieces}",
|
|
2372
|
+
"ev.lost.tail": " e se soltou de {joints}; o objeto agora tem {pieces}",
|
|
2373
|
+
"ev.lost.alone": "{where}: {verb} e se soltou de {joints}; o objeto agora tem {pieces}",
|
|
2374
|
+
"ev.cracks": "{where}: rachando sob o peso, {n} trincas at\xE9 agora, a maior com {cm} cm, {hp}",
|
|
2375
|
+
"ev.disassemble": "{obj}: soltando {kind} entre {a} e {b} com {tool}: {pct}%",
|
|
2376
|
+
"ev.ignite": "{where}: pegou fogo a {temp} \xB0C",
|
|
2377
|
+
"ev.ignite.many": "{obj}: {n} pe\xE7as pegaram fogo a {temp} \xB0C ({parts})",
|
|
2378
|
+
"ev.extinguish": "{where}: fogo apagado",
|
|
2379
|
+
"ev.extinguish.many": "{obj}: fogo apagado em {n} pe\xE7as ({parts})",
|
|
2380
|
+
"ev.burnout": "{where}: virou cinza",
|
|
2381
|
+
"ev.melt": "{where}: derretendo, {pct}% j\xE1 derreteu",
|
|
2382
|
+
"ev.drip": "{where}: pingando material derretido",
|
|
2383
|
+
"ev.explode": "{where}: explodiu, {energy} J em {radius} m",
|
|
2384
|
+
"ev.destroyed": "{obj}: destru\xEDdo",
|
|
2385
|
+
"ev.damage": "{where}: dano {type} de {amount}, {hp}",
|
|
2386
|
+
"ev.damage.overload": "{where}: esmagado pelo peso que sustenta, perdeu {amount} hp at\xE9 agora, {hp}",
|
|
2387
|
+
"ev.stress": "{where}: sobrecarregado a {stress}% da capacidade, sustentando {load} kg",
|
|
2388
|
+
"ev.collapse": "{obj}: desabou, ca\xEDram {pieces} ({mass} kg): {parts}",
|
|
2389
|
+
"ev.stain_ignite": "A mancha de {liquid} em x {x} \xB7 z {z} pegou fogo e vai queimar por uns {seconds} s",
|
|
2390
|
+
"ev.stain_burned": "A mancha em chamas se consumiu",
|
|
2391
|
+
"ev.fire_dries": "O fogo secou as manchas molhadas por perto",
|
|
2392
|
+
"fl.fill": "{who}: encheu de {liquid} (+{liters} l)",
|
|
2393
|
+
"fl.pour": "{who}: despejando {liquid} a {rate} l/s",
|
|
2394
|
+
"fl.spill": "{who}: derramou {liquid} pela borda a {rate} l/s",
|
|
2395
|
+
"fl.leak": "{who}: vazando {liquid} a {rate} l/s",
|
|
2396
|
+
"fl.drink": "{who}: bebeu {liters} l, sede +{thirst}",
|
|
2397
|
+
"fl.burn": "quente demais: queimou a boca",
|
|
2398
|
+
"fl.empty": "{who}: vazio",
|
|
2399
|
+
"fl.splash": "Respingo de {liquid}",
|
|
2400
|
+
"fl.stain": "Mancha de {liquid} {surface} em {where}",
|
|
2401
|
+
"fl.stains": "{n} manchas de {liquid} {surface}, a \xFAltima em {where}",
|
|
2402
|
+
"fl.dried": "Uma mancha secou",
|
|
2403
|
+
"fl.dried.mark": "Uma mancha secou e deixou marca",
|
|
2404
|
+
"fl.dried.known": "A mancha de {liquid} {surface} em {where} secou",
|
|
2405
|
+
"fl.dried.known.mark": "A mancha de {liquid} {surface} em {where} secou e deixou marca",
|
|
2406
|
+
"fl.dried.many": "{n} manchas de {liquid} secaram",
|
|
2407
|
+
"fl.dried.many.mark": "{n} manchas de {liquid} secaram e deixaram marca",
|
|
2408
|
+
"fl.mix": "{who}: {from} misturou em {into}",
|
|
2409
|
+
"act.anim_start": '{name}: anima\xE7\xE3o "{clip}" ({seconds} s)',
|
|
2410
|
+
"act.anim_event": '{name}: "{clip}" evento {type}',
|
|
2411
|
+
"act.anim_end": '{name}: anima\xE7\xE3o "{clip}" terminou',
|
|
2412
|
+
"act.area_skip": 'Item "{item}" da \xE1rea n\xE3o est\xE1 carregado nesta sala: ficou de fora',
|
|
2413
|
+
"act.module_loaded": 'M\xF3dulo "{name}" carregado: {items} item(ns), {scenarios} cen\xE1rio(s), verifica\xE7\xF5es do contrato: {result}',
|
|
2414
|
+
"mod.all_ok": "todas passaram",
|
|
2415
|
+
"mod.some_failed": "algumas FALHARAM",
|
|
2416
|
+
"mod.destructible_ok": "{name}: contrato de destrui\xE7\xE3o ok ({parts} pe\xE7as, golpe e estado bem formados)",
|
|
2417
|
+
"mod.destructible_fail": "{name}: contrato de destrui\xE7\xE3o quebrado: {problems}",
|
|
2418
|
+
"mod.animation_ok": '{name}: anima\xE7\xE3o "{clip}" ok ({seconds} s)',
|
|
2419
|
+
"mod.spec_ok": "{name}: spec ok ({parts} pe\xE7as)",
|
|
2420
|
+
"mod.spec_fail": "{name}: spec quebrada: {problems}",
|
|
2421
|
+
"mod.spec_warn": "{name}: aviso na spec: {warning}",
|
|
2422
|
+
"mod.animation_fail": '{name}: anima\xE7\xE3o "{clip}" quebrada: {problems}',
|
|
2423
|
+
"ui.animations": "Anima\xE7\xF5es",
|
|
2424
|
+
"ui.animations_aim": "Mire num item ou segure-o para ver as anima\xE7\xF5es.",
|
|
2425
|
+
"ui.animations_none": "Este item n\xE3o traz anima\xE7\xF5es.",
|
|
2426
|
+
"mod.bench_ok": "{name}: bancada ok ({controls} controles, {layers} camadas, {metrics} contadores)",
|
|
2427
|
+
"mod.bench_fail": "{name}: bancada quebrada: {problems}",
|
|
2428
|
+
"mod.metric_failed": '{name}: o contador "{metric}" lan\xE7ou erro e mostra n/a: {error}',
|
|
2429
|
+
"mod.listener_failed": "{name}: um ouvinte da bancada lan\xE7ou erro: {error}",
|
|
2430
|
+
"ui.bench": "Bancada",
|
|
2431
|
+
"ui.layers": "Camadas",
|
|
2432
|
+
"ui.metrics": "Contadores",
|
|
2433
|
+
"metric.fps": "FPS",
|
|
2434
|
+
"metric.frame_ms": "ms/quadro",
|
|
2435
|
+
"metric.draw_calls": "Chamadas de desenho",
|
|
2436
|
+
"metric.triangles": "Tri\xE2ngulos",
|
|
2437
|
+
"metric.objects": "Objetos",
|
|
2438
|
+
"metric.fragments": "Fragmentos",
|
|
2439
|
+
"metric.particles": "Part\xEDculas",
|
|
2440
|
+
"metric.decals": "Decalques",
|
|
2441
|
+
"act.spawn": "Criou {name} em x {x} \xB7 z {z}",
|
|
2442
|
+
"act.clear": "Limpou a sala",
|
|
2443
|
+
"act.reset": "Consertou todos os objetos",
|
|
2444
|
+
"act.tool": "Equipou {weapon}",
|
|
2445
|
+
"act.shoot.hit": "{weapon}: disparo, {energy} J, acertou {where} a {dist} m",
|
|
2446
|
+
"act.shoot.miss": "{weapon}: disparo, {energy} J, n\xE3o acertou nada",
|
|
2447
|
+
"act.shoot.pellets": "{weapon}: disparo, {n} proj\xE9teis de at\xE9 {energy} J, {hits} acertaram {where} a {dist} m",
|
|
2448
|
+
"act.melee.hit": "{weapon}: golpe, {energy} J, acertou {where} a {dist} m",
|
|
2449
|
+
"act.melee.miss": "{weapon}: golpe no ar",
|
|
2450
|
+
"act.strike": "{weapon}: {clip} come\xE7a, impacto em {seconds} s",
|
|
2451
|
+
"act.strike.leg": "{weapon}: {clip} com a perna {leg} come\xE7a, impacto em {seconds} s",
|
|
2452
|
+
"leg.left": "esquerda",
|
|
2453
|
+
"leg.right": "direita",
|
|
2454
|
+
"act.flame.hit": "{weapon}: chama a {temp} \xB0C em {where}",
|
|
2455
|
+
"act.flame.air": "{weapon}: chama, nada ao alcance",
|
|
2456
|
+
"act.heat.hit": "{weapon}: esquentando {where} a {temp} \xB0C",
|
|
2457
|
+
"act.heat.air": "{weapon}: nada ao alcance",
|
|
2458
|
+
"act.throw.grenade": "{weapon}: arremessada, pavio de {fuse} s",
|
|
2459
|
+
"act.throw.molotov": "{weapon}: arremessado, quebra no impacto",
|
|
2460
|
+
"act.launch": "{weapon}: disparado, arma depois de {arm} m",
|
|
2461
|
+
"act.place": "{weapon}: carga posta, espera {triggers}",
|
|
2462
|
+
"act.tripwire": "{weapon}: {who} cruzou o fio",
|
|
2463
|
+
"act.dud": "{weapon}: bateu antes de armar ({distance} m), n\xE3o explodiu",
|
|
2464
|
+
"act.backblast": "{weapon}: sopro atr\xE1s do tubo, {radius} m, {n} objetos no cone, {player} no atirador",
|
|
2465
|
+
"act.blast_player": "{weapon}: voc\xEA est\xE1 na zona {zone}, a {distance} m (atordoado {stun} s)",
|
|
2466
|
+
"hud.no_charges": "Nenhuma carga posta espera o detonador",
|
|
2467
|
+
"blast.zone.lethal": "letal",
|
|
2468
|
+
"blast.zone.injury": "de ferimento",
|
|
2469
|
+
"act.explode": "{weapon}: explodiu, {energy} J em {radius} m, {n} objetos ao alcance",
|
|
2470
|
+
"act.molotov_fire": "{weapon}: fogo no ch\xE3o, raio de {radius} m por {seconds} s",
|
|
2471
|
+
"act.tool.none": "{weapon}: nada ao alcance",
|
|
2472
|
+
"act.tool.nojoint": "{weapon}: nenhuma junta perto de {where}",
|
|
2473
|
+
"act.tool.refused": "{weapon} n\xE3o solta {kind} de {obj}; precisa de: {need}",
|
|
2474
|
+
"act.extinguish.hit": "{weapon}: espuma em {what}",
|
|
2475
|
+
"act.extinguish.air": "{weapon}: espuma, nada ao alcance",
|
|
2476
|
+
"act.grab": "Pegou {name} ({kg} kg)",
|
|
2477
|
+
"act.drop": "Soltou {name}",
|
|
2478
|
+
"act.throw": "Arremessou {name} a {speed} m/s",
|
|
2479
|
+
"act.landing": "{name} caiu a {speed} m/s: {energy} J",
|
|
2480
|
+
"act.bench_failed": "{name} falhou: {error}",
|
|
2481
|
+
"loading.working": "Trabalhando\u2026",
|
|
2482
|
+
"loading.open_area": "Abrindo {area}\u2026",
|
|
2483
|
+
"loading.spawn": "Montando {name}\u2026",
|
|
2484
|
+
"act.loosened": "{name} \xE9 pesado mas estava solto: a explos\xE3o o moveu",
|
|
2485
|
+
"act.impact": "{name} bateu em {where} a {speed} m/s: {energy} J de dano de impacto",
|
|
2486
|
+
"act.use_item": "{name}: {use}",
|
|
2487
|
+
"act.tap.on": "Abriu a torneira",
|
|
2488
|
+
"act.tap.off": "Fechou a torneira",
|
|
2489
|
+
"use.drink": "beber",
|
|
2490
|
+
"use.sip": "golinho",
|
|
2491
|
+
"use.chug": "virar",
|
|
2492
|
+
"use.pour": "despejar",
|
|
2493
|
+
"use.pour_out": "esvaziar",
|
|
2494
|
+
"use.open": "abrir",
|
|
2495
|
+
"use.close": "fechar",
|
|
2496
|
+
"hud.reloading": "Recarregando...",
|
|
2497
|
+
"hud.empty": "Sem muni\xE7\xE3o",
|
|
2498
|
+
"hud.no_fuel": "Sem combust\xEDvel",
|
|
2499
|
+
"hud.fault": "Travou ({fault}): aperte X para destravar",
|
|
2500
|
+
"hud.safe": "Trava ligada: aperte B",
|
|
2501
|
+
"hud.clearing": "Destravando...",
|
|
2502
|
+
"hud.safety_on": "Trava ligada",
|
|
2503
|
+
"hud.safety_off": "Trava desligada",
|
|
2504
|
+
"fault.misfire": "nega",
|
|
2505
|
+
"fault.stovepipe": "chamin\xE9",
|
|
2506
|
+
"fault.double_feed": "dupla alimenta\xE7\xE3o",
|
|
2507
|
+
"fault.failure_to_extract": "falha de extra\xE7\xE3o",
|
|
2508
|
+
"hud.nothing_to_grab": "Nada para pegar aqui",
|
|
2509
|
+
"hud.cannot_grab": "{name} n\xE3o d\xE1 para pegar",
|
|
2510
|
+
"hud.too_heavy": "{name} \xE9 pesado demais ({kg} kg)",
|
|
2511
|
+
"hud.empty_container": "{name} est\xE1 vazio",
|
|
2512
|
+
"hud.refused": "Ferramenta errada para {kind}",
|
|
2513
|
+
"hud.drop": "soltar",
|
|
2514
|
+
"hud.rotate": "girar",
|
|
2515
|
+
"hud.throw": "arremessar",
|
|
2516
|
+
"hud.tilt": "inclinar (segurar)",
|
|
2517
|
+
"hud.equip": "equipar {weapon}",
|
|
2518
|
+
"ui.help": "Clique: olhar \xB7 WASD: andar \xB7 Shift: correr \xB7 C: agachar \xB7 Espa\xE7o: pular \xB7 1-0 / roda: ferramenta \xB7 bot\xE3o esquerdo: usar \xB7 E: pegar/soltar \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 Tab: pain\xE9is \xB7 Esc: soltar o mouse",
|
|
2519
|
+
"ui.help.camera": "V: 1\xAA/3\xAA pessoa (Shift+V: chute)",
|
|
2520
|
+
"camera.first": "1\xAA pessoa",
|
|
2521
|
+
"camera.third": "3\xAA pessoa",
|
|
2522
|
+
"act.camera": "C\xE2mera: {mode}",
|
|
2523
|
+
"ui.log": "Registro de comportamento",
|
|
2524
|
+
"ui.filter.action": "a\xE7\xF5es",
|
|
2525
|
+
"ui.filter.event": "eventos",
|
|
2526
|
+
"ui.filter.check": "testes",
|
|
2527
|
+
"ui.autoscroll": "rolar sozinho",
|
|
2528
|
+
"log.export_cancelled": "exporta\xE7\xE3o cancelada",
|
|
2529
|
+
"ui.copy_json": "Copiar JSON",
|
|
2530
|
+
"ui.download_ndjson": "Baixar NDJSON",
|
|
2531
|
+
"ui.download_txt": "Baixar TXT",
|
|
2532
|
+
"ui.clear_log": "Limpar",
|
|
2533
|
+
"ui.catalog": "Cat\xE1logo",
|
|
2534
|
+
"ui.clear": "Limpar",
|
|
2535
|
+
"ui.reset_all": "Consertar tudo",
|
|
2536
|
+
"ui.slow": "C\xE2mera lenta 0,25\xD7",
|
|
2537
|
+
"ui.fast": "Tempo \xD710",
|
|
2538
|
+
"ui.tap": "Torneira",
|
|
2539
|
+
"ui.fullscreen": "Tela cheia",
|
|
2540
|
+
"ui.fullscreen.hint": "A tela cheia aberta por aqui segura o teclado todo no jogo, inclusive Ctrl+W (o F11 n\xE3o)",
|
|
2541
|
+
"ui.blast.shelter": "Abrigo",
|
|
2542
|
+
"ui.blast.fragmentHits": "Estilha\xE7os",
|
|
2543
|
+
"ui.blast.confinement": "Confinamento",
|
|
2544
|
+
"ui.blast.shelter.hint": "Explos\xF5es: paredes e outras partes entre a explos\xE3o e o alvo ficam com a sua parte",
|
|
2545
|
+
"ui.blast.fragmentHits.hint": "Explos\xF5es: os peda\xE7os do que quebra voam e acertam o que est\xE1 atr\xE1s",
|
|
2546
|
+
"ui.blast.confinement.hint": "Explos\xF5es: a press\xE3o sobe num ambiente fechado",
|
|
2547
|
+
"metric.blast_shelter": "Explos\xE3o que chegou (%)",
|
|
2548
|
+
"metric.blast_confinement": "Press\xE3o da explos\xE3o (x)",
|
|
2549
|
+
"metric.blast_fragments": "Estilha\xE7os que acertaram",
|
|
2550
|
+
"act.blast_report": "Explos\xE3o: {shelter}% chegou ao alvo mais abrigado, press\xE3o x{confinement}, {fragments} estilha\xE7os acertaram, {n} alvos",
|
|
2551
|
+
"ui.quality": "Qualidade",
|
|
2552
|
+
"ui.quality.low": "baixa",
|
|
2553
|
+
"ui.quality.medium": "m\xE9dia",
|
|
2554
|
+
"ui.quality.high": "alta",
|
|
2555
|
+
"ui.equip": "Equipar como ferramenta",
|
|
2556
|
+
"ui.search_items": "Buscar item (todas as abas)",
|
|
2557
|
+
"ui.search_tests": "Buscar teste",
|
|
2558
|
+
"ui.no_results": "Nada encontrado",
|
|
2559
|
+
"ui.objects": "{n} objetos",
|
|
2560
|
+
"ui.tests": "Testes",
|
|
2561
|
+
"ui.run_all": "Rodar todos",
|
|
2562
|
+
"ui.running": "Rodando...",
|
|
2563
|
+
"ui.report": "{passed} passaram, {failed} falharam",
|
|
2564
|
+
"ui.panel": "Pain\xE9is",
|
|
2565
|
+
"ui.inspector": "Inspetor",
|
|
2566
|
+
"ui.aim_at_something": "Mire em um objeto",
|
|
2567
|
+
"ins.material": "Material",
|
|
2568
|
+
"ins.status": "Estado",
|
|
2569
|
+
"ins.burning": "em chamas",
|
|
2570
|
+
"ins.hp": "Pontos de vida",
|
|
2571
|
+
"ins.temperature": "Temperatura",
|
|
2572
|
+
"ins.char": "Carbonizado",
|
|
2573
|
+
"ins.melt": "Derretido",
|
|
2574
|
+
"ins.liters": "L\xEDquido",
|
|
2575
|
+
"ins.damage": "Marcas",
|
|
2576
|
+
"ins.damage_counts": "{dents}, {holes}, {cracks}",
|
|
2577
|
+
"ins.mass": "Massa",
|
|
2578
|
+
"ins.load": "Carga",
|
|
2579
|
+
"ins.stress": "Tens\xE3o",
|
|
2580
|
+
"ins.joints": "Juntas",
|
|
2581
|
+
"ins.open": "aberta",
|
|
2582
|
+
"sc.start": "Cen\xE1rio: {name}",
|
|
2583
|
+
"sc.pass": "Cen\xE1rio passou: {name}",
|
|
2584
|
+
"sc.fail": "Cen\xE1rio FALHOU: {name}",
|
|
2585
|
+
"sc.error": "o cen\xE1rio lan\xE7ou um erro: {msg}",
|
|
2586
|
+
"leg.still": "leg_1 continua presa",
|
|
2587
|
+
"leg.gone": "leg_1 soltou",
|
|
2588
|
+
"roof.standing": "laje de p\xE9",
|
|
2589
|
+
"roof.fell": "laje caiu"
|
|
2590
|
+
}
|
|
3005
2591
|
};
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
return makeScenarios(makeT(locale), (n, d = 1) => pt ? (Math.round(n * 10 ** d) / 10 ** d).toFixed(d).replace(".", ",") : (Math.round(n * 10 ** d) / 10 ** d).toFixed(d), locale);
|
|
3009
|
-
}
|
|
3010
|
-
var SCENARIOS = scenariosFor("en");
|
|
2592
|
+
var locales = { en, "pt-BR": ptBR };
|
|
2593
|
+
var resolveStrings = (locale) => locales[locale ?? "en"] ?? locales.en;
|
|
3011
2594
|
|
|
3012
2595
|
// src/bench-panel.ts
|
|
3013
2596
|
var SEGMENTED_MAX = 4;
|
|
@@ -3382,6 +2965,19 @@ var thumb = (thumbnail, credit) => {
|
|
|
3382
2965
|
e.style.setProperty("--tr-thumb", `url("${thumbSource(thumbnail)}")`);
|
|
3383
2966
|
return e;
|
|
3384
2967
|
};
|
|
2968
|
+
async function toggleFullscreen() {
|
|
2969
|
+
const keyboard = navigator.keyboard;
|
|
2970
|
+
try {
|
|
2971
|
+
if (document.fullscreenElement) {
|
|
2972
|
+
keyboard?.unlock?.();
|
|
2973
|
+
await document.exitFullscreen();
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
await document.documentElement.requestFullscreen();
|
|
2977
|
+
await keyboard?.lock?.();
|
|
2978
|
+
} catch {
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
3385
2981
|
function createUi(o) {
|
|
3386
2982
|
const { t } = o;
|
|
3387
2983
|
const style = mk2("style");
|
|
@@ -3593,6 +3189,8 @@ function createUi(o) {
|
|
|
3593
3189
|
o.onFast(fast);
|
|
3594
3190
|
});
|
|
3595
3191
|
const tapBtn = ctl(t("ui.tap"), () => o.onTap());
|
|
3192
|
+
const fullscreenBtn = ctl(t("ui.fullscreen"), () => void toggleFullscreen());
|
|
3193
|
+
fullscreenBtn.title = t("ui.fullscreen.hint");
|
|
3596
3194
|
const blastBtns = ["shelter", "fragmentHits", "confinement"].map((key) => ({ key, b: ctl(t(`ui.blast.${key}`), () => o.onBlast(key)) }));
|
|
3597
3195
|
for (const { key, b } of blastBtns) b.title = t(`ui.blast.${key}.hint`);
|
|
3598
3196
|
const qSel = mk2("select");
|
|
@@ -3758,6 +3356,9 @@ function createUi(o) {
|
|
|
3758
3356
|
toolBox.textContent = "";
|
|
3759
3357
|
toolBox.append(document.createTextNode(s.toolName));
|
|
3760
3358
|
if (s.ammo) toolBox.append(mk2("small", void 0, s.ammo));
|
|
3359
|
+
if (s.camera) toolBox.append(mk2("small", void 0, t(`camera.${s.camera}`)));
|
|
3360
|
+
const helpText = s.camera ? `${t("ui.help")} \xB7 ${t("ui.help.camera")}` : t("ui.help");
|
|
3361
|
+
if (help.textContent !== helpText) help.textContent = helpText;
|
|
3761
3362
|
if (s.status) status.textContent = s.status;
|
|
3762
3363
|
else if (!toastTimer) status.textContent = "";
|
|
3763
3364
|
if (s.progress) {
|
|
@@ -3847,6 +3448,10 @@ function createUi(o) {
|
|
|
3847
3448
|
// src/room.ts
|
|
3848
3449
|
var FIXED_DT = 1 / 60;
|
|
3849
3450
|
var METRICS_INTERVAL_S = 0.25;
|
|
3451
|
+
var CARRY_NEAR = 0.62;
|
|
3452
|
+
var CARRY_PALM = 0.012;
|
|
3453
|
+
var CARRY_GRIP = 0.45;
|
|
3454
|
+
var MAX_STRIDE_SPEED = 10;
|
|
3850
3455
|
var v3 = (v) => [v.x, v.y, v.z];
|
|
3851
3456
|
var dist3 = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
3852
3457
|
function createTestRoom(options) {
|
|
@@ -3860,6 +3465,7 @@ function createTestRoom(options) {
|
|
|
3860
3465
|
if (vars) for (const k of Object.keys(vars)) s = s.split(`{${k}}`).join(String(vars[k]));
|
|
3861
3466
|
return s;
|
|
3862
3467
|
};
|
|
3468
|
+
const loading = createLoading({ container: options.container, draw: options.ui !== false, working: t("loading.working") });
|
|
3863
3469
|
const num = (n, digits = 1) => {
|
|
3864
3470
|
const s = (Math.round(n * 10 ** digits) / 10 ** digits).toFixed(digits);
|
|
3865
3471
|
return locale.toLowerCase().startsWith("pt") ? s.replace(".", ",") : s;
|
|
@@ -3895,13 +3501,13 @@ function createTestRoom(options) {
|
|
|
3895
3501
|
const onResize = () => resize();
|
|
3896
3502
|
window.addEventListener("resize", onResize);
|
|
3897
3503
|
const arenaSize = options.arenaSize ?? 14;
|
|
3898
|
-
const arena = createArena(THREE, arenaSize, scene);
|
|
3504
|
+
const arena = createArena(THREE, arenaSize, scene, options.scenery, options.tap);
|
|
3899
3505
|
scene.add(arena.group);
|
|
3900
3506
|
const ownWorld = !options.world;
|
|
3901
|
-
const world = options.world ?? createDestruction({ seed: 1, ...options.worldOptions, materials: [...
|
|
3507
|
+
const world = options.world ?? createDestruction({ seed: 1, ...options.worldOptions, materials: [...options.worldOptions?.materials ?? []] });
|
|
3902
3508
|
const pristineWorld = ownWorld ? world.serialize() : null;
|
|
3903
|
-
const
|
|
3904
|
-
const p = world.part(
|
|
3509
|
+
const topOk = (t2) => {
|
|
3510
|
+
const p = world.part(t2.id, t2.topPart);
|
|
3905
3511
|
return !!p && p.status !== "broken" && p.status !== "melted" && p.status !== "ashes";
|
|
3906
3512
|
};
|
|
3907
3513
|
const restingTops = [];
|
|
@@ -3910,7 +3516,12 @@ function createTestRoom(options) {
|
|
|
3910
3516
|
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);
|
|
3911
3517
|
return top;
|
|
3912
3518
|
};
|
|
3913
|
-
const
|
|
3519
|
+
const sceneryTopAt = (x, z) => {
|
|
3520
|
+
let top = 0;
|
|
3521
|
+
for (const t2 of arena.tops) if (t2.top > top && x > t2.minX && x < t2.maxX && z > t2.minZ && z < t2.maxZ && topOk(t2)) top = t2.top;
|
|
3522
|
+
return top;
|
|
3523
|
+
};
|
|
3524
|
+
const groundAt = (x, z) => Math.max(sceneryTopAt(x, z), restingTopAt(x, z));
|
|
3914
3525
|
const ownFluids = !options.fluids;
|
|
3915
3526
|
const fluids = options.fluids ?? createFluids({ seed: 1, locale, liquids: options.liquids, groundAt });
|
|
3916
3527
|
const pristineFluids = ownFluids ? fluids.serialize() : null;
|
|
@@ -3958,6 +3569,19 @@ function createTestRoom(options) {
|
|
|
3958
3569
|
groundAt,
|
|
3959
3570
|
pointScale,
|
|
3960
3571
|
onSound: playSound,
|
|
3572
|
+
...options.blastShove ? { blastShove: options.blastShove } : {},
|
|
3573
|
+
// a blast moves what is heavy but not nailed down: the renderer makes it a piece the moment the shove reaches it
|
|
3574
|
+
shoveable: (objectId) => {
|
|
3575
|
+
const e = entries.get(objectId);
|
|
3576
|
+
return e?.free === true && !e.piece;
|
|
3577
|
+
},
|
|
3578
|
+
onLoosened: (objectId, piece) => {
|
|
3579
|
+
const e = entries.get(objectId);
|
|
3580
|
+
if (!e) return;
|
|
3581
|
+
e.piece = piece;
|
|
3582
|
+
rendererPieces.set(objectId, piece);
|
|
3583
|
+
action("loosened", t("act.loosened", { name: e.name }), { objectId, itemId: e.so.itemId });
|
|
3584
|
+
},
|
|
3961
3585
|
onPiece: (piece) => {
|
|
3962
3586
|
rendererPieces.set(piece.objectId, piece);
|
|
3963
3587
|
loose.push(piece);
|
|
@@ -4012,7 +3636,6 @@ function createTestRoom(options) {
|
|
|
4012
3636
|
};
|
|
4013
3637
|
let tool = toolState(toolId);
|
|
4014
3638
|
const catalogMap = /* @__PURE__ */ new Map();
|
|
4015
|
-
for (const i of demoItems(locale)) catalogMap.set(i.id, i);
|
|
4016
3639
|
for (const i of options.catalog) catalogMap.set(i.id, i);
|
|
4017
3640
|
const catalog = [...options.catalog];
|
|
4018
3641
|
const entryOf = (objectId) => entries.get(objectId) ?? splitOf.get(objectId);
|
|
@@ -4052,7 +3675,7 @@ function createTestRoom(options) {
|
|
|
4052
3675
|
});
|
|
4053
3676
|
let pending = [];
|
|
4054
3677
|
const eventListeners = /* @__PURE__ */ new Set();
|
|
4055
|
-
const listeners = { spawn: /* @__PURE__ */ new Set(), tool: /* @__PURE__ */ new Set(), grab: /* @__PURE__ */ new Set(), throw: /* @__PURE__ */ new Set(), event: eventListeners };
|
|
3678
|
+
const listeners = { spawn: /* @__PURE__ */ new Set(), tool: /* @__PURE__ */ new Set(), grab: /* @__PURE__ */ new Set(), throw: /* @__PURE__ */ new Set(), event: eventListeners, camera: /* @__PURE__ */ new Set() };
|
|
4056
3679
|
const fire = (type, data) => {
|
|
4057
3680
|
for (const fn of listeners[type] ?? []) fn(data);
|
|
4058
3681
|
};
|
|
@@ -4121,6 +3744,7 @@ function createTestRoom(options) {
|
|
|
4121
3744
|
blockers,
|
|
4122
3745
|
limit: arenaSize,
|
|
4123
3746
|
start: arena.playerStart,
|
|
3747
|
+
crouchKeys: options.crouch?.keys,
|
|
4124
3748
|
hooks: {
|
|
4125
3749
|
primaryDown: () => {
|
|
4126
3750
|
useHeld = true;
|
|
@@ -4179,19 +3803,38 @@ function createTestRoom(options) {
|
|
|
4179
3803
|
};
|
|
4180
3804
|
const addArena = () => {
|
|
4181
3805
|
for (const o of arena.objects) {
|
|
4182
|
-
const root = buildFromSpec(THREE, o.spec, { colors: o.colors });
|
|
3806
|
+
const root = o.build ? o.build(THREE) : buildFromSpec(THREE, o.spec, { colors: o.colors });
|
|
4183
3807
|
root.position.set(o.position[0], o.position[1], o.position[2]);
|
|
3808
|
+
root.rotation.y = o.rotationY;
|
|
4184
3809
|
scene.add(root);
|
|
4185
3810
|
root.updateMatrixWorld(true);
|
|
4186
|
-
world.add(structuredCloneSpec(o.spec), { id: o.id, position: o.position });
|
|
4187
|
-
registerEntry(o.id, o.id, root, o.spec, S.text[`arena.${o.id}`] ?? o.id, void 0, void 0, true, false);
|
|
3811
|
+
world.add(structuredCloneSpec(o.spec), { id: o.id, position: o.position, ...o.rotationY ? { rotation: [0, Math.sin(o.rotationY / 2), 0, Math.cos(o.rotationY / 2)] } : {} });
|
|
3812
|
+
registerEntry(o.id, o.id, root, o.spec, o.label ?? S.text[`arena.${o.id}`] ?? o.id, void 0, void 0, true, false);
|
|
4188
3813
|
}
|
|
4189
3814
|
};
|
|
4190
3815
|
addArena();
|
|
3816
|
+
const wrapped = /* @__PURE__ */ new WeakMap();
|
|
3817
|
+
const letGo = (root) => {
|
|
3818
|
+
const inner = wrapped.get(root);
|
|
3819
|
+
if (!inner) return;
|
|
3820
|
+
inner.removeFromParent();
|
|
3821
|
+
wrapped.delete(root);
|
|
3822
|
+
};
|
|
3823
|
+
const buildRoot = (item) => {
|
|
3824
|
+
const built = item.build(THREE);
|
|
3825
|
+
if (item.impl || Math.abs(built.scale.x - 1) < 1e-6 && Math.abs(built.scale.y - 1) < 1e-6 && Math.abs(built.scale.z - 1) < 1e-6) return built;
|
|
3826
|
+
const holder = new THREE.Group();
|
|
3827
|
+
holder.name = built.name;
|
|
3828
|
+
holder.userData = built.userData;
|
|
3829
|
+
holder.add(built);
|
|
3830
|
+
wrapped.set(holder, built);
|
|
3831
|
+
Object.defineProperty(holder, "visible", { get: () => built.visible, set: (v) => void (built.visible = v), configurable: true });
|
|
3832
|
+
return holder;
|
|
3833
|
+
};
|
|
4191
3834
|
const spawn = (itemId, position, rotationY) => {
|
|
4192
3835
|
const item = catalogMap.get(itemId);
|
|
4193
3836
|
if (!item) throw new Error(`game-test-room: unknown item "${itemId}"`);
|
|
4194
|
-
const root = item
|
|
3837
|
+
const root = buildRoot(item);
|
|
4195
3838
|
let pos;
|
|
4196
3839
|
if (position) pos = [position[0], position[1], position[2]];
|
|
4197
3840
|
else {
|
|
@@ -4234,6 +3877,7 @@ function createTestRoom(options) {
|
|
|
4234
3877
|
if (own) world.register(own, { id: objectId, position: pos, rotation: [q.x, q.y, q.z, q.w] });
|
|
4235
3878
|
else world.add(spec, { id: objectId, position: pos, rotation: [q.x, q.y, q.z, q.w] });
|
|
4236
3879
|
const e = registerEntry(objectId, item.id, root, spec, item.name, parts, item, false, movable);
|
|
3880
|
+
if (!own && !movable && item.movable === void 0) e.free = true;
|
|
4237
3881
|
action("spawn", t("act.spawn", { name: item.name, x: num(pos[0], 1), z: num(pos[2], 1) }), { objectId, itemId: item.id, data: { position: pos, movable, massKg: Math.round(massOfObject(objectId) * 10) / 10 } });
|
|
4238
3882
|
fire("spawn", e.so);
|
|
4239
3883
|
return e.so;
|
|
@@ -4262,6 +3906,7 @@ function createTestRoom(options) {
|
|
|
4262
3906
|
}
|
|
4263
3907
|
if (world.has(id)) world.remove(id);
|
|
4264
3908
|
e.so.root.removeFromParent();
|
|
3909
|
+
letGo(e.so.root);
|
|
4265
3910
|
entries.delete(id);
|
|
4266
3911
|
rendererPieces.delete(id);
|
|
4267
3912
|
specs.delete(id);
|
|
@@ -4336,6 +3981,7 @@ function createTestRoom(options) {
|
|
|
4336
3981
|
for (const s of [...arena.surfaces, ...options.surfaces ?? []]) fluids.addSurface(s);
|
|
4337
3982
|
}
|
|
4338
3983
|
pending = [];
|
|
3984
|
+
strikes = [];
|
|
4339
3985
|
rng = 12345;
|
|
4340
3986
|
counter = 0;
|
|
4341
3987
|
slot = 0;
|
|
@@ -4348,6 +3994,108 @@ function createTestRoom(options) {
|
|
|
4348
3994
|
};
|
|
4349
3995
|
const aimO = [0, 0, 0];
|
|
4350
3996
|
const aimD = [0, 0, -1];
|
|
3997
|
+
const third = { ...THIRD_PERSON_DEFAULTS, ...options.thirdPerson };
|
|
3998
|
+
let avatar = null;
|
|
3999
|
+
let cameraMode = options.cameraMode ?? "first";
|
|
4000
|
+
let crouchForced = false;
|
|
4001
|
+
const avatarState = { speed: 0, running: false, crouch: false, down: false, armed: false, pitch: 0 };
|
|
4002
|
+
let feetPrev = null;
|
|
4003
|
+
let armedForced = null;
|
|
4004
|
+
let camDistance = Infinity;
|
|
4005
|
+
const tmpHand = new THREE.Vector3();
|
|
4006
|
+
const thirdPose = (dt) => {
|
|
4007
|
+
const pose = thirdPersonPose(
|
|
4008
|
+
{ x: player.x, z: player.z, feet: player.feet, yaw: player.yaw, pitch: player.pitch, crouch: avatarState.crouch },
|
|
4009
|
+
third,
|
|
4010
|
+
(o, d, max) => withoutHeld(() => world.raycast(o, d, max))?.distance ?? null
|
|
4011
|
+
);
|
|
4012
|
+
camDistance = pose.free <= camDistance ? pose.free : dt > 0 ? camDistance + (pose.free - camDistance) * (1 - Math.exp(-dt * third.recover)) : camDistance;
|
|
4013
|
+
return { ...pose, position: pose.at(camDistance), free: camDistance };
|
|
4014
|
+
};
|
|
4015
|
+
const thirdActive = () => cameraMode === "third" && avatar !== null;
|
|
4016
|
+
const syncAvatarVisible = () => {
|
|
4017
|
+
if (!avatar) return;
|
|
4018
|
+
avatar.root.visible = cameraMode === "third";
|
|
4019
|
+
if (avatar.firstPerson) avatar.firstPerson.root.visible = cameraMode === "first";
|
|
4020
|
+
};
|
|
4021
|
+
const setCameraMode = (mode) => {
|
|
4022
|
+
if (mode !== "first" && mode !== "third") return;
|
|
4023
|
+
if (mode === cameraMode) return;
|
|
4024
|
+
cameraMode = mode;
|
|
4025
|
+
camDistance = Infinity;
|
|
4026
|
+
syncAvatarVisible();
|
|
4027
|
+
action("camera", t("act.camera", { mode: t(`camera.${mode}`) }), { data: { mode, avatar: avatar !== null } });
|
|
4028
|
+
fire("camera", mode);
|
|
4029
|
+
};
|
|
4030
|
+
const setAvatar = (next) => {
|
|
4031
|
+
if (avatar === next) return;
|
|
4032
|
+
avatar?.root.removeFromParent();
|
|
4033
|
+
avatar?.firstPerson?.root.removeFromParent();
|
|
4034
|
+
avatar = next;
|
|
4035
|
+
feetPrev = null;
|
|
4036
|
+
if (avatar) {
|
|
4037
|
+
scene.add(avatar.root);
|
|
4038
|
+
if (avatar.firstPerson) scene.add(avatar.firstPerson.root);
|
|
4039
|
+
syncAvatarVisible();
|
|
4040
|
+
stepAvatar(0);
|
|
4041
|
+
}
|
|
4042
|
+
ui?.hud(hudState());
|
|
4043
|
+
};
|
|
4044
|
+
const stepAvatar = (dt) => {
|
|
4045
|
+
if (!avatar) return;
|
|
4046
|
+
const x = player.x;
|
|
4047
|
+
const z = player.z;
|
|
4048
|
+
if (dt > 0 && feetPrev) {
|
|
4049
|
+
const raw = Math.hypot(x - feetPrev[0], z - feetPrev[1]) / dt;
|
|
4050
|
+
avatarState.speed = raw > MAX_STRIDE_SPEED ? 0 : avatarState.speed + (raw - avatarState.speed) * (1 - Math.exp(-dt * 15));
|
|
4051
|
+
if (avatarState.speed < 0.02) avatarState.speed = 0;
|
|
4052
|
+
}
|
|
4053
|
+
feetPrev = [x, z];
|
|
4054
|
+
avatarState.running = !player.crouching && (player.keys.has("ShiftLeft") || player.keys.has("ShiftRight"));
|
|
4055
|
+
avatarState.crouch = crouchForced || player.crouching;
|
|
4056
|
+
avatarState.armed = armedForced ?? tool.def.action.kind === "firearm";
|
|
4057
|
+
avatarState.pitch = player.pitch;
|
|
4058
|
+
avatarState.holding = heldPiece !== null;
|
|
4059
|
+
avatarState.itemUse = useItemPlayer ? { id: useItemName, ...useItemPlayer.pose() } : heldPiece && heldTilt > 0.01 ? { id: "tilt", position: [0.2, -0.1, -0.38], rotation: [-heldTilt, 0, 0] } : null;
|
|
4060
|
+
avatar.root.position.set(x, player.feet, z);
|
|
4061
|
+
avatar.root.rotation.set(0, player.yaw + Math.PI, 0);
|
|
4062
|
+
avatar.update(dt, avatarState);
|
|
4063
|
+
avatar.root.updateMatrixWorld(true);
|
|
4064
|
+
};
|
|
4065
|
+
let handSource = null;
|
|
4066
|
+
let carriedHand = null;
|
|
4067
|
+
let debugView = null;
|
|
4068
|
+
let lastHand = { stance: "relaxed", triggerFinger: null, targets: null };
|
|
4069
|
+
const strikeClips = options.strikeClips ?? DEFAULT_STRIKE_CLIPS;
|
|
4070
|
+
const handStances = options.handStances ?? DEFAULT_HAND_STANCES;
|
|
4071
|
+
const stepFirstPerson = (dt) => {
|
|
4072
|
+
const arms = avatar?.firstPerson;
|
|
4073
|
+
if (!arms) return;
|
|
4074
|
+
const targets = handSource?.() ?? carriedHand;
|
|
4075
|
+
const state = {
|
|
4076
|
+
camera,
|
|
4077
|
+
targets,
|
|
4078
|
+
stance: handStances[toolId] ?? (targets ? "hold" : "relaxed"),
|
|
4079
|
+
speed: avatarState.speed,
|
|
4080
|
+
running: avatarState.running,
|
|
4081
|
+
crouch: avatarState.crouch,
|
|
4082
|
+
contact: (origin, direction, maxDistance) => rayWorld(origin, direction, maxDistance)?.distance ?? null,
|
|
4083
|
+
triggerFinger: targets?.fingers?.index ?? null
|
|
4084
|
+
};
|
|
4085
|
+
lastHand = { stance: state.stance, triggerFinger: state.triggerFinger, targets };
|
|
4086
|
+
arms.update(dt, state);
|
|
4087
|
+
};
|
|
4088
|
+
const applyThirdView = () => {
|
|
4089
|
+
if (!thirdActive()) return () => void 0;
|
|
4090
|
+
const home = camera.position.clone();
|
|
4091
|
+
const pose = thirdPose(0);
|
|
4092
|
+
camera.position.set(pose.position[0], pose.position[1], pose.position[2]);
|
|
4093
|
+
camera.updateMatrixWorld(true);
|
|
4094
|
+
return () => {
|
|
4095
|
+
camera.position.copy(home);
|
|
4096
|
+
camera.updateMatrixWorld(true);
|
|
4097
|
+
};
|
|
4098
|
+
};
|
|
4351
4099
|
const aim = () => {
|
|
4352
4100
|
camera.updateMatrixWorld(true);
|
|
4353
4101
|
camera.getWorldPosition(tmpV);
|
|
@@ -4358,6 +4106,21 @@ function createTestRoom(options) {
|
|
|
4358
4106
|
aimD[0] = tmpV2.x;
|
|
4359
4107
|
aimD[1] = tmpV2.y;
|
|
4360
4108
|
aimD[2] = tmpV2.z;
|
|
4109
|
+
if (thirdActive()) {
|
|
4110
|
+
const pose = thirdPose(0);
|
|
4111
|
+
const reach = 80;
|
|
4112
|
+
const hit = withoutHeld(() => world.raycast(pose.position, pose.direction, reach));
|
|
4113
|
+
const dist = hit ? hit.distance : reach;
|
|
4114
|
+
const tx = pose.position[0] + pose.direction[0] * dist - aimO[0];
|
|
4115
|
+
const ty = pose.position[1] + pose.direction[1] * dist - aimO[1];
|
|
4116
|
+
const tz = pose.position[2] + pose.direction[2] * dist - aimO[2];
|
|
4117
|
+
const len = Math.hypot(tx, ty, tz);
|
|
4118
|
+
if (len > 0.5 && (tx * pose.direction[0] + ty * pose.direction[1] + tz * pose.direction[2]) / len > 0.5) {
|
|
4119
|
+
aimD[0] = tx / len;
|
|
4120
|
+
aimD[1] = ty / len;
|
|
4121
|
+
aimD[2] = tz / len;
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4361
4124
|
};
|
|
4362
4125
|
const FAR = [0, 0, 0, 1];
|
|
4363
4126
|
const withoutHeld = (fn) => {
|
|
@@ -4511,7 +4274,7 @@ function createTestRoom(options) {
|
|
|
4511
4274
|
withoutHeld(() => {
|
|
4512
4275
|
for (const s of shots) {
|
|
4513
4276
|
energy = Math.max(energy, s.energy);
|
|
4514
|
-
const results = world.shoot(s.origin, s.direction, s.energy, s.caliber, "player");
|
|
4277
|
+
const results = world.shoot(s.origin, s.direction, s.energy, s.caliber, "player", shotExtras(s));
|
|
4515
4278
|
const r0 = results.find((r) => r.part && r.point);
|
|
4516
4279
|
let end = [s.origin[0] + s.direction[0] * 40, s.origin[1] + s.direction[1] * 40, s.origin[2] + s.direction[2] * 40];
|
|
4517
4280
|
if (r0?.point && r0.part) {
|
|
@@ -4569,7 +4332,7 @@ function createTestRoom(options) {
|
|
|
4569
4332
|
const r = world.raycast(u.origin, d, u.range);
|
|
4570
4333
|
if (!r) continue;
|
|
4571
4334
|
if (!target) target = r;
|
|
4572
|
-
world.hit(r.object, {
|
|
4335
|
+
world.hit(r.object, { ...useToHit(u, r.point, r.normal), type, direction: d, source: "player" });
|
|
4573
4336
|
if (type === "fire") igniteNear(r.point, 0.25);
|
|
4574
4337
|
else renderer.emit("sparks", r.point, r.normal, 2, 4);
|
|
4575
4338
|
}
|
|
@@ -4603,7 +4366,7 @@ function createTestRoom(options) {
|
|
|
4603
4366
|
action("melee", t("act.melee.miss", { weapon: w }), { data: { weapon: id } });
|
|
4604
4367
|
return;
|
|
4605
4368
|
}
|
|
4606
|
-
world.hitPart(r.object, r.part, {
|
|
4369
|
+
world.hitPart(r.object, r.part, { ...useToHit(u, r.point, r.normal), source: "player" });
|
|
4607
4370
|
pushPiece(r.object, u.direction, 2 * u.energy / 6, r.point);
|
|
4608
4371
|
action("melee", t("act.melee.hit", { weapon: w, energy: num(u.energy, 0), where: hitWhere(r), dist: num(r.distance, 1) }), {
|
|
4609
4372
|
objectId: r.object,
|
|
@@ -4612,6 +4375,35 @@ function createTestRoom(options) {
|
|
|
4612
4375
|
data: { weapon: id, damage: u.damage, energyJ: u.energy, distanceM: Math.round(r.distance * 100) / 100 }
|
|
4613
4376
|
});
|
|
4614
4377
|
};
|
|
4378
|
+
let strikes = [];
|
|
4379
|
+
const kickLeg = options.kickLeg ?? "alternate";
|
|
4380
|
+
let nextLeg = "right";
|
|
4381
|
+
const strikeUse = (u, id = toolId) => {
|
|
4382
|
+
const clip = strikeClips[id];
|
|
4383
|
+
const leg = id === "kick" ? kickLeg === "alternate" ? nextLeg : kickLeg : void 0;
|
|
4384
|
+
const seconds = clip === void 0 ? null : avatar?.strike?.(clip, leg ? { leg } : void 0) ?? null;
|
|
4385
|
+
if (leg && seconds !== null && kickLeg === "alternate") nextLeg = leg === "right" ? "left" : "right";
|
|
4386
|
+
if (clip === void 0 || seconds === null) {
|
|
4387
|
+
meleeUse(u, id);
|
|
4388
|
+
return;
|
|
4389
|
+
}
|
|
4390
|
+
action("strike", t(leg ? "act.strike.leg" : "act.strike", { weapon: toolName(id), clip, seconds: num(seconds, 2), leg: leg ? t(`leg.${leg}`) : "" }), { data: { weapon: id, clip, impactIn: seconds, ...leg ? { leg } : {} } });
|
|
4391
|
+
strikes.push({
|
|
4392
|
+
key: id,
|
|
4393
|
+
remaining: seconds,
|
|
4394
|
+
land: () => {
|
|
4395
|
+
aim();
|
|
4396
|
+
meleeUse({ ...u, origin: [aimO[0], aimO[1], aimO[2]], direction: [aimD[0], aimD[1], aimD[2]] }, id);
|
|
4397
|
+
}
|
|
4398
|
+
});
|
|
4399
|
+
};
|
|
4400
|
+
const stepStrikes = (dt) => {
|
|
4401
|
+
if (strikes.length === 0) return;
|
|
4402
|
+
for (const strike of strikes) strike.remaining -= dt;
|
|
4403
|
+
const landed = strikes.filter((strike) => strike.remaining <= 0);
|
|
4404
|
+
strikes = strikes.filter((strike) => strike.remaining > 0);
|
|
4405
|
+
for (const strike of landed) strike.land();
|
|
4406
|
+
};
|
|
4615
4407
|
const toolWork = (u, dt) => {
|
|
4616
4408
|
const w = toolName();
|
|
4617
4409
|
const r = rayWorld(u.origin, u.direction, u.reach);
|
|
@@ -4630,7 +4422,7 @@ function createTestRoom(options) {
|
|
|
4630
4422
|
const p = world.disassemble(r.object, j.id, tool_, dt);
|
|
4631
4423
|
const kind = dStrings.jointKinds[j.kind] ?? j.kind;
|
|
4632
4424
|
if (p < 0) {
|
|
4633
|
-
const need =
|
|
4425
|
+
const need = JOINT_DEFAULTS[j.kind].tools.map((k) => dStrings.toolKinds[k] ?? k).join(", ") || t("tool.none");
|
|
4634
4426
|
progress = { label: t("hud.refused", { kind }), value: 0, at: simTime };
|
|
4635
4427
|
log.coalesce(
|
|
4636
4428
|
`refuse:${r.object}:${j.id}:${toolId}`,
|
|
@@ -4640,7 +4432,7 @@ function createTestRoom(options) {
|
|
|
4640
4432
|
text: t("act.tool.refused", { weapon: w, kind, obj: nameOf(r.object), need }),
|
|
4641
4433
|
objectId: r.object,
|
|
4642
4434
|
itemId: entryOf(r.object)?.so.itemId,
|
|
4643
|
-
data: { weapon: toolId, joint: j.id, kind: j.kind, needs:
|
|
4435
|
+
data: { weapon: toolId, joint: j.id, kind: j.kind, needs: JOINT_DEFAULTS[j.kind].tools }
|
|
4644
4436
|
},
|
|
4645
4437
|
2
|
|
4646
4438
|
);
|
|
@@ -4714,12 +4506,18 @@ function createTestRoom(options) {
|
|
|
4714
4506
|
action,
|
|
4715
4507
|
emitHost,
|
|
4716
4508
|
startFire,
|
|
4717
|
-
say
|
|
4509
|
+
say,
|
|
4510
|
+
projectileModel: options.projectileModel
|
|
4718
4511
|
});
|
|
4719
4512
|
const useTool = (dt, held) => {
|
|
4720
4513
|
aim();
|
|
4721
4514
|
const cat = tool.def.category;
|
|
4722
|
-
if (cat === "hand")
|
|
4515
|
+
if (cat === "hand") {
|
|
4516
|
+
if (strikes.some((strike) => strike.key === "hand" || strike.key === "fists")) return;
|
|
4517
|
+
strikeUse({ kind: "melee", damage: "blunt", energy: 25, radius: 0.1, reach: 0.8, origin: [aimO[0], aimO[1], aimO[2]], direction: [aimD[0], aimD[1], aimD[2]] }, "hand");
|
|
4518
|
+
return;
|
|
4519
|
+
}
|
|
4520
|
+
if (strikes.some((strike) => strike.key === toolId)) return;
|
|
4723
4521
|
const res = tool.trigger([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]], dt, held, "player");
|
|
4724
4522
|
flushEvents(toolId, tool, res.recoil);
|
|
4725
4523
|
if (res.status === "empty") {
|
|
@@ -4741,7 +4539,7 @@ function createTestRoom(options) {
|
|
|
4741
4539
|
for (const u of res.uses) {
|
|
4742
4540
|
switch (u.kind) {
|
|
4743
4541
|
case "melee":
|
|
4744
|
-
|
|
4542
|
+
strikeUse(u);
|
|
4745
4543
|
break;
|
|
4746
4544
|
case "flame":
|
|
4747
4545
|
heatRays(u, "fire");
|
|
@@ -4780,11 +4578,11 @@ function createTestRoom(options) {
|
|
|
4780
4578
|
if (res) applyUses(res, 0);
|
|
4781
4579
|
};
|
|
4782
4580
|
const kick = () => {
|
|
4783
|
-
if (!arsenal.weapons.has("kick")) return;
|
|
4581
|
+
if (!arsenal.weapons.has("kick") || strikes.some((strike) => strike.key === "kick")) return;
|
|
4784
4582
|
aim();
|
|
4785
4583
|
const res = toolState("kick").trigger([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]], 0, false, "player");
|
|
4786
4584
|
if (res.status !== "ok") return;
|
|
4787
|
-
for (const u of res.uses) if (u.kind === "melee")
|
|
4585
|
+
for (const u of res.uses) if (u.kind === "melee") strikeUse(u, "kick");
|
|
4788
4586
|
};
|
|
4789
4587
|
const setTool = (id) => {
|
|
4790
4588
|
if (!arsenal.weapons.has(id)) throw new Error(`game-test-room: unknown weapon "${id}"`);
|
|
@@ -4811,6 +4609,8 @@ function createTestRoom(options) {
|
|
|
4811
4609
|
let heldTilt = 0;
|
|
4812
4610
|
let tiltKey = 0;
|
|
4813
4611
|
const heldLocal = new THREE.Vector3();
|
|
4612
|
+
const heldSize = new THREE.Vector3(0.2, 0.2, 0.2);
|
|
4613
|
+
let heldRadius = 0.2;
|
|
4814
4614
|
const heldVel = [0, 0, 0];
|
|
4815
4615
|
let useItemPlayer = null;
|
|
4816
4616
|
let useItemName = "";
|
|
@@ -4855,11 +4655,15 @@ function createTestRoom(options) {
|
|
|
4855
4655
|
heldName = nameOf(r.object);
|
|
4856
4656
|
piece.held = true;
|
|
4857
4657
|
piece.sleeping = false;
|
|
4658
|
+
const savedQ = piece.root.quaternion.clone();
|
|
4659
|
+
piece.root.quaternion.identity();
|
|
4660
|
+
piece.root.updateMatrixWorld(true);
|
|
4858
4661
|
const box2 = new THREE.Box3().setFromObject(piece.root);
|
|
4859
|
-
|
|
4662
|
+
box2.getCenter(heldLocal).sub(piece.root.position);
|
|
4663
|
+
box2.getSize(heldSize);
|
|
4664
|
+
piece.root.quaternion.copy(savedQ);
|
|
4860
4665
|
piece.root.updateMatrixWorld(true);
|
|
4861
|
-
|
|
4862
|
-
heldLocal.applyQuaternion(piece.root.quaternion.clone().invert());
|
|
4666
|
+
heldRadius = heldSize.length() / 3;
|
|
4863
4667
|
heldYaw = 0;
|
|
4864
4668
|
heldTilt = 0;
|
|
4865
4669
|
heldVel[0] = heldVel[1] = heldVel[2] = 0;
|
|
@@ -4880,24 +4684,93 @@ function createTestRoom(options) {
|
|
|
4880
4684
|
fire("throw", { objectId: id, speed });
|
|
4881
4685
|
return true;
|
|
4882
4686
|
};
|
|
4687
|
+
const carriedTargets = (root, gripQ) => {
|
|
4688
|
+
const width2 = Math.max(heldSize.x, heldSize.z);
|
|
4689
|
+
const sideGrip = heldSize.y > width2 * 1.04 && width2 <= 0.22;
|
|
4690
|
+
if (sideGrip) {
|
|
4691
|
+
const center2 = heldLocal.clone().applyQuaternion(root.quaternion).add(root.position);
|
|
4692
|
+
const radius = Math.min(heldSize.x, heldSize.z) * 0.5;
|
|
4693
|
+
const handX = new THREE.Vector3(-0.35, 0, -0.94).normalize().applyQuaternion(gripQ);
|
|
4694
|
+
const handY = new THREE.Vector3(0.94, 0, -0.35).normalize().applyQuaternion(gripQ);
|
|
4695
|
+
const handZ = new THREE.Vector3().crossVectors(handX, handY).normalize();
|
|
4696
|
+
const q2 = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(handX, handY, handZ));
|
|
4697
|
+
const up = new THREE.Vector3(0, 1, 0).applyQuaternion(gripQ);
|
|
4698
|
+
const palm = center2.clone().addScaledVector(handY, radius + CARRY_PALM).addScaledVector(handX, -0.022).addScaledVector(up, heldSize.y > 0.16 ? -heldSize.y * 0.08 : -5e-3);
|
|
4699
|
+
if (useItemName === "open" || useItemName === "close") {
|
|
4700
|
+
const leftX = new THREE.Vector3(0.35, 0, -0.94).normalize().applyQuaternion(gripQ);
|
|
4701
|
+
const leftY = new THREE.Vector3(-0.94, 0, -0.35).normalize().applyQuaternion(gripQ);
|
|
4702
|
+
const leftZ = new THREE.Vector3().crossVectors(leftX, leftY).normalize();
|
|
4703
|
+
const lq = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(leftX, leftY, leftZ));
|
|
4704
|
+
const leftPalm = center2.clone().addScaledVector(up, heldSize.y * 0.45).addScaledVector(leftY, radius * 0.65 + CARRY_PALM).addScaledVector(leftX, -0.018);
|
|
4705
|
+
return {
|
|
4706
|
+
R: { position: [palm.x, palm.y, palm.z], quaternion: [q2.x, q2.y, q2.z, q2.w] },
|
|
4707
|
+
L: { position: [leftPalm.x, leftPalm.y, leftPalm.z], quaternion: [lq.x, lq.y, lq.z, lq.w] },
|
|
4708
|
+
grip: 0.85,
|
|
4709
|
+
gripL: 0.8
|
|
4710
|
+
};
|
|
4711
|
+
}
|
|
4712
|
+
return { R: { position: [palm.x, palm.y, palm.z], quaternion: [q2.x, q2.y, q2.z, q2.w] }, grip: 0.85 };
|
|
4713
|
+
}
|
|
4714
|
+
const box2 = new THREE.Box3().setFromObject(root);
|
|
4715
|
+
const center = box2.getCenter(new THREE.Vector3());
|
|
4716
|
+
const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(gripQ).normalize();
|
|
4717
|
+
const back = new THREE.Vector3(0, -1, 0).applyQuaternion(gripQ).normalize();
|
|
4718
|
+
const z = new THREE.Vector3().crossVectors(forward, back).normalize();
|
|
4719
|
+
const y = new THREE.Vector3().crossVectors(z, forward).normalize();
|
|
4720
|
+
const q = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(forward, y, z));
|
|
4721
|
+
return { R: { position: [center.x, box2.min.y - CARRY_PALM, center.z], quaternion: [q.x, q.y, q.z, q.w] }, grip: CARRY_GRIP };
|
|
4722
|
+
};
|
|
4723
|
+
const gripQuat = new THREE.Quaternion();
|
|
4883
4724
|
const followHeld = (dt) => {
|
|
4884
4725
|
const piece = heldPiece;
|
|
4726
|
+
carriedHand = null;
|
|
4885
4727
|
if (!piece) return;
|
|
4886
4728
|
camera.updateMatrixWorld(true);
|
|
4887
4729
|
const root = piece.root;
|
|
4888
4730
|
let pos;
|
|
4889
4731
|
const targetQ = tmpQ;
|
|
4890
|
-
|
|
4732
|
+
const hand = thirdActive() ? avatar?.hand?.("R") : void 0;
|
|
4733
|
+
const isDrink = useItemName === "drink" || useItemName === "sip" || useItemName === "chug";
|
|
4734
|
+
if (hand) {
|
|
4735
|
+
const handWorld = hand.getWorldPosition(tmpHand);
|
|
4736
|
+
const yawQ = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw);
|
|
4737
|
+
const pose = useItemPlayer?.pose();
|
|
4738
|
+
const rx = pose ? isDrink ? Math.abs(pose.rotation[0]) * 0.9 : pose.rotation[0] : -heldTilt;
|
|
4739
|
+
const ry = pose ? pose.rotation[1] : 0;
|
|
4740
|
+
const rz = pose ? pose.rotation[2] : 0;
|
|
4741
|
+
gripQuat.copy(yawQ).multiply(new THREE.Quaternion().setFromEuler(new THREE.Euler(Math.max(-1.35, Math.min(1.25, rx)), ry, rz, "XYZ")));
|
|
4742
|
+
targetQ.copy(yawQ).multiply(new THREE.Quaternion().setFromEuler(new THREE.Euler(rx, ry, rz, "XYZ")));
|
|
4743
|
+
targetQ.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), heldYaw));
|
|
4744
|
+
const gripOffset = new THREE.Vector3(-0.02, 0.015, -0.04).applyQuaternion(gripQuat);
|
|
4745
|
+
pos = tmpV.copy(handWorld).add(gripOffset);
|
|
4746
|
+
} else if (useItemPlayer) {
|
|
4891
4747
|
const pose = useItemPlayer.pose();
|
|
4892
|
-
const
|
|
4893
|
-
|
|
4894
|
-
const
|
|
4895
|
-
|
|
4748
|
+
const frameQ = avatar?.firstPerson ? camera.quaternion : new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw);
|
|
4749
|
+
const raise = isDrink ? Math.max(0, Math.min(1, (pose.position[1] + 0.2) / 0.22)) : 0;
|
|
4750
|
+
const px2 = pose.position[0] + (isDrink ? 0.02 * raise : 0);
|
|
4751
|
+
const py2 = pose.position[1] - (isDrink ? 0.08 * raise : 0);
|
|
4752
|
+
const pz2 = pose.position[2] - (isDrink ? 0.05 * raise : 0);
|
|
4753
|
+
pos = tmpV.set(px2, py2, pz2).applyQuaternion(frameQ).add(camera.position);
|
|
4754
|
+
const rx = isDrink ? Math.abs(pose.rotation[0]) * 0.85 : pose.rotation[0];
|
|
4755
|
+
const eq = new THREE.Quaternion().setFromEuler(new THREE.Euler(rx, pose.rotation[1], pose.rotation[2], "XYZ"));
|
|
4756
|
+
const wristEq = new THREE.Quaternion().setFromEuler(new THREE.Euler(Math.max(-1.35, Math.min(1.25, rx)), pose.rotation[1], pose.rotation[2], "XYZ"));
|
|
4757
|
+
gripQuat.copy(frameQ).multiply(wristEq);
|
|
4758
|
+
targetQ.copy(frameQ).multiply(eq);
|
|
4896
4759
|
targetQ.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), heldYaw));
|
|
4897
4760
|
} else {
|
|
4898
|
-
|
|
4761
|
+
const small = heldRadius < 0.22;
|
|
4762
|
+
const holdX = avatar?.firstPerson ? small ? 0.22 : 0.08 : 0.05;
|
|
4763
|
+
const holdY = avatar?.firstPerson ? small ? -0.2 : -0.26 : -0.28;
|
|
4764
|
+
const holdZ = avatar?.firstPerson ? small ? -0.42 : -Math.min(1.15, CARRY_NEAR + heldRadius) : -1.15;
|
|
4765
|
+
pos = tmpV.set(holdX, holdY, holdZ).applyMatrix4(camera.matrixWorld);
|
|
4766
|
+
const baseQ = avatar?.firstPerson && small ? camera.quaternion : new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw);
|
|
4767
|
+
gripQuat.copy(baseQ).multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.max(-1.35, -heldTilt)));
|
|
4899
4768
|
targetQ.setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw + heldYaw);
|
|
4900
|
-
|
|
4769
|
+
if (avatar?.firstPerson && small && heldTilt <= 1e-4) {
|
|
4770
|
+
targetQ.copy(camera.quaternion).multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), heldYaw));
|
|
4771
|
+
} else {
|
|
4772
|
+
targetQ.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), -heldTilt));
|
|
4773
|
+
}
|
|
4901
4774
|
}
|
|
4902
4775
|
const k = Math.min(1, (useItemPlayer ? 30 : 16) * dt);
|
|
4903
4776
|
const px = root.position.x;
|
|
@@ -4912,6 +4785,7 @@ function createTestRoom(options) {
|
|
|
4912
4785
|
heldVel[2] = (root.position.z - pz) / dt;
|
|
4913
4786
|
}
|
|
4914
4787
|
root.updateMatrixWorld(true);
|
|
4788
|
+
if (avatar?.firstPerson && cameraMode === "first") carriedHand = carriedTargets(root, gripQuat);
|
|
4915
4789
|
};
|
|
4916
4790
|
const useItem = (useId) => {
|
|
4917
4791
|
const e = heldEntry;
|
|
@@ -4921,11 +4795,13 @@ function createTestRoom(options) {
|
|
|
4921
4795
|
const c = fluids.container(e.containerId);
|
|
4922
4796
|
if (!c) return false;
|
|
4923
4797
|
if (def.liquids && def.liquids.length > 0 && (!c.liquid || !def.liquids.includes(c.liquid))) return false;
|
|
4924
|
-
|
|
4798
|
+
const consuming = useId === "drink" || useId === "sip" || useId === "chug" || useId === "pour" || useId === "pour_out";
|
|
4799
|
+
if (consuming && c.liters <= 1e-6) {
|
|
4925
4800
|
say(t("hud.empty_container", { name: e.name }));
|
|
4926
4801
|
return false;
|
|
4927
4802
|
}
|
|
4928
4803
|
if (useItemPlayer && !useItemPlayer.done) return false;
|
|
4804
|
+
if (consuming && c.closed) fluids.open(e.containerId);
|
|
4929
4805
|
useItemPlayer = createItemUse(fluids, e.containerId, def);
|
|
4930
4806
|
useItemName = def.id;
|
|
4931
4807
|
action("use_item", t("act.use_item", { name: e.name, use: S.text[`use.${def.id}`] ?? def.id }), { objectId: e.so.objectId, itemId: e.so.itemId, data: { use: def.id } });
|
|
@@ -4941,7 +4817,7 @@ function createTestRoom(options) {
|
|
|
4941
4817
|
};
|
|
4942
4818
|
const TAP_ID = "room_tap";
|
|
4943
4819
|
const TAP_RATE = 0.25;
|
|
4944
|
-
fluids.addTap({ id: TAP_ID, position: arena.tapSpout, liquid: "water", rate: TAP_RATE, temperature: 16, on: false });
|
|
4820
|
+
if (arena.tapSpout) fluids.addTap({ id: TAP_ID, position: arena.tapSpout, liquid: "water", rate: TAP_RATE, temperature: 16, on: false });
|
|
4945
4821
|
const isTapOn = () => fluids.taps().some((x) => x.id === TAP_ID && x.on);
|
|
4946
4822
|
const toggleTap = (on = !isTapOn()) => {
|
|
4947
4823
|
fluids.setTap(TAP_ID, on);
|
|
@@ -5047,10 +4923,11 @@ function createTestRoom(options) {
|
|
|
5047
4923
|
if (heldEntry?.uses[0]) useItem(heldEntry.uses[0]);
|
|
5048
4924
|
break;
|
|
5049
4925
|
case "KeyC":
|
|
5050
|
-
if (heldEntry?.uses[1]) useItem(heldEntry.uses[1]);
|
|
4926
|
+
if (heldEntry?.uses[1] && !(options.crouch?.keys ?? ["KeyC"]).includes("KeyC")) useItem(heldEntry.uses[1]);
|
|
5051
4927
|
break;
|
|
5052
4928
|
case "KeyV":
|
|
5053
4929
|
if (heldEntry?.uses[2]) useItem(heldEntry.uses[2]);
|
|
4930
|
+
else if (avatar && !e.shiftKey) setCameraMode(cameraMode === "first" ? "third" : "first");
|
|
5054
4931
|
else kick();
|
|
5055
4932
|
break;
|
|
5056
4933
|
case "KeyT":
|
|
@@ -5098,7 +4975,7 @@ function createTestRoom(options) {
|
|
|
5098
4975
|
const joints = st.joints.filter((j) => j.a === r.part || j.b === r.part).map((j) => ({
|
|
5099
4976
|
id: j.id,
|
|
5100
4977
|
kind: dStrings.jointKinds[j.kind] ?? j.kind,
|
|
5101
|
-
tools:
|
|
4978
|
+
tools: JOINT_DEFAULTS[j.kind].tools.map((k) => dStrings.toolKinds[k] ?? k),
|
|
5102
4979
|
open: j.open,
|
|
5103
4980
|
progress: j.progress
|
|
5104
4981
|
}));
|
|
@@ -5138,6 +5015,7 @@ function createTestRoom(options) {
|
|
|
5138
5015
|
const roomMetrics = createRoomMetrics({ gl, destruction: renderer, fluids: fluidView, effects: vfx.world, ids: roomMetricIds, blast: () => lastBlast });
|
|
5139
5016
|
let qualityName = quality;
|
|
5140
5017
|
const syncContainers = (sdt) => {
|
|
5018
|
+
const drinkingHeld = useItemPlayer !== null && (useItemName === "drink" || useItemName === "sip" || useItemName === "chug");
|
|
5141
5019
|
for (const e of entries.values()) {
|
|
5142
5020
|
if (!e.containerId) continue;
|
|
5143
5021
|
const host2 = hostOf(e);
|
|
@@ -5146,7 +5024,8 @@ function createTestRoom(options) {
|
|
|
5146
5024
|
const p = tmpV.set(e.fluidOffset[0], e.fluidOffset[1], e.fluidOffset[2]).applyMatrix4(host2.root.matrixWorld);
|
|
5147
5025
|
const vel = sdt > 0 ? [(p.x - e.last[0]) / sdt, (p.y - e.last[1]) / sdt, (p.z - e.last[2]) / sdt] : void 0;
|
|
5148
5026
|
e.last = [p.x, p.y, p.z];
|
|
5149
|
-
|
|
5027
|
+
const rot = drinkingHeld && e === heldEntry ? [0, 0, 0, 1] : [q.x, q.y, q.z, q.w];
|
|
5028
|
+
fluids.setTransform(e.containerId, [p.x, p.y, p.z], rot, vel);
|
|
5150
5029
|
}
|
|
5151
5030
|
};
|
|
5152
5031
|
const syncLiters = () => {
|
|
@@ -5172,6 +5051,7 @@ function createTestRoom(options) {
|
|
|
5172
5051
|
if (paused) return;
|
|
5173
5052
|
simTime += dt;
|
|
5174
5053
|
stepWalk(dt);
|
|
5054
|
+
stepStrikes(dt);
|
|
5175
5055
|
const reps = fastForward ? 10 : 1;
|
|
5176
5056
|
updateAnimations(dt);
|
|
5177
5057
|
updateItemUse(dt);
|
|
@@ -5210,6 +5090,7 @@ function createTestRoom(options) {
|
|
|
5210
5090
|
const update = (dt) => {
|
|
5211
5091
|
if (!ownsLoop) roomMetrics.frame(dt);
|
|
5212
5092
|
player.update(dt);
|
|
5093
|
+
stepAvatar(paused ? 0 : dt);
|
|
5213
5094
|
if (tiltKey || heldTilt > 0) {
|
|
5214
5095
|
heldTilt = Math.max(0, Math.min(2.4, heldTilt + (tiltKey ? 2.2 : -3) * dt));
|
|
5215
5096
|
}
|
|
@@ -5221,6 +5102,7 @@ function createTestRoom(options) {
|
|
|
5221
5102
|
}
|
|
5222
5103
|
if (!paused) followHeld(dt);
|
|
5223
5104
|
stepSim(dt * timeScale);
|
|
5105
|
+
stepFirstPerson(paused ? 0 : dt);
|
|
5224
5106
|
if (progress && simTime - progress.at > 0.25) progress = null;
|
|
5225
5107
|
hudAcc += dt;
|
|
5226
5108
|
if (hudAcc >= 0.1 && ui) {
|
|
@@ -5256,6 +5138,7 @@ function createTestRoom(options) {
|
|
|
5256
5138
|
tap: isTapOn(),
|
|
5257
5139
|
blast: { shelter: blastOn("shelter"), fragmentHits: blastOn("fragmentHits"), confinement: blastOn("confinement") },
|
|
5258
5140
|
quality: qualityName,
|
|
5141
|
+
camera: avatar ? cameraMode : null,
|
|
5259
5142
|
objects: [...entries.values()].filter((e) => !e.arena).length
|
|
5260
5143
|
};
|
|
5261
5144
|
};
|
|
@@ -5457,6 +5340,8 @@ function createTestRoom(options) {
|
|
|
5457
5340
|
environment,
|
|
5458
5341
|
groundAt,
|
|
5459
5342
|
effects: vfx,
|
|
5343
|
+
loading,
|
|
5344
|
+
...options.blastShove ? { blastShove: options.blastShove } : {},
|
|
5460
5345
|
tool: () => ({
|
|
5461
5346
|
weapon: toolId,
|
|
5462
5347
|
category: tool.def.category,
|
|
@@ -5480,6 +5365,15 @@ function createTestRoom(options) {
|
|
|
5480
5365
|
flushEvents(toolId, tool);
|
|
5481
5366
|
return result;
|
|
5482
5367
|
},
|
|
5368
|
+
player: () => ({
|
|
5369
|
+
position: player.position,
|
|
5370
|
+
yaw: player.yaw,
|
|
5371
|
+
pitch: player.pitch,
|
|
5372
|
+
crouching: false,
|
|
5373
|
+
speed: player.speed,
|
|
5374
|
+
running: player.running,
|
|
5375
|
+
cameraMode: "first"
|
|
5376
|
+
}),
|
|
5483
5377
|
configureTool(settings) {
|
|
5484
5378
|
const condition = settings[SETTING_IDS.condition];
|
|
5485
5379
|
const ammoQuality = settings[SETTING_IDS.ammoQuality];
|
|
@@ -5501,8 +5395,14 @@ function createTestRoom(options) {
|
|
|
5501
5395
|
frameListeners.add(update2);
|
|
5502
5396
|
return () => frameListeners.delete(update2);
|
|
5503
5397
|
},
|
|
5504
|
-
|
|
5505
|
-
|
|
5398
|
+
setHandTargets(source) {
|
|
5399
|
+
handSource = source;
|
|
5400
|
+
return () => {
|
|
5401
|
+
if (handSource === source) handSource = null;
|
|
5402
|
+
};
|
|
5403
|
+
},
|
|
5404
|
+
log(text, data, type) {
|
|
5405
|
+
action(type ?? "module_note", text, { data });
|
|
5506
5406
|
},
|
|
5507
5407
|
addBlocker(blocked) {
|
|
5508
5408
|
blockers.add(blocked);
|
|
@@ -5513,13 +5413,17 @@ function createTestRoom(options) {
|
|
|
5513
5413
|
return () => clearListeners.delete(listener);
|
|
5514
5414
|
}
|
|
5515
5415
|
};
|
|
5416
|
+
let sendingInitial = false;
|
|
5516
5417
|
const lendBench = (store) => ({
|
|
5517
5418
|
value: store.value,
|
|
5518
5419
|
layer: store.layer,
|
|
5519
5420
|
onChange: store.onChange,
|
|
5520
5421
|
patch: store.patch,
|
|
5521
5422
|
sheet: store.setSheet,
|
|
5522
|
-
|
|
5423
|
+
// the initial and restored values only set the module's state: they do not move the player's camera (`ModuleArea.view` is how a module asks for a view when its area opens)
|
|
5424
|
+
look: (eye, target) => {
|
|
5425
|
+
if (!sendingInitial) room.look(eye, target);
|
|
5426
|
+
}
|
|
5523
5427
|
});
|
|
5524
5428
|
const checkModuleBench = (mod, fail) => {
|
|
5525
5429
|
const store = createBenchStore(mod, locale, (error) => reportOnce(`${mod.id}:listener`, t("mod.listener_failed", { name: mod.name, error: failureText(error) })));
|
|
@@ -5531,6 +5435,29 @@ function createTestRoom(options) {
|
|
|
5531
5435
|
for (const p of r.problems) fail(`bench: ${p}`);
|
|
5532
5436
|
return r.ok ? store : null;
|
|
5533
5437
|
};
|
|
5438
|
+
const LIGHTS_CHECK_MS = 250;
|
|
5439
|
+
const warmUp = () => {
|
|
5440
|
+
const probes = [renderer.warmUpProbes(), vfx.warmUpProbes(), fluidView.warmUpProbes()];
|
|
5441
|
+
const groups = probes.map((probe) => probe.group);
|
|
5442
|
+
scene.add(...groups);
|
|
5443
|
+
gl.render(scene, camera);
|
|
5444
|
+
scene.remove(...groups);
|
|
5445
|
+
for (const probe of probes) probe.dispose();
|
|
5446
|
+
};
|
|
5447
|
+
const warmUpEffects = () => {
|
|
5448
|
+
const ids = vfx.catalog?.() ?? [];
|
|
5449
|
+
const flames = ids.filter((id) => /fire|flame/i.test(id)).slice(0, 1);
|
|
5450
|
+
gl.compile(scene, camera);
|
|
5451
|
+
const handles = ["m67", ...flames].filter((id) => ids.includes(id)).map((effect) => vfx.play({ effect, point: [0, 0.1, -4], direction: [0, 1, 0], normal: [0, 1, 0] }));
|
|
5452
|
+
for (let i = 0; i < 3; i++) {
|
|
5453
|
+
vfx.update(0.02);
|
|
5454
|
+
gl.render(scene, camera);
|
|
5455
|
+
}
|
|
5456
|
+
for (const h of handles) if (h !== null) vfx.stop(h);
|
|
5457
|
+
vfx.world.clear();
|
|
5458
|
+
vfx.update(0);
|
|
5459
|
+
gl.render(scene, camera);
|
|
5460
|
+
};
|
|
5534
5461
|
const loadModule = (mod) => {
|
|
5535
5462
|
const report = { module: mod.id, items: [], scenarios: [], ok: true, problems: [] };
|
|
5536
5463
|
const fail = (problem) => {
|
|
@@ -5546,6 +5473,7 @@ function createTestRoom(options) {
|
|
|
5546
5473
|
build: (T) => ti.build(T),
|
|
5547
5474
|
spec: ti.spec,
|
|
5548
5475
|
movable: ti.movable,
|
|
5476
|
+
slow: ti.slow,
|
|
5549
5477
|
fluid: ti.fluid,
|
|
5550
5478
|
weapon: ti.weapon,
|
|
5551
5479
|
impl: ti.impl,
|
|
@@ -5560,8 +5488,9 @@ function createTestRoom(options) {
|
|
|
5560
5488
|
report.items.push(item.id);
|
|
5561
5489
|
let partIds = [];
|
|
5562
5490
|
const base = { module: mod.id, item: item.id };
|
|
5491
|
+
let checked;
|
|
5563
5492
|
try {
|
|
5564
|
-
const root = item
|
|
5493
|
+
const root = checked = buildRoot(item);
|
|
5565
5494
|
root.updateMatrixWorld(true);
|
|
5566
5495
|
if (item.impl) {
|
|
5567
5496
|
const probe = item.impl(root, `${item.id}_check`);
|
|
@@ -5583,6 +5512,7 @@ function createTestRoom(options) {
|
|
|
5583
5512
|
moduleCheck(false, t("mod.destructible_fail", { name: item.name, problems: msg }), { ...base, check: "destructible", problems: [msg] }, item.id);
|
|
5584
5513
|
fail(`${item.id}: ${msg}`);
|
|
5585
5514
|
}
|
|
5515
|
+
if (checked) letGo(checked);
|
|
5586
5516
|
for (const clip of item.animations ?? []) {
|
|
5587
5517
|
const r = checkAnimation(clip, partIds);
|
|
5588
5518
|
moduleCheck(r.ok, r.ok ? t("mod.animation_ok", { name: item.name, clip: clip.id, seconds: num(clip.duration, 1) }) : t("mod.animation_fail", { name: item.name, clip: clip.id, problems: r.problems.join("; ") }), { ...base, check: "animation", clip: clip.id, problems: r.problems }, item.id);
|
|
@@ -5618,12 +5548,17 @@ function createTestRoom(options) {
|
|
|
5618
5548
|
if (!activeBench || !benches.has(activeBench)) activeBench = mod.id;
|
|
5619
5549
|
} else if (activeBench === mod.id) activeBench = lastBench();
|
|
5620
5550
|
if (mod.attach) detachers.set(mod.id, mod.attach(bench ? { ...host, bench: lendBench(bench) } : host));
|
|
5621
|
-
|
|
5551
|
+
sendingInitial = true;
|
|
5552
|
+
try {
|
|
5553
|
+
bench?.sendInitial();
|
|
5554
|
+
} finally {
|
|
5555
|
+
sendingInitial = false;
|
|
5556
|
+
}
|
|
5622
5557
|
showBench();
|
|
5623
5558
|
ui?.refresh();
|
|
5624
5559
|
return report;
|
|
5625
5560
|
};
|
|
5626
|
-
const allScenarios = () => [...
|
|
5561
|
+
const allScenarios = () => [...extraScenarios];
|
|
5627
5562
|
let scenarioRunning = false;
|
|
5628
5563
|
const step = (seconds) => {
|
|
5629
5564
|
const dt = seconds > 6 ? 1 / 30 : FIXED_DT;
|
|
@@ -5676,8 +5611,13 @@ function createTestRoom(options) {
|
|
|
5676
5611
|
return report;
|
|
5677
5612
|
};
|
|
5678
5613
|
const layArea = (layout) => {
|
|
5679
|
-
const
|
|
5680
|
-
|
|
5614
|
+
const known = layout.placements.filter((p) => {
|
|
5615
|
+
if (catalogMap.has(p.item)) return true;
|
|
5616
|
+
action("area_skip", t("act.area_skip", { item: p.item }), { itemId: p.item });
|
|
5617
|
+
return false;
|
|
5618
|
+
});
|
|
5619
|
+
const holders = known.filter((p) => p.surface);
|
|
5620
|
+
const rest = known.filter((p) => !p.surface);
|
|
5681
5621
|
const placed = [...holders, ...rest].map((p) => {
|
|
5682
5622
|
const so = spawn(p.item, p.position, p.rotationY ?? 0);
|
|
5683
5623
|
if (p.surface) {
|
|
@@ -5693,6 +5633,10 @@ function createTestRoom(options) {
|
|
|
5693
5633
|
if (layout.view) room.look(layout.view.eye, layout.view.target);
|
|
5694
5634
|
return placed.map(({ so }) => so);
|
|
5695
5635
|
};
|
|
5636
|
+
const areaLabel = (area) => {
|
|
5637
|
+
const mod = [...modules.values()].find((m) => m.id === area || m.group === area);
|
|
5638
|
+
return mod?.group ?? S.groups[area] ?? area;
|
|
5639
|
+
};
|
|
5696
5640
|
const openArea = (area) => {
|
|
5697
5641
|
clear();
|
|
5698
5642
|
const mod = [...modules.values()].find((m) => m.id === area || m.group === area);
|
|
@@ -5805,14 +5749,59 @@ function createTestRoom(options) {
|
|
|
5805
5749
|
},
|
|
5806
5750
|
equip,
|
|
5807
5751
|
openArea,
|
|
5752
|
+
openAreaAsync: (area) => loading.busy(t("loading.open_area", { area: areaLabel(area) }), () => openArea(area)),
|
|
5753
|
+
loading,
|
|
5754
|
+
busy: (label, work, busyOptions) => loading.busy(label, work, busyOptions),
|
|
5808
5755
|
loadModule,
|
|
5756
|
+
warmUp: () => {
|
|
5757
|
+
warmUp();
|
|
5758
|
+
warmUpEffects();
|
|
5759
|
+
},
|
|
5809
5760
|
animations: animationsOf,
|
|
5810
5761
|
playAnimation,
|
|
5811
5762
|
bench(moduleId) {
|
|
5812
5763
|
const id = moduleId ?? activeBench;
|
|
5813
5764
|
return (id ? benches.get(id) : void 0) ?? null;
|
|
5814
5765
|
},
|
|
5815
|
-
applyCamera: () =>
|
|
5766
|
+
applyCamera: () => {
|
|
5767
|
+
const back = applyThirdView();
|
|
5768
|
+
const shake = vfx.applyCamera();
|
|
5769
|
+
return () => {
|
|
5770
|
+
shake();
|
|
5771
|
+
back();
|
|
5772
|
+
};
|
|
5773
|
+
},
|
|
5774
|
+
setAvatar,
|
|
5775
|
+
get avatar() {
|
|
5776
|
+
return avatar;
|
|
5777
|
+
},
|
|
5778
|
+
setAvatarState(state) {
|
|
5779
|
+
if (state.crouch !== void 0) {
|
|
5780
|
+
crouchForced = state.crouch;
|
|
5781
|
+
avatarState.crouch = state.crouch || player.crouching;
|
|
5782
|
+
}
|
|
5783
|
+
if (state.down !== void 0) avatarState.down = state.down;
|
|
5784
|
+
if (state.armed !== void 0) armedForced = state.armed;
|
|
5785
|
+
},
|
|
5786
|
+
avatarState: () => ({ ...avatarState }),
|
|
5787
|
+
handState: () => ({ ...lastHand }),
|
|
5788
|
+
setDebugCamera(view) {
|
|
5789
|
+
if (!view) {
|
|
5790
|
+
debugView = null;
|
|
5791
|
+
return;
|
|
5792
|
+
}
|
|
5793
|
+
debugView ?? (debugView = new THREE.PerspectiveCamera(60, camera.aspect, 0.01, 200));
|
|
5794
|
+
debugView.fov = view.fov ?? 40;
|
|
5795
|
+
debugView.aspect = camera.aspect;
|
|
5796
|
+
debugView.position.set(...view.position);
|
|
5797
|
+
debugView.lookAt(...view.target);
|
|
5798
|
+
debugView.updateProjectionMatrix();
|
|
5799
|
+
debugView.updateMatrixWorld(true);
|
|
5800
|
+
},
|
|
5801
|
+
setCameraMode,
|
|
5802
|
+
get cameraMode() {
|
|
5803
|
+
return cameraMode;
|
|
5804
|
+
},
|
|
5816
5805
|
metrics() {
|
|
5817
5806
|
return { room: roomMetrics.sample(), module: moduleMetrics() };
|
|
5818
5807
|
},
|
|
@@ -5827,6 +5816,7 @@ function createTestRoom(options) {
|
|
|
5827
5816
|
window.removeEventListener("resize", onResize);
|
|
5828
5817
|
player.dispose();
|
|
5829
5818
|
ui?.dispose();
|
|
5819
|
+
loading.dispose();
|
|
5830
5820
|
renderer.dispose();
|
|
5831
5821
|
vfx.dispose();
|
|
5832
5822
|
craterPatch?.dispose();
|
|
@@ -5841,6 +5831,17 @@ function createTestRoom(options) {
|
|
|
5841
5831
|
}
|
|
5842
5832
|
}
|
|
5843
5833
|
};
|
|
5834
|
+
const slowly = (moduleId, controlId, run) => {
|
|
5835
|
+
const control = benches.get(moduleId)?.controls().find((c) => c.id === controlId);
|
|
5836
|
+
const slow = control?.slow;
|
|
5837
|
+
if (!slow) {
|
|
5838
|
+
run();
|
|
5839
|
+
return;
|
|
5840
|
+
}
|
|
5841
|
+
void loading.busy(typeof slow === "string" ? slow : control.name, run).catch((error) => {
|
|
5842
|
+
action("bench_failed", t("act.bench_failed", { name: control.name, error: error instanceof Error ? error.message : String(error) }), { data: { module: moduleId, control: controlId } });
|
|
5843
|
+
});
|
|
5844
|
+
};
|
|
5844
5845
|
let ui = null;
|
|
5845
5846
|
if (options.ui !== false) {
|
|
5846
5847
|
const groups = () => {
|
|
@@ -5860,7 +5861,9 @@ function createTestRoom(options) {
|
|
|
5860
5861
|
groups,
|
|
5861
5862
|
loadout: () => loadout.map((id) => ({ id, name: arsenal.name(id) })),
|
|
5862
5863
|
onSpawn: (id) => {
|
|
5863
|
-
|
|
5864
|
+
const item = catalogMap.get(id);
|
|
5865
|
+
if (item?.slow) void loading.busy(typeof item.slow === "string" ? item.slow : t("loading.spawn", { name: item.name }), () => spawn(id));
|
|
5866
|
+
else spawn(id);
|
|
5864
5867
|
},
|
|
5865
5868
|
onEquip: (itemId) => {
|
|
5866
5869
|
const w = catalogMap.get(itemId)?.weapon;
|
|
@@ -5900,13 +5903,13 @@ function createTestRoom(options) {
|
|
|
5900
5903
|
},
|
|
5901
5904
|
onResize: resize,
|
|
5902
5905
|
onControl: (id, value) => {
|
|
5903
|
-
if (activeBench) benches.get(activeBench)?.set(id, value);
|
|
5906
|
+
if (activeBench) slowly(activeBench, id, () => benches.get(activeBench)?.set(id, value));
|
|
5904
5907
|
},
|
|
5905
5908
|
onLayer: (id, on) => {
|
|
5906
5909
|
if (activeBench) benches.get(activeBench)?.setLayer(id, on);
|
|
5907
5910
|
},
|
|
5908
5911
|
onAction: (id) => {
|
|
5909
|
-
if (activeBench) benches.get(activeBench)?.act(id);
|
|
5912
|
+
if (activeBench) slowly(activeBench, id, () => benches.get(activeBench)?.act(id));
|
|
5910
5913
|
}
|
|
5911
5914
|
});
|
|
5912
5915
|
ui.hud(hudState());
|
|
@@ -5916,15 +5919,32 @@ function createTestRoom(options) {
|
|
|
5916
5919
|
if (ownRenderer && typeof requestAnimationFrame === "function") {
|
|
5917
5920
|
let last = performance.now();
|
|
5918
5921
|
ownsLoop = true;
|
|
5922
|
+
let nextLightsCheck = 0;
|
|
5923
|
+
let warmedLights = "";
|
|
5924
|
+
const lightsKey = () => {
|
|
5925
|
+
let key = "";
|
|
5926
|
+
scene.traverseVisible((object) => {
|
|
5927
|
+
if (object.isLight) key += `${object.type[0]}${object.castShadow ? "s" : ""}`;
|
|
5928
|
+
});
|
|
5929
|
+
return key;
|
|
5930
|
+
};
|
|
5919
5931
|
const loop = (now) => {
|
|
5920
5932
|
raf = requestAnimationFrame(loop);
|
|
5933
|
+
if (now >= nextLightsCheck) {
|
|
5934
|
+
nextLightsCheck = now + LIGHTS_CHECK_MS;
|
|
5935
|
+
const key = lightsKey();
|
|
5936
|
+
if (key !== warmedLights) {
|
|
5937
|
+
warmedLights = key;
|
|
5938
|
+
warmUp();
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5921
5941
|
const realDelta = (now - last) / 1e3;
|
|
5922
5942
|
roomMetrics.frame(realDelta);
|
|
5923
5943
|
const dt = Math.min(0.05, realDelta);
|
|
5924
5944
|
last = now;
|
|
5925
5945
|
if (!scenarioRunning) update(dt);
|
|
5926
|
-
const restoreCamera =
|
|
5927
|
-
gl.render(scene, camera);
|
|
5946
|
+
const restoreCamera = room.applyCamera();
|
|
5947
|
+
gl.render(scene, debugView ?? camera);
|
|
5928
5948
|
restoreCamera();
|
|
5929
5949
|
roomMetrics.rendered();
|
|
5930
5950
|
};
|
|
@@ -5933,6 +5953,7 @@ function createTestRoom(options) {
|
|
|
5933
5953
|
if (options.initial) {
|
|
5934
5954
|
for (const [i, id] of options.initial.entries()) spawn(id, [(i - (options.initial.length - 1) / 2) * 1.3, 0, 0.5]);
|
|
5935
5955
|
}
|
|
5956
|
+
if (options.avatar) setAvatar(options.avatar);
|
|
5936
5957
|
if (options.area) openArea(options.area);
|
|
5937
5958
|
return room;
|
|
5938
5959
|
}
|
|
@@ -5940,20 +5961,21 @@ function structuredCloneSpec(spec) {
|
|
|
5940
5961
|
return JSON.parse(JSON.stringify(spec));
|
|
5941
5962
|
}
|
|
5942
5963
|
export {
|
|
5943
|
-
|
|
5944
|
-
|
|
5964
|
+
DEFAULT_HAND_STANCES,
|
|
5965
|
+
DEFAULT_STRIKE_CLIPS,
|
|
5966
|
+
LOADING_CLASS,
|
|
5967
|
+
LOADING_CSS,
|
|
5945
5968
|
MATERIAL_COLORS,
|
|
5946
|
-
SCENARIOS,
|
|
5947
5969
|
brickWallSpec,
|
|
5948
5970
|
buildFromSpec,
|
|
5971
|
+
createLoading,
|
|
5949
5972
|
createLog,
|
|
5950
5973
|
createTestRoom,
|
|
5951
5974
|
decodeBenchState,
|
|
5952
|
-
demoItems,
|
|
5953
5975
|
encodeBenchState,
|
|
5976
|
+
loadingMarkup,
|
|
5954
5977
|
locales,
|
|
5955
|
-
|
|
5956
|
-
resolveStrings
|
|
5957
|
-
scenariosFor
|
|
5978
|
+
nextPaint,
|
|
5979
|
+
resolveStrings
|
|
5958
5980
|
};
|
|
5959
5981
|
//# sourceMappingURL=index.js.map
|