@adatechnology/game-test-room 0.12.0 → 0.14.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.
@@ -469,6 +469,13 @@ var GameTestRoom = (() => {
469
469
  }
470
470
  };
471
471
  }
472
+ var KIT_ORIGIN_KEY = "kitOrigin";
473
+ function markKitOrigin(object, origin) {
474
+ const node = object;
475
+ node.userData = node.userData ?? {};
476
+ node.userData[KIT_ORIGIN_KEY] = { ...origin };
477
+ return object;
478
+ }
472
479
 
473
480
  // ../game-destruction/dist/index.js
474
481
  var FIRE_PRIME_C = 300;
@@ -4276,13 +4283,14 @@ var GameTestRoom = (() => {
4276
4283
  const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
4277
4284
  let exitPoint;
4278
4285
  if (penetrated) {
4279
- exitPoint = vec.madd(entry, d, Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant));
4286
+ const exitDist = p.spec.contains || p.spec.fill !== void 0 && p.spec.fill < 0.5 || p.thickness < pathLen * 0.5 ? pathLen : Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant);
4287
+ exitPoint = vec.madd(entry, d, exitDist);
4280
4288
  const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
4281
4289
  if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
4282
4290
  addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
4283
4291
  if (mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_DISHING) addDent(o, p, entry, n, Math.max(caliber * 3 / 1e3, 0.012), clamp2(E * 2e-5, 1e-3, 0.03), src);
4284
4292
  if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
4285
- if (p.spec.contains && (p.st.liters ?? 0) > 0) {
4293
+ if (p.spec.contains && (p.st.liters ?? 0) > 1e-4) {
4286
4294
  emit2({
4287
4295
  ...base(o, src),
4288
4296
  type: "leak",
@@ -15509,7 +15517,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
15509
15517
  for (const mesh2 of anchors.meshes) mesh2.material.dispose();
15510
15518
  anchors = void 0;
15511
15519
  };
15512
- const root = new THREE.Group();
15520
+ const root = markKitOrigin(new THREE.Group(), { owner: "game-vfx-three", kind: "effect" });
15513
15521
  options.scene.add(root);
15514
15522
  const direction = new THREE.Vector3(...options.lightDirection ?? LAB_LIGHT_DIRECTION).normalize();
15515
15523
  const sky = hexToLinearRgb(SKY_COLOR);
@@ -16728,7 +16736,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
16728
16736
  depthWrite: false,
16729
16737
  polygonOffset: true,
16730
16738
  polygonOffsetFactor: -4,
16731
- polygonOffsetUnits: -4
16739
+ polygonOffsetUnits: -4,
16740
+ side: this.THREE.DoubleSide
16732
16741
  }));
16733
16742
  }
16734
16743
  /** Opaque patch in the colour of what shows under a spalled coat. */
@@ -17120,6 +17129,27 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
17120
17129
  }
17121
17130
  addHole(mv, worldPoint, worldNormal, diameter) {
17122
17131
  const { s, pos, quat: quat2 } = this.decalPose(mv.mesh, worldPoint, worldNormal, 25e-4);
17132
+ const work = mv.host ? mv.host.work : mv.work;
17133
+ if (work && mv.touched) {
17134
+ const ln = worldNormal.clone().transformDirection(this.m4.copy(mv.mesh.matrixWorld).invert()).normalize();
17135
+ let minDistSq = Infinity;
17136
+ let nearestDisp = 0;
17137
+ const from = mv.range ? mv.range[0] * 3 : 0;
17138
+ const to = mv.range ? mv.range[1] * 3 : work.length;
17139
+ for (let i = from; i < to; i += 3) {
17140
+ const dx = work[i] - pos.x;
17141
+ const dy = work[i + 1] - pos.y;
17142
+ const dz = work[i + 2] - pos.z;
17143
+ const d2 = dx * dx + dy * dy + dz * dz;
17144
+ if (d2 < minDistSq) {
17145
+ minDistSq = d2;
17146
+ nearestDisp = dx * ln.x + dy * ln.y + dz * ln.z;
17147
+ }
17148
+ }
17149
+ if (minDistSq < (0.06 / s) ** 2 && nearestDisp < 0) {
17150
+ pos.addScaledVector(ln, nearestDisp);
17151
+ }
17152
+ }
17123
17153
  const disc = new this.THREE.Mesh(this.kit.discGeometry(), this.kit.discMaterial());
17124
17154
  disc.position.copy(pos);
17125
17155
  disc.quaternion.copy(quat2);
@@ -17589,7 +17619,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
17589
17619
  gas: 15921906,
17590
17620
  sap: 12098362,
17591
17621
  acid: 10211900,
17592
- blood: 9046538
17622
+ blood: 9046538,
17623
+ soda: 3808786,
17624
+ coffee: 3875860,
17625
+ beer: 14259516,
17626
+ milk: 16052714,
17627
+ wine: 7016479,
17628
+ juice: 15238923
17593
17629
  };
17594
17630
  var SMOKE_TONES = { white: 15132390, grey: 9079434, black: 2763306 };
17595
17631
  var DEFAULT_SPEED = {
@@ -18114,13 +18150,39 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
18114
18150
  }
18115
18151
  case "leak": {
18116
18152
  const key = `${ev.object}|${ev.part}`;
18153
+ const b = this.bounds.get(ev.object);
18154
+ let localPoint;
18155
+ let localDirection;
18156
+ if (b) {
18157
+ b.root.updateWorldMatrix(true, false);
18158
+ const inv = new this.THREE.Matrix4().copy(b.root.matrixWorld).invert();
18159
+ const lp = this.v1.set(ev.point[0], ev.point[1], ev.point[2]).applyMatrix4(inv);
18160
+ const ld = this.v2.set(ev.direction[0], ev.direction[1], ev.direction[2]).transformDirection(inv).normalize();
18161
+ localPoint = [lp.x, lp.y, lp.z];
18162
+ localDirection = [ld.x, ld.y, ld.z];
18163
+ }
18117
18164
  const same = this.leaks.find((l) => l.object === ev.object && l.part === ev.part && Math.hypot(l.point[0] - ev.point[0], l.point[1] - ev.point[1], l.point[2] - ev.point[2]) < 0.03);
18118
18165
  if (same) {
18119
18166
  same.rate = ev.rate;
18120
18167
  same.direction = ev.direction;
18168
+ if (localPoint) same.localPoint = localPoint;
18169
+ if (localDirection) same.localDirection = localDirection;
18121
18170
  } else {
18122
18171
  if (this.leaks.length >= 24) this.leaks.shift();
18123
- this.leaks.push({ object: ev.object, part: ev.part, fluid: ev.fluid, point: ev.point, direction: ev.direction, rate: ev.rate, pressurized: ev.pressurized, acc: 0, opened: this.world.part(ev.object, ev.part)?.holes.some((h) => h.through) ?? false });
18172
+ this.leaks.push({
18173
+ object: ev.object,
18174
+ part: ev.part,
18175
+ fluid: ev.fluid,
18176
+ point: ev.point,
18177
+ direction: ev.direction,
18178
+ rate: ev.rate,
18179
+ pressurized: ev.pressurized,
18180
+ acc: 0,
18181
+ opened: this.world.part(ev.object, ev.part)?.holes.some((h) => h.through) ?? false,
18182
+ localPoint,
18183
+ localDirection,
18184
+ life: 0
18185
+ });
18124
18186
  }
18125
18187
  void key;
18126
18188
  break;
@@ -18556,21 +18618,44 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
18556
18618
  updateLeaks(dt) {
18557
18619
  for (let i = this.leaks.length - 1; i >= 0; i--) {
18558
18620
  const l = this.leaks[i];
18621
+ const b = this.bounds.get(l.object);
18559
18622
  const ps = this.world.part(l.object, l.part);
18560
- if (!ps || ps.liters === void 0 || ps.liters <= 0 || l.opened && !ps.holes.some((h) => h.through)) {
18623
+ if (!b || !ps || ps.liters === void 0 || ps.liters <= 1e-4 || l.opened && !ps.holes.some((h) => h.through)) {
18561
18624
  this.leaks.splice(i, 1);
18562
18625
  continue;
18563
18626
  }
18627
+ l.life = (l.life ?? 0) + dt;
18628
+ if (l.pressurized && l.life > 0.8) l.pressurized = false;
18629
+ if (l.lastLiters !== void 0 && Math.abs(ps.liters - l.lastLiters) < 1e-5) {
18630
+ l.idleSeconds = (l.idleSeconds ?? 0) + dt;
18631
+ if (l.idleSeconds > 0.4) {
18632
+ this.leaks.splice(i, 1);
18633
+ continue;
18634
+ }
18635
+ } else {
18636
+ l.idleSeconds = 0;
18637
+ l.lastLiters = ps.liters;
18638
+ }
18639
+ let pt2 = l.point;
18640
+ let dir = l.direction;
18641
+ if (l.localPoint && l.localDirection) {
18642
+ b.root.updateWorldMatrix(true, false);
18643
+ const wp = this.v1.set(l.localPoint[0], l.localPoint[1], l.localPoint[2]).applyMatrix4(b.root.matrixWorld);
18644
+ const wd = this.v2.set(l.localDirection[0], l.localDirection[1], l.localDirection[2]).transformDirection(b.root.matrixWorld).normalize();
18645
+ pt2 = [wp.x, wp.y, wp.z];
18646
+ dir = [wd.x, wd.y, wd.z];
18647
+ l.point = pt2;
18648
+ l.direction = dir;
18649
+ }
18564
18650
  l.acc += clamp6(20 + l.rate * 400, 15, 120) * dt;
18565
18651
  const color = FLUID_COLORS[l.fluid] ?? 11184810;
18566
- const d = l.direction;
18567
- const dl = Math.hypot(d[0], d[1], d[2]) || 1;
18652
+ const dl = Math.hypot(dir[0], dir[1], dir[2]) || 1;
18568
18653
  const sp = l.pressurized ? 2.5 + this.rng() * 1.5 : 0.3 + this.rng() * 0.3;
18569
18654
  while (l.acc >= 1) {
18570
18655
  l.acc -= 1;
18571
- const vel = [d[0] / dl * sp + (this.rng() - 0.5) * 0.25, d[1] / dl * sp + (this.rng() - 0.5) * 0.25, d[2] / dl * sp + (this.rng() - 0.5) * 0.25];
18572
- if (l.fluid === "gas") this.vfx.spawn("steam", l.point, vel, { color, size: 0.5, alpha: 0.5 });
18573
- else this.vfx.spawn("drips", l.point, vel, { color, tag: this.rng() < 0.03 ? this.tagOf(l.object) : 0, owner: this.owner });
18656
+ const vel = [dir[0] / dl * sp + (this.rng() - 0.5) * 0.25, dir[1] / dl * sp + (this.rng() - 0.5) * 0.25, dir[2] / dl * sp + (this.rng() - 0.5) * 0.25];
18657
+ if (l.fluid === "gas") this.vfx.spawn("steam", pt2, vel, { color, size: 0.5, alpha: 0.5 });
18658
+ else this.vfx.spawn("drips", pt2, vel, { color, tag: this.rng() < 0.03 ? this.tagOf(l.object) : 0, owner: this.owner });
18574
18659
  }
18575
18660
  }
18576
18661
  }
@@ -38065,7 +38150,7 @@ void main(){
38065
38150
  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 }));
38066
38151
  }
38067
38152
  function createArena(THREE, size, scene2, scenery, tapOption) {
38068
- const group = new THREE.Group();
38153
+ const group = markKitOrigin(new THREE.Group(), { owner: "game-test-room", kind: "room" });
38069
38154
  group.name = "test-room-arena";
38070
38155
  scene2.background = new THREE.Color(10466504);
38071
38156
  group.add(new THREE.HemisphereLight(13624319, 7037527, 0.75));
@@ -39658,6 +39743,9 @@ void main(){
39658
39743
  var EYE = 1.7;
39659
39744
  var PLAYER_RADIUS = 0.3;
39660
39745
  var WALK = 3.4;
39746
+ var CROUCH_DROP = 0.55;
39747
+ var CROUCH_WALK = 1.7;
39748
+ var CROUCH_RATE = 9;
39661
39749
  var RUN = 6.2;
39662
39750
  var JUMP = 4.6;
39663
39751
  var GRAVITY4 = 13;
@@ -39668,17 +39756,20 @@ void main(){
39668
39756
  let pitch = 0;
39669
39757
  let y = 0;
39670
39758
  let eye = EYE;
39759
+ let crouch = 0;
39671
39760
  let vy2 = 0;
39672
39761
  let walked = 0;
39673
39762
  let running = false;
39674
39763
  const px = { x: o.start[0], z: o.start[2] };
39675
39764
  let locked = false;
39676
39765
  const keys = /* @__PURE__ */ new Set();
39766
+ const crouchKeys = o.crouchKeys ?? ["KeyC"];
39767
+ const isCrouchHeld = () => crouchKeys.some((code) => keys.has(code));
39677
39768
  const euler = new THREE.Euler(0, 0, 0, "YXZ");
39678
39769
  const apply = () => {
39679
39770
  euler.set(pitch, yaw, 0, "YXZ");
39680
39771
  cam.quaternion.setFromEuler(euler);
39681
- cam.position.set(px.x, eye + y, px.z);
39772
+ cam.position.set(px.x, eye + y - CROUCH_DROP * crouch, px.z);
39682
39773
  cam.updateMatrixWorld(true);
39683
39774
  };
39684
39775
  apply();
@@ -39705,6 +39796,20 @@ void main(){
39705
39796
  };
39706
39797
  const onLockChange = () => {
39707
39798
  locked = document.pointerLockElement === o.dom;
39799
+ if (locked) {
39800
+ try {
39801
+ const nav = typeof navigator !== "undefined" ? navigator : null;
39802
+ nav?.keyboard?.lock?.().catch(() => {
39803
+ });
39804
+ } catch {
39805
+ }
39806
+ } else {
39807
+ try {
39808
+ const nav = typeof navigator !== "undefined" ? navigator : null;
39809
+ nav?.keyboard?.unlock?.();
39810
+ } catch {
39811
+ }
39812
+ }
39708
39813
  };
39709
39814
  const onMouseDown = (e) => {
39710
39815
  o.dom.focus?.();
@@ -39774,6 +39879,10 @@ void main(){
39774
39879
  const onKeyDown = (e) => {
39775
39880
  if (typing(e)) return;
39776
39881
  if (e.code === "Space" || e.code === "Tab") e.preventDefault();
39882
+ const isControlHeld = e.ctrlKey || e.metaKey || keys.has("ControlLeft") || keys.has("ControlRight");
39883
+ if (isControlHeld || e.altKey && locked) {
39884
+ if (!/^F\d+$/.test(e.code)) e.preventDefault();
39885
+ }
39777
39886
  if (!e.repeat) keys.add(e.code);
39778
39887
  o.hooks.key(e.code, true, e);
39779
39888
  };
@@ -39784,6 +39893,11 @@ void main(){
39784
39893
  };
39785
39894
  const onContext = (e) => e.preventDefault();
39786
39895
  const onBlur = () => keys.clear();
39896
+ const onBeforeUnload = (e) => {
39897
+ if (!locked && !keys.has("ControlLeft") && !keys.has("ControlRight")) return;
39898
+ e.preventDefault();
39899
+ e.returnValue = "";
39900
+ };
39787
39901
  o.dom.addEventListener("mousedown", onMouseDown);
39788
39902
  window.addEventListener("mouseup", onMouseUp);
39789
39903
  window.addEventListener("mousemove", onMouseMove);
@@ -39793,6 +39907,7 @@ void main(){
39793
39907
  window.addEventListener("keydown", onKeyDown);
39794
39908
  window.addEventListener("keyup", onKeyUp);
39795
39909
  window.addEventListener("blur", onBlur);
39910
+ window.addEventListener("beforeunload", onBeforeUnload);
39796
39911
  return {
39797
39912
  get yaw() {
39798
39913
  return yaw;
@@ -39821,12 +39936,18 @@ void main(){
39821
39936
  get running() {
39822
39937
  return running;
39823
39938
  },
39939
+ get crouching() {
39940
+ return isCrouchHeld();
39941
+ },
39824
39942
  keys,
39825
39943
  update(dt) {
39826
- const run = keys.has("ShiftLeft") || keys.has("ShiftRight");
39944
+ const crouched = isCrouchHeld();
39945
+ crouch += ((crouched ? 1 : 0) - crouch) * Math.min(1, dt * CROUCH_RATE);
39946
+ if (!crouched && crouch < 1e-3) crouch = 0;
39947
+ const run = !crouched && (keys.has("ShiftLeft") || keys.has("ShiftRight"));
39827
39948
  const fromX = px.x;
39828
39949
  const fromZ = px.z;
39829
- const speed = run ? RUN : WALK;
39950
+ const speed = crouched ? CROUCH_WALK : run ? RUN : WALK;
39830
39951
  let f = 0;
39831
39952
  let r = 0;
39832
39953
  if (keys.has("KeyW") || keys.has("ArrowUp")) f += 1;
@@ -39902,13 +40023,14 @@ void main(){
39902
40023
  window.removeEventListener("keydown", onKeyDown);
39903
40024
  window.removeEventListener("keyup", onKeyUp);
39904
40025
  window.removeEventListener("blur", onBlur);
40026
+ window.removeEventListener("beforeunload", onBeforeUnload);
39905
40027
  if (document.pointerLockElement === o.dom) document.exitPointerLock?.();
39906
40028
  }
39907
40029
  };
39908
40030
  }
39909
40031
 
39910
40032
  // src/avatar.ts
39911
- var DEFAULT_STRIKE_CLIPS = { fists: "punch_right", shove: "shove_two_hands", kick: "front_kick" };
40033
+ var DEFAULT_STRIKE_CLIPS = { hand: "punch_right", fists: "punch_right", shove: "shove_two_hands", kick: "front_kick" };
39912
40034
  var DEFAULT_HAND_STANCES = { fists: "fists" };
39913
40035
  var THIRD_PERSON_DEFAULTS = {
39914
40036
  back: 3.8,
@@ -40149,7 +40271,7 @@ void main(){
40149
40271
  "hud.throw": "throw",
40150
40272
  "hud.tilt": "tilt (hold)",
40151
40273
  "hud.equip": "equip {weapon}",
40152
- "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",
40274
+ "ui.help": "Click: look \xB7 WASD: walk \xB7 Shift: run \xB7 C: crouch \xB7 Space: jump \xB7 1-0 / wheel: tool \xB7 LMB: use \xB7 E: grab/drop \xB7 G or RMB: throw \xB7 R: reload \xB7 Q: fire mode \xB7 X: clear a jam \xB7 B: safety \xB7 Y: detonate charges set down \xB7 Tab: panels \xB7 Esc: free the mouse",
40153
40275
  "ui.help.camera": "V: 1st/3rd person (Shift+V: kick)",
40154
40276
  "camera.first": "1st person",
40155
40277
  "camera.third": "3rd person",
@@ -40170,6 +40292,8 @@ void main(){
40170
40292
  "ui.slow": "Slow motion 0.25\xD7",
40171
40293
  "ui.fast": "Time \xD710",
40172
40294
  "ui.tap": "Tap",
40295
+ "ui.fullscreen": "Full screen",
40296
+ "ui.fullscreen.hint": "Full screen opened from here keeps the whole keyboard in the game, Ctrl+W included (F11 does not)",
40173
40297
  "ui.blast.shelter": "Shelter",
40174
40298
  "ui.blast.fragmentHits": "Fragments",
40175
40299
  "ui.blast.confinement": "Confinement",
@@ -40428,7 +40552,7 @@ void main(){
40428
40552
  "hud.throw": "arremessar",
40429
40553
  "hud.tilt": "inclinar (segurar)",
40430
40554
  "hud.equip": "equipar {weapon}",
40431
- "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",
40555
+ "ui.help": "Clique: olhar \xB7 WASD: andar \xB7 Shift: correr \xB7 C: agachar \xB7 Espa\xE7o: pular \xB7 1-0 / roda: ferramenta \xB7 bot\xE3o esquerdo: usar \xB7 E: pegar/soltar \xB7 G ou bot\xE3o direito: arremessar \xB7 R: recarregar \xB7 Q: modo de tiro \xB7 X: destravar \xB7 B: trava \xB7 Y: detonar cargas postas \xB7 Tab: pain\xE9is \xB7 Esc: soltar o mouse",
40432
40556
  "ui.help.camera": "V: 1\xAA/3\xAA pessoa (Shift+V: chute)",
40433
40557
  "camera.first": "1\xAA pessoa",
40434
40558
  "camera.third": "3\xAA pessoa",
@@ -40449,6 +40573,8 @@ void main(){
40449
40573
  "ui.slow": "C\xE2mera lenta 0,25\xD7",
40450
40574
  "ui.fast": "Tempo \xD710",
40451
40575
  "ui.tap": "Torneira",
40576
+ "ui.fullscreen": "Tela cheia",
40577
+ "ui.fullscreen.hint": "A tela cheia aberta por aqui segura o teclado todo no jogo, inclusive Ctrl+W (o F11 n\xE3o)",
40452
40578
  "ui.blast.shelter": "Abrigo",
40453
40579
  "ui.blast.fragmentHits": "Estilha\xE7os",
40454
40580
  "ui.blast.confinement": "Confinamento",
@@ -40876,6 +41002,19 @@ void main(){
40876
41002
  e.style.setProperty("--tr-thumb", `url("${thumbSource(thumbnail)}")`);
40877
41003
  return e;
40878
41004
  };
41005
+ async function toggleFullscreen() {
41006
+ const keyboard = navigator.keyboard;
41007
+ try {
41008
+ if (document.fullscreenElement) {
41009
+ keyboard?.unlock?.();
41010
+ await document.exitFullscreen();
41011
+ return;
41012
+ }
41013
+ await document.documentElement.requestFullscreen();
41014
+ await keyboard?.lock?.();
41015
+ } catch {
41016
+ }
41017
+ }
40879
41018
  function createUi(o) {
40880
41019
  const { t } = o;
40881
41020
  const style = mk2("style");
@@ -41087,6 +41226,8 @@ void main(){
41087
41226
  o.onFast(fast);
41088
41227
  });
41089
41228
  const tapBtn = ctl(t("ui.tap"), () => o.onTap());
41229
+ const fullscreenBtn = ctl(t("ui.fullscreen"), () => void toggleFullscreen());
41230
+ fullscreenBtn.title = t("ui.fullscreen.hint");
41090
41231
  const blastBtns = ["shelter", "fragmentHits", "confinement"].map((key) => ({ key, b: ctl(t(`ui.blast.${key}`), () => o.onBlast(key)) }));
41091
41232
  for (const { key, b } of blastBtns) b.title = t(`ui.blast.${key}.hint`);
41092
41233
  const qSel = mk2("select");
@@ -41640,6 +41781,7 @@ void main(){
41640
41781
  blockers,
41641
41782
  limit: arenaSize,
41642
41783
  start: arena.playerStart,
41784
+ crouchKeys: options.crouch?.keys,
41643
41785
  hooks: {
41644
41786
  primaryDown: () => {
41645
41787
  useHeld = true;
@@ -41892,6 +42034,7 @@ void main(){
41892
42034
  const third = { ...THIRD_PERSON_DEFAULTS, ...options.thirdPerson };
41893
42035
  let avatar = null;
41894
42036
  let cameraMode = options.cameraMode ?? "first";
42037
+ let crouchForced = false;
41895
42038
  const avatarState = { speed: 0, running: false, crouch: false, down: false, armed: false, pitch: 0 };
41896
42039
  let feetPrev = null;
41897
42040
  let armedForced = null;
@@ -41945,9 +42088,12 @@ void main(){
41945
42088
  if (avatarState.speed < 0.02) avatarState.speed = 0;
41946
42089
  }
41947
42090
  feetPrev = [x, z];
41948
- avatarState.running = player.keys.has("ShiftLeft") || player.keys.has("ShiftRight");
42091
+ avatarState.running = !player.crouching && (player.keys.has("ShiftLeft") || player.keys.has("ShiftRight"));
42092
+ avatarState.crouch = crouchForced || player.crouching;
41949
42093
  avatarState.armed = armedForced ?? tool.def.action.kind === "firearm";
41950
42094
  avatarState.pitch = player.pitch;
42095
+ avatarState.holding = heldPiece !== null;
42096
+ avatarState.itemUse = useItemPlayer ? { id: useItemName, ...useItemPlayer.pose() } : heldPiece && heldTilt > 0.01 ? { id: "tilt", position: [0.2, -0.1, -0.38], rotation: [-heldTilt, 0, 0] } : null;
41951
42097
  avatar.root.position.set(x, player.feet, z);
41952
42098
  avatar.root.rotation.set(0, player.yaw + Math.PI, 0);
41953
42099
  avatar.update(dt, avatarState);
@@ -42403,7 +42549,11 @@ void main(){
42403
42549
  const useTool = (dt, held) => {
42404
42550
  aim();
42405
42551
  const cat = tool.def.category;
42406
- if (cat === "hand") return;
42552
+ if (cat === "hand") {
42553
+ if (strikes.some((strike) => strike.key === "hand" || strike.key === "fists")) return;
42554
+ strikeUse({ kind: "melee", damage: "blunt", energy: 25, radius: 0.1, reach: 0.8, origin: [aimO[0], aimO[1], aimO[2]], direction: [aimD[0], aimD[1], aimD[2]] }, "hand");
42555
+ return;
42556
+ }
42407
42557
  if (strikes.some((strike) => strike.key === toolId)) return;
42408
42558
  const res = tool.trigger([aimO[0], aimO[1], aimO[2]], [aimD[0], aimD[1], aimD[2]], dt, held, "player");
42409
42559
  flushEvents(toolId, tool, res.recoil);
@@ -42496,6 +42646,7 @@ void main(){
42496
42646
  let heldTilt = 0;
42497
42647
  let tiltKey = 0;
42498
42648
  const heldLocal = new THREE.Vector3();
42649
+ const heldSize = new THREE.Vector3(0.2, 0.2, 0.2);
42499
42650
  let heldRadius = 0.2;
42500
42651
  const heldVel = [0, 0, 0];
42501
42652
  let useItemPlayer = null;
@@ -42541,12 +42692,15 @@ void main(){
42541
42692
  heldName = nameOf(r.object);
42542
42693
  piece.held = true;
42543
42694
  piece.sleeping = false;
42695
+ const savedQ = piece.root.quaternion.clone();
42696
+ piece.root.quaternion.identity();
42697
+ piece.root.updateMatrixWorld(true);
42544
42698
  const box2 = new THREE.Box3().setFromObject(piece.root);
42545
- const c = box2.getCenter(new THREE.Vector3());
42699
+ box2.getCenter(heldLocal).sub(piece.root.position);
42700
+ box2.getSize(heldSize);
42701
+ piece.root.quaternion.copy(savedQ);
42546
42702
  piece.root.updateMatrixWorld(true);
42547
- heldLocal.copy(c).sub(piece.root.position);
42548
- heldLocal.applyQuaternion(piece.root.quaternion.clone().invert());
42549
- heldRadius = box2.getSize(new THREE.Vector3()).length() / 3;
42703
+ heldRadius = heldSize.length() / 3;
42550
42704
  heldYaw = 0;
42551
42705
  heldTilt = 0;
42552
42706
  heldVel[0] = heldVel[1] = heldVel[2] = 0;
@@ -42567,18 +42721,43 @@ void main(){
42567
42721
  fire("throw", { objectId: id, speed });
42568
42722
  return true;
42569
42723
  };
42570
- const carriedTargets = (root) => {
42724
+ const carriedTargets = (root, gripQ) => {
42725
+ const width2 = Math.max(heldSize.x, heldSize.z);
42726
+ const sideGrip = heldSize.y > width2 * 1.04 && width2 <= 0.22;
42727
+ if (sideGrip) {
42728
+ const center2 = heldLocal.clone().applyQuaternion(root.quaternion).add(root.position);
42729
+ const radius = Math.min(heldSize.x, heldSize.z) * 0.5;
42730
+ const handX = new THREE.Vector3(-0.35, 0, -0.94).normalize().applyQuaternion(gripQ);
42731
+ const handY = new THREE.Vector3(0.94, 0, -0.35).normalize().applyQuaternion(gripQ);
42732
+ const handZ = new THREE.Vector3().crossVectors(handX, handY).normalize();
42733
+ const q2 = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(handX, handY, handZ));
42734
+ const up = new THREE.Vector3(0, 1, 0).applyQuaternion(gripQ);
42735
+ const palm = center2.clone().addScaledVector(handY, radius + CARRY_PALM).addScaledVector(handX, -0.022).addScaledVector(up, heldSize.y > 0.16 ? -heldSize.y * 0.08 : -5e-3);
42736
+ if (useItemName === "open" || useItemName === "close") {
42737
+ const leftX = new THREE.Vector3(0.35, 0, -0.94).normalize().applyQuaternion(gripQ);
42738
+ const leftY = new THREE.Vector3(-0.94, 0, -0.35).normalize().applyQuaternion(gripQ);
42739
+ const leftZ = new THREE.Vector3().crossVectors(leftX, leftY).normalize();
42740
+ const lq = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(leftX, leftY, leftZ));
42741
+ const leftPalm = center2.clone().addScaledVector(up, heldSize.y * 0.45).addScaledVector(leftY, radius * 0.65 + CARRY_PALM).addScaledVector(leftX, -0.018);
42742
+ return {
42743
+ R: { position: [palm.x, palm.y, palm.z], quaternion: [q2.x, q2.y, q2.z, q2.w] },
42744
+ L: { position: [leftPalm.x, leftPalm.y, leftPalm.z], quaternion: [lq.x, lq.y, lq.z, lq.w] },
42745
+ grip: 0.85,
42746
+ gripL: 0.8
42747
+ };
42748
+ }
42749
+ return { R: { position: [palm.x, palm.y, palm.z], quaternion: [q2.x, q2.y, q2.z, q2.w] }, grip: 0.85 };
42750
+ }
42571
42751
  const box2 = new THREE.Box3().setFromObject(root);
42572
42752
  const center = box2.getCenter(new THREE.Vector3());
42573
- const forward = camera2.getWorldDirection(new THREE.Vector3());
42574
- forward.y = 0;
42575
- if (forward.lengthSq() < 1e-6) forward.set(0, 0, -1);
42576
- forward.normalize();
42577
- const back = new THREE.Vector3(0, -1, 0);
42578
- const z = forward.clone().cross(back).normalize();
42579
- const q = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(forward, back, z));
42753
+ const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(gripQ).normalize();
42754
+ const back = new THREE.Vector3(0, -1, 0).applyQuaternion(gripQ).normalize();
42755
+ const z = new THREE.Vector3().crossVectors(forward, back).normalize();
42756
+ const y = new THREE.Vector3().crossVectors(z, forward).normalize();
42757
+ const q = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().makeBasis(forward, y, z));
42580
42758
  return { R: { position: [center.x, box2.min.y - CARRY_PALM, center.z], quaternion: [q.x, q.y, q.z, q.w] }, grip: CARRY_GRIP };
42581
42759
  };
42760
+ const gripQuat = new THREE.Quaternion();
42582
42761
  const followHeld = (dt) => {
42583
42762
  const piece = heldPiece;
42584
42763
  carriedHand = null;
@@ -42588,21 +42767,47 @@ void main(){
42588
42767
  let pos;
42589
42768
  const targetQ = tmpQ;
42590
42769
  const hand = thirdActive() ? avatar?.hand?.("R") : void 0;
42770
+ const isDrink = useItemName === "drink" || useItemName === "sip" || useItemName === "chug";
42591
42771
  if (hand) {
42592
- pos = hand.getWorldPosition(tmpHand);
42593
- targetQ.setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw + heldYaw);
42594
- targetQ.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), -heldTilt));
42772
+ const handWorld = hand.getWorldPosition(tmpHand);
42773
+ const yawQ = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw);
42774
+ const pose = useItemPlayer?.pose();
42775
+ const rx = pose ? isDrink ? Math.abs(pose.rotation[0]) * 0.9 : pose.rotation[0] : -heldTilt;
42776
+ const ry = pose ? pose.rotation[1] : 0;
42777
+ const rz = pose ? pose.rotation[2] : 0;
42778
+ gripQuat.copy(yawQ).multiply(new THREE.Quaternion().setFromEuler(new THREE.Euler(Math.max(-1.35, Math.min(1.25, rx)), ry, rz, "XYZ")));
42779
+ targetQ.copy(yawQ).multiply(new THREE.Quaternion().setFromEuler(new THREE.Euler(rx, ry, rz, "XYZ")));
42780
+ targetQ.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), heldYaw));
42781
+ const gripOffset = new THREE.Vector3(-0.02, 0.015, -0.04).applyQuaternion(gripQuat);
42782
+ pos = tmpV2.copy(handWorld).add(gripOffset);
42595
42783
  } else if (useItemPlayer) {
42596
42784
  const pose = useItemPlayer.pose();
42597
- const handQ = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw);
42598
- pos = tmpV2.set(pose.position[0], pose.position[1], pose.position[2]).applyQuaternion(handQ).add(camera2.position);
42599
- const eq2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(pose.rotation[0], pose.rotation[1], pose.rotation[2], "XYZ"));
42600
- targetQ.copy(handQ).multiply(eq2);
42785
+ const frameQ = avatar?.firstPerson ? camera2.quaternion : new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw);
42786
+ const raise = isDrink ? Math.max(0, Math.min(1, (pose.position[1] + 0.2) / 0.22)) : 0;
42787
+ const px2 = pose.position[0] + (isDrink ? 0.02 * raise : 0);
42788
+ const py2 = pose.position[1] - (isDrink ? 0.08 * raise : 0);
42789
+ const pz2 = pose.position[2] - (isDrink ? 0.05 * raise : 0);
42790
+ pos = tmpV2.set(px2, py2, pz2).applyQuaternion(frameQ).add(camera2.position);
42791
+ const rx = isDrink ? Math.abs(pose.rotation[0]) * 0.85 : pose.rotation[0];
42792
+ const eq2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(rx, pose.rotation[1], pose.rotation[2], "XYZ"));
42793
+ const wristEq = new THREE.Quaternion().setFromEuler(new THREE.Euler(Math.max(-1.35, Math.min(1.25, rx)), pose.rotation[1], pose.rotation[2], "XYZ"));
42794
+ gripQuat.copy(frameQ).multiply(wristEq);
42795
+ targetQ.copy(frameQ).multiply(eq2);
42601
42796
  targetQ.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), heldYaw));
42602
42797
  } else {
42603
- pos = tmpV2.set(0.05, -0.28, avatar?.firstPerson ? -Math.min(1.15, CARRY_NEAR + heldRadius) : -1.15).applyMatrix4(camera2.matrixWorld);
42798
+ const small = heldRadius < 0.22;
42799
+ const holdX = avatar?.firstPerson ? small ? 0.22 : 0.08 : 0.05;
42800
+ const holdY = avatar?.firstPerson ? small ? -0.2 : -0.26 : -0.28;
42801
+ const holdZ = avatar?.firstPerson ? small ? -0.42 : -Math.min(1.15, CARRY_NEAR + heldRadius) : -1.15;
42802
+ pos = tmpV2.set(holdX, holdY, holdZ).applyMatrix4(camera2.matrixWorld);
42803
+ const baseQ = avatar?.firstPerson && small ? camera2.quaternion : new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw);
42804
+ gripQuat.copy(baseQ).multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.max(-1.35, -heldTilt)));
42604
42805
  targetQ.setFromAxisAngle(new THREE.Vector3(0, 1, 0), player.yaw + heldYaw);
42605
- targetQ.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), -heldTilt));
42806
+ if (avatar?.firstPerson && small && heldTilt <= 1e-4) {
42807
+ targetQ.copy(camera2.quaternion).multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), heldYaw));
42808
+ } else {
42809
+ targetQ.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), -heldTilt));
42810
+ }
42606
42811
  }
42607
42812
  const k = Math.min(1, (useItemPlayer ? 30 : 16) * dt);
42608
42813
  const px = root.position.x;
@@ -42617,7 +42822,7 @@ void main(){
42617
42822
  heldVel[2] = (root.position.z - pz) / dt;
42618
42823
  }
42619
42824
  root.updateMatrixWorld(true);
42620
- if (avatar?.firstPerson && cameraMode === "first") carriedHand = carriedTargets(root);
42825
+ if (avatar?.firstPerson && cameraMode === "first") carriedHand = carriedTargets(root, gripQuat);
42621
42826
  };
42622
42827
  const useItem = (useId) => {
42623
42828
  const e = heldEntry;
@@ -42627,11 +42832,13 @@ void main(){
42627
42832
  const c = fluids.container(e.containerId);
42628
42833
  if (!c) return false;
42629
42834
  if (def.liquids && def.liquids.length > 0 && (!c.liquid || !def.liquids.includes(c.liquid))) return false;
42630
- if ((useId === "drink" || useId === "sip" || useId === "chug" || useId === "pour" || useId === "pour_out") && c.liters <= 1e-6) {
42835
+ const consuming = useId === "drink" || useId === "sip" || useId === "chug" || useId === "pour" || useId === "pour_out";
42836
+ if (consuming && c.liters <= 1e-6) {
42631
42837
  say(t("hud.empty_container", { name: e.name }));
42632
42838
  return false;
42633
42839
  }
42634
42840
  if (useItemPlayer && !useItemPlayer.done) return false;
42841
+ if (consuming && c.closed) fluids.open(e.containerId);
42635
42842
  useItemPlayer = createItemUse(fluids, e.containerId, def);
42636
42843
  useItemName = def.id;
42637
42844
  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 } });
@@ -42753,7 +42960,7 @@ void main(){
42753
42960
  if (heldEntry?.uses[0]) useItem(heldEntry.uses[0]);
42754
42961
  break;
42755
42962
  case "KeyC":
42756
- if (heldEntry?.uses[1]) useItem(heldEntry.uses[1]);
42963
+ if (heldEntry?.uses[1] && !(options.crouch?.keys ?? ["KeyC"]).includes("KeyC")) useItem(heldEntry.uses[1]);
42757
42964
  break;
42758
42965
  case "KeyV":
42759
42966
  if (heldEntry?.uses[2]) useItem(heldEntry.uses[2]);
@@ -42845,6 +43052,7 @@ void main(){
42845
43052
  const roomMetrics = createRoomMetrics({ gl, destruction: renderer, fluids: fluidView, effects: vfx.world, ids: roomMetricIds, blast: () => lastBlast });
42846
43053
  let qualityName = quality;
42847
43054
  const syncContainers = (sdt) => {
43055
+ const drinkingHeld = useItemPlayer !== null && (useItemName === "drink" || useItemName === "sip" || useItemName === "chug");
42848
43056
  for (const e of entries.values()) {
42849
43057
  if (!e.containerId) continue;
42850
43058
  const host2 = hostOf(e);
@@ -42853,7 +43061,8 @@ void main(){
42853
43061
  const p = tmpV2.set(e.fluidOffset[0], e.fluidOffset[1], e.fluidOffset[2]).applyMatrix4(host2.root.matrixWorld);
42854
43062
  const vel = sdt > 0 ? [(p.x - e.last[0]) / sdt, (p.y - e.last[1]) / sdt, (p.z - e.last[2]) / sdt] : void 0;
42855
43063
  e.last = [p.x, p.y, p.z];
42856
- fluids.setTransform(e.containerId, [p.x, p.y, p.z], [q.x, q.y, q.z, q.w], vel);
43064
+ const rot = drinkingHeld && e === heldEntry ? [0, 0, 0, 1] : [q.x, q.y, q.z, q.w];
43065
+ fluids.setTransform(e.containerId, [p.x, p.y, p.z], rot, vel);
42857
43066
  }
42858
43067
  };
42859
43068
  const syncLiters = () => {
@@ -43577,6 +43786,7 @@ void main(){
43577
43786
  },
43578
43787
  equip,
43579
43788
  openArea,
43789
+ modules: () => [...modules.values()].map((m) => ({ id: m.id, name: m.name, group: m.group, hasArea: typeof m.area === "function" })),
43580
43790
  openAreaAsync: (area) => loading.busy(t("loading.open_area", { area: areaLabel(area) }), () => openArea(area)),
43581
43791
  loading,
43582
43792
  busy: (label, work, busyOptions) => loading.busy(label, work, busyOptions),
@@ -43604,7 +43814,10 @@ void main(){
43604
43814
  return avatar;
43605
43815
  },
43606
43816
  setAvatarState(state) {
43607
- if (state.crouch !== void 0) avatarState.crouch = state.crouch;
43817
+ if (state.crouch !== void 0) {
43818
+ crouchForced = state.crouch;
43819
+ avatarState.crouch = state.crouch || player.crouching;
43820
+ }
43608
43821
  if (state.down !== void 0) avatarState.down = state.down;
43609
43822
  if (state.armed !== void 0) armedForced = state.armed;
43610
43823
  },