@adatechnology/game-test-room 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +12 -7
- package/dist/index.global.js +3245 -524
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +394 -22
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// src/room.ts
|
|
2
2
|
import { createDestruction, JOINT_DEFAULTS, resolveStrings as resolveDestructionStrings } from "@adatechnology/game-destruction";
|
|
3
|
-
import { createAudioEngine, createAudioSink } from "@adatechnology/game-audio";
|
|
4
|
-
import {
|
|
3
|
+
import { createAudioEngine, createAudioSink, loadKitSamples } from "@adatechnology/game-audio";
|
|
4
|
+
import { createDestructionRenderer, createDestructionSound, dragSoundKey, impactKey, impactSoundKeys, specFromObject3D } from "@adatechnology/game-destruction-three";
|
|
5
|
+
import { bodyMass, KNOCK_CHEST, knockOf, surfaceOfMaterial } from "@adatechnology/game-contracts";
|
|
5
6
|
|
|
6
7
|
// src/body-sounds.ts
|
|
7
8
|
import { BODY_FOLEY_BANK } from "@adatechnology/game-audio";
|
|
@@ -31,13 +32,14 @@ function blowOfTool(id, category, table = DEFAULT_TOOL_BLOWS) {
|
|
|
31
32
|
}
|
|
32
33
|
var levelOf = (energyJ) => Math.max(0.5, Math.min(1, 0.5 + energyJ / 800));
|
|
33
34
|
var STRIDE_METRES = { walk: 0.72, run: 1.05 };
|
|
35
|
+
var SLAM_HURT_SPEED = 3.5;
|
|
34
36
|
function createBodySounds(audio) {
|
|
35
37
|
audio?.register(BODY_FOLEY_BANK);
|
|
36
38
|
const played = [];
|
|
37
39
|
const play = (id, position, volume, delay = 0) => {
|
|
38
40
|
played.push(id);
|
|
39
41
|
if (played.length > 64) played.shift();
|
|
40
|
-
audio?.play(id, { position, volume, ...delay > 0 ? { delay } : {} });
|
|
42
|
+
audio?.play(id, { position, volume, urgent: true, ...delay > 0 ? { delay } : {} });
|
|
41
43
|
};
|
|
42
44
|
return {
|
|
43
45
|
swing(blow, position, delay = 0) {
|
|
@@ -49,6 +51,10 @@ function createBodySounds(audio) {
|
|
|
49
51
|
},
|
|
50
52
|
jump: (position) => play("jump", position, 0.6),
|
|
51
53
|
land: (surface, speed, position) => play(landSoundId(surface, speed), position, Math.min(1, 0.45 + speed / 8)),
|
|
54
|
+
slam(speed, position) {
|
|
55
|
+
play("body_fall", position, Math.min(1, 0.4 + speed / 8));
|
|
56
|
+
if (speed >= SLAM_HURT_SPEED) play("player_hurt", position, Math.min(1, 0.5 + speed / 12));
|
|
57
|
+
},
|
|
52
58
|
played
|
|
53
59
|
};
|
|
54
60
|
}
|
|
@@ -73,7 +79,6 @@ function createStrideCounter(stride = STRIDE_METRES) {
|
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
// src/room.ts
|
|
76
|
-
import { createDestructionRenderer, createDestructionSound, dragSoundKey, impactKey, specFromObject3D } from "@adatechnology/game-destruction-three";
|
|
77
82
|
import { createArsenal, DEFAULT_LOADOUT } from "@adatechnology/game-weapons";
|
|
78
83
|
import { createFilmLcg, createFluids, createItemUse, ITEM_USES, resolveStrings as resolveFluidStrings } from "@adatechnology/game-fluids";
|
|
79
84
|
import { createFilmBridge, createFilmManager, createFluidRenderer } from "@adatechnology/game-fluids-three";
|
|
@@ -316,6 +321,38 @@ function createArena(THREE, size, scene, scenery, tapOption) {
|
|
|
316
321
|
};
|
|
317
322
|
}
|
|
318
323
|
|
|
324
|
+
// src/hand-sounds.ts
|
|
325
|
+
import { LEAF_FOLEY_SETS } from "@adatechnology/game-audio";
|
|
326
|
+
function pickupClassOf(def) {
|
|
327
|
+
if (!def) return "plastic";
|
|
328
|
+
const tags = def.tags ?? [];
|
|
329
|
+
if (def.id === "paper") return "paper";
|
|
330
|
+
if (tags.includes("metal")) return "metal";
|
|
331
|
+
if (tags.includes("glass") || tags.includes("ceramic")) return "glass";
|
|
332
|
+
if (tags.includes("plastic") || tags.includes("rubber")) return "plastic";
|
|
333
|
+
if (tags.includes("wood")) return "wood";
|
|
334
|
+
if (tags.includes("fabric")) return "fabric";
|
|
335
|
+
if (tags.includes("plant")) return "food";
|
|
336
|
+
return "plastic";
|
|
337
|
+
}
|
|
338
|
+
var pickupSoundOf = (sounds, def) => `item_pickup_${sounds?.handling ?? pickupClassOf(def)}`;
|
|
339
|
+
function leafFoleyOf(sounds, part, impactKey2, slides2) {
|
|
340
|
+
const named = sounds?.leaves?.[part] ?? sounds?.leaves?.["*"];
|
|
341
|
+
const set = named ? LEAF_FOLEY_SETS[named] : void 0;
|
|
342
|
+
if (set) return set;
|
|
343
|
+
if (slides2) return LEAF_FOLEY_SETS.drawer;
|
|
344
|
+
return /metal|glass/.test(impactKey2) ? LEAF_FOLEY_SETS.metal_door : LEAF_FOLEY_SETS.wood_door;
|
|
345
|
+
}
|
|
346
|
+
var openSoundOf = (sounds, def) => sounds?.open ?? (pickupClassOf(def) === "metal" ? "can_open" : "bottle_open");
|
|
347
|
+
var USE_SOUNDS = {
|
|
348
|
+
drink: "drink_gulp",
|
|
349
|
+
sip: "drink_gulp",
|
|
350
|
+
chug: "drink_gulp",
|
|
351
|
+
eat: "eat_chew"
|
|
352
|
+
};
|
|
353
|
+
var USE_SOUND_EVERY = 0.55;
|
|
354
|
+
var useSoundOf = (sounds, useId) => sounds?.uses?.[useId] ?? USE_SOUNDS[useId];
|
|
355
|
+
|
|
319
356
|
// src/bench.ts
|
|
320
357
|
var snap = (control, value) => {
|
|
321
358
|
const clamped = Math.min(control.max, Math.max(control.min, value));
|
|
@@ -765,7 +802,7 @@ function createExplosives(context) {
|
|
|
765
802
|
if (!detonation.cone) told(results);
|
|
766
803
|
if (detonation.energy > 0) {
|
|
767
804
|
const isPlayed = context.effects.explosion(detonation.explosive, detonation.point, { direction: detonation.direction }) !== null;
|
|
768
|
-
renderer.explode(detonation.point, detonation.energy, reach, { visual: !isPlayed, report: results });
|
|
805
|
+
renderer.explode(detonation.point, detonation.energy, reach, { visual: !isPlayed, report: results, effect: detonation.explosive });
|
|
769
806
|
}
|
|
770
807
|
if (detonation.stunSeconds !== void 0) renderer.emit("flash", detonation.point, [0, 1, 0], 1, 1);
|
|
771
808
|
const onPlayer = blastEffect(detonation, toArray(camera.position));
|
|
@@ -2088,6 +2125,8 @@ function createNarrator(c) {
|
|
|
2088
2125
|
}
|
|
2089
2126
|
|
|
2090
2127
|
// src/player.ts
|
|
2128
|
+
import { stepKnock } from "@adatechnology/game-contracts";
|
|
2129
|
+
var SLAM_MIN_SPEED = 1.5;
|
|
2091
2130
|
var LANDING_MIN_SPEED = 1;
|
|
2092
2131
|
var EYE = 1.7;
|
|
2093
2132
|
var PLAYER_RADIUS = 0.3;
|
|
@@ -2105,6 +2144,9 @@ var GRAVITY = 13;
|
|
|
2105
2144
|
var FLOOR_CLIMB_SPEED = 3;
|
|
2106
2145
|
var FLOOR_FALL_SPEED = 6;
|
|
2107
2146
|
var LOOK = 22e-4;
|
|
2147
|
+
var LYING_EYE = 0.3;
|
|
2148
|
+
var LYING_PITCH = 1.05;
|
|
2149
|
+
var LYING_ROLL = 0.35;
|
|
2108
2150
|
function createPlayer(THREE, o) {
|
|
2109
2151
|
const cam = o.camera;
|
|
2110
2152
|
let yaw = 0;
|
|
@@ -2142,10 +2184,15 @@ function createPlayer(THREE, o) {
|
|
|
2142
2184
|
const reach = (gap > 0 ? FLOOR_CLIMB_SPEED : FLOOR_FALL_SPEED) * dt;
|
|
2143
2185
|
floorLevel += Math.max(-reach, Math.min(reach, gap));
|
|
2144
2186
|
};
|
|
2187
|
+
let knocked = null;
|
|
2188
|
+
let rollSide = 1;
|
|
2145
2189
|
const apply = () => {
|
|
2146
|
-
|
|
2190
|
+
const lying = knocked?.lying ?? 0;
|
|
2191
|
+
const fallen = lying * lying * (3 - 2 * lying);
|
|
2192
|
+
euler.set(pitch + (LYING_PITCH - pitch) * fallen, yaw, LYING_ROLL * rollSide * fallen, "YXZ");
|
|
2147
2193
|
cam.quaternion.setFromEuler(euler);
|
|
2148
|
-
|
|
2194
|
+
const standing = eye - CROUCH_DROP * crouch - bend;
|
|
2195
|
+
cam.position.set(px.x, floorLevel + y + standing + (LYING_EYE - standing) * fallen, px.z);
|
|
2149
2196
|
cam.updateMatrixWorld(true);
|
|
2150
2197
|
};
|
|
2151
2198
|
apply();
|
|
@@ -2320,6 +2367,41 @@ function createPlayer(THREE, o) {
|
|
|
2320
2367
|
window.addEventListener("keyup", onKeyUp);
|
|
2321
2368
|
window.addEventListener("blur", onBlur);
|
|
2322
2369
|
window.addEventListener("beforeunload", onBeforeUnload);
|
|
2370
|
+
const stepKnocked = (dt) => {
|
|
2371
|
+
const k = knocked;
|
|
2372
|
+
if (!k) return;
|
|
2373
|
+
const floor = Math.max(floorHere(), o.footing?.(px.x, px.z, PLAYER_RADIUS * FOOT, y).floor ?? -Infinity);
|
|
2374
|
+
floorUnder = floor;
|
|
2375
|
+
const at = { x: px.x, y, z: px.z };
|
|
2376
|
+
const fromY = y;
|
|
2377
|
+
const fallSpeed = Math.max(0, -k.velocity[1]);
|
|
2378
|
+
const done = stepKnock(k, at, dt, () => floor, true);
|
|
2379
|
+
const wantX = at.x - px.x;
|
|
2380
|
+
const wantZ = at.z - px.z;
|
|
2381
|
+
const fromX = px.x;
|
|
2382
|
+
const fromZ = px.z;
|
|
2383
|
+
const vx = k.velocity[0];
|
|
2384
|
+
const vz = k.velocity[2];
|
|
2385
|
+
move(wantX, wantZ);
|
|
2386
|
+
const stoppedX = Math.abs(wantX) > 1e-6 && Math.abs(px.x - fromX) < Math.abs(wantX) * 0.5;
|
|
2387
|
+
const stoppedZ = Math.abs(wantZ) > 1e-6 && Math.abs(px.z - fromZ) < Math.abs(wantZ) * 0.5;
|
|
2388
|
+
if (stoppedX) k.velocity[0] = 0;
|
|
2389
|
+
if (stoppedZ) k.velocity[2] = 0;
|
|
2390
|
+
const into = Math.hypot(stoppedX ? vx : 0, stoppedZ ? vz : 0);
|
|
2391
|
+
if (into >= SLAM_MIN_SPEED) o.onSlam?.({ against: "obstacle", speed: into, direction: [stoppedX ? vx / into : 0, 0, stoppedZ ? vz / into : 0] });
|
|
2392
|
+
y = at.y;
|
|
2393
|
+
if (fromY > floor && y <= floor) {
|
|
2394
|
+
const speed = Math.max(fallSpeed, (fromY - y) / Math.max(dt, 1e-6));
|
|
2395
|
+
if (k.down && speed >= SLAM_MIN_SPEED * 0.5) o.onSlam?.({ against: "ground", speed: Math.hypot(speed, Math.hypot(vx, vz) * 0.5), direction: [0, -1, 0] });
|
|
2396
|
+
else if (speed >= LANDING_MIN_SPEED) o.onFeet?.({ type: "land", speed });
|
|
2397
|
+
}
|
|
2398
|
+
vy = 0;
|
|
2399
|
+
walked = dt > 0 ? Math.hypot(px.x - fromX, px.z - fromZ) / dt : 0;
|
|
2400
|
+
running = false;
|
|
2401
|
+
if (done) knocked = null;
|
|
2402
|
+
followFloor(dt);
|
|
2403
|
+
apply();
|
|
2404
|
+
};
|
|
2323
2405
|
return {
|
|
2324
2406
|
get walkIntent() {
|
|
2325
2407
|
return walkKeys();
|
|
@@ -2375,7 +2457,28 @@ function createPlayer(THREE, o) {
|
|
|
2375
2457
|
return isCrouched();
|
|
2376
2458
|
},
|
|
2377
2459
|
keys,
|
|
2460
|
+
knock(next) {
|
|
2461
|
+
if (seated) return;
|
|
2462
|
+
if (knocked) {
|
|
2463
|
+
knocked.velocity = [knocked.velocity[0] + next.velocity[0], knocked.velocity[1] + next.velocity[1], knocked.velocity[2] + next.velocity[2]];
|
|
2464
|
+
knocked.airborne || (knocked.airborne = next.airborne);
|
|
2465
|
+
knocked.down || (knocked.down = next.down);
|
|
2466
|
+
} else knocked = { ...next, velocity: [...next.velocity] };
|
|
2467
|
+
if (next.down) {
|
|
2468
|
+
const right = Math.cos(yaw) * next.velocity[0] - Math.sin(yaw) * next.velocity[2];
|
|
2469
|
+
rollSide = right >= 0 ? -1 : 1;
|
|
2470
|
+
crouchLatched = false;
|
|
2471
|
+
}
|
|
2472
|
+
if (knocked.airborne) vy = Math.max(vy, knocked.velocity[1]);
|
|
2473
|
+
},
|
|
2474
|
+
get knocked() {
|
|
2475
|
+
return knocked;
|
|
2476
|
+
},
|
|
2378
2477
|
update(dt) {
|
|
2478
|
+
if (knocked) {
|
|
2479
|
+
stepKnocked(dt);
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2379
2482
|
if (crouchPress) crouchPress.held += dt;
|
|
2380
2483
|
const crouched = isCrouched();
|
|
2381
2484
|
crouch += ((crouched ? 1 : 0) - crouch) * Math.min(1, dt * CROUCH_RATE);
|
|
@@ -2415,6 +2518,7 @@ function createPlayer(THREE, o) {
|
|
|
2415
2518
|
apply();
|
|
2416
2519
|
},
|
|
2417
2520
|
setPose(position, yw, pt) {
|
|
2521
|
+
knocked = null;
|
|
2418
2522
|
px.x = position[0];
|
|
2419
2523
|
px.z = position[2];
|
|
2420
2524
|
floorLevel = floorHere();
|
|
@@ -2426,6 +2530,7 @@ function createPlayer(THREE, o) {
|
|
|
2426
2530
|
apply();
|
|
2427
2531
|
},
|
|
2428
2532
|
look(position, target) {
|
|
2533
|
+
knocked = null;
|
|
2429
2534
|
px.x = position[0];
|
|
2430
2535
|
px.z = position[2];
|
|
2431
2536
|
floorLevel = floorHere();
|
|
@@ -2782,6 +2887,8 @@ var TURN_RATE = 8;
|
|
|
2782
2887
|
var MAX_WALK_SECONDS = 1.5;
|
|
2783
2888
|
var APPROACH_SPEED2 = 3;
|
|
2784
2889
|
var FLOOR_GAP = 0.03;
|
|
2890
|
+
var LEAF_SLAM_VOLUME = 0.85;
|
|
2891
|
+
var shutVolume = (massKg) => Math.min(1, 0.3 + massKg * 0.03);
|
|
2785
2892
|
var key = (objectId, part) => `${objectId}\0${part}`;
|
|
2786
2893
|
var slides = (clip, part) => {
|
|
2787
2894
|
const last = clip.keys[clip.keys.length - 1]?.parts?.[part];
|
|
@@ -3027,7 +3134,7 @@ function createRoomDoors(options) {
|
|
|
3027
3134
|
using = { objectId, leaf, obj, grip, pull, phase: control.phase, progress: 0, walked: 0, there: goal === null };
|
|
3028
3135
|
const at = obj.localToWorld(grip.clone());
|
|
3029
3136
|
if (goal === null) {
|
|
3030
|
-
options.sound("leaf_rattle", [at.x, at.y, at.z], 0.6);
|
|
3137
|
+
options.sound(options.leafFoley?.(objectId, leaf.part, slides(leaf.clip, leaf.part))?.locked ?? "leaf_rattle", [at.x, at.y, at.z], 0.6);
|
|
3031
3138
|
event("locked", using);
|
|
3032
3139
|
} else event("reach", using);
|
|
3033
3140
|
return true;
|
|
@@ -3112,17 +3219,27 @@ function createRoomDoors(options) {
|
|
|
3112
3219
|
const at = u.obj.localToWorld(tmp.copy(u.grip));
|
|
3113
3220
|
const point = [at.x, at.y, at.z];
|
|
3114
3221
|
const slide = slides(u.leaf.clip, u.leaf.part);
|
|
3115
|
-
const
|
|
3222
|
+
const foley = options.leafFoley?.(u.objectId, u.leaf.part, slide) ?? null;
|
|
3223
|
+
const own = (moment) => options.leafSound?.(u.objectId, u.leaf.part, moment);
|
|
3224
|
+
const shut = own("shut") ?? options.impactKey(u.objectId, u.leaf.part).replace("impact_", "leaf_shut_");
|
|
3116
3225
|
if (wasPhase === "reach" && s.phase !== "reach") {
|
|
3117
|
-
|
|
3118
|
-
|
|
3226
|
+
const opening = foley ? void 0 : own("open");
|
|
3227
|
+
if (foley) {
|
|
3228
|
+
if (was < 0.02) options.sound(foley.open, point, 0.5);
|
|
3229
|
+
if (foley.turn) options.sound(foley.turn, point, 0.35);
|
|
3230
|
+
} else if (slide) options.sound("leaf_slide", point, 0.5);
|
|
3231
|
+
else if (opening) {
|
|
3232
|
+
if (was < 0.02) options.sound(opening, point, 0.5);
|
|
3233
|
+
} else {
|
|
3119
3234
|
if (was < 0.02) options.sound("leaf_latch", point, 0.5);
|
|
3120
3235
|
options.sound(/metal|glass/.test(shut) ? "leaf_creak_metal" : "leaf_creak_wood", point, 0.35);
|
|
3121
3236
|
}
|
|
3122
3237
|
}
|
|
3123
3238
|
if (s.arrived) {
|
|
3124
3239
|
if (s.opening <= 0) {
|
|
3125
|
-
|
|
3240
|
+
const volume = shutVolume(massOf(u.objectId, u.leaf.part));
|
|
3241
|
+
if (foley) options.sound(foley.slam && volume >= LEAF_SLAM_VOLUME ? foley.slam : foley.close, point, volume);
|
|
3242
|
+
else options.sound(slide ? "leaf_slide" : shut, point, volume);
|
|
3126
3243
|
event("closed", u);
|
|
3127
3244
|
} else event("opened", u);
|
|
3128
3245
|
}
|
|
@@ -3393,6 +3510,10 @@ var en = {
|
|
|
3393
3510
|
"loading.open_area": "Opening {area}\u2026",
|
|
3394
3511
|
"loading.spawn": "Building {name}\u2026",
|
|
3395
3512
|
"act.loosened": "{name} was heavy but free: the blast moved it",
|
|
3513
|
+
"act.knocked_down": "The blast threw the player off their feet at {speed} m/s ({reach}% got through the cover)",
|
|
3514
|
+
"act.player_slammed": "The player was thrown into {what} at {speed} m/s",
|
|
3515
|
+
"act.player_fell": "The player hit the floor at {speed} m/s",
|
|
3516
|
+
"act.player_shoved": "The blast pushed the player at {speed} m/s ({reach}% got through the cover)",
|
|
3396
3517
|
"act.came_apart": "{name} came apart: what stayed is loose and falls",
|
|
3397
3518
|
"act.shoved": "{name} stands free: the shots moved it",
|
|
3398
3519
|
"act.impact": "{name} hit {where} at {speed} m/s: {energy} J of blunt damage",
|
|
@@ -3751,6 +3872,10 @@ var ptBR = {
|
|
|
3751
3872
|
"loading.open_area": "Abrindo {area}\u2026",
|
|
3752
3873
|
"loading.spawn": "Montando {name}\u2026",
|
|
3753
3874
|
"act.loosened": "{name} \xE9 pesado mas estava solto: a explos\xE3o o moveu",
|
|
3875
|
+
"act.knocked_down": "A explos\xE3o derrubou o jogador a {speed} m/s ({reach}% passou pela cobertura)",
|
|
3876
|
+
"act.player_slammed": "O jogador foi arremessado contra {what} a {speed} m/s",
|
|
3877
|
+
"act.player_fell": "O jogador bateu no ch\xE3o a {speed} m/s",
|
|
3878
|
+
"act.player_shoved": "A explos\xE3o empurrou o jogador a {speed} m/s ({reach}% passou pela cobertura)",
|
|
3754
3879
|
"act.came_apart": "{name} se desmontou: o que ficou est\xE1 solto e cai",
|
|
3755
3880
|
"act.shoved": "{name} estava solto no ch\xE3o: os tiros o moveram",
|
|
3756
3881
|
"act.impact": "{name} bateu em {where} a {speed} m/s: {energy} J de dano de impacto",
|
|
@@ -4576,9 +4701,16 @@ function createUi(o) {
|
|
|
4576
4701
|
nodes.delete(oldest.value);
|
|
4577
4702
|
logBody.firstElementChild?.remove();
|
|
4578
4703
|
}
|
|
4579
|
-
if (autoscroll)
|
|
4704
|
+
if (autoscroll) isScrollDue = true;
|
|
4580
4705
|
}
|
|
4706
|
+
let isScrollDue = false;
|
|
4707
|
+
const scrollToEnd = () => {
|
|
4708
|
+
if (!isScrollDue) return;
|
|
4709
|
+
isScrollDue = false;
|
|
4710
|
+
logBody.scrollTop = logBody.scrollHeight;
|
|
4711
|
+
};
|
|
4581
4712
|
const flushLog = () => {
|
|
4713
|
+
scrollToEnd();
|
|
4582
4714
|
if (pendingAdds.length === 0 && pendingUpdates.size === 0) return;
|
|
4583
4715
|
if (!isLogVisible()) {
|
|
4584
4716
|
isLogStale = true;
|
|
@@ -4971,6 +5103,134 @@ function createUi(o) {
|
|
|
4971
5103
|
return api;
|
|
4972
5104
|
}
|
|
4973
5105
|
|
|
5106
|
+
// src/module-views.ts
|
|
5107
|
+
var FRAME_PX = 3;
|
|
5108
|
+
function createModuleViews(THREE, gl) {
|
|
5109
|
+
const entries = [];
|
|
5110
|
+
let mainCamera = null;
|
|
5111
|
+
const overlay = new THREE.Scene();
|
|
5112
|
+
const ortho = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0, 2);
|
|
5113
|
+
ortho.position.z = 1;
|
|
5114
|
+
const plane = new THREE.PlaneGeometry(1, 1);
|
|
5115
|
+
const roundedMask = (rounded, aspect) => {
|
|
5116
|
+
if (!rounded || typeof document === "undefined") return null;
|
|
5117
|
+
const canvas = document.createElement("canvas");
|
|
5118
|
+
canvas.width = 256;
|
|
5119
|
+
canvas.height = Math.max(16, Math.round(256 / aspect));
|
|
5120
|
+
const g = canvas.getContext("2d");
|
|
5121
|
+
if (!g) return null;
|
|
5122
|
+
const r = Math.min(canvas.width, canvas.height) * Math.min(0.5, rounded);
|
|
5123
|
+
g.fillStyle = "#000";
|
|
5124
|
+
g.fillRect(0, 0, canvas.width, canvas.height);
|
|
5125
|
+
g.fillStyle = "#fff";
|
|
5126
|
+
g.beginPath();
|
|
5127
|
+
g.roundRect(0, 0, canvas.width, canvas.height, r);
|
|
5128
|
+
g.fill();
|
|
5129
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
5130
|
+
texture.colorSpace = THREE.NoColorSpace;
|
|
5131
|
+
return texture;
|
|
5132
|
+
};
|
|
5133
|
+
const size = () => {
|
|
5134
|
+
if (typeof gl.getSize !== "function") return { w: 960, h: 600 };
|
|
5135
|
+
const v = new THREE.Vector2();
|
|
5136
|
+
gl.getSize(v);
|
|
5137
|
+
return { w: v.x, h: v.y };
|
|
5138
|
+
};
|
|
5139
|
+
const take = (entry) => {
|
|
5140
|
+
const i = entries.indexOf(entry);
|
|
5141
|
+
if (i < 0) return;
|
|
5142
|
+
entries.splice(i, 1);
|
|
5143
|
+
overlay.remove(entry.quad, entry.frame);
|
|
5144
|
+
entry.target.dispose();
|
|
5145
|
+
entry.quad.material.dispose();
|
|
5146
|
+
entry.frame.material.dispose();
|
|
5147
|
+
entry.alpha?.dispose();
|
|
5148
|
+
};
|
|
5149
|
+
return {
|
|
5150
|
+
add(view) {
|
|
5151
|
+
const { w, h } = size();
|
|
5152
|
+
const scale = view.resolution ?? 0.5;
|
|
5153
|
+
const pr = typeof gl.getPixelRatio === "function" ? gl.getPixelRatio() : 1;
|
|
5154
|
+
const target = new THREE.WebGLRenderTarget(Math.max(16, Math.round(w * view.rect.width * pr * scale)), Math.max(16, Math.round(h * view.rect.height * pr * scale)), { samples: 2 });
|
|
5155
|
+
target.texture.colorSpace = THREE.LinearSRGBColorSpace;
|
|
5156
|
+
const alpha = roundedMask(view.rounded ?? 0, w * view.rect.width / Math.max(1, h * view.rect.height));
|
|
5157
|
+
const quad = new THREE.Mesh(plane, new THREE.MeshBasicMaterial({ map: target.texture, side: THREE.DoubleSide, depthTest: false, depthWrite: false, ...alpha ? { alphaMap: alpha, transparent: true } : {} }));
|
|
5158
|
+
const frame = new THREE.Mesh(plane, new THREE.MeshBasicMaterial({ color: 723982, side: THREE.DoubleSide, depthTest: false, depthWrite: false, ...alpha ? { alphaMap: alpha, transparent: true } : {} }));
|
|
5159
|
+
quad.renderOrder = 1;
|
|
5160
|
+
frame.visible = quad.visible = false;
|
|
5161
|
+
overlay.add(frame, quad);
|
|
5162
|
+
const entry = { view, target, quad, frame, alpha };
|
|
5163
|
+
entries.push(entry);
|
|
5164
|
+
return () => take(entry);
|
|
5165
|
+
},
|
|
5166
|
+
setMain(camera) {
|
|
5167
|
+
mainCamera = camera;
|
|
5168
|
+
},
|
|
5169
|
+
main(fallback) {
|
|
5170
|
+
if (!mainCamera) return fallback;
|
|
5171
|
+
const persp = mainCamera;
|
|
5172
|
+
const back = fallback;
|
|
5173
|
+
if (persp.isPerspectiveCamera && back.isPerspectiveCamera && persp.aspect !== back.aspect) {
|
|
5174
|
+
persp.aspect = back.aspect;
|
|
5175
|
+
persp.updateProjectionMatrix();
|
|
5176
|
+
}
|
|
5177
|
+
return mainCamera;
|
|
5178
|
+
},
|
|
5179
|
+
render(scene) {
|
|
5180
|
+
if (!entries.length) return;
|
|
5181
|
+
const { w, h } = size();
|
|
5182
|
+
const autoClear = gl.autoClear;
|
|
5183
|
+
const shadows = gl.shadowMap.autoUpdate;
|
|
5184
|
+
gl.shadowMap.autoUpdate = false;
|
|
5185
|
+
for (const entry of entries) {
|
|
5186
|
+
const cam = entry.view.camera;
|
|
5187
|
+
const aspect = w * entry.view.rect.width / Math.max(1, h * entry.view.rect.height);
|
|
5188
|
+
if (cam.isPerspectiveCamera && Math.abs(cam.aspect - aspect) > 1e-3) {
|
|
5189
|
+
cam.aspect = aspect;
|
|
5190
|
+
cam.updateProjectionMatrix();
|
|
5191
|
+
}
|
|
5192
|
+
gl.setRenderTarget(entry.target);
|
|
5193
|
+
gl.clear();
|
|
5194
|
+
gl.render(scene, cam);
|
|
5195
|
+
}
|
|
5196
|
+
gl.setRenderTarget(null);
|
|
5197
|
+
gl.autoClear = false;
|
|
5198
|
+
gl.setScissorTest(true);
|
|
5199
|
+
for (const entry of entries) {
|
|
5200
|
+
const r = entry.view.rect;
|
|
5201
|
+
const x = r.x * w;
|
|
5202
|
+
const y = (1 - r.y - r.height) * h;
|
|
5203
|
+
const vw = r.width * w;
|
|
5204
|
+
const vh = r.height * h;
|
|
5205
|
+
for (const mesh of [entry.frame, entry.quad]) mesh.visible = false;
|
|
5206
|
+
entry.frame.visible = true;
|
|
5207
|
+
gl.setViewport(x - FRAME_PX, y - FRAME_PX, vw + 2 * FRAME_PX, vh + 2 * FRAME_PX);
|
|
5208
|
+
gl.setScissor(x - FRAME_PX, y - FRAME_PX, vw + 2 * FRAME_PX, vh + 2 * FRAME_PX);
|
|
5209
|
+
gl.render(overlay, ortho);
|
|
5210
|
+
entry.frame.visible = false;
|
|
5211
|
+
entry.quad.visible = true;
|
|
5212
|
+
entry.quad.scale.x = entry.view.mirror ? -1 : 1;
|
|
5213
|
+
gl.setViewport(x, y, vw, vh);
|
|
5214
|
+
gl.setScissor(x, y, vw, vh);
|
|
5215
|
+
gl.render(overlay, ortho);
|
|
5216
|
+
entry.quad.visible = false;
|
|
5217
|
+
}
|
|
5218
|
+
gl.setScissorTest(false);
|
|
5219
|
+
gl.setViewport(0, 0, w, h);
|
|
5220
|
+
gl.autoClear = autoClear;
|
|
5221
|
+
gl.shadowMap.autoUpdate = shadows;
|
|
5222
|
+
},
|
|
5223
|
+
get count() {
|
|
5224
|
+
return entries.length;
|
|
5225
|
+
},
|
|
5226
|
+
dispose() {
|
|
5227
|
+
for (const entry of [...entries]) take(entry);
|
|
5228
|
+
plane.dispose();
|
|
5229
|
+
mainCamera = null;
|
|
5230
|
+
}
|
|
5231
|
+
};
|
|
5232
|
+
}
|
|
5233
|
+
|
|
4974
5234
|
// src/room.ts
|
|
4975
5235
|
var FIXED_DT = 1 / 60;
|
|
4976
5236
|
var EXPLOSIVE_EFFECT_IDS = ["m67", "molotov", "pipe_bomb", "m79_40mm", "m72_66mm", "at4_84mm", "dynamite", "c4", "claymore"];
|
|
@@ -4999,6 +5259,12 @@ var CAP_RISE = 0.03;
|
|
|
4999
5259
|
var MAX_STRIDE_SPEED = 10;
|
|
5000
5260
|
var v3 = (v) => [v.x, v.y, v.z];
|
|
5001
5261
|
var dist3 = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
|
|
5262
|
+
var vehicleLeafKey = (spec, part, moment) => {
|
|
5263
|
+
const tags = spec.parts.find((p) => p.id === part)?.tags ?? [];
|
|
5264
|
+
const leaf = tags.includes("hood") ? "hood" : tags.includes("lid") || tags.includes("trunk") ? "trunk" : "door";
|
|
5265
|
+
return `vehicle_${leaf}_${moment}`;
|
|
5266
|
+
};
|
|
5267
|
+
var MARK_EVENTS = /* @__PURE__ */ new Set(["dent", "hole", "crack", "chip", "stage", "fracture"]);
|
|
5002
5268
|
function createTestRoom(options) {
|
|
5003
5269
|
const THREE = options.THREE;
|
|
5004
5270
|
const locale = options.locale ?? "en";
|
|
@@ -5045,6 +5311,7 @@ function createTestRoom(options) {
|
|
|
5045
5311
|
};
|
|
5046
5312
|
const onResize = () => resize();
|
|
5047
5313
|
window.addEventListener("resize", onResize);
|
|
5314
|
+
const moduleViews = createModuleViews(THREE, gl);
|
|
5048
5315
|
const arenaSize = options.arenaSize ?? 14;
|
|
5049
5316
|
const arena = createArena(THREE, arenaSize, scene, options.scenery, options.tap);
|
|
5050
5317
|
scene.add(arena.group);
|
|
@@ -5104,10 +5371,23 @@ function createTestRoom(options) {
|
|
|
5104
5371
|
const ownAudio = options.audio === void 0;
|
|
5105
5372
|
const audio = options.audio === false ? null : options.audio ?? createAudioEngine({ deferredStart: {} });
|
|
5106
5373
|
const destructionSound = audio ? createDestructionSound(audio) : null;
|
|
5374
|
+
if (audio && options.samplesBaseUrl !== null) {
|
|
5375
|
+
void loadKitSamples(audio, { baseUrl: options.samplesBaseUrl ?? "./sfx/" }).then((bank) => {
|
|
5376
|
+
if (!bank || typeof window === "undefined") return;
|
|
5377
|
+
const preload = () => {
|
|
5378
|
+
window.removeEventListener("keydown", preload, true);
|
|
5379
|
+
window.removeEventListener("pointerdown", preload, true);
|
|
5380
|
+
void audio.loadSamples(["latch", "creak_wood", "shut_wood", "rattle", "grab_hard", "grab_soft"]).catch(() => void 0);
|
|
5381
|
+
};
|
|
5382
|
+
window.addEventListener("keydown", preload, true);
|
|
5383
|
+
window.addEventListener("pointerdown", preload, true);
|
|
5384
|
+
});
|
|
5385
|
+
}
|
|
5107
5386
|
const bodySounds = createBodySounds(audio);
|
|
5108
|
-
const playSound = (key2, point, volume) => {
|
|
5109
|
-
destructionSound?.onSound(key2, point, volume);
|
|
5110
|
-
options.onSound?.(key2, point, volume);
|
|
5387
|
+
const playSound = (key2, point, volume, detail) => {
|
|
5388
|
+
destructionSound?.onSound(key2, point, volume, detail);
|
|
5389
|
+
if (detail) options.onSound?.(key2, point, volume, detail);
|
|
5390
|
+
else options.onSound?.(key2, point, volume);
|
|
5111
5391
|
};
|
|
5112
5392
|
const lights = createLightBudget(THREE, scene, { ...ROOM_LIGHT_SIZES, ...options.lightSizes });
|
|
5113
5393
|
const warmup = createProgramWarmup(THREE);
|
|
@@ -5177,7 +5457,7 @@ function createTestRoom(options) {
|
|
|
5177
5457
|
const size = Math.min(GROUND_BURST_MAX, Math.ceil(result.radius * GROUND_BURST_PER_METRE));
|
|
5178
5458
|
renderer.emit(material.debris === "clods" ? "dust" : material.debris, result.point, [0, 1, 0], Math.max(2, size), Math.min(4, 1 + result.radius * 4));
|
|
5179
5459
|
if (result.radius > GROUND_DUST_RADIUS) renderer.emit("dust", result.point, [0, 1, 0], size, 1.4);
|
|
5180
|
-
playSound(impactKey(def), result.point, Math.min(1, 0.25 + result.radius));
|
|
5460
|
+
playSound(impactKey(def), result.point, Math.min(1, 0.25 + result.radius), impactSoundKeys(material.id, def));
|
|
5181
5461
|
};
|
|
5182
5462
|
terrain.onDeform((region) => renderer.wakeRegion(region.x, region.z, region.radius));
|
|
5183
5463
|
const groundImpl = createTerrainImpl(terrain, { id: GROUND_ID, name: t("ground.name"), onBlow: showGroundBlow });
|
|
@@ -5405,6 +5685,7 @@ function createTestRoom(options) {
|
|
|
5405
5685
|
floorAt: terrain.heightAt,
|
|
5406
5686
|
start: arena.playerStart,
|
|
5407
5687
|
crouchKeys: options.crouch?.keys,
|
|
5688
|
+
onSlam: (event) => slamPlayer(event),
|
|
5408
5689
|
onFeet: (event) => {
|
|
5409
5690
|
const at = player.position;
|
|
5410
5691
|
if (event.type === "jump") bodySounds.jump(at);
|
|
@@ -5484,7 +5765,11 @@ function createTestRoom(options) {
|
|
|
5484
5765
|
return piece;
|
|
5485
5766
|
},
|
|
5486
5767
|
dragSound: (objectId, point, volume) => playSound(dragSoundKey(world.materials.get(heaviestMaterial(objectId) ?? "")), point, volume),
|
|
5487
|
-
hitSound: (objectId, point) =>
|
|
5768
|
+
hitSound: (objectId, point) => {
|
|
5769
|
+
const material = heaviestMaterial(objectId) ?? "";
|
|
5770
|
+
const def = world.materials.get(material);
|
|
5771
|
+
playSound(impactKey(def), point, 0.5, impactSoundKeys(material, def, "blunt"));
|
|
5772
|
+
},
|
|
5488
5773
|
onEvent: (ev) => {
|
|
5489
5774
|
const vars = { name: ev.name, kg: num(ev.massKg, 0) };
|
|
5490
5775
|
action(`push_${ev.type}`, t(`act.push_${ev.type}`, vars), { objectId: ev.objectId, itemId: entryOf(ev.objectId)?.so.itemId, data: { massKg: Math.round(ev.massKg * 10) / 10 } });
|
|
@@ -5574,7 +5859,13 @@ function createTestRoom(options) {
|
|
|
5574
5859
|
isLocked: (objectId, part) => lockedLeaves.has(`${objectId}\0*`) || lockedLeaves.has(`${objectId}\0${part}`),
|
|
5575
5860
|
nameOf,
|
|
5576
5861
|
impactKey: (objectId, part) => impactKey(world.materials.get(world.part(objectId, part)?.material ?? heaviestMaterial(objectId) ?? "")),
|
|
5862
|
+
leafSound: (objectId, part, moment) => {
|
|
5863
|
+
const spec = specs.get(objectId);
|
|
5864
|
+
return spec?.tags?.includes("vehicle") ? vehicleLeafKey(spec, part, moment) : void 0;
|
|
5865
|
+
},
|
|
5577
5866
|
sound: playSound,
|
|
5867
|
+
// a vehicle's doors, hood and lid have their own sounds in the vehicle bank (game-vehicles-three), not those of the house
|
|
5868
|
+
leafFoley: (objectId, part, slides2) => specs.get(objectId)?.tags?.includes("vehicle") ? null : leafFoleyOf(entryOf(objectId)?.item?.sounds, part, impactKey(world.materials.get(world.part(objectId, part)?.material ?? heaviestMaterial(objectId) ?? "")), slides2),
|
|
5578
5869
|
onEvent: (ev) => {
|
|
5579
5870
|
const vars = { name: ev.name, percent: Math.round(ev.opening * 100) };
|
|
5580
5871
|
action(`door_${ev.type}`, t(`act.door_${ev.type}`, vars), { objectId: ev.objectId, itemId: entryOf(ev.objectId)?.so.itemId, part: ev.part, data: { opening: Math.round(ev.opening * 1e3) / 1e3, ...ev.by ? { by: ev.by } : {} } });
|
|
@@ -5983,6 +6274,37 @@ function createTestRoom(options) {
|
|
|
5983
6274
|
let avatar = null;
|
|
5984
6275
|
let cameraMode = options.cameraMode ?? "first";
|
|
5985
6276
|
let crouchForced = false;
|
|
6277
|
+
let downForced = false;
|
|
6278
|
+
const playerMass = options.playerMass ?? bodyMass(1.75);
|
|
6279
|
+
const SLAM_REACH = PLAYER_RADIUS + 0.35;
|
|
6280
|
+
const SLAM_SHOULDER = 1.3;
|
|
6281
|
+
const slamPlayer = (event) => {
|
|
6282
|
+
const [x, feet, z] = player.position;
|
|
6283
|
+
const energy = 0.5 * playerMass * event.speed * event.speed;
|
|
6284
|
+
let hit = null;
|
|
6285
|
+
if (event.against === "obstacle") {
|
|
6286
|
+
const from = [x, feet + ((player.knocked?.lying ?? 0) > 0.5 ? 0.3 : SLAM_SHOULDER), z];
|
|
6287
|
+
hit = rayWorld(from, event.direction, SLAM_REACH);
|
|
6288
|
+
if (hit) world.hitPart(hit.object, hit.part, { type: "blunt", point: hit.point, direction: event.direction, energy, radius: 0.2, ...hit.normal ? { normal: hit.normal } : {} });
|
|
6289
|
+
}
|
|
6290
|
+
bodySounds.slam(event.speed, [x, feet + 0.5, z]);
|
|
6291
|
+
action("player_slammed", t(event.against === "obstacle" ? "act.player_slammed" : "act.player_fell", { speed: num(event.speed, 1), what: hit ? nameOf(hit.object) : "" }), {
|
|
6292
|
+
...hit ? { objectId: hit.object, part: hit.part } : {},
|
|
6293
|
+
data: { against: event.against, speed: event.speed, energy, ...hit ? { object: hit.object, part: hit.part } : {} }
|
|
6294
|
+
});
|
|
6295
|
+
};
|
|
6296
|
+
const knockPlayer = (event) => {
|
|
6297
|
+
if (seating.seat) return;
|
|
6298
|
+
const feet = [player.x, player.feet, player.z];
|
|
6299
|
+
const reach = world.blastReach(event.point, [feet[0], feet[1] + KNOCK_CHEST, feet[2]], event.source ? { ignore: [event.source] } : {});
|
|
6300
|
+
const knock = knockOf({ point: event.point, energy: event.energy, radius: event.radius, reach }, feet, playerMass, options.blastShove);
|
|
6301
|
+
if (!knock) return;
|
|
6302
|
+
player.knock(knock);
|
|
6303
|
+
const speed = Math.hypot(knock.velocity[0], knock.velocity[1], knock.velocity[2]);
|
|
6304
|
+
action(knock.down ? "player_knocked_down" : "player_shoved", t(knock.down ? "act.knocked_down" : "act.player_shoved", { speed: num(speed, 1), reach: num(reach * 100, 0) }), {
|
|
6305
|
+
data: { speed, reach, down: knock.down, source: event.source }
|
|
6306
|
+
});
|
|
6307
|
+
};
|
|
5986
6308
|
const avatarState = { speed: 0, running: false, crouch: false, down: false, armed: false, pitch: 0 };
|
|
5987
6309
|
let feetPrev = null;
|
|
5988
6310
|
let armedForced = null;
|
|
@@ -6038,6 +6360,7 @@ function createTestRoom(options) {
|
|
|
6038
6360
|
feetPrev = [x, z];
|
|
6039
6361
|
avatarState.running = !player.crouching && (player.keys.has("ShiftLeft") || player.keys.has("ShiftRight"));
|
|
6040
6362
|
avatarState.crouch = crouchForced || player.crouching || push.isBent;
|
|
6363
|
+
avatarState.down = downForced || Boolean(player.knocked?.down || (player.knocked?.lying ?? 0) > 0.5);
|
|
6041
6364
|
avatarState.armed = armedForced ?? tool.def.action.kind === "firearm";
|
|
6042
6365
|
avatarState.pitch = player.pitch;
|
|
6043
6366
|
avatarState.holding = heldPiece !== null;
|
|
@@ -6110,14 +6433,18 @@ function createTestRoom(options) {
|
|
|
6110
6433
|
if (!avatar?.reach) return;
|
|
6111
6434
|
avatar.reach(targets, { camera, keepOutside: { contact: (origin, direction, maxDistance) => rayWorld(origin, direction, maxDistance)?.distance ?? null } });
|
|
6112
6435
|
};
|
|
6436
|
+
const thirdEuler = new THREE.Euler(0, 0, 0, "YXZ");
|
|
6113
6437
|
const applyThirdView = () => {
|
|
6114
6438
|
if (!thirdActive()) return () => void 0;
|
|
6115
6439
|
const home = camera.position.clone();
|
|
6440
|
+
const turn = camera.quaternion.clone();
|
|
6116
6441
|
const pose = thirdPose(0);
|
|
6117
6442
|
camera.position.set(pose.position[0], pose.position[1], pose.position[2]);
|
|
6443
|
+
if (player.knocked) camera.quaternion.setFromEuler(thirdEuler.set(player.pitch, player.yaw, 0, "YXZ"));
|
|
6118
6444
|
camera.updateMatrixWorld(true);
|
|
6119
6445
|
return () => {
|
|
6120
6446
|
camera.position.copy(home);
|
|
6447
|
+
camera.quaternion.copy(turn);
|
|
6121
6448
|
camera.updateMatrixWorld(true);
|
|
6122
6449
|
};
|
|
6123
6450
|
};
|
|
@@ -6499,7 +6826,13 @@ function createTestRoom(options) {
|
|
|
6499
6826
|
action("melee", t("act.melee.miss", { weapon: w }), { data: { weapon: id } });
|
|
6500
6827
|
return;
|
|
6501
6828
|
}
|
|
6502
|
-
|
|
6829
|
+
const hit = useToHit(u, r.point, r.normal);
|
|
6830
|
+
const result = world.hitPart(r.object, r.part, { ...hit, source: "player" });
|
|
6831
|
+
if (!result.events.some((e) => MARK_EVENTS.has(e.type))) {
|
|
6832
|
+
const material = world.part(r.object, r.part)?.material ?? heaviestMaterial(r.object) ?? "";
|
|
6833
|
+
const def = world.materials.get(material);
|
|
6834
|
+
playSound(impactKey(def), r.point, Math.min(0.8, 0.25 + u.energy / 400), impactSoundKeys(material, def, hit.type));
|
|
6835
|
+
}
|
|
6503
6836
|
const blow = blowOfTool(id, toolDef(id)?.category ?? "");
|
|
6504
6837
|
if (blow) bodySounds.hit(blow, blowTarget(r), r.point, u.energy);
|
|
6505
6838
|
pushPiece(r.object, u.direction, 2 * u.energy / 6, r.point);
|
|
@@ -6857,7 +7190,9 @@ function createTestRoom(options) {
|
|
|
6857
7190
|
heldYaw = 0;
|
|
6858
7191
|
heldTilt = 0;
|
|
6859
7192
|
heldVel[0] = heldVel[1] = heldVel[2] = 0;
|
|
7193
|
+
playSound(pickupSoundOf(heldEntry?.item?.sounds, world.materials.get(heaviestMaterial(r.object) ?? "")), r.point, Math.min(1, 0.45 + kg * 0.1));
|
|
6860
7194
|
action("grab", t("act.grab", { name: heldName, kg: num(kg, 1) }), { objectId: r.object, itemId: entryOf(r.object)?.so.itemId, data: { massKg: Math.round(kg * 10) / 10 } });
|
|
7195
|
+
playSound(impactKey(world.materials.get(heaviestMaterial(r.object) ?? "")).replace("impact_", "grab_"), v3(piece.root.getWorldPosition(tmpV)), 0.8);
|
|
6861
7196
|
fire("grab", { held: true, objectId: r.object });
|
|
6862
7197
|
return true;
|
|
6863
7198
|
};
|
|
@@ -6870,6 +7205,7 @@ function createTestRoom(options) {
|
|
|
6870
7205
|
releaseHeld();
|
|
6871
7206
|
piece.velocity = [0, 0, 0];
|
|
6872
7207
|
renderer.impulse(id, [aimD[0] * speed * piece.mass, (aimD[1] * speed + 1.2) * piece.mass, aimD[2] * speed * piece.mass]);
|
|
7208
|
+
playSound("item_throw", [piece.root.position.x, piece.root.position.y, piece.root.position.z], Math.min(1, 0.3 + speed * 0.04));
|
|
6873
7209
|
action("throw", t("act.throw", { name, speed: num(speed, 1) }), { objectId: id, itemId: entryOf(id)?.so.itemId, data: { speedMs: speed, massKg: piece.mass } });
|
|
6874
7210
|
fire("throw", { objectId: id, speed });
|
|
6875
7211
|
return true;
|
|
@@ -6981,6 +7317,7 @@ function createTestRoom(options) {
|
|
|
6981
7317
|
e.opened = true;
|
|
6982
7318
|
if (e.containerId) fluids.open(e.containerId);
|
|
6983
7319
|
seal.open?.(wrapped.get(e.so.root) ?? e.so.root);
|
|
7320
|
+
playSound(openSoundOf(e.item?.sounds, world.materials.get(heaviestMaterial(e.so.objectId) ?? "")), v3(e.so.root.getWorldPosition(tmpV)), 0.7);
|
|
6984
7321
|
const tool2 = opener.tool ? catalogMap.get(opener.tool)?.name ?? (arsenal.weapons.has(opener.tool) ? toolName(opener.tool) : opener.tool) : void 0;
|
|
6985
7322
|
action("open_seal", tool2 ? t("act.open_with", { name: e.name, tool: tool2 }) : t("act.open_by_hand", { name: e.name }), { objectId: e.so.objectId, itemId: e.so.itemId, data: { tool: opener.tool ?? "hand" } });
|
|
6986
7323
|
return true;
|
|
@@ -7029,17 +7366,28 @@ function createTestRoom(options) {
|
|
|
7029
7366
|
if (consuming && c.closed) fluids.open(e.containerId);
|
|
7030
7367
|
useItemPlayer = createItemUse(fluids, e.containerId, def);
|
|
7031
7368
|
useItemName = def.id;
|
|
7369
|
+
useSoundIn = 0;
|
|
7032
7370
|
if (eating) useLiters = c.liters;
|
|
7033
7371
|
action("use_item", t("act.use_item", { name: e.name, use: S.text[`use.${def.id}`] ?? def.id }), { objectId: e.so.objectId, itemId: e.so.itemId, data: { use: def.id } });
|
|
7034
7372
|
return true;
|
|
7035
7373
|
};
|
|
7036
7374
|
let useLiters = null;
|
|
7375
|
+
let useSoundIn = 0;
|
|
7037
7376
|
const updateItemUse = (dt) => {
|
|
7038
7377
|
if (!useItemPlayer) return;
|
|
7039
7378
|
const e = heldEntry;
|
|
7040
7379
|
const c = e?.containerId ? fluids.container(e.containerId) : void 0;
|
|
7041
7380
|
const before = c?.liters;
|
|
7042
7381
|
useItemPlayer.update(dt);
|
|
7382
|
+
const useSound = e && useLiters !== null ? useSoundOf(e.item?.sounds, useItemName) : void 0;
|
|
7383
|
+
if (useSound && e) {
|
|
7384
|
+
useSoundIn -= dt;
|
|
7385
|
+
const now = e.containerId ? fluids.container(e.containerId)?.liters : void 0;
|
|
7386
|
+
if (useSoundIn <= 0 && before !== void 0 && now !== void 0 && now < before) {
|
|
7387
|
+
playSound(useSound, v3(e.so.root.getWorldPosition(tmpV)), 0.6);
|
|
7388
|
+
useSoundIn = USE_SOUND_EVERY;
|
|
7389
|
+
}
|
|
7390
|
+
}
|
|
7043
7391
|
const seal = e?.item?.seal;
|
|
7044
7392
|
twisting = null;
|
|
7045
7393
|
if (e && seal?.openUse === useItemName && !e.opened) {
|
|
@@ -7249,7 +7597,9 @@ function createTestRoom(options) {
|
|
|
7249
7597
|
if (!loadout.includes(weaponId)) loadout.push(weaponId);
|
|
7250
7598
|
setTool(weaponId);
|
|
7251
7599
|
};
|
|
7600
|
+
let hudAimHit;
|
|
7252
7601
|
const inspectTarget = () => {
|
|
7602
|
+
if (hudAimHit !== void 0) return hudAimHit;
|
|
7253
7603
|
aim();
|
|
7254
7604
|
return rayWorld(aimO, aimD, 14);
|
|
7255
7605
|
};
|
|
@@ -7434,6 +7784,7 @@ function createTestRoom(options) {
|
|
|
7434
7784
|
}
|
|
7435
7785
|
syncLiters();
|
|
7436
7786
|
renderer.update(dt);
|
|
7787
|
+
destructionSound?.update(dt);
|
|
7437
7788
|
for (const e of entries.values()) e.follower?.update?.();
|
|
7438
7789
|
terrainView.sync(camera.position);
|
|
7439
7790
|
showGroundFire(dt);
|
|
@@ -7490,12 +7841,12 @@ function createTestRoom(options) {
|
|
|
7490
7841
|
const updatePresent = (dt) => {
|
|
7491
7842
|
reachAvatar();
|
|
7492
7843
|
if (progress && simTime - progress.at > 0.25) progress = null;
|
|
7844
|
+
ui?.flushLog();
|
|
7493
7845
|
hudAcc += dt;
|
|
7494
7846
|
if (hudAcc >= 0.1 && ui) {
|
|
7495
7847
|
hudAcc = 0;
|
|
7496
7848
|
ui.hud(hudState());
|
|
7497
7849
|
}
|
|
7498
|
-
ui?.flushLog();
|
|
7499
7850
|
metricsAcc += dt;
|
|
7500
7851
|
if (metricsAcc >= METRICS_INTERVAL_S && ui) {
|
|
7501
7852
|
metricsAcc = 0;
|
|
@@ -7503,6 +7854,14 @@ function createTestRoom(options) {
|
|
|
7503
7854
|
}
|
|
7504
7855
|
};
|
|
7505
7856
|
const hudState = () => {
|
|
7857
|
+
hudAimHit = inspectTarget();
|
|
7858
|
+
try {
|
|
7859
|
+
return readHudState();
|
|
7860
|
+
} finally {
|
|
7861
|
+
hudAimHit = void 0;
|
|
7862
|
+
}
|
|
7863
|
+
};
|
|
7864
|
+
const readHudState = () => {
|
|
7506
7865
|
const page = Math.floor(toolIndex / 10);
|
|
7507
7866
|
const slots = loadout.slice(page * 10, page * 10 + 10).map((id) => arsenal.name(id));
|
|
7508
7867
|
const ammo = tool.def.action.kind === "firearm" ? `${tool.ammo}/${tool.capacity}${tool.reserve ? ` \xB7 ${tool.reserve}` : ""}` : tool.def.action.kind === "hand" ? "" : tool.def.action.kind === "melee" || tool.def.action.kind === "tool" ? "" : `${num(tool.ammo, 0)}`;
|
|
@@ -7693,6 +8052,7 @@ function createTestRoom(options) {
|
|
|
7693
8052
|
const environment = options.environment ?? createEnvironmentBus();
|
|
7694
8053
|
const audioSink = audio ? createAudioSink(audio) : void 0;
|
|
7695
8054
|
function emitHost(event) {
|
|
8055
|
+
if (event.type === "explosion") knockPlayer(event);
|
|
7696
8056
|
for (const listener of hostListeners) listener(event);
|
|
7697
8057
|
}
|
|
7698
8058
|
function flushEvents(id, state, recoil) {
|
|
@@ -7752,7 +8112,10 @@ function createTestRoom(options) {
|
|
|
7752
8112
|
lights,
|
|
7753
8113
|
pools,
|
|
7754
8114
|
loading,
|
|
8115
|
+
addView: (view) => moduleViews.add(view),
|
|
8116
|
+
setViewCamera: (view) => moduleViews.setMain(view ?? null),
|
|
7755
8117
|
...options.blastShove ? { blastShove: options.blastShove } : {},
|
|
8118
|
+
blastReach: (point, target, ignore) => world.blastReach(point, target, ignore ? { ignore } : {}),
|
|
7756
8119
|
tool: () => ({
|
|
7757
8120
|
weapon: toolId,
|
|
7758
8121
|
category: tool.def.category,
|
|
@@ -7927,6 +8290,7 @@ function createTestRoom(options) {
|
|
|
7927
8290
|
impl: ti.impl,
|
|
7928
8291
|
animations: ti.animations,
|
|
7929
8292
|
seats: ti.seats,
|
|
8293
|
+
sounds: ti.sounds,
|
|
7930
8294
|
thumbnail: ti.thumbnail,
|
|
7931
8295
|
thumbnailCredit: ti.thumbnailCredit
|
|
7932
8296
|
};
|
|
@@ -8313,7 +8677,10 @@ function createTestRoom(options) {
|
|
|
8313
8677
|
crouchForced = state.crouch;
|
|
8314
8678
|
avatarState.crouch = state.crouch || player.crouching;
|
|
8315
8679
|
}
|
|
8316
|
-
if (state.down !== void 0)
|
|
8680
|
+
if (state.down !== void 0) {
|
|
8681
|
+
downForced = state.down;
|
|
8682
|
+
avatarState.down = state.down || Boolean(player.knocked?.down);
|
|
8683
|
+
}
|
|
8317
8684
|
if (state.armed !== void 0) armedForced = state.armed;
|
|
8318
8685
|
},
|
|
8319
8686
|
avatarState: () => ({ ...avatarState }),
|
|
@@ -8334,6 +8701,9 @@ function createTestRoom(options) {
|
|
|
8334
8701
|
get playerPosition() {
|
|
8335
8702
|
return player.position;
|
|
8336
8703
|
},
|
|
8704
|
+
get playerKnock() {
|
|
8705
|
+
return player.knocked;
|
|
8706
|
+
},
|
|
8337
8707
|
sit: (objectId, seatId) => seating.sit(objectId, seatId),
|
|
8338
8708
|
stand: () => seating.stand(),
|
|
8339
8709
|
get seat() {
|
|
@@ -8357,6 +8727,7 @@ function createTestRoom(options) {
|
|
|
8357
8727
|
if (ownAudio) audio?.dispose();
|
|
8358
8728
|
for (const detach of detachers.values()) detach();
|
|
8359
8729
|
detachers.clear();
|
|
8730
|
+
moduleViews.dispose();
|
|
8360
8731
|
for (const store of benches.values()) store.dispose();
|
|
8361
8732
|
benches.clear();
|
|
8362
8733
|
stopLoop?.();
|
|
@@ -8519,7 +8890,8 @@ function createTestRoom(options) {
|
|
|
8519
8890
|
for (const batcher of batchers.values()) batcher.update();
|
|
8520
8891
|
const restoreCamera = room.applyCamera();
|
|
8521
8892
|
graphics?.beforeRender(camera);
|
|
8522
|
-
gl.render(scene, debugView ?? camera);
|
|
8893
|
+
gl.render(scene, debugView ?? moduleViews.main(camera));
|
|
8894
|
+
moduleViews.render(scene);
|
|
8523
8895
|
graphics?.afterRender();
|
|
8524
8896
|
restoreCamera();
|
|
8525
8897
|
pacer.submitted();
|