@adatechnology/game-weapons-three 0.16.0 → 0.17.1

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.
@@ -44,6 +44,8 @@ var GameWeaponsThree = (() => {
44
44
  P13_VALVE_MESH: () => P13_VALVE_MESH,
45
45
  RELOAD_SAMPLE: () => RELOAD_SAMPLE,
46
46
  RELOAD_SAMPLE_HITS: () => RELOAD_SAMPLE_HITS,
47
+ VIEW_HOLD: () => VIEW_HOLD,
48
+ VIEW_MOTION: () => VIEW_MOTION,
47
49
  WEAPON_ITEM_PREFIX: () => WEAPON_ITEM_PREFIX,
48
50
  WEAPON_THUMBNAILS: () => WEAPON_THUMBNAILS,
49
51
  WEIGHT_INTENSITY: () => WEIGHT_INTENSITY,
@@ -91,6 +93,7 @@ var GameWeaponsThree = (() => {
91
93
  registerWeaponItemModel: () => registerWeaponItemModel,
92
94
  rigStock: () => rigStock,
93
95
  rigUnderbarrel: () => rigUnderbarrel,
96
+ toWorldLens: () => toWorldLens,
94
97
  weaponProp: () => weaponProp,
95
98
  weaponSampleBank: () => weaponSampleBank,
96
99
  weaponSampleDefaults: () => weaponSampleDefaults,
@@ -165,6 +168,7 @@ var GameWeaponsThree = (() => {
165
168
  const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
166
169
  return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
167
170
  };
171
+ var FIRST_PERSON_LAYER = 9;
168
172
  var TOOL_CLIP = {
169
173
  DRAW: "draw",
170
174
  SHOT: "shot",
@@ -12148,7 +12152,9 @@ var GameWeaponsThree = (() => {
12148
12152
  dimensions: {
12149
12153
  length: 0.801,
12150
12154
  barrel: 0.559,
12151
- emptyMass: 39
12155
+ emptyMass: 39,
12156
+ barrels: 6,
12157
+ barrelMass: 2
12152
12158
  },
12153
12159
  archetype: "rifle_7_62x39",
12154
12160
  sources: [
@@ -13128,19 +13134,41 @@ var GameWeaponsThree = (() => {
13128
13134
  }
13129
13135
  };
13130
13136
  var DEFAULT_HEAT = {
13131
- cap: 140,
13132
- displayMax: 120,
13133
- decayBase: 1.6,
13134
- decayPerPoint: 0.03,
13135
- faultFrom: 60,
13136
- faultSpan: 40,
13137
- perShot: { shell: 3, big: 6, tiny: 0.5, pistol: 1.2, rifle: 2 },
13137
+ cap: 900,
13138
+ displayMax: 750,
13139
+ faultFrom: 350,
13140
+ faultSpan: 200,
13141
+ specificHeat: 490,
13142
+ barrelShare: { shell: 0.48, big: 0.48, tiny: 0.3, pistol: 0.3, rifle: 0.48 },
13143
+ barrelKilogramsPerMetre: {
13144
+ pistol: 1,
13145
+ revolver: 1.4,
13146
+ smg: 1.2,
13147
+ carbine: 1.6,
13148
+ rifle: 1.6,
13149
+ dmr: 2.4,
13150
+ sniper: 2.4,
13151
+ hunting: 2.4,
13152
+ machine_gun: 3,
13153
+ shotgun: 1.1
13154
+ },
13155
+ barrelFallback: { metres: 0.4, kilogramsPerMetre: 1.6 },
13156
+ cooling: { baseSeconds: 30, secondsPerKilogram: 250 },
13138
13157
  perRocket: 35,
13139
- visual: { smokeFrom: 70, smokeSeconds: 2.5, glowFrom: 80, glowSpan: 40 }
13158
+ visual: { smokeFrom: 250, smokeSpan: 300, smokeSeconds: 2.5, glowFrom: 450, glowSpan: 300 }
13140
13159
  };
13141
13160
  var GALLERY_HEAT = {
13142
13161
  ...DEFAULT_HEAT,
13143
- perShot: { shell: 6, big: 9, tiny: 1.2, pistol: 3, rifle: 4.5 }
13162
+ cap: 140,
13163
+ displayMax: 120,
13164
+ faultFrom: 60,
13165
+ faultSpan: 40,
13166
+ visual: { smokeFrom: 70, smokeSpan: 100, smokeSeconds: 2.5, glowFrom: 80, glowSpan: 40 },
13167
+ gallery: {
13168
+ perShot: { shell: 6, big: 9, tiny: 1.2, pistol: 3, rifle: 4.5 },
13169
+ decayBase: 1.6,
13170
+ decayPerPoint: 0.03
13171
+ }
13144
13172
  };
13145
13173
  var defaultFaultChance = (context) => {
13146
13174
  const factor = context.def.action.kind === "firearm" ? context.def.action.faultFactor ?? 1 : 1;
@@ -13423,17 +13451,76 @@ var GameWeaponsThree = (() => {
13423
13451
  ...explosive.stunSeconds !== void 0 ? { stunSeconds: explosive.stunSeconds } : {}
13424
13452
  };
13425
13453
  };
13454
+ var EPS2 = 1e-9;
13455
+ var MANUAL_MODES = /* @__PURE__ */ new Set(["pump", "bolt", "lever"]);
13456
+ var weaponSpeed = (action, caliber) => action.muzzleSpeed ?? caliber.muzzleSpeed * (action.barrel ?? 1);
13457
+ var weaponNoise = (action, caliber) => action.noise ?? caliber.noise ?? 0;
13458
+ var timingsOf = (action) => "timings" in action ? action.timings : void 0;
13459
+ var drawSeconds = (action, rules) => timingsOf(action)?.draw ?? rules.draw;
13460
+ var clearSeconds = (action, rules) => timingsOf(action)?.clear ?? rules.clear;
13461
+ var manualCycleSeconds = (action, mode) => MANUAL_MODES.has(mode) ? action.timings?.cycle ?? 0 : 0;
13462
+ var planRounds = (action, mode, ammo, firstAt, frameEnd) => {
13463
+ const interval = 60 / action.rpm;
13464
+ const times = [];
13465
+ if (mode === "auto") {
13466
+ for (let t = firstAt; times.length < ammo && (times.length === 0 ? t <= frameEnd + EPS2 : t < frameEnd - EPS2); t += interval) times.push(t);
13467
+ return times;
13468
+ }
13469
+ if (mode === "burst") {
13470
+ const count = Math.min(action.burst ?? 3, ammo);
13471
+ for (let k = 0; k < count; k++) times.push(firstAt + k * interval);
13472
+ return times;
13473
+ }
13474
+ if (mode === "volley") return Array.from({ length: Math.min(action.magazine, ammo) }, () => firstAt);
13475
+ return [firstAt];
13476
+ };
13426
13477
  var SUBSTEP_SECONDS = 1 / 60;
13427
- var decayHeat = (heat, seconds, rules) => {
13478
+ var MAX_ROUNDS_SEARCHED = 1e5;
13479
+ var barrelMassOf = (def, rules) => {
13480
+ const dimensions = def.dimensions;
13481
+ const kilogramsPerMetre = rules.barrelKilogramsPerMetre[def.category] ?? rules.barrelFallback.kilogramsPerMetre;
13482
+ const single = dimensions?.barrelMass ?? (dimensions?.barrel ?? rules.barrelFallback.metres) * kilogramsPerMetre;
13483
+ return single * (dimensions?.barrels ?? 1);
13484
+ };
13485
+ var coolingTimeConstant = (barrelMass, rules) => rules.cooling.baseSeconds + rules.cooling.secondsPerKilogram * barrelMass;
13486
+ var decayHeat = (heat, seconds, barrelMass, rules) => {
13487
+ if (heat <= 0) return 0;
13488
+ if (rules.gallery) return decayGallery(heat, seconds, rules.gallery);
13489
+ return heat * Math.exp(-seconds / coolingTimeConstant(barrelMass, rules));
13490
+ };
13491
+ var decayGallery = (heat, seconds, gallery) => {
13428
13492
  const steps = Math.max(1, Math.ceil(seconds / SUBSTEP_SECONDS - 1e-9));
13429
13493
  const step = seconds / steps;
13430
13494
  let current = heat;
13431
- for (let i = 0; i < steps && current > 0; i++) current = Math.max(0, current - step * (rules.decayBase + current * rules.decayPerPoint));
13495
+ for (let i = 0; i < steps && current > 0; i++) current = Math.max(0, current - step * (gallery.decayBase + current * gallery.decayPerPoint));
13432
13496
  return current;
13433
13497
  };
13434
- var heatPerRound = (action, caliber, rules) => {
13498
+ var heatPerRound = ({ action, caliber, energy, barrelMass, rules }) => {
13435
13499
  if (action.kind === "launcher") return rules.perRocket;
13436
- return caliber?.caseType ? rules.perShot[caliber.caseType] : 0;
13500
+ const caseType = caliber?.caseType;
13501
+ if (!caseType) return 0;
13502
+ if (rules.gallery) return rules.gallery.perShot[caseType];
13503
+ return rules.barrelShare[caseType] * energy / (barrelMass * rules.specificHeat);
13504
+ };
13505
+ var roundsToWarning = ({ def, caliber, energy, rpm, rules }) => {
13506
+ if (def.action.kind !== "firearm" || rpm <= 0) return Infinity;
13507
+ const barrelMass = barrelMassOf(def, rules);
13508
+ const perRound = heatPerRound({ action: def.action, caliber, energy: energy * (caliber.pellets ?? 1), barrelMass, rules });
13509
+ const interval = 60 / rpm;
13510
+ let heat = 0;
13511
+ for (let round5 = 1; round5 <= MAX_ROUNDS_SEARCHED; round5++) {
13512
+ heat = decayHeat(heat, round5 === 1 ? 0 : interval, barrelMass, rules) + perRound;
13513
+ if (heat >= rules.faultFrom) return round5;
13514
+ }
13515
+ return Infinity;
13516
+ };
13517
+ var roundsToWarningOf = (arsenal, weaponId) => {
13518
+ const def = arsenal.weapons.get(weaponId);
13519
+ if (!def || def.action.kind !== "firearm") return Infinity;
13520
+ const caliber = arsenal.calibers.get(def.action.caliber);
13521
+ if (!caliber) return Infinity;
13522
+ const energy = 0.5 * (caliber.bulletGrams / 1e3) * weaponSpeed(def.action, caliber) ** 2;
13523
+ return roundsToWarning({ def, caliber, energy, rpm: def.action.rpm, rules: arsenal.rules.heat });
13437
13524
  };
13438
13525
  var burstNoise = (base, recentShots, rules) => base * (1 + Math.min(rules.cap, rules.step * recentShots));
13439
13526
  var magazineOut = { stage: "magazine_out", at: 0 };
@@ -13560,29 +13647,6 @@ var GameWeaponsThree = (() => {
13560
13647
  return roll2.ammoQuality.faultChance > 0 && roll2.random() < roll2.ammoQuality.faultChance ? "misfire" : null;
13561
13648
  };
13562
13649
  var isDudWithoutJam = (action, fault) => fault === "misfire" && action.kind === "firearm" && MISFIRE_ONLY.has(action.family);
13563
- var EPS2 = 1e-9;
13564
- var MANUAL_MODES = /* @__PURE__ */ new Set(["pump", "bolt", "lever"]);
13565
- var weaponSpeed = (action, caliber) => action.muzzleSpeed ?? caliber.muzzleSpeed * (action.barrel ?? 1);
13566
- var weaponNoise = (action, caliber) => action.noise ?? caliber.noise ?? 0;
13567
- var timingsOf = (action) => "timings" in action ? action.timings : void 0;
13568
- var drawSeconds = (action, rules) => timingsOf(action)?.draw ?? rules.draw;
13569
- var clearSeconds = (action, rules) => timingsOf(action)?.clear ?? rules.clear;
13570
- var manualCycleSeconds = (action, mode) => MANUAL_MODES.has(mode) ? action.timings?.cycle ?? 0 : 0;
13571
- var planRounds = (action, mode, ammo, firstAt, frameEnd) => {
13572
- const interval = 60 / action.rpm;
13573
- const times = [];
13574
- if (mode === "auto") {
13575
- for (let t = firstAt; times.length < ammo && (times.length === 0 ? t <= frameEnd + EPS2 : t < frameEnd - EPS2); t += interval) times.push(t);
13576
- return times;
13577
- }
13578
- if (mode === "burst") {
13579
- const count = Math.min(action.burst ?? 3, ammo);
13580
- for (let k = 0; k < count; k++) times.push(firstAt + k * interval);
13581
- return times;
13582
- }
13583
- if (mode === "volley") return Array.from({ length: Math.min(action.magazine, ammo) }, () => firstAt);
13584
- return [firstAt];
13585
- };
13586
13650
  var activeParts = (fitted, catalogue) => fitted.flatMap((part) => {
13587
13651
  const def = catalogue.get(part.id);
13588
13652
  const isOn = def?.kind !== "light" && def?.kind !== "laser" ? true : part.on === true;
@@ -13815,6 +13879,10 @@ var GameWeaponsThree = (() => {
13815
13879
  get safety() {
13816
13880
  return this._safety;
13817
13881
  }
13882
+ /** Kg of steel that heat up with the shots. */
13883
+ get barrelMass() {
13884
+ return barrelMassOf(this.def, this.rules.heat);
13885
+ }
13818
13886
  get heat() {
13819
13887
  return this._heat;
13820
13888
  }
@@ -13867,7 +13935,7 @@ var GameWeaponsThree = (() => {
13867
13935
  }
13868
13936
  advance(seconds) {
13869
13937
  if (seconds <= 0) return;
13870
- this._heat = decayHeat(this._heat, seconds, this.rules.heat);
13938
+ this._heat = decayHeat(this._heat, seconds, this.barrelMass, this.rules.heat);
13871
13939
  this.clock += seconds;
13872
13940
  }
13873
13941
  /** Complete what falls at or before `t`: scheduled events and the reload steps. */
@@ -14419,7 +14487,7 @@ var GameWeaponsThree = (() => {
14419
14487
  res.shots.push(shot);
14420
14488
  }
14421
14489
  this._ammo -= 1;
14422
- this._heat = Math.min(this.rules.heat.cap, this._heat + heatPerRound(a, cal, this.rules.heat) * this.load.heatFactor);
14490
+ this._heat = Math.min(this.rules.heat.cap, this._heat + heatPerRound({ action: a, caliber: cal, energy: energy * pellets, barrelMass: this.barrelMass, rules: this.rules.heat }) * this.load.heatFactor);
14423
14491
  this.readyAt = t + 60 / a.rpm;
14424
14492
  this.extra = Math.min(bloomNow + (a.bloomDeg ?? 0), a.spreadDeg * (BLOOM_CAP - 1));
14425
14493
  this.extraClock = t;
@@ -14508,7 +14576,7 @@ var GameWeaponsThree = (() => {
14508
14576
  }
14509
14577
  const t = Math.max(start, this.readyAt);
14510
14578
  this._ammo -= 1;
14511
- this._heat = Math.min(this.rules.heat.cap, this._heat + heatPerRound(a, null, this.rules.heat));
14579
+ this._heat = Math.min(this.rules.heat.cap, this._heat + heatPerRound({ action: a, caliber: null, energy: 0, barrelMass: this.barrelMass, rules: this.rules.heat }));
14512
14580
  this.emit({ type: "shot", shots: 0, rounds: 1, mode: this.mode, at: t });
14513
14581
  return result("ok", { uses: launcherUses(a, this.explosive, origin, dir), recoil: [a.recoil[0], a.recoil[1]], noise: a.noise ?? 0, rounds: 1, roundTimes: [t - start] });
14514
14582
  }
@@ -37549,6 +37617,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37549
37617
  signature = "";
37550
37618
  });
37551
37619
  return {
37620
+ get aimProgress() {
37621
+ return aimProgress;
37622
+ },
37552
37623
  handleChange(change) {
37553
37624
  if (change.kind === "action") return act(change.id);
37554
37625
  if (change.kind === "control" && change.id === SUPPORTED_CONTROL_ID && typeof change.value === "boolean") {
@@ -43589,6 +43660,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
43589
43660
  shoulderGripTo: 0.34,
43590
43661
  /** The farthest apart two hands hold a long gun (a support hand past it slides back along the gun, toward the grip). */
43591
43662
  supportSpan: 0.5,
43663
+ /** A gun carried at the hip (`carry: 'hip'`): its rear grip low at the right hip, a little ahead of it. */
43664
+ hipGrip: [0.25, -0.5, -0.24],
43592
43665
  /** How much of the aim's pitch a tool held low in one hand follows. */
43593
43666
  toolPitch: 0.5
43594
43667
  };
@@ -43600,6 +43673,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
43600
43673
  holder.scale.setScalar(1);
43601
43674
  hand.position.set(...frame2.position);
43602
43675
  hand.quaternion.set(...frame2.quaternion);
43676
+ if (placement.hip) return placeAtHip(placement);
43603
43677
  const shouldered = !placement.handgun && placement.left !== null && placement.lengthMetres >= BODY_HOLD.shoulderedFrom;
43604
43678
  if (shouldered) {
43605
43679
  holder.position.set(0, BODY_HOLD.boreAbove, placement.rear);
@@ -43623,6 +43697,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
43623
43697
  hand.position.add(want.sub(now));
43624
43698
  hand.updateMatrixWorld(true);
43625
43699
  };
43700
+ var placeAtHip = (placement) => {
43701
+ const { THREE, hand, holder } = placement;
43702
+ scratch.a ?? (scratch.a = new THREE.Vector3());
43703
+ scratch.b ?? (scratch.b = new THREE.Vector3());
43704
+ scratch.euler ?? (scratch.euler = new THREE.Euler());
43705
+ const turn2 = scratch.euler.setFromQuaternion(hand.quaternion, "YXZ");
43706
+ hand.quaternion.setFromEuler(turn2.set(turn2.x * BODY_HOLD.toolPitch, turn2.y, 0, "YXZ"));
43707
+ holder.position.set(0, 0, 0);
43708
+ hand.updateMatrixWorld(true);
43709
+ if (!placement.right) return;
43710
+ const want = scratch.a.set(...BODY_HOLD.hipGrip).applyQuaternion(hand.quaternion).add(hand.position);
43711
+ const now = placement.right.part.localToWorld(scratch.b.copy(placement.right.offset));
43712
+ hand.position.add(want.sub(now));
43713
+ hand.updateMatrixWorld(true);
43714
+ };
43626
43715
  var limitSupportSpan = (targets) => {
43627
43716
  const right = targets.R?.position;
43628
43717
  const left = targets.L?.position;
@@ -43646,6 +43735,57 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
43646
43735
  };
43647
43736
  };
43648
43737
 
43738
+ // src/fx/view-hold.ts
43739
+ var VIEW_HOLD = {
43740
+ long: { grip: [0.2, -0.28, -0.38], fov: 84, sights: { distance: 0.34, fromRear: 0.3 } },
43741
+ handgun: { grip: [0.13, -0.15, -0.33], fov: 76, sights: { distance: 0.42, fromRear: 0.02 } },
43742
+ launcher: { grip: [0.2, -0.14, -0.3], fov: 84, sights: { distance: 0.18, fromRear: 0.25 } },
43743
+ tool: { grip: [0.21, -0.31, -0.36], fov: 84, sights: { distance: 0.3, fromRear: 0.1 }, tilt: 0.3 },
43744
+ // the gun at the hip, its rear grip low and to the right, the barrels ahead of the body; it has no sights to bring to the eye
43745
+ hip: { grip: [0.44, -0.32, -0.42], fov: 84, sights: { distance: 0.6, fromRear: 0.1 } }
43746
+ };
43747
+ var NO_GRIP_REAR = [0.2, -0.24, -0.12];
43748
+ var SIGHT_UNDER_EYE = 0.02;
43749
+ var scratch2 = { grip: null, hip: null, aimed: null, tilt: null };
43750
+ var ease = (k) => k * k * (3 - 2 * k);
43751
+ function placeInView(placement) {
43752
+ const { THREE, hand, holder, camera: camera2, right, box: box5, kind } = placement;
43753
+ const hold = VIEW_HOLD[kind];
43754
+ holder.scale.setScalar(1);
43755
+ hand.position.copy(camera2.position);
43756
+ hand.quaternion.copy(camera2.quaternion);
43757
+ if (hold.tilt) {
43758
+ scratch2.tilt ?? (scratch2.tilt = new THREE.Quaternion());
43759
+ hand.quaternion.multiply(scratch2.tilt.setFromAxisAngle(new THREE.Vector3(1, 0, 0), hold.tilt));
43760
+ }
43761
+ hand.scale.setScalar(1);
43762
+ holder.position.set(0, 0, 0);
43763
+ hand.updateMatrixWorld(true);
43764
+ scratch2.hip ?? (scratch2.hip = new THREE.Vector3());
43765
+ const hip = scratch2.hip;
43766
+ if (right) {
43767
+ scratch2.grip ?? (scratch2.grip = new THREE.Vector3());
43768
+ const grip = hand.worldToLocal(right.part.localToWorld(scratch2.grip.copy(right.offset)));
43769
+ hip.set(hold.grip[0] - grip.x, hold.grip[1] - grip.y, hold.grip[2] - grip.z);
43770
+ } else hip.set(NO_GRIP_REAR[0], NO_GRIP_REAR[1], NO_GRIP_REAR[2] + box5.min.x);
43771
+ const k = ease(Math.max(0, Math.min(1, placement.aim)));
43772
+ if (k > 0) {
43773
+ const along = box5.min.x + hold.sights.fromRear;
43774
+ scratch2.aimed ?? (scratch2.aimed = new THREE.Vector3());
43775
+ hip.lerp(scratch2.aimed.set(0, -SIGHT_UNDER_EYE - box5.max.y, along - hold.sights.distance), k);
43776
+ }
43777
+ holder.position.copy(hip).add(placement.offset);
43778
+ hand.updateMatrixWorld(true);
43779
+ }
43780
+ function toWorldLens(camera2, point, worldFov, lensFov) {
43781
+ const k = Math.tan(worldFov * Math.PI / 360) / Math.tan(lensFov * Math.PI / 360);
43782
+ camera2.updateMatrixWorld();
43783
+ camera2.worldToLocal(point);
43784
+ point.x *= k;
43785
+ point.y *= k;
43786
+ return camera2.localToWorld(point);
43787
+ }
43788
+
43649
43789
  // src/fx/hand-hold.ts
43650
43790
  var HAND_HOLD = {
43651
43791
  /** Half the width of a pistol grip, the palm sits this far (plus the palm) off its middle. */
@@ -43724,6 +43864,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
43724
43864
  const toRoot = (x, y, z) => V(THREE, inner.position.x + x * inner.scale.x, y, z);
43725
43865
  const handgun = parts.family === "pistol" || parts.family === "revolver";
43726
43866
  root.updateWorldMatrix(true, true);
43867
+ if (parts.holdRight || parts.holdLeft) return namedHold(THREE, model, holderScale);
43727
43868
  let gripAt;
43728
43869
  let up;
43729
43870
  if (parts.grip) {
@@ -43770,6 +43911,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
43770
43911
  gripLeft: HAND_HOLD.forend
43771
43912
  };
43772
43913
  }
43914
+ function namedHold(THREE, model, holderScale) {
43915
+ const { parts } = model;
43916
+ const on = (mark, fingers) => mark ? { part: mark, offset: V(THREE, 0, HAND_HOLD.palm, 0), quaternion: handFrame(THREE, fingers, V(THREE, 0, 1, 0)) } : null;
43917
+ return {
43918
+ right: on(parts.holdRight, V(THREE, 1, 0, 0)),
43919
+ left: on(parts.holdLeft, V(THREE, 0, 0, 1)),
43920
+ leftMagazine: null,
43921
+ scale: holderScale,
43922
+ grip: HAND_HOLD.closed,
43923
+ gripLeft: HAND_HOLD.closed
43924
+ };
43925
+ }
43773
43926
  function holdOfProp(THREE, group, category, scale) {
43774
43927
  const handle = group.getObjectByName("handle") ?? group.getObjectByName("body") ?? group;
43775
43928
  const head = group.getObjectByName("head");
@@ -51237,6 +51390,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
51237
51390
  const q2 = new THREE.Quaternion();
51238
51391
  const pointForward = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2);
51239
51392
  const frontOf = new THREE.Vector3();
51393
+ const restBox = new THREE.Box3();
51394
+ const noMotion = new THREE.Vector3();
51395
+ let inLens = false;
51396
+ const setLens = (on) => {
51397
+ if (on !== inLens) options.viewmodelFov?.(on ? VIEW_HOLD.tool.fov : null);
51398
+ inLens = on;
51399
+ const layer3 = on ? FIRST_PERSON_LAYER : 0;
51400
+ hand.traverse((node) => node.layers.set(layer3));
51401
+ };
51240
51402
  const equip = (weapon) => {
51241
51403
  if (model) hand.remove(model);
51242
51404
  model = null;
@@ -51244,6 +51406,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
51244
51406
  def = arsenal.weapons.get(weapon) ?? null;
51245
51407
  if (!def || BODY_ACTIONS.has(def.id)) return;
51246
51408
  model = buildWeaponModel(THREE, def, options.items);
51409
+ restBox.setFromObject(model);
51410
+ inLens = false;
51247
51411
  const length2 = new THREE.Box3().setFromObject(model).getSize(v).x;
51248
51412
  rear = new THREE.Box3().setFromObject(model).min.x;
51249
51413
  lengthMetres = length2;
@@ -51344,6 +51508,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
51344
51508
  return {
51345
51509
  unequip() {
51346
51510
  if (model) hand.remove(model);
51511
+ if (inLens) setLens(false);
51347
51512
  model = null;
51348
51513
  inHand = null;
51349
51514
  hold = null;
@@ -51352,6 +51517,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
51352
51517
  if (!model || !def || !SPOUTS.has(def.action.kind)) return null;
51353
51518
  hand.updateMatrixWorld(true);
51354
51519
  const at3 = model.localToWorld(v.copy(frontOf));
51520
+ if (inLens) toWorldLens(camera2, at3, camera2.fov ?? 72, VIEW_HOLD.tool.fov);
51355
51521
  const along = v2.set(1, 0, 0).applyQuaternion(model.getWorldQuaternion(q2)).normalize();
51356
51522
  return { position: [at3.x, at3.y, at3.z], direction: [along.x, along.y, along.z], drawsFlash: false };
51357
51523
  },
@@ -51374,7 +51540,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
51374
51540
  update(dt) {
51375
51541
  kick *= Math.exp(-dt * 14);
51376
51542
  dip += ((reloading ? 1 : 0) - dip) * Math.min(1, dt * 8);
51377
- if (inHand && model) {
51543
+ if (inHand && model && options.firstPerson?.()) {
51544
+ setLens(true);
51545
+ placeInView({ THREE, hand, holder: model, camera: camera2, right: hold?.right ?? null, box: restBox, kind: "tool", aim: 0, offset: noMotion });
51546
+ hand.position.add(v.set(0, -0.12 * dip, 0.05 * kick).applyQuaternion(camera2.quaternion));
51547
+ hand.updateMatrixWorld(true);
51548
+ } else if (inHand && model) {
51549
+ if (inLens) setLens(false);
51378
51550
  const handgun = def?.category === "pistol" || def?.category === "revolver";
51379
51551
  placeOnBody({ THREE, hand, holder: model, frame: options.bodyHold?.() ?? eyeFrame(), rear, lengthMetres, handgun, right: hold?.right ?? null, left: hold?.left ?? null });
51380
51552
  hand.position.add(v.set(0, -0.12 * dip, 0.05 * kick).applyQuaternion(camera2.quaternion));
@@ -53915,6 +54087,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
53915
54087
  cylinderZ(group, 0.016, 0.07, M.poly, 0.06, -0.01, side * 0.2, 14);
53916
54088
  }
53917
54089
  parts.trigger = box5(group, 0.012, 0.02, 0.016, M.dark, 0.1, 0.085, 0.14, 0.3);
54090
+ parts.holdRight = kit.mark(group, 0.06, 6e-3, 0.2);
54091
+ parts.holdLeft = kit.mark(group, 0.22, 0.164, 0);
54092
+ parts.carry = "hip";
53918
54093
  parts.safety = box5(group, 0.014, 6e-3, 0.01, M.dark, 0.2, 0.073, -0.076, 0.4);
53919
54094
  }
53920
54095
  function buildFeed(kit, parts, group) {
@@ -54346,7 +54521,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
54346
54521
  var ROOT = "root";
54347
54522
  var AXIS_INDEX = { x: 0, y: 1, z: 2 };
54348
54523
  var EASE_STEPS = 10;
54349
- var ease = (progress) => progress < 0.5 ? 2 * progress * progress : 1 - (-2 * progress + 2) ** 2 / 2;
54524
+ var ease2 = (progress) => progress < 0.5 ? 2 * progress * progress : 1 - (-2 * progress + 2) ** 2 / 2;
54350
54525
  var move = (part, axis2, start, duration, from, to) => ({ part, channel: "position", axis: axis2, start, duration, from, to });
54351
54526
  var turn = (part, axis2, start, duration, from, to) => ({ part, channel: "rotation", axis: axis2, start, duration, from, to });
54352
54527
  var cue = (t, type, data) => ({ t, type, ...data ? { data } : {} });
@@ -54360,7 +54535,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
54360
54535
  for (const tween of tweens) {
54361
54536
  if (tween.start > t || tween.duration === 0 && (tween.start === t ? !includeInstants : false)) continue;
54362
54537
  if (tween.duration === 0 || t >= tween.start + tween.duration) value = tween.to;
54363
- else value = tween.from + (tween.to - tween.from) * ease((t - tween.start) / tween.duration);
54538
+ else value = tween.from + (tween.to - tween.from) * ease2((t - tween.start) / tween.duration);
54364
54539
  }
54365
54540
  return value;
54366
54541
  };
@@ -57287,7 +57462,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
57287
57462
  var round4 = call("round");
57288
57463
  var clamp3 = call("clamp");
57289
57464
  var lerp2 = call("lerp");
57290
- var ease2 = call("ease");
57465
+ var ease3 = call("ease");
57291
57466
  var lt = call("lt");
57292
57467
  var gt = call("gt");
57293
57468
  var le = call("le");
@@ -60972,7 +61147,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
60972
61147
  const { core, def, random, x, y, z } = context;
60973
61148
  const mark = def.mark;
60974
61149
  const keepsMarks = !core.marklessAt?.(x, z);
60975
- if (keepsMarks && mark.crater && mark.dent && y < 1.2) {
61150
+ if (keepsMarks && mark.crater && mark.dent && y < 1.2 && (core.cratersAt?.(x, z) ?? true)) {
60976
61151
  core.emit({ type: "crater", x, z, radius: mark.crater, depth: mark.dent });
60977
61152
  core.trace?.crater?.(x, z, mark.crater, mark.dent);
60978
61153
  addCrater(core.marks, core.ground, random, { x, z, radius: mark.crater, depth: mark.dent, marks: core.layers.mark });
@@ -62730,6 +62905,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
62730
62905
  clock: { time: 0, lastBlast: -1, lod: 1, forcedLod: null },
62731
62906
  groundAt,
62732
62907
  ...options.marklessAt ? { marklessAt: options.marklessAt } : {},
62908
+ ...options.cratersAt ? { cratersAt: options.cratersAt } : {},
62733
62909
  emit: (request) => options.onRequest?.(request)
62734
62910
  };
62735
62911
  function definitionOf(id) {
@@ -64419,38 +64595,6 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64419
64595
  };
64420
64596
  }
64421
64597
 
64422
- // src/fx/view-hold.ts
64423
- var VIEW_HOLD = {
64424
- /** The right hand's grip, from the eye. */
64425
- grip: [0.2, -0.2, -0.3],
64426
- /** A handgun's grip: higher and nearer the middle, the arms out in front. */
64427
- handgunGrip: [0.13, -0.15, -0.33],
64428
- /** A rocket launcher's grip: its tube rides on the shoulder, so the hands are higher. */
64429
- launcherGrip: [0.2, -0.14, -0.3],
64430
- /** A gun with no grip anchor: the rear of its bore, from the eye. */
64431
- rear: [0.2, -0.24, -0.12]
64432
- };
64433
- var scratch2 = { grip: null };
64434
- function placeInView(placement) {
64435
- const { THREE, hand, holder, camera: camera2, right } = placement;
64436
- holder.scale.setScalar(1);
64437
- hand.position.copy(camera2.position);
64438
- hand.quaternion.copy(camera2.quaternion);
64439
- hand.scale.setScalar(1);
64440
- holder.position.set(0, 0, 0);
64441
- hand.updateMatrixWorld(true);
64442
- if (!right) {
64443
- holder.position.set(VIEW_HOLD.rear[0], VIEW_HOLD.rear[1], VIEW_HOLD.rear[2] + placement.rear);
64444
- hand.updateMatrixWorld(true);
64445
- return;
64446
- }
64447
- scratch2.grip ?? (scratch2.grip = new THREE.Vector3());
64448
- const grip = hand.worldToLocal(right.part.localToWorld(scratch2.grip.copy(right.offset)));
64449
- const [x, y, z] = placement.kind === "handgun" ? VIEW_HOLD.handgunGrip : placement.kind === "launcher" ? VIEW_HOLD.launcherGrip : VIEW_HOLD.grip;
64450
- holder.position.set(x - grip.x, y - grip.y, z - grip.z);
64451
- hand.updateMatrixWorld(true);
64452
- }
64453
-
64454
64598
  // src/fx/heavy.ts
64455
64599
  var PI14 = Math.PI;
64456
64600
  var ROCKET_LIGHT_NAME = "rocket-light";
@@ -64835,11 +64979,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
64835
64979
  switch (event.type) {
64836
64980
  case "muzzle_flash":
64837
64981
  if (data.at === "ugl") {
64838
- if (parts.uglMuzzle && world.layers.muzzleFlash) effects.muzzleFlash(positionOf3(parts.uglMuzzle), root.getWorldQuaternion(new THREE.Quaternion()), UGL_FLASH);
64982
+ if (parts.uglMuzzle && world.layers.muzzleFlash) world.afterPose(() => parts.uglMuzzle && effects.muzzleFlash(positionOf3(parts.uglMuzzle), root.getWorldQuaternion(new THREE.Quaternion()), UGL_FLASH));
64839
64983
  } else if (parts.muzzle && world.layers.muzzleFlash && (session.def.action.kind === "firearm" || session.def.action.kind === "launcher")) {
64840
64984
  const profile = muzzleSmokeOf({ caliber: session.caliber, weapon: session.def, suppressed: session.suppressed });
64841
64985
  const size = session.def.action.kind === "launcher" ? 2.2 : FLASH_BY_CASE[session.caseType];
64842
- effects.muzzleFlash(positionOf3(parts.muzzle), root.getWorldQuaternion(new THREE.Quaternion()), size, profile);
64986
+ const muzzle2 = parts.muzzle;
64987
+ world.afterPose(() => effects.muzzleFlash(positionOf3(muzzle2), root.getWorldQuaternion(new THREE.Quaternion()), size, profile));
64843
64988
  }
64844
64989
  break;
64845
64990
  case "eject_casing":
@@ -65377,7 +65522,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65377
65522
 
65378
65523
  // src/fx/viewmodel.ts
65379
65524
  var PI16 = Math.PI;
65380
- var HEAT_DISPLAY_MAX_FALLBACK = 120;
65525
+ var HEAT_DISPLAY_MAX_FALLBACK = 750;
65526
+ var SMOKE_WISPS_PER_SECOND = 10;
65527
+ var SMOKE_THICKNESS_GAIN = 1.7;
65381
65528
  var WALL_PULL = { margin: 0.03, max: 1.2, rate: 14, probe: 3 };
65382
65529
  var HAND_IN_VIEW = { floor: -0.88, max: 0.3, rate: 10 };
65383
65530
  var LAUNCH_VOLUME = 0.6;
@@ -65396,6 +65543,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65396
65543
  const held = sinceShot < TRIGGER_FINGER.burstSeconds ? TRIGGER_FINGER.pulled - TRIGGER_FINGER.tremor * (0.5 + 0.5 * Math.sin(sinceShot * 220)) : 0;
65397
65544
  return Math.max(ready + (TRIGGER_FINGER.pulled - ready) * pull, held);
65398
65545
  };
65546
+ var VIEW_MOTION = { sway: 6e-3, swayMax: 0.02, swayRate: 9, bobX: 6e-3, bobY: 8e-3, run: 1.6, fullSpeed: 4, frequency: 3.2 };
65399
65547
  function createViewmodel(options) {
65400
65548
  const { THREE, scene, camera: camera2, arsenal, groundAt, audio } = options;
65401
65549
  const kit = modelKit(THREE);
@@ -65403,6 +65551,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65403
65551
  const layers = options.layers ?? createLayers();
65404
65552
  const distance3 = () => options.soundDistance?.() ?? 0;
65405
65553
  const particles = createParticles(THREE, scene, groundAt, options.particleLimit);
65554
+ const spawnParticle = particles.spawn.bind(particles);
65555
+ particles.spawn = (object2, velocity, particleOptions) => {
65556
+ if (lens2) toWorldLens(camera2, object2.position, worldFovOf(), lens2.fov);
65557
+ spawnParticle(object2, velocity, particleOptions);
65558
+ };
65406
65559
  particles.onBounce = (kind, hit) => {
65407
65560
  if (!audio || kind !== "casing" && kind !== "magazine") return;
65408
65561
  const surface = hit ? options.surfaceAt?.(hit.object.position.x, hit.object.position.z) : void 0;
@@ -65415,7 +65568,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65415
65568
  };
65416
65569
  if (!options.effects) warnOnce("game-weapons-three.own-effects-world", "game-weapons-three made an effects world of its own because the host lent none (options.effects); pass the host effects so the effects share one budget");
65417
65570
  const ownVfx = options.effects ? void 0 : createVfxRenderer(THREE, { scene, camera: camera2, vfx: { profile: "medium" }, ...options.lights ? { lights: options.lights } : {} });
65418
- const effects = createEffects(THREE, options.effects ?? ownVfx, random);
65571
+ const worldSink = options.effects ?? ownVfx;
65572
+ const lensSink = worldSink ? {
65573
+ play: (request) => {
65574
+ if (!lens2 || !request.point) return worldSink.play(request);
65575
+ const at3 = toWorldLens(camera2, lensPoint.set(...request.point), worldFovOf(), lens2.fov);
65576
+ return worldSink.play({ ...request, point: [at3.x, at3.y, at3.z] });
65577
+ },
65578
+ stop: (handle) => worldSink.stop(handle)
65579
+ } : void 0;
65580
+ const effects = createEffects(THREE, lensSink, random);
65419
65581
  const hand = new THREE.Group();
65420
65582
  hand.name = VIEWMODEL_NODE_NAME;
65421
65583
  scene.add(hand);
@@ -65441,6 +65603,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65441
65603
  const rootQuaternion = new THREE.Quaternion();
65442
65604
  const muzzleAt = new THREE.Vector3();
65443
65605
  const muzzleAlong = new THREE.Vector3();
65606
+ const posed = [];
65444
65607
  const world = {
65445
65608
  kit,
65446
65609
  scene,
@@ -65454,13 +65617,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65454
65617
  const played = audio?.cue(cue3, profile, lead);
65455
65618
  options.onSound?.({ weapon, cue: cue3, profile, ...played ? { played } : {} });
65456
65619
  },
65457
- schedule: (delay, seconds, step, done) => void tasks.push({ delay, seconds, elapsed: 0, step: (progress) => step(ease(progress)), done })
65620
+ afterPose: (emit2) => void posed.push(emit2),
65621
+ schedule: (delay, seconds, step, done) => void tasks.push({ delay, seconds, elapsed: 0, step: (progress) => step(ease2(progress)), done })
65458
65622
  };
65459
65623
  const heatMax = arsenal.rules.heat.displayMax || HEAT_DISPLAY_MAX_FALLBACK;
65460
65624
  const visual = arsenal.rules.heat.visual;
65461
65625
  const hasSuppressor = (parts) => parts.some((part) => arsenal.attachments.get(part.id)?.kind === "suppressor");
65462
65626
  const release = () => {
65463
65627
  if (!session) return;
65628
+ if (lens2) options.viewmodelFov?.(null);
65629
+ lens2 = null;
65630
+ lensLayer = -1;
65464
65631
  session.attachments?.dispose();
65465
65632
  discardObject3D(session.model.root);
65466
65633
  player = null;
@@ -65500,6 +65667,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65500
65667
  const modelBox = new THREE.Box3().setFromObject(model.root);
65501
65668
  const length2 = modelBox.getSize(new THREE.Vector3()).x;
65502
65669
  modelRear = modelBox.min.x;
65670
+ restBox.copy(modelBox);
65671
+ lens2 = null;
65672
+ lensLayer = -1;
65503
65673
  holder.add(model.root);
65504
65674
  const caliber = caliberOf(arsenal, def);
65505
65675
  const dims = caliber?.cartridge;
@@ -65562,6 +65732,42 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65562
65732
  let reach = 0;
65563
65733
  let pull = 0;
65564
65734
  let lift = 0;
65735
+ const restBox = new THREE.Box3();
65736
+ let lens2 = null;
65737
+ let lensLayer = -1;
65738
+ const lensPoint = new THREE.Vector3();
65739
+ const worldFovOf = () => camera2.fov ?? 72;
65740
+ const setLens = (kind) => {
65741
+ const fov = kind ? VIEW_HOLD[kind].fov : null;
65742
+ if ((lens2?.fov ?? null) !== fov) options.viewmodelFov?.(fov);
65743
+ lens2 = kind && fov !== null ? { kind, fov } : null;
65744
+ const layer3 = lens2 ? FIRST_PERSON_LAYER : 0;
65745
+ if (layer3 === lensLayer && !lens2) return;
65746
+ lensLayer = layer3;
65747
+ hand.traverse((node) => node.layers.set(layer3));
65748
+ };
65749
+ const motion = { phase: 0, swayX: 0, swayY: 0, lastYaw: Number.NaN, lastPitch: Number.NaN };
65750
+ const motionEuler = new THREE.Euler(0, 0, 0, "YXZ");
65751
+ const motionOffset = new THREE.Vector3();
65752
+ const viewMotion = (dt, aim) => {
65753
+ const moving = options.motion?.() ?? { speed: 0, running: false };
65754
+ const calm = 1 - 0.8 * Math.max(0, Math.min(1, aim));
65755
+ motionEuler.setFromQuaternion(camera2.quaternion, "YXZ");
65756
+ const turnYaw = Number.isNaN(motion.lastYaw) || dt <= 0 ? 0 : Math.atan2(Math.sin(motionEuler.y - motion.lastYaw), Math.cos(motionEuler.y - motion.lastYaw)) / dt;
65757
+ const turnPitch = Number.isNaN(motion.lastPitch) || dt <= 0 ? 0 : (motionEuler.x - motion.lastPitch) / dt;
65758
+ motion.lastYaw = motionEuler.y;
65759
+ motion.lastPitch = motionEuler.x;
65760
+ const follow = Math.min(1, dt * VIEW_MOTION.swayRate);
65761
+ motion.swayX += (Math.max(-VIEW_MOTION.swayMax, Math.min(VIEW_MOTION.swayMax, turnYaw * VIEW_MOTION.sway)) - motion.swayX) * follow;
65762
+ motion.swayY += (Math.max(-VIEW_MOTION.swayMax, Math.min(VIEW_MOTION.swayMax, -turnPitch * VIEW_MOTION.sway)) - motion.swayY) * follow;
65763
+ const stride = Math.min(1, moving.speed / VIEW_MOTION.fullSpeed) * (moving.running ? VIEW_MOTION.run : 1);
65764
+ motion.phase += dt * moving.speed * VIEW_MOTION.frequency;
65765
+ return motionOffset.set(
65766
+ (motion.swayX + Math.sin(motion.phase) * VIEW_MOTION.bobX * stride) * calm,
65767
+ (motion.swayY - Math.abs(Math.cos(motion.phase)) * VIEW_MOTION.bobY * stride) * calm,
65768
+ 0
65769
+ );
65770
+ };
65565
65771
  const ahead = new THREE.Vector3();
65566
65772
  const up = new THREE.Vector3();
65567
65773
  const seen = new THREE.Vector3();
@@ -65594,13 +65800,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65594
65800
  const family = session.model.parts.family;
65595
65801
  session.holderScale = 1;
65596
65802
  if (options.firstPerson?.()) {
65597
- placeInView({ THREE, hand, holder, camera: camera2, right: hold?.right ?? null, rear: modelRear, kind: family === "pistol" || family === "revolver" ? "handgun" : family === "rocket" ? "launcher" : "long" });
65803
+ const kind = session.model.parts.carry === "hip" ? "hip" : family === "pistol" || family === "revolver" ? "handgun" : family === "rocket" ? "launcher" : "long";
65804
+ const aim = kind === "hip" ? 0 : options.aim?.() ?? 0;
65805
+ placeInView({ THREE, hand, holder, camera: camera2, right: hold?.right ?? null, box: restBox, kind, aim, offset: viewMotion(dt, aim) });
65806
+ setLens(kind);
65598
65807
  lift = 0;
65599
- pullFromWall(dt);
65808
+ pull = 0;
65600
65809
  return;
65601
65810
  }
65811
+ setLens(null);
65602
65812
  hand.scale.set(1, 1, 1);
65603
- placeOnBody({ THREE, hand, holder, frame: options.bodyHold?.() ?? eyeFrame(), rear: modelRear, lengthMetres: session.lengthMetres, handgun: family === "pistol" || family === "revolver", right: hold?.right ?? null, left: hold?.left ?? null });
65813
+ placeOnBody({ THREE, hand, holder, frame: options.bodyHold?.() ?? eyeFrame(), rear: modelRear, lengthMetres: session.lengthMetres, handgun: family === "pistol" || family === "revolver", hip: session.model.parts.carry === "hip", right: hold?.right ?? null, left: hold?.left ?? null });
65604
65814
  hand.updateMatrixWorld(true);
65605
65815
  lift += (liftToKeepInView() - lift) * Math.min(1, dt * HAND_IN_VIEW.rate);
65606
65816
  if (lift > 1e-4) hand.position.addScaledVector(up.set(0, 1, 0).applyQuaternion(camera2.quaternion), lift);
@@ -65676,6 +65886,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65676
65886
  if (session) {
65677
65887
  place(dt);
65678
65888
  player?.update(dt);
65889
+ if (posed.length) {
65890
+ hand.updateMatrixWorld(true);
65891
+ for (const emit2 of posed.splice(0)) emit2();
65892
+ }
65679
65893
  const wantsMagazine = reloading || options.tool?.().reloading === true;
65680
65894
  magazineHand += ((wantsMagazine ? 1 : 0) - magazineHand) * Math.min(1, dt * HAND_HOLD.magRate);
65681
65895
  const tool2 = options.tool?.();
@@ -65691,10 +65905,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65691
65905
  }
65692
65906
  secondsSinceShot += dt;
65693
65907
  if (tool2 && tool2.heat !== void 0 && tool2.heat * heatMax > visual.smokeFrom && secondsSinceShot < visual.smokeSeconds && session.model.parts.muzzle) {
65694
- smokeDebt += dt * (tool2.heat * heatMax - visual.smokeFrom) * 0.1;
65908
+ const smokeLevel = Math.min(1, (tool2.heat * heatMax - visual.smokeFrom) / visual.smokeSpan);
65909
+ smokeDebt += dt * smokeLevel * SMOKE_WISPS_PER_SECOND;
65695
65910
  while (smokeDebt > 1) {
65696
65911
  smokeDebt -= 1;
65697
- const k = 0.5 + (tool2.heat * heatMax - visual.smokeFrom) / 60;
65912
+ const k = 0.5 + smokeLevel * SMOKE_THICKNESS_GAIN;
65698
65913
  effects.smoke(worldPositionOf(session, session.model.parts.muzzle, new THREE.Vector3()), new THREE.Vector3((random() - 0.5) * 0.015, 0.07 + random() * 0.08, (random() - 0.5) * 0.015), 4e-3 * k, 1.2 + random() * 0.6, Math.min(0.18, 0.06 + k * 0.04));
65699
65914
  }
65700
65915
  }
@@ -65721,6 +65936,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65721
65936
  if (!session || !mark) return null;
65722
65937
  hand.updateMatrixWorld(true);
65723
65938
  const at3 = worldPositionOf(session, mark, muzzleAt);
65939
+ if (lens2) toWorldLens(camera2, at3, worldFovOf(), lens2.fov);
65724
65940
  const along = muzzleAlong.set(1, 0, 0).applyQuaternion(session.model.root.getWorldQuaternion(rootQuaternion)).normalize();
65725
65941
  const kind = session.def.action.kind;
65726
65942
  return { position: [at3.x, at3.y, at3.z], direction: [along.x, along.y, along.z], drawsFlash: kind === "firearm" || kind === "launcher" };
@@ -65730,7 +65946,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65730
65946
  hand.updateMatrixWorld(true);
65731
65947
  const targets = targetsOf(THREE, hold, session.model.root.getWorldQuaternion(rootQuaternion), magazineHand, hold.works ? workWeights(hold.works, player?.running ?? []) : void 0);
65732
65948
  const trigger = session.model.parts.trigger;
65733
- if (trigger) targets.fingers = { index: indexCurl(trigger.rotation.z - triggerRest, clock - lastShot) };
65949
+ if (session.model.parts.carry === "hip") targets.fingers = { index: 1 };
65950
+ else if (trigger) targets.fingers = { index: indexCurl(trigger.rotation.z - triggerRest, clock - lastShot) };
65734
65951
  return limitSupportSpan(targets);
65735
65952
  },
65736
65953
  get inHand() {
@@ -65780,6 +65997,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
65780
65997
  ...options.contact ? { contact: options.contact } : {},
65781
65998
  ...options.bodyHold ? { bodyHold: options.bodyHold } : {},
65782
65999
  ...options.firstPerson ? { firstPerson: options.firstPerson } : {},
66000
+ ...options.viewmodelFov ? { viewmodelFov: options.viewmodelFov } : {},
66001
+ ...options.aim ? { aim: options.aim } : {},
66002
+ ...options.motion ? { motion: options.motion } : {},
65783
66003
  ...options.effects ? { effects: options.effects } : {},
65784
66004
  ...options.flyRocket === void 0 ? {} : { flyRocket: options.flyRocket },
65785
66005
  ...options.particleLimit ? { particleLimit: options.particleLimit } : {},
@@ -66895,7 +67115,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
66895
67115
  fuseLength: "Dynamite fuse length",
66896
67116
  fuseLengthHint: "Metres of safety fuse (131 s per metre): a long one is set down lit, for a delayed trap",
66897
67117
  layers: { muzzle_flash: "Muzzle flash", casings: "Ejected casings", magazines: "Dropped magazines" },
66898
- metrics: { casings_live: "Casings in the scene", magazines_live: "Magazines in the scene", heat: "Barrel heat" }
67118
+ metrics: { casings_live: "Casings in the scene", magazines_live: "Magazines in the scene", heat: "Barrel temperature above ambient", rounds_to_warning: "Rounds to the heat warning (cyclic rate, from cold)" }
66899
67119
  },
66900
67120
  "pt-BR": {
66901
67121
  group: "Armas",
@@ -66931,7 +67151,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
66931
67151
  fuseLength: "Pavio da dinamite",
66932
67152
  fuseLengthHint: "Metros de pavio de seguran\xE7a (131 s por metro): um longo \xE9 posto no ch\xE3o aceso, para armadilha com atraso",
66933
67153
  layers: { muzzle_flash: "Clar\xE3o do cano", casings: "C\xE1psulas ejetadas", magazines: "Pentes que caem" },
66934
- metrics: { casings_live: "C\xE1psulas na cena", magazines_live: "Pentes na cena", heat: "Calor do cano" }
67154
+ metrics: { casings_live: "C\xE1psulas na cena", magazines_live: "Pentes na cena", heat: "Temperatura do cano acima do ambiente", rounds_to_warning: "Tiros at\xE9 o aviso de calor (cad\xEAncia, a frio)" }
66935
67155
  }
66936
67156
  };
66937
67157
  var textOf = (locale) => TEXT[locale] ?? TEXT.en;
@@ -67055,7 +67275,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
67055
67275
  let host = null;
67056
67276
  let soundDistance = 0;
67057
67277
  let soundIndoors = false;
67058
- const heatPercent = () => Math.min(100, (host?.tool().heat ?? 0) * 100);
67278
+ const barrelCelsius = () => (host?.tool().heat ?? 0) * arsenal.rules.heat.displayMax;
67279
+ const roundsToWarning2 = () => {
67280
+ const weaponId = host?.tool().weapon;
67281
+ const rounds = weaponId ? roundsToWarningOf(arsenal, weaponId) : Infinity;
67282
+ return Number.isFinite(rounds) ? rounds : "-";
67283
+ };
67059
67284
  return {
67060
67285
  id: "weapons_area",
67061
67286
  name: text.name,
@@ -67108,7 +67333,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
67108
67333
  metrics: () => [
67109
67334
  { id: "casings_live", name: text.metrics.casings_live, read: () => fx?.casings ?? 0 },
67110
67335
  { id: "magazines_live", name: text.metrics.magazines_live, read: () => fx?.magazines ?? 0 },
67111
- { id: "heat", name: text.metrics.heat, unit: "%", max: 100, digits: 0, read: heatPercent },
67336
+ { id: "heat", name: text.metrics.heat, unit: "\xB0C", max: arsenal.rules.heat.displayMax, digits: 0, read: barrelCelsius },
67337
+ { id: "rounds_to_warning", name: text.metrics.rounds_to_warning, read: roundsToWarning2 },
67112
67338
  ...accessoryMetrics(accessoryText, () => host?.tool() ?? EMPTY_TOOL)
67113
67339
  ],
67114
67340
  attach(lent) {
@@ -67151,6 +67377,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
67151
67377
  contact: createLaserRaycast(lent.THREE, lent.scene, lent.camera, { now: () => simulatedMs }),
67152
67378
  ...lent.holdFrame ? { bodyHold: () => lent.holdFrame?.() ?? null } : {},
67153
67379
  firstPerson: () => lent.player().cameraMode === "first",
67380
+ // the lens of the gun in first person, how far it is brought to the eye (the right button) and how the body moves (the bob and the sway follow it)
67381
+ ...lent.setViewmodelFov ? { viewmodelFov: (degrees) => lent.setViewmodelFov?.(degrees) } : {},
67382
+ aim: () => accessories.aimProgress,
67383
+ motion: () => ({ speed: lent.player().speed, running: lent.player().running }),
67154
67384
  ...lent.effects ? { effects: lent.effects } : {},
67155
67385
  ...lent.particleLimit ? { particleLimit: lent.particleLimit } : {},
67156
67386
  ...isLightBudget(lent.lights) ? { lights: lent.lights } : {},