@adatechnology/game-test-room 0.11.0 → 0.12.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 +291 -29
- package/dist/index.global.js +4843 -1998
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1535 -1640
- 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,1254 +7,281 @@ 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
|
}
|
|
786
|
-
|
|
787
|
-
return { name: "brick wall", parts, joints, tags: ["structure"] };
|
|
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");
|
|
284
|
+
if (layers.some((layer) => layer.id === id) && (text === "1" || text === "0")) out.set(id, text === "1");
|
|
1258
285
|
}
|
|
1259
286
|
return out;
|
|
1260
287
|
}
|
|
@@ -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 {
|
|
@@ -2447,6 +1717,8 @@ function createPlayer(THREE, o) {
|
|
|
2447
1717
|
let y = 0;
|
|
2448
1718
|
let eye = EYE;
|
|
2449
1719
|
let vy = 0;
|
|
1720
|
+
let walked = 0;
|
|
1721
|
+
let running = false;
|
|
2450
1722
|
const px = { x: o.start[0], z: o.start[2] };
|
|
2451
1723
|
let locked = false;
|
|
2452
1724
|
const keys = /* @__PURE__ */ new Set();
|
|
@@ -2579,9 +1851,29 @@ function createPlayer(THREE, o) {
|
|
|
2579
1851
|
get locked() {
|
|
2580
1852
|
return locked;
|
|
2581
1853
|
},
|
|
1854
|
+
get x() {
|
|
1855
|
+
return px.x;
|
|
1856
|
+
},
|
|
1857
|
+
get z() {
|
|
1858
|
+
return px.z;
|
|
1859
|
+
},
|
|
1860
|
+
get feet() {
|
|
1861
|
+
return y;
|
|
1862
|
+
},
|
|
1863
|
+
get position() {
|
|
1864
|
+
return [px.x, y, px.z];
|
|
1865
|
+
},
|
|
1866
|
+
get speed() {
|
|
1867
|
+
return walked;
|
|
1868
|
+
},
|
|
1869
|
+
get running() {
|
|
1870
|
+
return running;
|
|
1871
|
+
},
|
|
2582
1872
|
keys,
|
|
2583
1873
|
update(dt) {
|
|
2584
1874
|
const run = keys.has("ShiftLeft") || keys.has("ShiftRight");
|
|
1875
|
+
const fromX = px.x;
|
|
1876
|
+
const fromZ = px.z;
|
|
2585
1877
|
const speed = run ? RUN : WALK;
|
|
2586
1878
|
let f = 0;
|
|
2587
1879
|
let r = 0;
|
|
@@ -2598,6 +1890,8 @@ function createPlayer(THREE, o) {
|
|
|
2598
1890
|
const rz = -Math.sin(yaw);
|
|
2599
1891
|
move((fx * f + rx * r) / len * speed * dt, (fz * f + rz * r) / len * speed * dt);
|
|
2600
1892
|
}
|
|
1893
|
+
walked = dt > 0 ? Math.hypot(px.x - fromX, px.z - fromZ) / dt : 0;
|
|
1894
|
+
running = run && walked > 0;
|
|
2601
1895
|
if (y <= 0 && keys.has("Space")) vy = JUMP;
|
|
2602
1896
|
if (y > 0 || vy > 0) {
|
|
2603
1897
|
vy -= GRAVITY * dt;
|
|
@@ -2661,353 +1955,601 @@ function createPlayer(THREE, o) {
|
|
|
2661
1955
|
};
|
|
2662
1956
|
}
|
|
2663
1957
|
|
|
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
|
-
];
|
|
1958
|
+
// src/avatar.ts
|
|
1959
|
+
var DEFAULT_STRIKE_CLIPS = { fists: "punch_right", shove: "shove_two_hands", kick: "front_kick" };
|
|
1960
|
+
var DEFAULT_HAND_STANCES = { fists: "fists" };
|
|
1961
|
+
var THIRD_PERSON_DEFAULTS = {
|
|
1962
|
+
back: 3.8,
|
|
1963
|
+
side: 0.9,
|
|
1964
|
+
up: 1.95,
|
|
1965
|
+
crouchUp: 1.4,
|
|
1966
|
+
lift: 0.1,
|
|
1967
|
+
margin: 0.3,
|
|
1968
|
+
minDistance: 0.2,
|
|
1969
|
+
probes: [0, 0.2, -0.2],
|
|
1970
|
+
recover: 4
|
|
1971
|
+
};
|
|
1972
|
+
function thirdPersonPose(input, options, probe) {
|
|
1973
|
+
const cy = Math.cos(input.pitch);
|
|
1974
|
+
const d = [-Math.sin(input.yaw) * cy, Math.sin(input.pitch), -Math.cos(input.yaw) * cy];
|
|
1975
|
+
const rx = Math.cos(input.yaw);
|
|
1976
|
+
const rz = -Math.sin(input.yaw);
|
|
1977
|
+
const pivot = [input.x + rx * options.side, input.feet + (input.crouch ? options.crouchUp : options.up), input.z + rz * options.side];
|
|
1978
|
+
const dx = -d[0] * options.back;
|
|
1979
|
+
const dy = -d[1] * options.back + options.lift;
|
|
1980
|
+
const dz = -d[2] * options.back;
|
|
1981
|
+
const wanted = Math.hypot(dx, dy, dz);
|
|
1982
|
+
const dir = [dx / wanted, dy / wanted, dz / wanted];
|
|
1983
|
+
let free = wanted;
|
|
1984
|
+
for (const o of options.probes) {
|
|
1985
|
+
const hit = probe([pivot[0] + rx * o, pivot[1], pivot[2] + rz * o], dir, wanted);
|
|
1986
|
+
if (hit !== null) free = Math.min(free, Math.max(options.minDistance, hit - options.margin));
|
|
1987
|
+
}
|
|
1988
|
+
const at = (distance) => [pivot[0] + dir[0] * distance, Math.max(0.1, pivot[1] + dir[1] * distance), pivot[2] + dir[2] * distance];
|
|
1989
|
+
return { position: at(free), at, pivot, direction: d, wanted, free };
|
|
3000
1990
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
1991
|
+
|
|
1992
|
+
// src/locale.ts
|
|
1993
|
+
var en = {
|
|
1994
|
+
items: {},
|
|
1995
|
+
groups: { furniture: "Furniture", structure: "Structure", vehicles: "Vehicles", plants: "Plants", items: "Items", containers: "Containers" },
|
|
1996
|
+
text: {
|
|
1997
|
+
"locale.decimal": ".",
|
|
1998
|
+
yes: "yes",
|
|
1999
|
+
no: "no",
|
|
2000
|
+
piece: "piece",
|
|
2001
|
+
coords: "x {x} \xB7 z {z}",
|
|
2002
|
+
"count.piece.one": "1 piece",
|
|
2003
|
+
"count.piece.other": "{n} pieces",
|
|
2004
|
+
"count.joint.one": "1 joint",
|
|
2005
|
+
"count.joint.other": "{n} joints",
|
|
2006
|
+
"count.dent.one": "1 dent",
|
|
2007
|
+
"count.dent.other": "{n} dents",
|
|
2008
|
+
"count.hole.one": "1 hole",
|
|
2009
|
+
"count.hole.other": "{n} holes",
|
|
2010
|
+
"count.crack.one": "1 crack",
|
|
2011
|
+
"count.crack.other": "{n} cracks",
|
|
2012
|
+
"joint.generic": "joint",
|
|
2013
|
+
"lost.broken": "broke",
|
|
2014
|
+
"lost.melted": "melted away",
|
|
2015
|
+
"lost.ashes": "burned to ash",
|
|
2016
|
+
"tool.none": "by hand only",
|
|
2017
|
+
"arena.target_wall": "Target wall",
|
|
2018
|
+
"arena.table": "Table",
|
|
2019
|
+
"arena.shelf": "Shelf",
|
|
2020
|
+
"surface.floor": "on the floor",
|
|
2021
|
+
"surface.ground": "on the floor",
|
|
2022
|
+
"surface.table": "on the table",
|
|
2023
|
+
"by.break": "broken loose",
|
|
2024
|
+
"by.tool": "undone with a tool",
|
|
2025
|
+
"by.part_lost": "the part next to it was lost",
|
|
2026
|
+
"dmg.ballistic": "ballistic",
|
|
2027
|
+
"dmg.blunt": "blunt",
|
|
2028
|
+
"dmg.slash": "slash",
|
|
2029
|
+
"dmg.explosive": "blast",
|
|
2030
|
+
"dmg.fire": "fire",
|
|
2031
|
+
"dmg.heat": "heat",
|
|
2032
|
+
"effect.caffeine": "caffeine",
|
|
2033
|
+
"effect.alcohol": "alcohol",
|
|
2034
|
+
"effect.poison": "poison",
|
|
2035
|
+
"effect.disgust": "disgust",
|
|
2036
|
+
"ev.dent": "{where}: dent of {mm} mm, {hp}",
|
|
2037
|
+
"ev.hole.through": "{where}: {mm} mm hole went through, {hp}",
|
|
2038
|
+
"ev.hole.stuck": "{where}: {mm} mm hole, did not go through, {hp}",
|
|
2039
|
+
"ev.dents": "{where}: {n} dents, deepest {mm} mm, {hp}",
|
|
2040
|
+
"ev.holes": "{where}: {n} holes of {mm} mm, {thr} went through, {hp}",
|
|
2041
|
+
"ev.leak.tail": ", leaking {liquid} at {rate} l/s",
|
|
2042
|
+
"ev.leak.alone": "{where}: leaking {liquid} at {rate} l/s",
|
|
2043
|
+
"ev.crack": "{where}: cracked, {rays} rays up to {cm} cm, {hp}",
|
|
2044
|
+
"ev.ricochet": "{where}: bullet ricocheted, {energy} J left",
|
|
2045
|
+
"ev.fracture.shatter": "{where}: shattered into {n} shards",
|
|
2046
|
+
"ev.fracture.crumble": "{where}: crumbled into {n} chunks",
|
|
2047
|
+
"ev.fracture.splinter": "{where}: splintered into {n} pieces",
|
|
2048
|
+
"ev.fracture.tear": "{where}: torn into {n} scraps",
|
|
2049
|
+
"ev.fracture.snap": "{where}: snapped into {n} pieces",
|
|
2050
|
+
"ev.fracture.none": "{where}: broke into {n} pieces",
|
|
2051
|
+
"ev.detach": "{obj}: {a} came off {b} ({by}); the object now has {pieces}",
|
|
2052
|
+
"ev.detach.many.break": "{obj}: {joints} broke loose; the object now has {pieces}",
|
|
2053
|
+
"ev.detach.many.tool": "{obj}: {joints} undone with a tool; the object now has {pieces}",
|
|
2054
|
+
"ev.lost.tail": " and came loose from {joints}; the object now has {pieces}",
|
|
2055
|
+
"ev.lost.alone": "{where}: {verb} and came loose from {joints}; the object now has {pieces}",
|
|
2056
|
+
"ev.cracks": "{where}: cracking under the load, {n} cracks so far, longest {cm} cm, {hp}",
|
|
2057
|
+
"ev.disassemble": "{obj}: undoing the {kind} between {a} and {b} with {tool}: {pct}%",
|
|
2058
|
+
"ev.ignite": "{where}: caught fire at {temp} \xB0C",
|
|
2059
|
+
"ev.ignite.many": "{obj}: {n} parts caught fire at {temp} \xB0C ({parts})",
|
|
2060
|
+
"ev.extinguish": "{where}: fire put out",
|
|
2061
|
+
"ev.extinguish.many": "{obj}: fire put out on {n} parts ({parts})",
|
|
2062
|
+
"ev.burnout": "{where}: burned to ash",
|
|
2063
|
+
"ev.melt": "{where}: melting, {pct}% melted away",
|
|
2064
|
+
"ev.drip": "{where}: dripping molten material",
|
|
2065
|
+
"ev.explode": "{where}: exploded, {energy} J within {radius} m",
|
|
2066
|
+
"ev.destroyed": "{obj}: destroyed",
|
|
2067
|
+
"ev.damage": "{where}: {type} damage {amount}, {hp}",
|
|
2068
|
+
"ev.damage.overload": "{where}: crushed by the weight it carries, lost {amount} hp so far, {hp}",
|
|
2069
|
+
"ev.stress": "{where}: overloaded at {stress}% of its capacity, carrying {load} kg",
|
|
2070
|
+
"ev.collapse": "{obj}: collapsed, {pieces} fell ({mass} kg): {parts}",
|
|
2071
|
+
"ev.stain_ignite": "The {liquid} stain at x {x} \xB7 z {z} caught fire and will burn for about {seconds} s",
|
|
2072
|
+
"ev.stain_burned": "The burning stain burned itself out",
|
|
2073
|
+
"ev.fire_dries": "Fire dried the wet stains nearby",
|
|
2074
|
+
"fl.fill": "{who}: filled with {liquid} (+{liters} l)",
|
|
2075
|
+
"fl.pour": "{who}: pouring {liquid} at {rate} l/s",
|
|
2076
|
+
"fl.spill": "{who}: spilled {liquid} over the lip at {rate} l/s",
|
|
2077
|
+
"fl.leak": "{who}: leaking {liquid} at {rate} l/s",
|
|
2078
|
+
"fl.drink": "{who}: drank {liters} l, thirst +{thirst}",
|
|
2079
|
+
"fl.burn": "too hot: burned the mouth",
|
|
2080
|
+
"fl.empty": "{who}: empty",
|
|
2081
|
+
"fl.splash": "Splash of {liquid}",
|
|
2082
|
+
"fl.stain": "Stain of {liquid} {surface} at {where}",
|
|
2083
|
+
"fl.stains": "{n} stains of {liquid} {surface}, the latest at {where}",
|
|
2084
|
+
"fl.dried": "A stain dried up",
|
|
2085
|
+
"fl.dried.mark": "A stain dried and left a mark",
|
|
2086
|
+
"fl.dried.known": "The {liquid} stain {surface} at {where} dried up",
|
|
2087
|
+
"fl.dried.known.mark": "The {liquid} stain {surface} at {where} dried and left a mark",
|
|
2088
|
+
"fl.dried.many": "{n} {liquid} stains dried up",
|
|
2089
|
+
"fl.dried.many.mark": "{n} {liquid} stains dried and left marks",
|
|
2090
|
+
"fl.mix": "{who}: {from} mixed into {into}",
|
|
2091
|
+
"act.anim_start": '{name}: animation "{clip}" ({seconds} s)',
|
|
2092
|
+
"act.anim_event": '{name}: "{clip}" event {type}',
|
|
2093
|
+
"act.anim_end": '{name}: animation "{clip}" finished',
|
|
2094
|
+
"act.area_skip": 'Area item "{item}" is not loaded in this room: left out',
|
|
2095
|
+
"act.module_loaded": 'Module "{name}" loaded: {items} item(s), {scenarios} scenario(s), contract checks: {result}',
|
|
2096
|
+
"mod.all_ok": "all passed",
|
|
2097
|
+
"mod.some_failed": "some FAILED",
|
|
2098
|
+
"mod.destructible_ok": "{name}: destructible contract ok ({parts} parts, hit and state well-formed)",
|
|
2099
|
+
"mod.destructible_fail": "{name}: destructible contract broken: {problems}",
|
|
2100
|
+
"mod.animation_ok": '{name}: animation "{clip}" ok ({seconds} s)',
|
|
2101
|
+
"mod.spec_ok": "{name}: spec ok ({parts} parts)",
|
|
2102
|
+
"mod.spec_fail": "{name}: spec broken: {problems}",
|
|
2103
|
+
"mod.spec_warn": "{name}: spec warning: {warning}",
|
|
2104
|
+
"mod.animation_fail": '{name}: animation "{clip}" broken: {problems}',
|
|
2105
|
+
"ui.animations": "Animations",
|
|
2106
|
+
"ui.animations_aim": "Aim at an item or hold it to see its animations.",
|
|
2107
|
+
"ui.animations_none": "This item brings no animations.",
|
|
2108
|
+
"mod.bench_ok": "{name}: bench ok ({controls} controls, {layers} layers, {metrics} metrics)",
|
|
2109
|
+
"mod.bench_fail": "{name}: bench broken: {problems}",
|
|
2110
|
+
"mod.metric_failed": '{name}: metric "{metric}" threw and shows n/a: {error}',
|
|
2111
|
+
"mod.listener_failed": "{name}: a bench listener threw: {error}",
|
|
2112
|
+
"ui.bench": "Bench",
|
|
2113
|
+
"ui.layers": "Layers",
|
|
2114
|
+
"ui.metrics": "Counters",
|
|
2115
|
+
"metric.fps": "FPS",
|
|
2116
|
+
"metric.frame_ms": "ms/frame",
|
|
2117
|
+
"metric.draw_calls": "Draw calls",
|
|
2118
|
+
"metric.triangles": "Triangles",
|
|
2119
|
+
"metric.objects": "Objects",
|
|
2120
|
+
"metric.fragments": "Fragments",
|
|
2121
|
+
"metric.particles": "Particles",
|
|
2122
|
+
"metric.decals": "Decals",
|
|
2123
|
+
"act.spawn": "Spawned {name} at x {x} \xB7 z {z}",
|
|
2124
|
+
"act.clear": "Cleared the room",
|
|
2125
|
+
"act.reset": "Repaired every object",
|
|
2126
|
+
"act.tool": "Equipped {weapon}",
|
|
2127
|
+
"act.shoot.hit": "{weapon}: shot, {energy} J, hit {where} at {dist} m",
|
|
2128
|
+
"act.shoot.miss": "{weapon}: shot, {energy} J, hit nothing",
|
|
2129
|
+
"act.shoot.pellets": "{weapon}: shot, {n} projectiles of up to {energy} J, {hits} hit {where} at {dist} m",
|
|
2130
|
+
"act.melee.hit": "{weapon}: strike, {energy} J, hit {where} at {dist} m",
|
|
2131
|
+
"act.melee.miss": "{weapon}: struck the air",
|
|
2132
|
+
"act.strike": "{weapon}: {clip} begins, impact in {seconds} s",
|
|
2133
|
+
"act.strike.leg": "{weapon}: {clip} with the {leg} leg begins, impact in {seconds} s",
|
|
2134
|
+
"leg.left": "left",
|
|
2135
|
+
"leg.right": "right",
|
|
2136
|
+
"act.flame.hit": "{weapon}: flame at {temp} \xB0C on {where}",
|
|
2137
|
+
"act.flame.air": "{weapon}: flame, nothing in range",
|
|
2138
|
+
"act.heat.hit": "{weapon}: heating {where} at {temp} \xB0C",
|
|
2139
|
+
"act.heat.air": "{weapon}: nothing in range",
|
|
2140
|
+
"act.throw.grenade": "{weapon}: thrown, fuse {fuse} s",
|
|
2141
|
+
"act.throw.molotov": "{weapon}: thrown, breaks on impact",
|
|
2142
|
+
"act.launch": "{weapon}: launched, arms after {arm} m",
|
|
2143
|
+
"act.place": "{weapon}: charge set down, waits for {triggers}",
|
|
2144
|
+
"act.tripwire": "{weapon}: {who} crossed the trip wire",
|
|
2145
|
+
"act.dud": "{weapon}: hit before arming ({distance} m), did not explode",
|
|
2146
|
+
"act.backblast": "{weapon}: blast behind the tube, {radius} m, {n} objects in the cone, {player} on the shooter",
|
|
2147
|
+
"act.blast_player": "{weapon}: you are in the {zone} zone, {distance} m away (stun {stun} s)",
|
|
2148
|
+
"hud.no_charges": "No charge set down waits for the remote detonator",
|
|
2149
|
+
"blast.zone.lethal": "lethal",
|
|
2150
|
+
"blast.zone.injury": "injury",
|
|
2151
|
+
"act.explode": "{weapon}: exploded, {energy} J within {radius} m, {n} objects in range",
|
|
2152
|
+
"act.molotov_fire": "{weapon}: fire on the ground, radius {radius} m for {seconds} s",
|
|
2153
|
+
"act.tool.none": "{weapon}: nothing in reach",
|
|
2154
|
+
"act.tool.nojoint": "{weapon}: no joint near {where}",
|
|
2155
|
+
"act.tool.refused": "{weapon} cannot undo the {kind} of {obj}; it needs: {need}",
|
|
2156
|
+
"act.extinguish.hit": "{weapon}: foam on {what}",
|
|
2157
|
+
"act.extinguish.air": "{weapon}: foam, nothing in range",
|
|
2158
|
+
"act.grab": "Grabbed {name} ({kg} kg)",
|
|
2159
|
+
"act.drop": "Dropped {name}",
|
|
2160
|
+
"act.throw": "Threw {name} at {speed} m/s",
|
|
2161
|
+
"act.landing": "{name} landed at {speed} m/s: {energy} J",
|
|
2162
|
+
"act.bench_failed": "{name} failed: {error}",
|
|
2163
|
+
"loading.working": "Working\u2026",
|
|
2164
|
+
"loading.open_area": "Opening {area}\u2026",
|
|
2165
|
+
"loading.spawn": "Building {name}\u2026",
|
|
2166
|
+
"act.loosened": "{name} was heavy but free: the blast moved it",
|
|
2167
|
+
"act.impact": "{name} hit {where} at {speed} m/s: {energy} J of blunt damage",
|
|
2168
|
+
"act.use_item": "{name}: {use}",
|
|
2169
|
+
"act.tap.on": "Tap opened",
|
|
2170
|
+
"act.tap.off": "Tap closed",
|
|
2171
|
+
"use.drink": "drink",
|
|
2172
|
+
"use.sip": "sip",
|
|
2173
|
+
"use.chug": "chug",
|
|
2174
|
+
"use.pour": "pour",
|
|
2175
|
+
"use.pour_out": "pour out",
|
|
2176
|
+
"use.open": "open",
|
|
2177
|
+
"use.close": "close",
|
|
2178
|
+
"hud.reloading": "Reloading...",
|
|
2179
|
+
"hud.empty": "Empty",
|
|
2180
|
+
"hud.no_fuel": "Out of fuel",
|
|
2181
|
+
"hud.fault": "Jammed ({fault}): press X to clear",
|
|
2182
|
+
"hud.safe": "Safety on: press B",
|
|
2183
|
+
"hud.clearing": "Clearing the jam...",
|
|
2184
|
+
"hud.safety_on": "Safety on",
|
|
2185
|
+
"hud.safety_off": "Safety off",
|
|
2186
|
+
"fault.misfire": "misfire",
|
|
2187
|
+
"fault.stovepipe": "stovepipe",
|
|
2188
|
+
"fault.double_feed": "double feed",
|
|
2189
|
+
"fault.failure_to_extract": "failure to extract",
|
|
2190
|
+
"hud.nothing_to_grab": "Nothing to grab here",
|
|
2191
|
+
"hud.cannot_grab": "{name} cannot be picked up",
|
|
2192
|
+
"hud.too_heavy": "{name} is too heavy ({kg} kg)",
|
|
2193
|
+
"hud.empty_container": "{name} is empty",
|
|
2194
|
+
"hud.refused": "Wrong tool for the {kind}",
|
|
2195
|
+
"hud.drop": "drop",
|
|
2196
|
+
"hud.rotate": "rotate",
|
|
2197
|
+
"hud.throw": "throw",
|
|
2198
|
+
"hud.tilt": "tilt (hold)",
|
|
2199
|
+
"hud.equip": "equip {weapon}",
|
|
2200
|
+
"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",
|
|
2201
|
+
"ui.help.camera": "V: 1st/3rd person (Shift+V: kick)",
|
|
2202
|
+
"camera.first": "1st person",
|
|
2203
|
+
"camera.third": "3rd person",
|
|
2204
|
+
"act.camera": "View: {mode}",
|
|
2205
|
+
"ui.log": "Behaviour log",
|
|
2206
|
+
"ui.filter.action": "actions",
|
|
2207
|
+
"ui.filter.event": "events",
|
|
2208
|
+
"ui.filter.check": "checks",
|
|
2209
|
+
"ui.autoscroll": "autoscroll",
|
|
2210
|
+
"log.export_cancelled": "export cancelled",
|
|
2211
|
+
"ui.copy_json": "Copy JSON",
|
|
2212
|
+
"ui.download_ndjson": "Download NDJSON",
|
|
2213
|
+
"ui.download_txt": "Download TXT",
|
|
2214
|
+
"ui.clear_log": "Clear",
|
|
2215
|
+
"ui.catalog": "Catalogue",
|
|
2216
|
+
"ui.clear": "Clear",
|
|
2217
|
+
"ui.reset_all": "Reset all",
|
|
2218
|
+
"ui.slow": "Slow motion 0.25\xD7",
|
|
2219
|
+
"ui.fast": "Time \xD710",
|
|
2220
|
+
"ui.tap": "Tap",
|
|
2221
|
+
"ui.blast.shelter": "Shelter",
|
|
2222
|
+
"ui.blast.fragmentHits": "Fragments",
|
|
2223
|
+
"ui.blast.confinement": "Confinement",
|
|
2224
|
+
"ui.blast.shelter.hint": "Blasts: walls and other parts between the blast and the target take their share",
|
|
2225
|
+
"ui.blast.fragmentHits.hint": "Blasts: the pieces of what breaks fly on and hit what is behind",
|
|
2226
|
+
"ui.blast.confinement.hint": "Blasts: pressure builds up in an enclosed room",
|
|
2227
|
+
"metric.blast_shelter": "Blast reaching (%)",
|
|
2228
|
+
"metric.blast_confinement": "Blast pressure (x)",
|
|
2229
|
+
"metric.blast_fragments": "Fragment hits",
|
|
2230
|
+
"act.blast_report": "Blast: {shelter}% reached the most sheltered target, pressure x{confinement}, {fragments} fragment hits, {n} targets",
|
|
2231
|
+
"ui.quality": "Quality",
|
|
2232
|
+
"ui.quality.low": "low",
|
|
2233
|
+
"ui.quality.medium": "medium",
|
|
2234
|
+
"ui.quality.high": "high",
|
|
2235
|
+
"ui.equip": "Equip as a tool",
|
|
2236
|
+
"ui.search_items": "Search items (all groups)",
|
|
2237
|
+
"ui.search_tests": "Search tests",
|
|
2238
|
+
"ui.no_results": "Nothing found",
|
|
2239
|
+
"ui.objects": "{n} objects",
|
|
2240
|
+
"ui.tests": "Tests",
|
|
2241
|
+
"ui.run_all": "Run all",
|
|
2242
|
+
"ui.running": "Running...",
|
|
2243
|
+
"ui.report": "{passed} passed, {failed} failed",
|
|
2244
|
+
"ui.panel": "Panels",
|
|
2245
|
+
"ui.inspector": "Inspector",
|
|
2246
|
+
"ui.aim_at_something": "Aim at an object",
|
|
2247
|
+
"ins.material": "Material",
|
|
2248
|
+
"ins.status": "Status",
|
|
2249
|
+
"ins.burning": "burning",
|
|
2250
|
+
"ins.hp": "Hit points",
|
|
2251
|
+
"ins.temperature": "Temperature",
|
|
2252
|
+
"ins.char": "Charred",
|
|
2253
|
+
"ins.melt": "Melted",
|
|
2254
|
+
"ins.liters": "Liquid",
|
|
2255
|
+
"ins.damage": "Marks",
|
|
2256
|
+
"ins.damage_counts": "{dents}, {holes}, {cracks}",
|
|
2257
|
+
"ins.mass": "Mass",
|
|
2258
|
+
"ins.load": "Load",
|
|
2259
|
+
"ins.stress": "Stress",
|
|
2260
|
+
"ins.joints": "Joints",
|
|
2261
|
+
"ins.open": "open",
|
|
2262
|
+
"sc.start": "Scenario: {name}",
|
|
2263
|
+
"sc.pass": "Scenario passed: {name}",
|
|
2264
|
+
"sc.fail": "Scenario FAILED: {name}",
|
|
2265
|
+
"sc.error": "the scenario threw an error: {msg}",
|
|
2266
|
+
"leg.still": "leg_1 still attached",
|
|
2267
|
+
"leg.gone": "leg_1 came off",
|
|
2268
|
+
"roof.standing": "roof standing",
|
|
2269
|
+
"roof.fell": "roof fell"
|
|
2270
|
+
}
|
|
2271
|
+
};
|
|
2272
|
+
var ptBR = {
|
|
2273
|
+
items: {},
|
|
2274
|
+
groups: { furniture: "M\xF3veis", structure: "Estrutura", vehicles: "Ve\xEDculos", plants: "Plantas", items: "Itens", containers: "Recipientes" },
|
|
2275
|
+
text: {
|
|
2276
|
+
"locale.decimal": ",",
|
|
2277
|
+
yes: "sim",
|
|
2278
|
+
no: "n\xE3o",
|
|
2279
|
+
piece: "pe\xE7a",
|
|
2280
|
+
coords: "x {x} \xB7 z {z}",
|
|
2281
|
+
"count.piece.one": "1 pe\xE7a",
|
|
2282
|
+
"count.piece.other": "{n} pe\xE7as",
|
|
2283
|
+
"count.joint.one": "1 encaixe",
|
|
2284
|
+
"count.joint.other": "{n} encaixes",
|
|
2285
|
+
"count.dent.one": "1 amassado",
|
|
2286
|
+
"count.dent.other": "{n} amassados",
|
|
2287
|
+
"count.hole.one": "1 furo",
|
|
2288
|
+
"count.hole.other": "{n} furos",
|
|
2289
|
+
"count.crack.one": "1 trinca",
|
|
2290
|
+
"count.crack.other": "{n} trincas",
|
|
2291
|
+
"joint.generic": "junta",
|
|
2292
|
+
"lost.broken": "quebrou",
|
|
2293
|
+
"lost.melted": "derreteu",
|
|
2294
|
+
"lost.ashes": "virou cinza",
|
|
2295
|
+
"tool.none": "s\xF3 com a m\xE3o",
|
|
2296
|
+
"arena.target_wall": "Parede de alvo",
|
|
2297
|
+
"arena.table": "Mesa",
|
|
2298
|
+
"arena.shelf": "Estante",
|
|
2299
|
+
"surface.floor": "no ch\xE3o",
|
|
2300
|
+
"surface.ground": "no ch\xE3o",
|
|
2301
|
+
"surface.table": "na mesa",
|
|
2302
|
+
"by.break": "arrebentou",
|
|
2303
|
+
"by.tool": "desmontado com ferramenta",
|
|
2304
|
+
"by.part_lost": "a pe\xE7a ao lado se perdeu",
|
|
2305
|
+
"dmg.ballistic": "de tiro",
|
|
2306
|
+
"dmg.blunt": "de impacto",
|
|
2307
|
+
"dmg.slash": "de corte",
|
|
2308
|
+
"dmg.explosive": "de explos\xE3o",
|
|
2309
|
+
"dmg.fire": "de fogo",
|
|
2310
|
+
"dmg.heat": "de calor",
|
|
2311
|
+
"effect.caffeine": "cafe\xEDna",
|
|
2312
|
+
"effect.alcohol": "\xE1lcool",
|
|
2313
|
+
"effect.poison": "veneno",
|
|
2314
|
+
"effect.disgust": "nojo",
|
|
2315
|
+
"ev.dent": "{where}: amassado de {mm} mm, {hp}",
|
|
2316
|
+
"ev.hole.through": "{where}: furo de {mm} mm atravessou, {hp}",
|
|
2317
|
+
"ev.hole.stuck": "{where}: furo de {mm} mm n\xE3o atravessou, {hp}",
|
|
2318
|
+
"ev.dents": "{where}: {n} amassados, o mais fundo com {mm} mm, {hp}",
|
|
2319
|
+
"ev.holes": "{where}: {n} furos de {mm} mm, {thr} atravessaram, {hp}",
|
|
2320
|
+
"ev.leak.tail": ", vazando {liquid} a {rate} l/s",
|
|
2321
|
+
"ev.leak.alone": "{where}: vazando {liquid} a {rate} l/s",
|
|
2322
|
+
"ev.crack": "{where}: trincou, {rays} raios de at\xE9 {cm} cm, {hp}",
|
|
2323
|
+
"ev.ricochet": "{where}: a bala ricocheteou, sobraram {energy} J",
|
|
2324
|
+
"ev.fracture.shatter": "{where}: estilha\xE7ou em {n} cacos",
|
|
2325
|
+
"ev.fracture.crumble": "{where}: esfarelou em {n} peda\xE7os",
|
|
2326
|
+
"ev.fracture.splinter": "{where}: lascou em {n} peda\xE7os",
|
|
2327
|
+
"ev.fracture.tear": "{where}: rasgou em {n} retalhos",
|
|
2328
|
+
"ev.fracture.snap": "{where}: partiu em {n} peda\xE7os",
|
|
2329
|
+
"ev.fracture.none": "{where}: quebrou em {n} peda\xE7os",
|
|
2330
|
+
"ev.detach": "{obj}: {a} soltou de {b} ({by}); o objeto agora tem {pieces}",
|
|
2331
|
+
"ev.detach.many.break": "{obj}: {joints} arrebentaram; o objeto agora tem {pieces}",
|
|
2332
|
+
"ev.detach.many.tool": "{obj}: {joints} soltos com ferramenta; o objeto agora tem {pieces}",
|
|
2333
|
+
"ev.lost.tail": " e se soltou de {joints}; o objeto agora tem {pieces}",
|
|
2334
|
+
"ev.lost.alone": "{where}: {verb} e se soltou de {joints}; o objeto agora tem {pieces}",
|
|
2335
|
+
"ev.cracks": "{where}: rachando sob o peso, {n} trincas at\xE9 agora, a maior com {cm} cm, {hp}",
|
|
2336
|
+
"ev.disassemble": "{obj}: soltando {kind} entre {a} e {b} com {tool}: {pct}%",
|
|
2337
|
+
"ev.ignite": "{where}: pegou fogo a {temp} \xB0C",
|
|
2338
|
+
"ev.ignite.many": "{obj}: {n} pe\xE7as pegaram fogo a {temp} \xB0C ({parts})",
|
|
2339
|
+
"ev.extinguish": "{where}: fogo apagado",
|
|
2340
|
+
"ev.extinguish.many": "{obj}: fogo apagado em {n} pe\xE7as ({parts})",
|
|
2341
|
+
"ev.burnout": "{where}: virou cinza",
|
|
2342
|
+
"ev.melt": "{where}: derretendo, {pct}% j\xE1 derreteu",
|
|
2343
|
+
"ev.drip": "{where}: pingando material derretido",
|
|
2344
|
+
"ev.explode": "{where}: explodiu, {energy} J em {radius} m",
|
|
2345
|
+
"ev.destroyed": "{obj}: destru\xEDdo",
|
|
2346
|
+
"ev.damage": "{where}: dano {type} de {amount}, {hp}",
|
|
2347
|
+
"ev.damage.overload": "{where}: esmagado pelo peso que sustenta, perdeu {amount} hp at\xE9 agora, {hp}",
|
|
2348
|
+
"ev.stress": "{where}: sobrecarregado a {stress}% da capacidade, sustentando {load} kg",
|
|
2349
|
+
"ev.collapse": "{obj}: desabou, ca\xEDram {pieces} ({mass} kg): {parts}",
|
|
2350
|
+
"ev.stain_ignite": "A mancha de {liquid} em x {x} \xB7 z {z} pegou fogo e vai queimar por uns {seconds} s",
|
|
2351
|
+
"ev.stain_burned": "A mancha em chamas se consumiu",
|
|
2352
|
+
"ev.fire_dries": "O fogo secou as manchas molhadas por perto",
|
|
2353
|
+
"fl.fill": "{who}: encheu de {liquid} (+{liters} l)",
|
|
2354
|
+
"fl.pour": "{who}: despejando {liquid} a {rate} l/s",
|
|
2355
|
+
"fl.spill": "{who}: derramou {liquid} pela borda a {rate} l/s",
|
|
2356
|
+
"fl.leak": "{who}: vazando {liquid} a {rate} l/s",
|
|
2357
|
+
"fl.drink": "{who}: bebeu {liters} l, sede +{thirst}",
|
|
2358
|
+
"fl.burn": "quente demais: queimou a boca",
|
|
2359
|
+
"fl.empty": "{who}: vazio",
|
|
2360
|
+
"fl.splash": "Respingo de {liquid}",
|
|
2361
|
+
"fl.stain": "Mancha de {liquid} {surface} em {where}",
|
|
2362
|
+
"fl.stains": "{n} manchas de {liquid} {surface}, a \xFAltima em {where}",
|
|
2363
|
+
"fl.dried": "Uma mancha secou",
|
|
2364
|
+
"fl.dried.mark": "Uma mancha secou e deixou marca",
|
|
2365
|
+
"fl.dried.known": "A mancha de {liquid} {surface} em {where} secou",
|
|
2366
|
+
"fl.dried.known.mark": "A mancha de {liquid} {surface} em {where} secou e deixou marca",
|
|
2367
|
+
"fl.dried.many": "{n} manchas de {liquid} secaram",
|
|
2368
|
+
"fl.dried.many.mark": "{n} manchas de {liquid} secaram e deixaram marca",
|
|
2369
|
+
"fl.mix": "{who}: {from} misturou em {into}",
|
|
2370
|
+
"act.anim_start": '{name}: anima\xE7\xE3o "{clip}" ({seconds} s)',
|
|
2371
|
+
"act.anim_event": '{name}: "{clip}" evento {type}',
|
|
2372
|
+
"act.anim_end": '{name}: anima\xE7\xE3o "{clip}" terminou',
|
|
2373
|
+
"act.area_skip": 'Item "{item}" da \xE1rea n\xE3o est\xE1 carregado nesta sala: ficou de fora',
|
|
2374
|
+
"act.module_loaded": 'M\xF3dulo "{name}" carregado: {items} item(ns), {scenarios} cen\xE1rio(s), verifica\xE7\xF5es do contrato: {result}',
|
|
2375
|
+
"mod.all_ok": "todas passaram",
|
|
2376
|
+
"mod.some_failed": "algumas FALHARAM",
|
|
2377
|
+
"mod.destructible_ok": "{name}: contrato de destrui\xE7\xE3o ok ({parts} pe\xE7as, golpe e estado bem formados)",
|
|
2378
|
+
"mod.destructible_fail": "{name}: contrato de destrui\xE7\xE3o quebrado: {problems}",
|
|
2379
|
+
"mod.animation_ok": '{name}: anima\xE7\xE3o "{clip}" ok ({seconds} s)',
|
|
2380
|
+
"mod.spec_ok": "{name}: spec ok ({parts} pe\xE7as)",
|
|
2381
|
+
"mod.spec_fail": "{name}: spec quebrada: {problems}",
|
|
2382
|
+
"mod.spec_warn": "{name}: aviso na spec: {warning}",
|
|
2383
|
+
"mod.animation_fail": '{name}: anima\xE7\xE3o "{clip}" quebrada: {problems}',
|
|
2384
|
+
"ui.animations": "Anima\xE7\xF5es",
|
|
2385
|
+
"ui.animations_aim": "Mire num item ou segure-o para ver as anima\xE7\xF5es.",
|
|
2386
|
+
"ui.animations_none": "Este item n\xE3o traz anima\xE7\xF5es.",
|
|
2387
|
+
"mod.bench_ok": "{name}: bancada ok ({controls} controles, {layers} camadas, {metrics} contadores)",
|
|
2388
|
+
"mod.bench_fail": "{name}: bancada quebrada: {problems}",
|
|
2389
|
+
"mod.metric_failed": '{name}: o contador "{metric}" lan\xE7ou erro e mostra n/a: {error}',
|
|
2390
|
+
"mod.listener_failed": "{name}: um ouvinte da bancada lan\xE7ou erro: {error}",
|
|
2391
|
+
"ui.bench": "Bancada",
|
|
2392
|
+
"ui.layers": "Camadas",
|
|
2393
|
+
"ui.metrics": "Contadores",
|
|
2394
|
+
"metric.fps": "FPS",
|
|
2395
|
+
"metric.frame_ms": "ms/quadro",
|
|
2396
|
+
"metric.draw_calls": "Chamadas de desenho",
|
|
2397
|
+
"metric.triangles": "Tri\xE2ngulos",
|
|
2398
|
+
"metric.objects": "Objetos",
|
|
2399
|
+
"metric.fragments": "Fragmentos",
|
|
2400
|
+
"metric.particles": "Part\xEDculas",
|
|
2401
|
+
"metric.decals": "Decalques",
|
|
2402
|
+
"act.spawn": "Criou {name} em x {x} \xB7 z {z}",
|
|
2403
|
+
"act.clear": "Limpou a sala",
|
|
2404
|
+
"act.reset": "Consertou todos os objetos",
|
|
2405
|
+
"act.tool": "Equipou {weapon}",
|
|
2406
|
+
"act.shoot.hit": "{weapon}: disparo, {energy} J, acertou {where} a {dist} m",
|
|
2407
|
+
"act.shoot.miss": "{weapon}: disparo, {energy} J, n\xE3o acertou nada",
|
|
2408
|
+
"act.shoot.pellets": "{weapon}: disparo, {n} proj\xE9teis de at\xE9 {energy} J, {hits} acertaram {where} a {dist} m",
|
|
2409
|
+
"act.melee.hit": "{weapon}: golpe, {energy} J, acertou {where} a {dist} m",
|
|
2410
|
+
"act.melee.miss": "{weapon}: golpe no ar",
|
|
2411
|
+
"act.strike": "{weapon}: {clip} come\xE7a, impacto em {seconds} s",
|
|
2412
|
+
"act.strike.leg": "{weapon}: {clip} com a perna {leg} come\xE7a, impacto em {seconds} s",
|
|
2413
|
+
"leg.left": "esquerda",
|
|
2414
|
+
"leg.right": "direita",
|
|
2415
|
+
"act.flame.hit": "{weapon}: chama a {temp} \xB0C em {where}",
|
|
2416
|
+
"act.flame.air": "{weapon}: chama, nada ao alcance",
|
|
2417
|
+
"act.heat.hit": "{weapon}: esquentando {where} a {temp} \xB0C",
|
|
2418
|
+
"act.heat.air": "{weapon}: nada ao alcance",
|
|
2419
|
+
"act.throw.grenade": "{weapon}: arremessada, pavio de {fuse} s",
|
|
2420
|
+
"act.throw.molotov": "{weapon}: arremessado, quebra no impacto",
|
|
2421
|
+
"act.launch": "{weapon}: disparado, arma depois de {arm} m",
|
|
2422
|
+
"act.place": "{weapon}: carga posta, espera {triggers}",
|
|
2423
|
+
"act.tripwire": "{weapon}: {who} cruzou o fio",
|
|
2424
|
+
"act.dud": "{weapon}: bateu antes de armar ({distance} m), n\xE3o explodiu",
|
|
2425
|
+
"act.backblast": "{weapon}: sopro atr\xE1s do tubo, {radius} m, {n} objetos no cone, {player} no atirador",
|
|
2426
|
+
"act.blast_player": "{weapon}: voc\xEA est\xE1 na zona {zone}, a {distance} m (atordoado {stun} s)",
|
|
2427
|
+
"hud.no_charges": "Nenhuma carga posta espera o detonador",
|
|
2428
|
+
"blast.zone.lethal": "letal",
|
|
2429
|
+
"blast.zone.injury": "de ferimento",
|
|
2430
|
+
"act.explode": "{weapon}: explodiu, {energy} J em {radius} m, {n} objetos ao alcance",
|
|
2431
|
+
"act.molotov_fire": "{weapon}: fogo no ch\xE3o, raio de {radius} m por {seconds} s",
|
|
2432
|
+
"act.tool.none": "{weapon}: nada ao alcance",
|
|
2433
|
+
"act.tool.nojoint": "{weapon}: nenhuma junta perto de {where}",
|
|
2434
|
+
"act.tool.refused": "{weapon} n\xE3o solta {kind} de {obj}; precisa de: {need}",
|
|
2435
|
+
"act.extinguish.hit": "{weapon}: espuma em {what}",
|
|
2436
|
+
"act.extinguish.air": "{weapon}: espuma, nada ao alcance",
|
|
2437
|
+
"act.grab": "Pegou {name} ({kg} kg)",
|
|
2438
|
+
"act.drop": "Soltou {name}",
|
|
2439
|
+
"act.throw": "Arremessou {name} a {speed} m/s",
|
|
2440
|
+
"act.landing": "{name} caiu a {speed} m/s: {energy} J",
|
|
2441
|
+
"act.bench_failed": "{name} falhou: {error}",
|
|
2442
|
+
"loading.working": "Trabalhando\u2026",
|
|
2443
|
+
"loading.open_area": "Abrindo {area}\u2026",
|
|
2444
|
+
"loading.spawn": "Montando {name}\u2026",
|
|
2445
|
+
"act.loosened": "{name} \xE9 pesado mas estava solto: a explos\xE3o o moveu",
|
|
2446
|
+
"act.impact": "{name} bateu em {where} a {speed} m/s: {energy} J de dano de impacto",
|
|
2447
|
+
"act.use_item": "{name}: {use}",
|
|
2448
|
+
"act.tap.on": "Abriu a torneira",
|
|
2449
|
+
"act.tap.off": "Fechou a torneira",
|
|
2450
|
+
"use.drink": "beber",
|
|
2451
|
+
"use.sip": "golinho",
|
|
2452
|
+
"use.chug": "virar",
|
|
2453
|
+
"use.pour": "despejar",
|
|
2454
|
+
"use.pour_out": "esvaziar",
|
|
2455
|
+
"use.open": "abrir",
|
|
2456
|
+
"use.close": "fechar",
|
|
2457
|
+
"hud.reloading": "Recarregando...",
|
|
2458
|
+
"hud.empty": "Sem muni\xE7\xE3o",
|
|
2459
|
+
"hud.no_fuel": "Sem combust\xEDvel",
|
|
2460
|
+
"hud.fault": "Travou ({fault}): aperte X para destravar",
|
|
2461
|
+
"hud.safe": "Trava ligada: aperte B",
|
|
2462
|
+
"hud.clearing": "Destravando...",
|
|
2463
|
+
"hud.safety_on": "Trava ligada",
|
|
2464
|
+
"hud.safety_off": "Trava desligada",
|
|
2465
|
+
"fault.misfire": "nega",
|
|
2466
|
+
"fault.stovepipe": "chamin\xE9",
|
|
2467
|
+
"fault.double_feed": "dupla alimenta\xE7\xE3o",
|
|
2468
|
+
"fault.failure_to_extract": "falha de extra\xE7\xE3o",
|
|
2469
|
+
"hud.nothing_to_grab": "Nada para pegar aqui",
|
|
2470
|
+
"hud.cannot_grab": "{name} n\xE3o d\xE1 para pegar",
|
|
2471
|
+
"hud.too_heavy": "{name} \xE9 pesado demais ({kg} kg)",
|
|
2472
|
+
"hud.empty_container": "{name} est\xE1 vazio",
|
|
2473
|
+
"hud.refused": "Ferramenta errada para {kind}",
|
|
2474
|
+
"hud.drop": "soltar",
|
|
2475
|
+
"hud.rotate": "girar",
|
|
2476
|
+
"hud.throw": "arremessar",
|
|
2477
|
+
"hud.tilt": "inclinar (segurar)",
|
|
2478
|
+
"hud.equip": "equipar {weapon}",
|
|
2479
|
+
"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",
|
|
2480
|
+
"ui.help.camera": "V: 1\xAA/3\xAA pessoa (Shift+V: chute)",
|
|
2481
|
+
"camera.first": "1\xAA pessoa",
|
|
2482
|
+
"camera.third": "3\xAA pessoa",
|
|
2483
|
+
"act.camera": "C\xE2mera: {mode}",
|
|
2484
|
+
"ui.log": "Registro de comportamento",
|
|
2485
|
+
"ui.filter.action": "a\xE7\xF5es",
|
|
2486
|
+
"ui.filter.event": "eventos",
|
|
2487
|
+
"ui.filter.check": "testes",
|
|
2488
|
+
"ui.autoscroll": "rolar sozinho",
|
|
2489
|
+
"log.export_cancelled": "exporta\xE7\xE3o cancelada",
|
|
2490
|
+
"ui.copy_json": "Copiar JSON",
|
|
2491
|
+
"ui.download_ndjson": "Baixar NDJSON",
|
|
2492
|
+
"ui.download_txt": "Baixar TXT",
|
|
2493
|
+
"ui.clear_log": "Limpar",
|
|
2494
|
+
"ui.catalog": "Cat\xE1logo",
|
|
2495
|
+
"ui.clear": "Limpar",
|
|
2496
|
+
"ui.reset_all": "Consertar tudo",
|
|
2497
|
+
"ui.slow": "C\xE2mera lenta 0,25\xD7",
|
|
2498
|
+
"ui.fast": "Tempo \xD710",
|
|
2499
|
+
"ui.tap": "Torneira",
|
|
2500
|
+
"ui.blast.shelter": "Abrigo",
|
|
2501
|
+
"ui.blast.fragmentHits": "Estilha\xE7os",
|
|
2502
|
+
"ui.blast.confinement": "Confinamento",
|
|
2503
|
+
"ui.blast.shelter.hint": "Explos\xF5es: paredes e outras partes entre a explos\xE3o e o alvo ficam com a sua parte",
|
|
2504
|
+
"ui.blast.fragmentHits.hint": "Explos\xF5es: os peda\xE7os do que quebra voam e acertam o que est\xE1 atr\xE1s",
|
|
2505
|
+
"ui.blast.confinement.hint": "Explos\xF5es: a press\xE3o sobe num ambiente fechado",
|
|
2506
|
+
"metric.blast_shelter": "Explos\xE3o que chegou (%)",
|
|
2507
|
+
"metric.blast_confinement": "Press\xE3o da explos\xE3o (x)",
|
|
2508
|
+
"metric.blast_fragments": "Estilha\xE7os que acertaram",
|
|
2509
|
+
"act.blast_report": "Explos\xE3o: {shelter}% chegou ao alvo mais abrigado, press\xE3o x{confinement}, {fragments} estilha\xE7os acertaram, {n} alvos",
|
|
2510
|
+
"ui.quality": "Qualidade",
|
|
2511
|
+
"ui.quality.low": "baixa",
|
|
2512
|
+
"ui.quality.medium": "m\xE9dia",
|
|
2513
|
+
"ui.quality.high": "alta",
|
|
2514
|
+
"ui.equip": "Equipar como ferramenta",
|
|
2515
|
+
"ui.search_items": "Buscar item (todas as abas)",
|
|
2516
|
+
"ui.search_tests": "Buscar teste",
|
|
2517
|
+
"ui.no_results": "Nada encontrado",
|
|
2518
|
+
"ui.objects": "{n} objetos",
|
|
2519
|
+
"ui.tests": "Testes",
|
|
2520
|
+
"ui.run_all": "Rodar todos",
|
|
2521
|
+
"ui.running": "Rodando...",
|
|
2522
|
+
"ui.report": "{passed} passaram, {failed} falharam",
|
|
2523
|
+
"ui.panel": "Pain\xE9is",
|
|
2524
|
+
"ui.inspector": "Inspetor",
|
|
2525
|
+
"ui.aim_at_something": "Mire em um objeto",
|
|
2526
|
+
"ins.material": "Material",
|
|
2527
|
+
"ins.status": "Estado",
|
|
2528
|
+
"ins.burning": "em chamas",
|
|
2529
|
+
"ins.hp": "Pontos de vida",
|
|
2530
|
+
"ins.temperature": "Temperatura",
|
|
2531
|
+
"ins.char": "Carbonizado",
|
|
2532
|
+
"ins.melt": "Derretido",
|
|
2533
|
+
"ins.liters": "L\xEDquido",
|
|
2534
|
+
"ins.damage": "Marcas",
|
|
2535
|
+
"ins.damage_counts": "{dents}, {holes}, {cracks}",
|
|
2536
|
+
"ins.mass": "Massa",
|
|
2537
|
+
"ins.load": "Carga",
|
|
2538
|
+
"ins.stress": "Tens\xE3o",
|
|
2539
|
+
"ins.joints": "Juntas",
|
|
2540
|
+
"ins.open": "aberta",
|
|
2541
|
+
"sc.start": "Cen\xE1rio: {name}",
|
|
2542
|
+
"sc.pass": "Cen\xE1rio passou: {name}",
|
|
2543
|
+
"sc.fail": "Cen\xE1rio FALHOU: {name}",
|
|
2544
|
+
"sc.error": "o cen\xE1rio lan\xE7ou um erro: {msg}",
|
|
2545
|
+
"leg.still": "leg_1 continua presa",
|
|
2546
|
+
"leg.gone": "leg_1 soltou",
|
|
2547
|
+
"roof.standing": "laje de p\xE9",
|
|
2548
|
+
"roof.fell": "laje caiu"
|
|
2549
|
+
}
|
|
3005
2550
|
};
|
|
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");
|
|
2551
|
+
var locales = { en, "pt-BR": ptBR };
|
|
2552
|
+
var resolveStrings = (locale) => locales[locale ?? "en"] ?? locales.en;
|
|
3011
2553
|
|
|
3012
2554
|
// src/bench-panel.ts
|
|
3013
2555
|
var SEGMENTED_MAX = 4;
|
|
@@ -3758,6 +3300,9 @@ function createUi(o) {
|
|
|
3758
3300
|
toolBox.textContent = "";
|
|
3759
3301
|
toolBox.append(document.createTextNode(s.toolName));
|
|
3760
3302
|
if (s.ammo) toolBox.append(mk2("small", void 0, s.ammo));
|
|
3303
|
+
if (s.camera) toolBox.append(mk2("small", void 0, t(`camera.${s.camera}`)));
|
|
3304
|
+
const helpText = s.camera ? `${t("ui.help")} \xB7 ${t("ui.help.camera")}` : t("ui.help");
|
|
3305
|
+
if (help.textContent !== helpText) help.textContent = helpText;
|
|
3761
3306
|
if (s.status) status.textContent = s.status;
|
|
3762
3307
|
else if (!toastTimer) status.textContent = "";
|
|
3763
3308
|
if (s.progress) {
|
|
@@ -3847,6 +3392,10 @@ function createUi(o) {
|
|
|
3847
3392
|
// src/room.ts
|
|
3848
3393
|
var FIXED_DT = 1 / 60;
|
|
3849
3394
|
var METRICS_INTERVAL_S = 0.25;
|
|
3395
|
+
var CARRY_NEAR = 0.62;
|
|
3396
|
+
var CARRY_PALM = 0.012;
|
|
3397
|
+
var CARRY_GRIP = 0.45;
|
|
3398
|
+
var MAX_STRIDE_SPEED = 10;
|
|
3850
3399
|
var v3 = (v) => [v.x, v.y, v.z];
|
|
3851
3400
|
var dist3 = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
3852
3401
|
function createTestRoom(options) {
|
|
@@ -3860,6 +3409,7 @@ function createTestRoom(options) {
|
|
|
3860
3409
|
if (vars) for (const k of Object.keys(vars)) s = s.split(`{${k}}`).join(String(vars[k]));
|
|
3861
3410
|
return s;
|
|
3862
3411
|
};
|
|
3412
|
+
const loading = createLoading({ container: options.container, draw: options.ui !== false, working: t("loading.working") });
|
|
3863
3413
|
const num = (n, digits = 1) => {
|
|
3864
3414
|
const s = (Math.round(n * 10 ** digits) / 10 ** digits).toFixed(digits);
|
|
3865
3415
|
return locale.toLowerCase().startsWith("pt") ? s.replace(".", ",") : s;
|
|
@@ -3895,13 +3445,13 @@ function createTestRoom(options) {
|
|
|
3895
3445
|
const onResize = () => resize();
|
|
3896
3446
|
window.addEventListener("resize", onResize);
|
|
3897
3447
|
const arenaSize = options.arenaSize ?? 14;
|
|
3898
|
-
const arena = createArena(THREE, arenaSize, scene);
|
|
3448
|
+
const arena = createArena(THREE, arenaSize, scene, options.scenery, options.tap);
|
|
3899
3449
|
scene.add(arena.group);
|
|
3900
3450
|
const ownWorld = !options.world;
|
|
3901
|
-
const world = options.world ?? createDestruction({ seed: 1, ...options.worldOptions, materials: [...
|
|
3451
|
+
const world = options.world ?? createDestruction({ seed: 1, ...options.worldOptions, materials: [...options.worldOptions?.materials ?? []] });
|
|
3902
3452
|
const pristineWorld = ownWorld ? world.serialize() : null;
|
|
3903
|
-
const
|
|
3904
|
-
const p = world.part(
|
|
3453
|
+
const topOk = (t2) => {
|
|
3454
|
+
const p = world.part(t2.id, t2.topPart);
|
|
3905
3455
|
return !!p && p.status !== "broken" && p.status !== "melted" && p.status !== "ashes";
|
|
3906
3456
|
};
|
|
3907
3457
|
const restingTops = [];
|
|
@@ -3910,7 +3460,12 @@ function createTestRoom(options) {
|
|
|
3910
3460
|
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
3461
|
return top;
|
|
3912
3462
|
};
|
|
3913
|
-
const
|
|
3463
|
+
const sceneryTopAt = (x, z) => {
|
|
3464
|
+
let top = 0;
|
|
3465
|
+
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;
|
|
3466
|
+
return top;
|
|
3467
|
+
};
|
|
3468
|
+
const groundAt = (x, z) => Math.max(sceneryTopAt(x, z), restingTopAt(x, z));
|
|
3914
3469
|
const ownFluids = !options.fluids;
|
|
3915
3470
|
const fluids = options.fluids ?? createFluids({ seed: 1, locale, liquids: options.liquids, groundAt });
|
|
3916
3471
|
const pristineFluids = ownFluids ? fluids.serialize() : null;
|
|
@@ -3958,6 +3513,19 @@ function createTestRoom(options) {
|
|
|
3958
3513
|
groundAt,
|
|
3959
3514
|
pointScale,
|
|
3960
3515
|
onSound: playSound,
|
|
3516
|
+
...options.blastShove ? { blastShove: options.blastShove } : {},
|
|
3517
|
+
// a blast moves what is heavy but not nailed down: the renderer makes it a piece the moment the shove reaches it
|
|
3518
|
+
shoveable: (objectId) => {
|
|
3519
|
+
const e = entries.get(objectId);
|
|
3520
|
+
return e?.free === true && !e.piece;
|
|
3521
|
+
},
|
|
3522
|
+
onLoosened: (objectId, piece) => {
|
|
3523
|
+
const e = entries.get(objectId);
|
|
3524
|
+
if (!e) return;
|
|
3525
|
+
e.piece = piece;
|
|
3526
|
+
rendererPieces.set(objectId, piece);
|
|
3527
|
+
action("loosened", t("act.loosened", { name: e.name }), { objectId, itemId: e.so.itemId });
|
|
3528
|
+
},
|
|
3961
3529
|
onPiece: (piece) => {
|
|
3962
3530
|
rendererPieces.set(piece.objectId, piece);
|
|
3963
3531
|
loose.push(piece);
|
|
@@ -4012,7 +3580,6 @@ function createTestRoom(options) {
|
|
|
4012
3580
|
};
|
|
4013
3581
|
let tool = toolState(toolId);
|
|
4014
3582
|
const catalogMap = /* @__PURE__ */ new Map();
|
|
4015
|
-
for (const i of demoItems(locale)) catalogMap.set(i.id, i);
|
|
4016
3583
|
for (const i of options.catalog) catalogMap.set(i.id, i);
|
|
4017
3584
|
const catalog = [...options.catalog];
|
|
4018
3585
|
const entryOf = (objectId) => entries.get(objectId) ?? splitOf.get(objectId);
|
|
@@ -4052,7 +3619,7 @@ function createTestRoom(options) {
|
|
|
4052
3619
|
});
|
|
4053
3620
|
let pending = [];
|
|
4054
3621
|
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 };
|
|
3622
|
+
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
3623
|
const fire = (type, data) => {
|
|
4057
3624
|
for (const fn of listeners[type] ?? []) fn(data);
|
|
4058
3625
|
};
|
|
@@ -4179,19 +3746,38 @@ function createTestRoom(options) {
|
|
|
4179
3746
|
};
|
|
4180
3747
|
const addArena = () => {
|
|
4181
3748
|
for (const o of arena.objects) {
|
|
4182
|
-
const root = buildFromSpec(THREE, o.spec, { colors: o.colors });
|
|
3749
|
+
const root = o.build ? o.build(THREE) : buildFromSpec(THREE, o.spec, { colors: o.colors });
|
|
4183
3750
|
root.position.set(o.position[0], o.position[1], o.position[2]);
|
|
3751
|
+
root.rotation.y = o.rotationY;
|
|
4184
3752
|
scene.add(root);
|
|
4185
3753
|
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);
|
|
3754
|
+
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)] } : {} });
|
|
3755
|
+
registerEntry(o.id, o.id, root, o.spec, o.label ?? S.text[`arena.${o.id}`] ?? o.id, void 0, void 0, true, false);
|
|
4188
3756
|
}
|
|
4189
3757
|
};
|
|
4190
3758
|
addArena();
|
|
3759
|
+
const wrapped = /* @__PURE__ */ new WeakMap();
|
|
3760
|
+
const letGo = (root) => {
|
|
3761
|
+
const inner = wrapped.get(root);
|
|
3762
|
+
if (!inner) return;
|
|
3763
|
+
inner.removeFromParent();
|
|
3764
|
+
wrapped.delete(root);
|
|
3765
|
+
};
|
|
3766
|
+
const buildRoot = (item) => {
|
|
3767
|
+
const built = item.build(THREE);
|
|
3768
|
+
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;
|
|
3769
|
+
const holder = new THREE.Group();
|
|
3770
|
+
holder.name = built.name;
|
|
3771
|
+
holder.userData = built.userData;
|
|
3772
|
+
holder.add(built);
|
|
3773
|
+
wrapped.set(holder, built);
|
|
3774
|
+
Object.defineProperty(holder, "visible", { get: () => built.visible, set: (v) => void (built.visible = v), configurable: true });
|
|
3775
|
+
return holder;
|
|
3776
|
+
};
|
|
4191
3777
|
const spawn = (itemId, position, rotationY) => {
|
|
4192
3778
|
const item = catalogMap.get(itemId);
|
|
4193
3779
|
if (!item) throw new Error(`game-test-room: unknown item "${itemId}"`);
|
|
4194
|
-
const root = item
|
|
3780
|
+
const root = buildRoot(item);
|
|
4195
3781
|
let pos;
|
|
4196
3782
|
if (position) pos = [position[0], position[1], position[2]];
|
|
4197
3783
|
else {
|
|
@@ -4234,6 +3820,7 @@ function createTestRoom(options) {
|
|
|
4234
3820
|
if (own) world.register(own, { id: objectId, position: pos, rotation: [q.x, q.y, q.z, q.w] });
|
|
4235
3821
|
else world.add(spec, { id: objectId, position: pos, rotation: [q.x, q.y, q.z, q.w] });
|
|
4236
3822
|
const e = registerEntry(objectId, item.id, root, spec, item.name, parts, item, false, movable);
|
|
3823
|
+
if (!own && !movable && item.movable === void 0) e.free = true;
|
|
4237
3824
|
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
3825
|
fire("spawn", e.so);
|
|
4239
3826
|
return e.so;
|
|
@@ -4262,6 +3849,7 @@ function createTestRoom(options) {
|
|
|
4262
3849
|
}
|
|
4263
3850
|
if (world.has(id)) world.remove(id);
|
|
4264
3851
|
e.so.root.removeFromParent();
|
|
3852
|
+
letGo(e.so.root);
|
|
4265
3853
|
entries.delete(id);
|
|
4266
3854
|
rendererPieces.delete(id);
|
|
4267
3855
|
specs.delete(id);
|
|
@@ -4336,6 +3924,7 @@ function createTestRoom(options) {
|
|
|
4336
3924
|
for (const s of [...arena.surfaces, ...options.surfaces ?? []]) fluids.addSurface(s);
|
|
4337
3925
|
}
|
|
4338
3926
|
pending = [];
|
|
3927
|
+
strikes = [];
|
|
4339
3928
|
rng = 12345;
|
|
4340
3929
|
counter = 0;
|
|
4341
3930
|
slot = 0;
|
|
@@ -4348,6 +3937,104 @@ function createTestRoom(options) {
|
|
|
4348
3937
|
};
|
|
4349
3938
|
const aimO = [0, 0, 0];
|
|
4350
3939
|
const aimD = [0, 0, -1];
|
|
3940
|
+
const third = { ...THIRD_PERSON_DEFAULTS, ...options.thirdPerson };
|
|
3941
|
+
let avatar = null;
|
|
3942
|
+
let cameraMode = options.cameraMode ?? "first";
|
|
3943
|
+
const avatarState = { speed: 0, running: false, crouch: false, down: false, armed: false, pitch: 0 };
|
|
3944
|
+
let feetPrev = null;
|
|
3945
|
+
let armedForced = null;
|
|
3946
|
+
let camDistance = Infinity;
|
|
3947
|
+
const tmpHand = new THREE.Vector3();
|
|
3948
|
+
const thirdPose = (dt) => {
|
|
3949
|
+
const pose = thirdPersonPose(
|
|
3950
|
+
{ x: player.x, z: player.z, feet: player.feet, yaw: player.yaw, pitch: player.pitch, crouch: avatarState.crouch },
|
|
3951
|
+
third,
|
|
3952
|
+
(o, d, max) => withoutHeld(() => world.raycast(o, d, max))?.distance ?? null
|
|
3953
|
+
);
|
|
3954
|
+
camDistance = pose.free <= camDistance ? pose.free : dt > 0 ? camDistance + (pose.free - camDistance) * (1 - Math.exp(-dt * third.recover)) : camDistance;
|
|
3955
|
+
return { ...pose, position: pose.at(camDistance), free: camDistance };
|
|
3956
|
+
};
|
|
3957
|
+
const thirdActive = () => cameraMode === "third" && avatar !== null;
|
|
3958
|
+
const syncAvatarVisible = () => {
|
|
3959
|
+
if (!avatar) return;
|
|
3960
|
+
avatar.root.visible = cameraMode === "third";
|
|
3961
|
+
if (avatar.firstPerson) avatar.firstPerson.root.visible = cameraMode === "first";
|
|
3962
|
+
};
|
|
3963
|
+
const setCameraMode = (mode) => {
|
|
3964
|
+
if (mode !== "first" && mode !== "third") return;
|
|
3965
|
+
if (mode === cameraMode) return;
|
|
3966
|
+
cameraMode = mode;
|
|
3967
|
+
camDistance = Infinity;
|
|
3968
|
+
syncAvatarVisible();
|
|
3969
|
+
action("camera", t("act.camera", { mode: t(`camera.${mode}`) }), { data: { mode, avatar: avatar !== null } });
|
|
3970
|
+
fire("camera", mode);
|
|
3971
|
+
};
|
|
3972
|
+
const setAvatar = (next) => {
|
|
3973
|
+
if (avatar === next) return;
|
|
3974
|
+
avatar?.root.removeFromParent();
|
|
3975
|
+
avatar?.firstPerson?.root.removeFromParent();
|
|
3976
|
+
avatar = next;
|
|
3977
|
+
feetPrev = null;
|
|
3978
|
+
if (avatar) {
|
|
3979
|
+
scene.add(avatar.root);
|
|
3980
|
+
if (avatar.firstPerson) scene.add(avatar.firstPerson.root);
|
|
3981
|
+
syncAvatarVisible();
|
|
3982
|
+
stepAvatar(0);
|
|
3983
|
+
}
|
|
3984
|
+
ui?.hud(hudState());
|
|
3985
|
+
};
|
|
3986
|
+
const stepAvatar = (dt) => {
|
|
3987
|
+
if (!avatar) return;
|
|
3988
|
+
const x = player.x;
|
|
3989
|
+
const z = player.z;
|
|
3990
|
+
if (dt > 0 && feetPrev) {
|
|
3991
|
+
const raw = Math.hypot(x - feetPrev[0], z - feetPrev[1]) / dt;
|
|
3992
|
+
avatarState.speed = raw > MAX_STRIDE_SPEED ? 0 : avatarState.speed + (raw - avatarState.speed) * (1 - Math.exp(-dt * 15));
|
|
3993
|
+
if (avatarState.speed < 0.02) avatarState.speed = 0;
|
|
3994
|
+
}
|
|
3995
|
+
feetPrev = [x, z];
|
|
3996
|
+
avatarState.running = player.keys.has("ShiftLeft") || player.keys.has("ShiftRight");
|
|
3997
|
+
avatarState.armed = armedForced ?? tool.def.action.kind === "firearm";
|
|
3998
|
+
avatarState.pitch = player.pitch;
|
|
3999
|
+
avatar.root.position.set(x, player.feet, z);
|
|
4000
|
+
avatar.root.rotation.set(0, player.yaw + Math.PI, 0);
|
|
4001
|
+
avatar.update(dt, avatarState);
|
|
4002
|
+
avatar.root.updateMatrixWorld(true);
|
|
4003
|
+
};
|
|
4004
|
+
let handSource = null;
|
|
4005
|
+
let carriedHand = null;
|
|
4006
|
+
let debugView = null;
|
|
4007
|
+
let lastHand = { stance: "relaxed", triggerFinger: null, targets: null };
|
|
4008
|
+
const strikeClips = options.strikeClips ?? DEFAULT_STRIKE_CLIPS;
|
|
4009
|
+
const handStances = options.handStances ?? DEFAULT_HAND_STANCES;
|
|
4010
|
+
const stepFirstPerson = (dt) => {
|
|
4011
|
+
const arms = avatar?.firstPerson;
|
|
4012
|
+
if (!arms) return;
|
|
4013
|
+
const targets = handSource?.() ?? carriedHand;
|
|
4014
|
+
const state = {
|
|
4015
|
+
camera,
|
|
4016
|
+
targets,
|
|
4017
|
+
stance: handStances[toolId] ?? (targets ? "hold" : "relaxed"),
|
|
4018
|
+
speed: avatarState.speed,
|
|
4019
|
+
running: avatarState.running,
|
|
4020
|
+
crouch: avatarState.crouch,
|
|
4021
|
+
contact: (origin, direction, maxDistance) => rayWorld(origin, direction, maxDistance)?.distance ?? null,
|
|
4022
|
+
triggerFinger: targets?.fingers?.index ?? null
|
|
4023
|
+
};
|
|
4024
|
+
lastHand = { stance: state.stance, triggerFinger: state.triggerFinger, targets };
|
|
4025
|
+
arms.update(dt, state);
|
|
4026
|
+
};
|
|
4027
|
+
const applyThirdView = () => {
|
|
4028
|
+
if (!thirdActive()) return () => void 0;
|
|
4029
|
+
const home = camera.position.clone();
|
|
4030
|
+
const pose = thirdPose(0);
|
|
4031
|
+
camera.position.set(pose.position[0], pose.position[1], pose.position[2]);
|
|
4032
|
+
camera.updateMatrixWorld(true);
|
|
4033
|
+
return () => {
|
|
4034
|
+
camera.position.copy(home);
|
|
4035
|
+
camera.updateMatrixWorld(true);
|
|
4036
|
+
};
|
|
4037
|
+
};
|
|
4351
4038
|
const aim = () => {
|
|
4352
4039
|
camera.updateMatrixWorld(true);
|
|
4353
4040
|
camera.getWorldPosition(tmpV);
|
|
@@ -4358,6 +4045,21 @@ function createTestRoom(options) {
|
|
|
4358
4045
|
aimD[0] = tmpV2.x;
|
|
4359
4046
|
aimD[1] = tmpV2.y;
|
|
4360
4047
|
aimD[2] = tmpV2.z;
|
|
4048
|
+
if (thirdActive()) {
|
|
4049
|
+
const pose = thirdPose(0);
|
|
4050
|
+
const reach = 80;
|
|
4051
|
+
const hit = withoutHeld(() => world.raycast(pose.position, pose.direction, reach));
|
|
4052
|
+
const dist = hit ? hit.distance : reach;
|
|
4053
|
+
const tx = pose.position[0] + pose.direction[0] * dist - aimO[0];
|
|
4054
|
+
const ty = pose.position[1] + pose.direction[1] * dist - aimO[1];
|
|
4055
|
+
const tz = pose.position[2] + pose.direction[2] * dist - aimO[2];
|
|
4056
|
+
const len = Math.hypot(tx, ty, tz);
|
|
4057
|
+
if (len > 0.5 && (tx * pose.direction[0] + ty * pose.direction[1] + tz * pose.direction[2]) / len > 0.5) {
|
|
4058
|
+
aimD[0] = tx / len;
|
|
4059
|
+
aimD[1] = ty / len;
|
|
4060
|
+
aimD[2] = tz / len;
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4361
4063
|
};
|
|
4362
4064
|
const FAR = [0, 0, 0, 1];
|
|
4363
4065
|
const withoutHeld = (fn) => {
|
|
@@ -4511,7 +4213,7 @@ function createTestRoom(options) {
|
|
|
4511
4213
|
withoutHeld(() => {
|
|
4512
4214
|
for (const s of shots) {
|
|
4513
4215
|
energy = Math.max(energy, s.energy);
|
|
4514
|
-
const results = world.shoot(s.origin, s.direction, s.energy, s.caliber, "player");
|
|
4216
|
+
const results = world.shoot(s.origin, s.direction, s.energy, s.caliber, "player", shotExtras(s));
|
|
4515
4217
|
const r0 = results.find((r) => r.part && r.point);
|
|
4516
4218
|
let end = [s.origin[0] + s.direction[0] * 40, s.origin[1] + s.direction[1] * 40, s.origin[2] + s.direction[2] * 40];
|
|
4517
4219
|
if (r0?.point && r0.part) {
|
|
@@ -4569,7 +4271,7 @@ function createTestRoom(options) {
|
|
|
4569
4271
|
const r = world.raycast(u.origin, d, u.range);
|
|
4570
4272
|
if (!r) continue;
|
|
4571
4273
|
if (!target) target = r;
|
|
4572
|
-
world.hit(r.object, {
|
|
4274
|
+
world.hit(r.object, { ...useToHit(u, r.point, r.normal), type, direction: d, source: "player" });
|
|
4573
4275
|
if (type === "fire") igniteNear(r.point, 0.25);
|
|
4574
4276
|
else renderer.emit("sparks", r.point, r.normal, 2, 4);
|
|
4575
4277
|
}
|
|
@@ -4603,7 +4305,7 @@ function createTestRoom(options) {
|
|
|
4603
4305
|
action("melee", t("act.melee.miss", { weapon: w }), { data: { weapon: id } });
|
|
4604
4306
|
return;
|
|
4605
4307
|
}
|
|
4606
|
-
world.hitPart(r.object, r.part, {
|
|
4308
|
+
world.hitPart(r.object, r.part, { ...useToHit(u, r.point, r.normal), source: "player" });
|
|
4607
4309
|
pushPiece(r.object, u.direction, 2 * u.energy / 6, r.point);
|
|
4608
4310
|
action("melee", t("act.melee.hit", { weapon: w, energy: num(u.energy, 0), where: hitWhere(r), dist: num(r.distance, 1) }), {
|
|
4609
4311
|
objectId: r.object,
|
|
@@ -4612,6 +4314,35 @@ function createTestRoom(options) {
|
|
|
4612
4314
|
data: { weapon: id, damage: u.damage, energyJ: u.energy, distanceM: Math.round(r.distance * 100) / 100 }
|
|
4613
4315
|
});
|
|
4614
4316
|
};
|
|
4317
|
+
let strikes = [];
|
|
4318
|
+
const kickLeg = options.kickLeg ?? "alternate";
|
|
4319
|
+
let nextLeg = "right";
|
|
4320
|
+
const strikeUse = (u, id = toolId) => {
|
|
4321
|
+
const clip = strikeClips[id];
|
|
4322
|
+
const leg = id === "kick" ? kickLeg === "alternate" ? nextLeg : kickLeg : void 0;
|
|
4323
|
+
const seconds = clip === void 0 ? null : avatar?.strike?.(clip, leg ? { leg } : void 0) ?? null;
|
|
4324
|
+
if (leg && seconds !== null && kickLeg === "alternate") nextLeg = leg === "right" ? "left" : "right";
|
|
4325
|
+
if (clip === void 0 || seconds === null) {
|
|
4326
|
+
meleeUse(u, id);
|
|
4327
|
+
return;
|
|
4328
|
+
}
|
|
4329
|
+
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 } : {} } });
|
|
4330
|
+
strikes.push({
|
|
4331
|
+
key: id,
|
|
4332
|
+
remaining: seconds,
|
|
4333
|
+
land: () => {
|
|
4334
|
+
aim();
|
|
4335
|
+
meleeUse({ ...u, origin: [aimO[0], aimO[1], aimO[2]], direction: [aimD[0], aimD[1], aimD[2]] }, id);
|
|
4336
|
+
}
|
|
4337
|
+
});
|
|
4338
|
+
};
|
|
4339
|
+
const stepStrikes = (dt) => {
|
|
4340
|
+
if (strikes.length === 0) return;
|
|
4341
|
+
for (const strike of strikes) strike.remaining -= dt;
|
|
4342
|
+
const landed = strikes.filter((strike) => strike.remaining <= 0);
|
|
4343
|
+
strikes = strikes.filter((strike) => strike.remaining > 0);
|
|
4344
|
+
for (const strike of landed) strike.land();
|
|
4345
|
+
};
|
|
4615
4346
|
const toolWork = (u, dt) => {
|
|
4616
4347
|
const w = toolName();
|
|
4617
4348
|
const r = rayWorld(u.origin, u.direction, u.reach);
|
|
@@ -4630,7 +4361,7 @@ function createTestRoom(options) {
|
|
|
4630
4361
|
const p = world.disassemble(r.object, j.id, tool_, dt);
|
|
4631
4362
|
const kind = dStrings.jointKinds[j.kind] ?? j.kind;
|
|
4632
4363
|
if (p < 0) {
|
|
4633
|
-
const need =
|
|
4364
|
+
const need = JOINT_DEFAULTS[j.kind].tools.map((k) => dStrings.toolKinds[k] ?? k).join(", ") || t("tool.none");
|
|
4634
4365
|
progress = { label: t("hud.refused", { kind }), value: 0, at: simTime };
|
|
4635
4366
|
log.coalesce(
|
|
4636
4367
|
`refuse:${r.object}:${j.id}:${toolId}`,
|
|
@@ -4640,7 +4371,7 @@ function createTestRoom(options) {
|
|
|
4640
4371
|
text: t("act.tool.refused", { weapon: w, kind, obj: nameOf(r.object), need }),
|
|
4641
4372
|
objectId: r.object,
|
|
4642
4373
|
itemId: entryOf(r.object)?.so.itemId,
|
|
4643
|
-
data: { weapon: toolId, joint: j.id, kind: j.kind, needs:
|
|
4374
|
+
data: { weapon: toolId, joint: j.id, kind: j.kind, needs: JOINT_DEFAULTS[j.kind].tools }
|
|
4644
4375
|
},
|
|
4645
4376
|
2
|
|
4646
4377
|
);
|
|
@@ -4714,12 +4445,14 @@ function createTestRoom(options) {
|
|
|
4714
4445
|
action,
|
|
4715
4446
|
emitHost,
|
|
4716
4447
|
startFire,
|
|
4717
|
-
say
|
|
4448
|
+
say,
|
|
4449
|
+
projectileModel: options.projectileModel
|
|
4718
4450
|
});
|
|
4719
4451
|
const useTool = (dt, held) => {
|
|
4720
4452
|
aim();
|
|
4721
4453
|
const cat = tool.def.category;
|
|
4722
4454
|
if (cat === "hand") return;
|
|
4455
|
+
if (strikes.some((strike) => strike.key === toolId)) return;
|
|
4723
4456
|
const res = tool.trigger([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]], dt, held, "player");
|
|
4724
4457
|
flushEvents(toolId, tool, res.recoil);
|
|
4725
4458
|
if (res.status === "empty") {
|
|
@@ -4741,7 +4474,7 @@ function createTestRoom(options) {
|
|
|
4741
4474
|
for (const u of res.uses) {
|
|
4742
4475
|
switch (u.kind) {
|
|
4743
4476
|
case "melee":
|
|
4744
|
-
|
|
4477
|
+
strikeUse(u);
|
|
4745
4478
|
break;
|
|
4746
4479
|
case "flame":
|
|
4747
4480
|
heatRays(u, "fire");
|
|
@@ -4780,11 +4513,11 @@ function createTestRoom(options) {
|
|
|
4780
4513
|
if (res) applyUses(res, 0);
|
|
4781
4514
|
};
|
|
4782
4515
|
const kick = () => {
|
|
4783
|
-
if (!arsenal.weapons.has("kick")) return;
|
|
4516
|
+
if (!arsenal.weapons.has("kick") || strikes.some((strike) => strike.key === "kick")) return;
|
|
4784
4517
|
aim();
|
|
4785
4518
|
const res = toolState("kick").trigger([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]], 0, false, "player");
|
|
4786
4519
|
if (res.status !== "ok") return;
|
|
4787
|
-
for (const u of res.uses) if (u.kind === "melee")
|
|
4520
|
+
for (const u of res.uses) if (u.kind === "melee") strikeUse(u, "kick");
|
|
4788
4521
|
};
|
|
4789
4522
|
const setTool = (id) => {
|
|
4790
4523
|
if (!arsenal.weapons.has(id)) throw new Error(`game-test-room: unknown weapon "${id}"`);
|
|
@@ -4811,6 +4544,7 @@ function createTestRoom(options) {
|
|
|
4811
4544
|
let heldTilt = 0;
|
|
4812
4545
|
let tiltKey = 0;
|
|
4813
4546
|
const heldLocal = new THREE.Vector3();
|
|
4547
|
+
let heldRadius = 0.2;
|
|
4814
4548
|
const heldVel = [0, 0, 0];
|
|
4815
4549
|
let useItemPlayer = null;
|
|
4816
4550
|
let useItemName = "";
|
|
@@ -4860,6 +4594,7 @@ function createTestRoom(options) {
|
|
|
4860
4594
|
piece.root.updateMatrixWorld(true);
|
|
4861
4595
|
heldLocal.copy(c).sub(piece.root.position);
|
|
4862
4596
|
heldLocal.applyQuaternion(piece.root.quaternion.clone().invert());
|
|
4597
|
+
heldRadius = box2.getSize(new THREE.Vector3()).length() / 3;
|
|
4863
4598
|
heldYaw = 0;
|
|
4864
4599
|
heldTilt = 0;
|
|
4865
4600
|
heldVel[0] = heldVel[1] = heldVel[2] = 0;
|
|
@@ -4880,14 +4615,32 @@ function createTestRoom(options) {
|
|
|
4880
4615
|
fire("throw", { objectId: id, speed });
|
|
4881
4616
|
return true;
|
|
4882
4617
|
};
|
|
4618
|
+
const carriedTargets = (root) => {
|
|
4619
|
+
const box2 = new THREE.Box3().setFromObject(root);
|
|
4620
|
+
const center = box2.getCenter(new THREE.Vector3());
|
|
4621
|
+
const forward = camera.getWorldDirection(new THREE.Vector3());
|
|
4622
|
+
forward.y = 0;
|
|
4623
|
+
if (forward.lengthSq() < 1e-6) forward.set(0, 0, -1);
|
|
4624
|
+
forward.normalize();
|
|
4625
|
+
const back = new THREE.Vector3(0, -1, 0);
|
|
4626
|
+
const z = forward.clone().cross(back).normalize();
|
|
4627
|
+
const q = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(forward, back, z));
|
|
4628
|
+
return { R: { position: [center.x, box2.min.y - CARRY_PALM, center.z], quaternion: [q.x, q.y, q.z, q.w] }, grip: CARRY_GRIP };
|
|
4629
|
+
};
|
|
4883
4630
|
const followHeld = (dt) => {
|
|
4884
4631
|
const piece = heldPiece;
|
|
4632
|
+
carriedHand = null;
|
|
4885
4633
|
if (!piece) return;
|
|
4886
4634
|
camera.updateMatrixWorld(true);
|
|
4887
4635
|
const root = piece.root;
|
|
4888
4636
|
let pos;
|
|
4889
4637
|
const targetQ = tmpQ;
|
|
4890
|
-
|
|
4638
|
+
const hand = thirdActive() ? avatar?.hand?.("R") : void 0;
|
|
4639
|
+
if (hand) {
|
|
4640
|
+
pos = hand.getWorldPosition(tmpHand);
|
|
4641
|
+
targetQ.setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw + heldYaw);
|
|
4642
|
+
targetQ.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), -heldTilt));
|
|
4643
|
+
} else if (useItemPlayer) {
|
|
4891
4644
|
const pose = useItemPlayer.pose();
|
|
4892
4645
|
const handQ = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw);
|
|
4893
4646
|
pos = tmpV.set(pose.position[0], pose.position[1], pose.position[2]).applyQuaternion(handQ).add(camera.position);
|
|
@@ -4895,7 +4648,7 @@ function createTestRoom(options) {
|
|
|
4895
4648
|
targetQ.copy(handQ).multiply(eq);
|
|
4896
4649
|
targetQ.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), heldYaw));
|
|
4897
4650
|
} else {
|
|
4898
|
-
pos = tmpV.set(0.05, -0.28, -1.15).applyMatrix4(camera.matrixWorld);
|
|
4651
|
+
pos = tmpV.set(0.05, -0.28, avatar?.firstPerson ? -Math.min(1.15, CARRY_NEAR + heldRadius) : -1.15).applyMatrix4(camera.matrixWorld);
|
|
4899
4652
|
targetQ.setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw + heldYaw);
|
|
4900
4653
|
targetQ.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), -heldTilt));
|
|
4901
4654
|
}
|
|
@@ -4912,6 +4665,7 @@ function createTestRoom(options) {
|
|
|
4912
4665
|
heldVel[2] = (root.position.z - pz) / dt;
|
|
4913
4666
|
}
|
|
4914
4667
|
root.updateMatrixWorld(true);
|
|
4668
|
+
if (avatar?.firstPerson && cameraMode === "first") carriedHand = carriedTargets(root);
|
|
4915
4669
|
};
|
|
4916
4670
|
const useItem = (useId) => {
|
|
4917
4671
|
const e = heldEntry;
|
|
@@ -4941,7 +4695,7 @@ function createTestRoom(options) {
|
|
|
4941
4695
|
};
|
|
4942
4696
|
const TAP_ID = "room_tap";
|
|
4943
4697
|
const TAP_RATE = 0.25;
|
|
4944
|
-
fluids.addTap({ id: TAP_ID, position: arena.tapSpout, liquid: "water", rate: TAP_RATE, temperature: 16, on: false });
|
|
4698
|
+
if (arena.tapSpout) fluids.addTap({ id: TAP_ID, position: arena.tapSpout, liquid: "water", rate: TAP_RATE, temperature: 16, on: false });
|
|
4945
4699
|
const isTapOn = () => fluids.taps().some((x) => x.id === TAP_ID && x.on);
|
|
4946
4700
|
const toggleTap = (on = !isTapOn()) => {
|
|
4947
4701
|
fluids.setTap(TAP_ID, on);
|
|
@@ -5051,6 +4805,7 @@ function createTestRoom(options) {
|
|
|
5051
4805
|
break;
|
|
5052
4806
|
case "KeyV":
|
|
5053
4807
|
if (heldEntry?.uses[2]) useItem(heldEntry.uses[2]);
|
|
4808
|
+
else if (avatar && !e.shiftKey) setCameraMode(cameraMode === "first" ? "third" : "first");
|
|
5054
4809
|
else kick();
|
|
5055
4810
|
break;
|
|
5056
4811
|
case "KeyT":
|
|
@@ -5098,7 +4853,7 @@ function createTestRoom(options) {
|
|
|
5098
4853
|
const joints = st.joints.filter((j) => j.a === r.part || j.b === r.part).map((j) => ({
|
|
5099
4854
|
id: j.id,
|
|
5100
4855
|
kind: dStrings.jointKinds[j.kind] ?? j.kind,
|
|
5101
|
-
tools:
|
|
4856
|
+
tools: JOINT_DEFAULTS[j.kind].tools.map((k) => dStrings.toolKinds[k] ?? k),
|
|
5102
4857
|
open: j.open,
|
|
5103
4858
|
progress: j.progress
|
|
5104
4859
|
}));
|
|
@@ -5172,6 +4927,7 @@ function createTestRoom(options) {
|
|
|
5172
4927
|
if (paused) return;
|
|
5173
4928
|
simTime += dt;
|
|
5174
4929
|
stepWalk(dt);
|
|
4930
|
+
stepStrikes(dt);
|
|
5175
4931
|
const reps = fastForward ? 10 : 1;
|
|
5176
4932
|
updateAnimations(dt);
|
|
5177
4933
|
updateItemUse(dt);
|
|
@@ -5210,6 +4966,7 @@ function createTestRoom(options) {
|
|
|
5210
4966
|
const update = (dt) => {
|
|
5211
4967
|
if (!ownsLoop) roomMetrics.frame(dt);
|
|
5212
4968
|
player.update(dt);
|
|
4969
|
+
stepAvatar(paused ? 0 : dt);
|
|
5213
4970
|
if (tiltKey || heldTilt > 0) {
|
|
5214
4971
|
heldTilt = Math.max(0, Math.min(2.4, heldTilt + (tiltKey ? 2.2 : -3) * dt));
|
|
5215
4972
|
}
|
|
@@ -5221,6 +4978,7 @@ function createTestRoom(options) {
|
|
|
5221
4978
|
}
|
|
5222
4979
|
if (!paused) followHeld(dt);
|
|
5223
4980
|
stepSim(dt * timeScale);
|
|
4981
|
+
stepFirstPerson(paused ? 0 : dt);
|
|
5224
4982
|
if (progress && simTime - progress.at > 0.25) progress = null;
|
|
5225
4983
|
hudAcc += dt;
|
|
5226
4984
|
if (hudAcc >= 0.1 && ui) {
|
|
@@ -5256,6 +5014,7 @@ function createTestRoom(options) {
|
|
|
5256
5014
|
tap: isTapOn(),
|
|
5257
5015
|
blast: { shelter: blastOn("shelter"), fragmentHits: blastOn("fragmentHits"), confinement: blastOn("confinement") },
|
|
5258
5016
|
quality: qualityName,
|
|
5017
|
+
camera: avatar ? cameraMode : null,
|
|
5259
5018
|
objects: [...entries.values()].filter((e) => !e.arena).length
|
|
5260
5019
|
};
|
|
5261
5020
|
};
|
|
@@ -5457,6 +5216,8 @@ function createTestRoom(options) {
|
|
|
5457
5216
|
environment,
|
|
5458
5217
|
groundAt,
|
|
5459
5218
|
effects: vfx,
|
|
5219
|
+
loading,
|
|
5220
|
+
...options.blastShove ? { blastShove: options.blastShove } : {},
|
|
5460
5221
|
tool: () => ({
|
|
5461
5222
|
weapon: toolId,
|
|
5462
5223
|
category: tool.def.category,
|
|
@@ -5480,6 +5241,15 @@ function createTestRoom(options) {
|
|
|
5480
5241
|
flushEvents(toolId, tool);
|
|
5481
5242
|
return result;
|
|
5482
5243
|
},
|
|
5244
|
+
player: () => ({
|
|
5245
|
+
position: player.position,
|
|
5246
|
+
yaw: player.yaw,
|
|
5247
|
+
pitch: player.pitch,
|
|
5248
|
+
crouching: false,
|
|
5249
|
+
speed: player.speed,
|
|
5250
|
+
running: player.running,
|
|
5251
|
+
cameraMode: "first"
|
|
5252
|
+
}),
|
|
5483
5253
|
configureTool(settings) {
|
|
5484
5254
|
const condition = settings[SETTING_IDS.condition];
|
|
5485
5255
|
const ammoQuality = settings[SETTING_IDS.ammoQuality];
|
|
@@ -5501,8 +5271,14 @@ function createTestRoom(options) {
|
|
|
5501
5271
|
frameListeners.add(update2);
|
|
5502
5272
|
return () => frameListeners.delete(update2);
|
|
5503
5273
|
},
|
|
5504
|
-
|
|
5505
|
-
|
|
5274
|
+
setHandTargets(source) {
|
|
5275
|
+
handSource = source;
|
|
5276
|
+
return () => {
|
|
5277
|
+
if (handSource === source) handSource = null;
|
|
5278
|
+
};
|
|
5279
|
+
},
|
|
5280
|
+
log(text, data, type) {
|
|
5281
|
+
action(type ?? "module_note", text, { data });
|
|
5506
5282
|
},
|
|
5507
5283
|
addBlocker(blocked) {
|
|
5508
5284
|
blockers.add(blocked);
|
|
@@ -5513,13 +5289,17 @@ function createTestRoom(options) {
|
|
|
5513
5289
|
return () => clearListeners.delete(listener);
|
|
5514
5290
|
}
|
|
5515
5291
|
};
|
|
5292
|
+
let sendingInitial = false;
|
|
5516
5293
|
const lendBench = (store) => ({
|
|
5517
5294
|
value: store.value,
|
|
5518
5295
|
layer: store.layer,
|
|
5519
5296
|
onChange: store.onChange,
|
|
5520
5297
|
patch: store.patch,
|
|
5521
5298
|
sheet: store.setSheet,
|
|
5522
|
-
|
|
5299
|
+
// 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)
|
|
5300
|
+
look: (eye, target) => {
|
|
5301
|
+
if (!sendingInitial) room.look(eye, target);
|
|
5302
|
+
}
|
|
5523
5303
|
});
|
|
5524
5304
|
const checkModuleBench = (mod, fail) => {
|
|
5525
5305
|
const store = createBenchStore(mod, locale, (error) => reportOnce(`${mod.id}:listener`, t("mod.listener_failed", { name: mod.name, error: failureText(error) })));
|
|
@@ -5531,6 +5311,29 @@ function createTestRoom(options) {
|
|
|
5531
5311
|
for (const p of r.problems) fail(`bench: ${p}`);
|
|
5532
5312
|
return r.ok ? store : null;
|
|
5533
5313
|
};
|
|
5314
|
+
const LIGHTS_CHECK_MS = 250;
|
|
5315
|
+
const warmUp = () => {
|
|
5316
|
+
const probes = [renderer.warmUpProbes(), vfx.warmUpProbes(), fluidView.warmUpProbes()];
|
|
5317
|
+
const groups = probes.map((probe) => probe.group);
|
|
5318
|
+
scene.add(...groups);
|
|
5319
|
+
gl.render(scene, camera);
|
|
5320
|
+
scene.remove(...groups);
|
|
5321
|
+
for (const probe of probes) probe.dispose();
|
|
5322
|
+
};
|
|
5323
|
+
const warmUpEffects = () => {
|
|
5324
|
+
const ids = vfx.catalog?.() ?? [];
|
|
5325
|
+
const flames = ids.filter((id) => /fire|flame/i.test(id)).slice(0, 1);
|
|
5326
|
+
gl.compile(scene, camera);
|
|
5327
|
+
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] }));
|
|
5328
|
+
for (let i = 0; i < 3; i++) {
|
|
5329
|
+
vfx.update(0.02);
|
|
5330
|
+
gl.render(scene, camera);
|
|
5331
|
+
}
|
|
5332
|
+
for (const h of handles) if (h !== null) vfx.stop(h);
|
|
5333
|
+
vfx.world.clear();
|
|
5334
|
+
vfx.update(0);
|
|
5335
|
+
gl.render(scene, camera);
|
|
5336
|
+
};
|
|
5534
5337
|
const loadModule = (mod) => {
|
|
5535
5338
|
const report = { module: mod.id, items: [], scenarios: [], ok: true, problems: [] };
|
|
5536
5339
|
const fail = (problem) => {
|
|
@@ -5546,6 +5349,7 @@ function createTestRoom(options) {
|
|
|
5546
5349
|
build: (T) => ti.build(T),
|
|
5547
5350
|
spec: ti.spec,
|
|
5548
5351
|
movable: ti.movable,
|
|
5352
|
+
slow: ti.slow,
|
|
5549
5353
|
fluid: ti.fluid,
|
|
5550
5354
|
weapon: ti.weapon,
|
|
5551
5355
|
impl: ti.impl,
|
|
@@ -5560,8 +5364,9 @@ function createTestRoom(options) {
|
|
|
5560
5364
|
report.items.push(item.id);
|
|
5561
5365
|
let partIds = [];
|
|
5562
5366
|
const base = { module: mod.id, item: item.id };
|
|
5367
|
+
let checked;
|
|
5563
5368
|
try {
|
|
5564
|
-
const root = item
|
|
5369
|
+
const root = checked = buildRoot(item);
|
|
5565
5370
|
root.updateMatrixWorld(true);
|
|
5566
5371
|
if (item.impl) {
|
|
5567
5372
|
const probe = item.impl(root, `${item.id}_check`);
|
|
@@ -5583,6 +5388,7 @@ function createTestRoom(options) {
|
|
|
5583
5388
|
moduleCheck(false, t("mod.destructible_fail", { name: item.name, problems: msg }), { ...base, check: "destructible", problems: [msg] }, item.id);
|
|
5584
5389
|
fail(`${item.id}: ${msg}`);
|
|
5585
5390
|
}
|
|
5391
|
+
if (checked) letGo(checked);
|
|
5586
5392
|
for (const clip of item.animations ?? []) {
|
|
5587
5393
|
const r = checkAnimation(clip, partIds);
|
|
5588
5394
|
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 +5424,17 @@ function createTestRoom(options) {
|
|
|
5618
5424
|
if (!activeBench || !benches.has(activeBench)) activeBench = mod.id;
|
|
5619
5425
|
} else if (activeBench === mod.id) activeBench = lastBench();
|
|
5620
5426
|
if (mod.attach) detachers.set(mod.id, mod.attach(bench ? { ...host, bench: lendBench(bench) } : host));
|
|
5621
|
-
|
|
5427
|
+
sendingInitial = true;
|
|
5428
|
+
try {
|
|
5429
|
+
bench?.sendInitial();
|
|
5430
|
+
} finally {
|
|
5431
|
+
sendingInitial = false;
|
|
5432
|
+
}
|
|
5622
5433
|
showBench();
|
|
5623
5434
|
ui?.refresh();
|
|
5624
5435
|
return report;
|
|
5625
5436
|
};
|
|
5626
|
-
const allScenarios = () => [...
|
|
5437
|
+
const allScenarios = () => [...extraScenarios];
|
|
5627
5438
|
let scenarioRunning = false;
|
|
5628
5439
|
const step = (seconds) => {
|
|
5629
5440
|
const dt = seconds > 6 ? 1 / 30 : FIXED_DT;
|
|
@@ -5676,8 +5487,13 @@ function createTestRoom(options) {
|
|
|
5676
5487
|
return report;
|
|
5677
5488
|
};
|
|
5678
5489
|
const layArea = (layout) => {
|
|
5679
|
-
const
|
|
5680
|
-
|
|
5490
|
+
const known = layout.placements.filter((p) => {
|
|
5491
|
+
if (catalogMap.has(p.item)) return true;
|
|
5492
|
+
action("area_skip", t("act.area_skip", { item: p.item }), { itemId: p.item });
|
|
5493
|
+
return false;
|
|
5494
|
+
});
|
|
5495
|
+
const holders = known.filter((p) => p.surface);
|
|
5496
|
+
const rest = known.filter((p) => !p.surface);
|
|
5681
5497
|
const placed = [...holders, ...rest].map((p) => {
|
|
5682
5498
|
const so = spawn(p.item, p.position, p.rotationY ?? 0);
|
|
5683
5499
|
if (p.surface) {
|
|
@@ -5693,6 +5509,10 @@ function createTestRoom(options) {
|
|
|
5693
5509
|
if (layout.view) room.look(layout.view.eye, layout.view.target);
|
|
5694
5510
|
return placed.map(({ so }) => so);
|
|
5695
5511
|
};
|
|
5512
|
+
const areaLabel = (area) => {
|
|
5513
|
+
const mod = [...modules.values()].find((m) => m.id === area || m.group === area);
|
|
5514
|
+
return mod?.group ?? S.groups[area] ?? area;
|
|
5515
|
+
};
|
|
5696
5516
|
const openArea = (area) => {
|
|
5697
5517
|
clear();
|
|
5698
5518
|
const mod = [...modules.values()].find((m) => m.id === area || m.group === area);
|
|
@@ -5805,14 +5625,56 @@ function createTestRoom(options) {
|
|
|
5805
5625
|
},
|
|
5806
5626
|
equip,
|
|
5807
5627
|
openArea,
|
|
5628
|
+
openAreaAsync: (area) => loading.busy(t("loading.open_area", { area: areaLabel(area) }), () => openArea(area)),
|
|
5629
|
+
loading,
|
|
5630
|
+
busy: (label, work, busyOptions) => loading.busy(label, work, busyOptions),
|
|
5808
5631
|
loadModule,
|
|
5632
|
+
warmUp: () => {
|
|
5633
|
+
warmUp();
|
|
5634
|
+
warmUpEffects();
|
|
5635
|
+
},
|
|
5809
5636
|
animations: animationsOf,
|
|
5810
5637
|
playAnimation,
|
|
5811
5638
|
bench(moduleId) {
|
|
5812
5639
|
const id = moduleId ?? activeBench;
|
|
5813
5640
|
return (id ? benches.get(id) : void 0) ?? null;
|
|
5814
5641
|
},
|
|
5815
|
-
applyCamera: () =>
|
|
5642
|
+
applyCamera: () => {
|
|
5643
|
+
const back = applyThirdView();
|
|
5644
|
+
const shake = vfx.applyCamera();
|
|
5645
|
+
return () => {
|
|
5646
|
+
shake();
|
|
5647
|
+
back();
|
|
5648
|
+
};
|
|
5649
|
+
},
|
|
5650
|
+
setAvatar,
|
|
5651
|
+
get avatar() {
|
|
5652
|
+
return avatar;
|
|
5653
|
+
},
|
|
5654
|
+
setAvatarState(state) {
|
|
5655
|
+
if (state.crouch !== void 0) avatarState.crouch = state.crouch;
|
|
5656
|
+
if (state.down !== void 0) avatarState.down = state.down;
|
|
5657
|
+
if (state.armed !== void 0) armedForced = state.armed;
|
|
5658
|
+
},
|
|
5659
|
+
avatarState: () => ({ ...avatarState }),
|
|
5660
|
+
handState: () => ({ ...lastHand }),
|
|
5661
|
+
setDebugCamera(view) {
|
|
5662
|
+
if (!view) {
|
|
5663
|
+
debugView = null;
|
|
5664
|
+
return;
|
|
5665
|
+
}
|
|
5666
|
+
debugView ?? (debugView = new THREE.PerspectiveCamera(60, camera.aspect, 0.01, 200));
|
|
5667
|
+
debugView.fov = view.fov ?? 40;
|
|
5668
|
+
debugView.aspect = camera.aspect;
|
|
5669
|
+
debugView.position.set(...view.position);
|
|
5670
|
+
debugView.lookAt(...view.target);
|
|
5671
|
+
debugView.updateProjectionMatrix();
|
|
5672
|
+
debugView.updateMatrixWorld(true);
|
|
5673
|
+
},
|
|
5674
|
+
setCameraMode,
|
|
5675
|
+
get cameraMode() {
|
|
5676
|
+
return cameraMode;
|
|
5677
|
+
},
|
|
5816
5678
|
metrics() {
|
|
5817
5679
|
return { room: roomMetrics.sample(), module: moduleMetrics() };
|
|
5818
5680
|
},
|
|
@@ -5827,6 +5689,7 @@ function createTestRoom(options) {
|
|
|
5827
5689
|
window.removeEventListener("resize", onResize);
|
|
5828
5690
|
player.dispose();
|
|
5829
5691
|
ui?.dispose();
|
|
5692
|
+
loading.dispose();
|
|
5830
5693
|
renderer.dispose();
|
|
5831
5694
|
vfx.dispose();
|
|
5832
5695
|
craterPatch?.dispose();
|
|
@@ -5841,6 +5704,17 @@ function createTestRoom(options) {
|
|
|
5841
5704
|
}
|
|
5842
5705
|
}
|
|
5843
5706
|
};
|
|
5707
|
+
const slowly = (moduleId, controlId, run) => {
|
|
5708
|
+
const control = benches.get(moduleId)?.controls().find((c) => c.id === controlId);
|
|
5709
|
+
const slow = control?.slow;
|
|
5710
|
+
if (!slow) {
|
|
5711
|
+
run();
|
|
5712
|
+
return;
|
|
5713
|
+
}
|
|
5714
|
+
void loading.busy(typeof slow === "string" ? slow : control.name, run).catch((error) => {
|
|
5715
|
+
action("bench_failed", t("act.bench_failed", { name: control.name, error: error instanceof Error ? error.message : String(error) }), { data: { module: moduleId, control: controlId } });
|
|
5716
|
+
});
|
|
5717
|
+
};
|
|
5844
5718
|
let ui = null;
|
|
5845
5719
|
if (options.ui !== false) {
|
|
5846
5720
|
const groups = () => {
|
|
@@ -5860,7 +5734,9 @@ function createTestRoom(options) {
|
|
|
5860
5734
|
groups,
|
|
5861
5735
|
loadout: () => loadout.map((id) => ({ id, name: arsenal.name(id) })),
|
|
5862
5736
|
onSpawn: (id) => {
|
|
5863
|
-
|
|
5737
|
+
const item = catalogMap.get(id);
|
|
5738
|
+
if (item?.slow) void loading.busy(typeof item.slow === "string" ? item.slow : t("loading.spawn", { name: item.name }), () => spawn(id));
|
|
5739
|
+
else spawn(id);
|
|
5864
5740
|
},
|
|
5865
5741
|
onEquip: (itemId) => {
|
|
5866
5742
|
const w = catalogMap.get(itemId)?.weapon;
|
|
@@ -5900,13 +5776,13 @@ function createTestRoom(options) {
|
|
|
5900
5776
|
},
|
|
5901
5777
|
onResize: resize,
|
|
5902
5778
|
onControl: (id, value) => {
|
|
5903
|
-
if (activeBench) benches.get(activeBench)?.set(id, value);
|
|
5779
|
+
if (activeBench) slowly(activeBench, id, () => benches.get(activeBench)?.set(id, value));
|
|
5904
5780
|
},
|
|
5905
5781
|
onLayer: (id, on) => {
|
|
5906
5782
|
if (activeBench) benches.get(activeBench)?.setLayer(id, on);
|
|
5907
5783
|
},
|
|
5908
5784
|
onAction: (id) => {
|
|
5909
|
-
if (activeBench) benches.get(activeBench)?.act(id);
|
|
5785
|
+
if (activeBench) slowly(activeBench, id, () => benches.get(activeBench)?.act(id));
|
|
5910
5786
|
}
|
|
5911
5787
|
});
|
|
5912
5788
|
ui.hud(hudState());
|
|
@@ -5916,15 +5792,32 @@ function createTestRoom(options) {
|
|
|
5916
5792
|
if (ownRenderer && typeof requestAnimationFrame === "function") {
|
|
5917
5793
|
let last = performance.now();
|
|
5918
5794
|
ownsLoop = true;
|
|
5795
|
+
let nextLightsCheck = 0;
|
|
5796
|
+
let warmedLights = "";
|
|
5797
|
+
const lightsKey = () => {
|
|
5798
|
+
let key = "";
|
|
5799
|
+
scene.traverseVisible((object) => {
|
|
5800
|
+
if (object.isLight) key += `${object.type[0]}${object.castShadow ? "s" : ""}`;
|
|
5801
|
+
});
|
|
5802
|
+
return key;
|
|
5803
|
+
};
|
|
5919
5804
|
const loop = (now) => {
|
|
5920
5805
|
raf = requestAnimationFrame(loop);
|
|
5806
|
+
if (now >= nextLightsCheck) {
|
|
5807
|
+
nextLightsCheck = now + LIGHTS_CHECK_MS;
|
|
5808
|
+
const key = lightsKey();
|
|
5809
|
+
if (key !== warmedLights) {
|
|
5810
|
+
warmedLights = key;
|
|
5811
|
+
warmUp();
|
|
5812
|
+
}
|
|
5813
|
+
}
|
|
5921
5814
|
const realDelta = (now - last) / 1e3;
|
|
5922
5815
|
roomMetrics.frame(realDelta);
|
|
5923
5816
|
const dt = Math.min(0.05, realDelta);
|
|
5924
5817
|
last = now;
|
|
5925
5818
|
if (!scenarioRunning) update(dt);
|
|
5926
|
-
const restoreCamera =
|
|
5927
|
-
gl.render(scene, camera);
|
|
5819
|
+
const restoreCamera = room.applyCamera();
|
|
5820
|
+
gl.render(scene, debugView ?? camera);
|
|
5928
5821
|
restoreCamera();
|
|
5929
5822
|
roomMetrics.rendered();
|
|
5930
5823
|
};
|
|
@@ -5933,6 +5826,7 @@ function createTestRoom(options) {
|
|
|
5933
5826
|
if (options.initial) {
|
|
5934
5827
|
for (const [i, id] of options.initial.entries()) spawn(id, [(i - (options.initial.length - 1) / 2) * 1.3, 0, 0.5]);
|
|
5935
5828
|
}
|
|
5829
|
+
if (options.avatar) setAvatar(options.avatar);
|
|
5936
5830
|
if (options.area) openArea(options.area);
|
|
5937
5831
|
return room;
|
|
5938
5832
|
}
|
|
@@ -5940,20 +5834,21 @@ function structuredCloneSpec(spec) {
|
|
|
5940
5834
|
return JSON.parse(JSON.stringify(spec));
|
|
5941
5835
|
}
|
|
5942
5836
|
export {
|
|
5943
|
-
|
|
5944
|
-
|
|
5837
|
+
DEFAULT_HAND_STANCES,
|
|
5838
|
+
DEFAULT_STRIKE_CLIPS,
|
|
5839
|
+
LOADING_CLASS,
|
|
5840
|
+
LOADING_CSS,
|
|
5945
5841
|
MATERIAL_COLORS,
|
|
5946
|
-
SCENARIOS,
|
|
5947
5842
|
brickWallSpec,
|
|
5948
5843
|
buildFromSpec,
|
|
5844
|
+
createLoading,
|
|
5949
5845
|
createLog,
|
|
5950
5846
|
createTestRoom,
|
|
5951
5847
|
decodeBenchState,
|
|
5952
|
-
demoItems,
|
|
5953
5848
|
encodeBenchState,
|
|
5849
|
+
loadingMarkup,
|
|
5954
5850
|
locales,
|
|
5955
|
-
|
|
5956
|
-
resolveStrings
|
|
5957
|
-
scenariosFor
|
|
5851
|
+
nextPaint,
|
|
5852
|
+
resolveStrings
|
|
5958
5853
|
};
|
|
5959
5854
|
//# sourceMappingURL=index.js.map
|