@adatechnology/game-test-room 0.27.0 → 0.28.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.
package/dist/index.d.ts CHANGED
@@ -485,7 +485,9 @@ interface RoomAvatar {
485
485
  */
486
486
  eye?(out?: THREE_NS.Vector3): THREE_NS.Vector3;
487
487
  /** Hides the head and neck from `camera` while the view is `first` (shadows, mirrors and other cameras keep seeing them). */
488
- setView?(mode: CameraMode, camera: THREE_NS.Object3D | null): void;
488
+ setView?(mode: CameraMode, camera: THREE_NS.Object3D | null, options?: {
489
+ readonly arms?: boolean;
490
+ }): void;
489
491
  /**
490
492
  * Plays the strike `clipId` (on the body, in both views) and answers the seconds until its impact, or null when this avatar has no such clip. The room then waits that long
491
493
  * before the blow lands (`strikeClips`); an avatar without `strike`, or one that answers null, gets the blow at the click.
@@ -704,6 +706,19 @@ interface SceneryTap {
704
706
  /** Turn about the vertical axis, radians (default 0). */
705
707
  rotationY?: number;
706
708
  }
709
+ /** What the room needs of first-person arms: a root it puts in the scene, and the pose of each frame (the eye, the hand targets of the tool and the stance, how fast the body moves). */
710
+ interface RoomFirstPersonArms {
711
+ readonly root: THREE_NS.Object3D;
712
+ update(dt: number, state: {
713
+ camera: THREE_NS.Object3D;
714
+ targets?: HandTargets | null;
715
+ stance?: string;
716
+ speed?: number;
717
+ running?: boolean;
718
+ crouch?: boolean;
719
+ }): void;
720
+ dispose(): void;
721
+ }
707
722
  interface TestRoomOptions {
708
723
  THREE: ThreeNamespace;
709
724
  /** Element the bench fills (canvas + HUD). */
@@ -790,6 +805,11 @@ interface TestRoomOptions {
790
805
  ui?: boolean;
791
806
  /** The player's body (see `TestRoom.setAvatar`). */
792
807
  avatar?: RoomAvatar;
808
+ /**
809
+ * Arms drawn for the first-person view (the viewmodel arms, `createFirstPersonArms` of game-characters-three fits it): in first person the room hides the body's arms from the eye, draws
810
+ * these on `FIRST_PERSON_LAYER` with the tool in the hands, and brings their hands to the tool's grips. Absent: the body's arms are the ones seen.
811
+ */
812
+ firstPersonArms?: RoomFirstPersonArms;
793
813
  /** The view the room starts in (default `first`; the V key and `TestRoom.setCameraMode` change it). */
794
814
  cameraMode?: CameraMode;
795
815
  /**
@@ -1299,4 +1319,4 @@ declare function encodeBenchState(controls: readonly BenchControl[], layers: rea
1299
1319
  /** Reads an encoded state as hostile input: unknown ids, NaN, options not offered and malformed pairs are dropped; numbers are clamped. */
1300
1320
  declare function decodeBenchState(encoded: string, controls: readonly ModuleControl[], layers: readonly ModuleLayer[]): Map<string, ControlValue>;
1301
1321
 
1302
- export { type AnimationInfo, type BenchControl, type BenchHandle, type BenchLayer, type BuildFromSpecOptions, type CameraMode, DEFAULT_HAND_STANCES, DEFAULT_STRIKE_CLIPS, DEFAULT_TRIALS, type HudState, type InspectInfo, type KickLeg, LOADING_CLASS, LOADING_CSS, type LoadingOptions, type LoadingState, type LogEntry, MATERIAL_COLORS, type ModuleReport, type NewEntry, type NewLogEntry, type ProjectileModel, type ProjectileQuery, type PunchHand, type RoomAvatar, type RoomAvatarState, type RoomDoorEvent, type RoomDoorState, type RoomFirstPersonState, type RoomGraphicsOptions, type RoomLoading, type RoomMetricId, type RoomPushEvent, type RoomPushState, type RoomReachView, type RoomSeat, type RoomStrings, type ScenarioContext, type ScenarioReport, type SceneryObject, type SceneryTap, type SpawnedObject, type StrikeComboOption, TRIAL_KINDS, type TestRoom, type TestRoomItem, type TestRoomLocale, type TestRoomLog, type TestRoomOptions, type TestRoomScenario, type TestTable, type TestTableContext, type TestTableObject, type TestTableOptions, type TestTablePage, type ThirdPersonOptions, type ThreeNamespace, type ToolMenuGroup, type ToolMenuSection, type ToolMenuTool, type TrialKind, type TrialTool, type UseStatus, type WritableLog, brickWallSpec, buildFromSpec, createLoading, createLog, createTestRoom, createTestTable, decodeBenchState, encodeBenchState, fitRow, loadingMarkup, locales, nextPaint, resolveStrings, rowCentres, trialView };
1322
+ export { type AnimationInfo, type BenchControl, type BenchHandle, type BenchLayer, type BuildFromSpecOptions, type CameraMode, DEFAULT_HAND_STANCES, DEFAULT_STRIKE_CLIPS, DEFAULT_TRIALS, type HudState, type InspectInfo, type KickLeg, LOADING_CLASS, LOADING_CSS, type LoadingOptions, type LoadingState, type LogEntry, MATERIAL_COLORS, type ModuleReport, type NewEntry, type NewLogEntry, type ProjectileModel, type ProjectileQuery, type PunchHand, type RoomAvatar, type RoomAvatarState, type RoomDoorEvent, type RoomDoorState, type RoomFirstPersonArms, type RoomFirstPersonState, type RoomGraphicsOptions, type RoomLoading, type RoomMetricId, type RoomPushEvent, type RoomPushState, type RoomReachView, type RoomSeat, type RoomStrings, type ScenarioContext, type ScenarioReport, type SceneryObject, type SceneryTap, type SpawnedObject, type StrikeComboOption, TRIAL_KINDS, type TestRoom, type TestRoomItem, type TestRoomLocale, type TestRoomLog, type TestRoomOptions, type TestRoomScenario, type TestTable, type TestTableContext, type TestTableObject, type TestTableOptions, type TestTablePage, type ThirdPersonOptions, type ThreeNamespace, type ToolMenuGroup, type ToolMenuSection, type ToolMenuTool, type TrialKind, type TrialTool, type UseStatus, type WritableLog, brickWallSpec, buildFromSpec, createLoading, createLog, createTestRoom, createTestTable, decodeBenchState, encodeBenchState, fitRow, loadingMarkup, locales, nextPaint, resolveStrings, rowCentres, trialView };
@@ -473,6 +473,7 @@ var GameTestRoom = (() => {
473
473
  if (!state.opened) return "sealed";
474
474
  return void 0;
475
475
  };
476
+ var FIRST_PERSON_LAYER = 9;
476
477
  var TWIST_LIFT = 0.012;
477
478
  var TURNING = 0.7;
478
479
  var smooth = (k) => k * k * (3 - 2 * k);
@@ -16749,7 +16750,7 @@ var GameTestRoom = (() => {
16749
16750
  const { core, def, random, x, y, z } = context;
16750
16751
  const mark = def.mark;
16751
16752
  const keepsMarks = !core.marklessAt?.(x, z);
16752
- if (keepsMarks && mark.crater && mark.dent && y < 1.2) {
16753
+ if (keepsMarks && mark.crater && mark.dent && y < 1.2 && (core.cratersAt?.(x, z) ?? true)) {
16753
16754
  core.emit({ type: "crater", x, z, radius: mark.crater, depth: mark.dent });
16754
16755
  core.trace?.crater?.(x, z, mark.crater, mark.dent);
16755
16756
  addCrater(core.marks, core.ground, random, { x, z, radius: mark.crater, depth: mark.dent, marks: core.layers.mark });
@@ -18515,6 +18516,7 @@ var GameTestRoom = (() => {
18515
18516
  clock: { time: 0, lastBlast: -1, lod: 1, forcedLod: null },
18516
18517
  groundAt,
18517
18518
  ...options.marklessAt ? { marklessAt: options.marklessAt } : {},
18519
+ ...options.cratersAt ? { cratersAt: options.cratersAt } : {},
18518
18520
  emit: (request) => options.onRequest?.(request)
18519
18521
  };
18520
18522
  function definitionOf(id) {
@@ -38174,7 +38176,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
38174
38176
  dimensions: {
38175
38177
  length: 0.801,
38176
38178
  barrel: 0.559,
38177
- emptyMass: 39
38179
+ emptyMass: 39,
38180
+ barrels: 6,
38181
+ barrelMass: 2
38178
38182
  },
38179
38183
  archetype: "rifle_7_62x39",
38180
38184
  sources: [
@@ -39154,19 +39158,41 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39154
39158
  }
39155
39159
  };
39156
39160
  var DEFAULT_HEAT = {
39157
- cap: 140,
39158
- displayMax: 120,
39159
- decayBase: 1.6,
39160
- decayPerPoint: 0.03,
39161
- faultFrom: 60,
39162
- faultSpan: 40,
39163
- perShot: { shell: 3, big: 6, tiny: 0.5, pistol: 1.2, rifle: 2 },
39161
+ cap: 900,
39162
+ displayMax: 750,
39163
+ faultFrom: 350,
39164
+ faultSpan: 200,
39165
+ specificHeat: 490,
39166
+ barrelShare: { shell: 0.48, big: 0.48, tiny: 0.3, pistol: 0.3, rifle: 0.48 },
39167
+ barrelKilogramsPerMetre: {
39168
+ pistol: 1,
39169
+ revolver: 1.4,
39170
+ smg: 1.2,
39171
+ carbine: 1.6,
39172
+ rifle: 1.6,
39173
+ dmr: 2.4,
39174
+ sniper: 2.4,
39175
+ hunting: 2.4,
39176
+ machine_gun: 3,
39177
+ shotgun: 1.1
39178
+ },
39179
+ barrelFallback: { metres: 0.4, kilogramsPerMetre: 1.6 },
39180
+ cooling: { baseSeconds: 30, secondsPerKilogram: 250 },
39164
39181
  perRocket: 35,
39165
- visual: { smokeFrom: 70, smokeSeconds: 2.5, glowFrom: 80, glowSpan: 40 }
39182
+ visual: { smokeFrom: 250, smokeSpan: 300, smokeSeconds: 2.5, glowFrom: 450, glowSpan: 300 }
39166
39183
  };
39167
39184
  var GALLERY_HEAT = {
39168
39185
  ...DEFAULT_HEAT,
39169
- perShot: { shell: 6, big: 9, tiny: 1.2, pistol: 3, rifle: 4.5 }
39186
+ cap: 140,
39187
+ displayMax: 120,
39188
+ faultFrom: 60,
39189
+ faultSpan: 40,
39190
+ visual: { smokeFrom: 70, smokeSpan: 100, smokeSeconds: 2.5, glowFrom: 80, glowSpan: 40 },
39191
+ gallery: {
39192
+ perShot: { shell: 6, big: 9, tiny: 1.2, pistol: 3, rifle: 4.5 },
39193
+ decayBase: 1.6,
39194
+ decayPerPoint: 0.03
39195
+ }
39170
39196
  };
39171
39197
  var defaultFaultChance = (context) => {
39172
39198
  const factor = context.def.action.kind === "firearm" ? context.def.action.faultFactor ?? 1 : 1;
@@ -39449,17 +39475,55 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39449
39475
  ...explosive.stunSeconds !== void 0 ? { stunSeconds: explosive.stunSeconds } : {}
39450
39476
  };
39451
39477
  };
39478
+ var EPS22 = 1e-9;
39479
+ var MANUAL_MODES = /* @__PURE__ */ new Set(["pump", "bolt", "lever"]);
39480
+ var weaponSpeed = (action, caliber) => action.muzzleSpeed ?? caliber.muzzleSpeed * (action.barrel ?? 1);
39481
+ var weaponNoise = (action, caliber) => action.noise ?? caliber.noise ?? 0;
39482
+ var timingsOf = (action) => "timings" in action ? action.timings : void 0;
39483
+ var drawSeconds = (action, rules) => timingsOf(action)?.draw ?? rules.draw;
39484
+ var clearSeconds = (action, rules) => timingsOf(action)?.clear ?? rules.clear;
39485
+ var manualCycleSeconds = (action, mode) => MANUAL_MODES.has(mode) ? action.timings?.cycle ?? 0 : 0;
39486
+ var planRounds = (action, mode, ammo, firstAt, frameEnd) => {
39487
+ const interval = 60 / action.rpm;
39488
+ const times = [];
39489
+ if (mode === "auto") {
39490
+ for (let t = firstAt; times.length < ammo && (times.length === 0 ? t <= frameEnd + EPS22 : t < frameEnd - EPS22); t += interval) times.push(t);
39491
+ return times;
39492
+ }
39493
+ if (mode === "burst") {
39494
+ const count = Math.min(action.burst ?? 3, ammo);
39495
+ for (let k = 0; k < count; k++) times.push(firstAt + k * interval);
39496
+ return times;
39497
+ }
39498
+ if (mode === "volley") return Array.from({ length: Math.min(action.magazine, ammo) }, () => firstAt);
39499
+ return [firstAt];
39500
+ };
39452
39501
  var SUBSTEP_SECONDS = 1 / 60;
39453
- var decayHeat = (heat, seconds, rules) => {
39502
+ var barrelMassOf = (def, rules) => {
39503
+ const dimensions = def.dimensions;
39504
+ const kilogramsPerMetre = rules.barrelKilogramsPerMetre[def.category] ?? rules.barrelFallback.kilogramsPerMetre;
39505
+ const single = dimensions?.barrelMass ?? (dimensions?.barrel ?? rules.barrelFallback.metres) * kilogramsPerMetre;
39506
+ return single * (dimensions?.barrels ?? 1);
39507
+ };
39508
+ var coolingTimeConstant = (barrelMass, rules) => rules.cooling.baseSeconds + rules.cooling.secondsPerKilogram * barrelMass;
39509
+ var decayHeat = (heat, seconds, barrelMass, rules) => {
39510
+ if (heat <= 0) return 0;
39511
+ if (rules.gallery) return decayGallery(heat, seconds, rules.gallery);
39512
+ return heat * Math.exp(-seconds / coolingTimeConstant(barrelMass, rules));
39513
+ };
39514
+ var decayGallery = (heat, seconds, gallery) => {
39454
39515
  const steps = Math.max(1, Math.ceil(seconds / SUBSTEP_SECONDS - 1e-9));
39455
39516
  const step = seconds / steps;
39456
39517
  let current = heat;
39457
- for (let i = 0; i < steps && current > 0; i++) current = Math.max(0, current - step * (rules.decayBase + current * rules.decayPerPoint));
39518
+ for (let i = 0; i < steps && current > 0; i++) current = Math.max(0, current - step * (gallery.decayBase + current * gallery.decayPerPoint));
39458
39519
  return current;
39459
39520
  };
39460
- var heatPerRound = (action, caliber, rules) => {
39521
+ var heatPerRound = ({ action, caliber, energy, barrelMass, rules }) => {
39461
39522
  if (action.kind === "launcher") return rules.perRocket;
39462
- return caliber?.caseType ? rules.perShot[caliber.caseType] : 0;
39523
+ const caseType = caliber?.caseType;
39524
+ if (!caseType) return 0;
39525
+ if (rules.gallery) return rules.gallery.perShot[caseType];
39526
+ return rules.barrelShare[caseType] * energy / (barrelMass * rules.specificHeat);
39463
39527
  };
39464
39528
  var burstNoise = (base, recentShots, rules) => base * (1 + Math.min(rules.cap, rules.step * recentShots));
39465
39529
  var magazineOut = { stage: "magazine_out", at: 0 };
@@ -39586,29 +39650,6 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39586
39650
  return roll.ammoQuality.faultChance > 0 && roll.random() < roll.ammoQuality.faultChance ? "misfire" : null;
39587
39651
  };
39588
39652
  var isDudWithoutJam = (action, fault) => fault === "misfire" && action.kind === "firearm" && MISFIRE_ONLY.has(action.family);
39589
- var EPS22 = 1e-9;
39590
- var MANUAL_MODES = /* @__PURE__ */ new Set(["pump", "bolt", "lever"]);
39591
- var weaponSpeed = (action, caliber) => action.muzzleSpeed ?? caliber.muzzleSpeed * (action.barrel ?? 1);
39592
- var weaponNoise = (action, caliber) => action.noise ?? caliber.noise ?? 0;
39593
- var timingsOf = (action) => "timings" in action ? action.timings : void 0;
39594
- var drawSeconds = (action, rules) => timingsOf(action)?.draw ?? rules.draw;
39595
- var clearSeconds = (action, rules) => timingsOf(action)?.clear ?? rules.clear;
39596
- var manualCycleSeconds = (action, mode) => MANUAL_MODES.has(mode) ? action.timings?.cycle ?? 0 : 0;
39597
- var planRounds = (action, mode, ammo, firstAt, frameEnd) => {
39598
- const interval = 60 / action.rpm;
39599
- const times = [];
39600
- if (mode === "auto") {
39601
- for (let t = firstAt; times.length < ammo && (times.length === 0 ? t <= frameEnd + EPS22 : t < frameEnd - EPS22); t += interval) times.push(t);
39602
- return times;
39603
- }
39604
- if (mode === "burst") {
39605
- const count = Math.min(action.burst ?? 3, ammo);
39606
- for (let k = 0; k < count; k++) times.push(firstAt + k * interval);
39607
- return times;
39608
- }
39609
- if (mode === "volley") return Array.from({ length: Math.min(action.magazine, ammo) }, () => firstAt);
39610
- return [firstAt];
39611
- };
39612
39653
  var activeParts = (fitted, catalogue) => fitted.flatMap((part) => {
39613
39654
  const def = catalogue.get(part.id);
39614
39655
  const isOn = def?.kind !== "light" && def?.kind !== "laser" ? true : part.on === true;
@@ -39841,6 +39882,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39841
39882
  get safety() {
39842
39883
  return this._safety;
39843
39884
  }
39885
+ /** Kg of steel that heat up with the shots. */
39886
+ get barrelMass() {
39887
+ return barrelMassOf(this.def, this.rules.heat);
39888
+ }
39844
39889
  get heat() {
39845
39890
  return this._heat;
39846
39891
  }
@@ -39893,7 +39938,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
39893
39938
  }
39894
39939
  advance(seconds) {
39895
39940
  if (seconds <= 0) return;
39896
- this._heat = decayHeat(this._heat, seconds, this.rules.heat);
39941
+ this._heat = decayHeat(this._heat, seconds, this.barrelMass, this.rules.heat);
39897
39942
  this.clock += seconds;
39898
39943
  }
39899
39944
  /** Complete what falls at or before `t`: scheduled events and the reload steps. */
@@ -40445,7 +40490,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
40445
40490
  res.shots.push(shot);
40446
40491
  }
40447
40492
  this._ammo -= 1;
40448
- this._heat = Math.min(this.rules.heat.cap, this._heat + heatPerRound(a, cal, this.rules.heat) * this.load.heatFactor);
40493
+ 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);
40449
40494
  this.readyAt = t + 60 / a.rpm;
40450
40495
  this.extra = Math.min(bloomNow + (a.bloomDeg ?? 0), a.spreadDeg * (BLOOM_CAP - 1));
40451
40496
  this.extraClock = t;
@@ -40534,7 +40579,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
40534
40579
  }
40535
40580
  const t = Math.max(start, this.readyAt);
40536
40581
  this._ammo -= 1;
40537
- this._heat = Math.min(this.rules.heat.cap, this._heat + heatPerRound(a, null, this.rules.heat));
40582
+ this._heat = Math.min(this.rules.heat.cap, this._heat + heatPerRound({ action: a, caliber: null, energy: 0, barrelMass: this.barrelMass, rules: this.rules.heat }));
40538
40583
  this.emit({ type: "shot", shots: 0, rounds: 1, mode: this.mode, at: t });
40539
40584
  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] });
40540
40585
  }
@@ -49409,6 +49454,7 @@ void main(){
49409
49454
  }
49410
49455
 
49411
49456
  // src/player.ts
49457
+ var RECOIL_RETURN = { share: 0.7, seconds: 0.25 };
49412
49458
  var SLAM_MIN_SPEED2 = 1.5;
49413
49459
  var LANDING_MIN_SPEED = 1;
49414
49460
  var EYE = 1.7;
@@ -49438,6 +49484,18 @@ void main(){
49438
49484
  const cam = o.camera;
49439
49485
  let yaw = 0;
49440
49486
  let pitch = 0;
49487
+ const recoilReturn = o.recoilReturn ?? RECOIL_RETURN;
49488
+ const recoilDebt = { pitch: 0, yaw: 0 };
49489
+ const returnRecoil = (dt) => {
49490
+ if (Math.abs(recoilDebt.pitch) + Math.abs(recoilDebt.yaw) < 1e-6) return;
49491
+ const k = 1 - Math.exp(-3 * dt / Math.max(1e-3, recoilReturn.seconds));
49492
+ const back = { pitch: recoilDebt.pitch * k, yaw: recoilDebt.yaw * k };
49493
+ recoilDebt.pitch -= back.pitch;
49494
+ recoilDebt.yaw -= back.yaw;
49495
+ pitch = Math.max(-1.5, Math.min(1.5, pitch - back.pitch));
49496
+ yaw -= back.yaw;
49497
+ apply();
49498
+ };
49441
49499
  let y = 0;
49442
49500
  let eye = EYE;
49443
49501
  let crouch = 0;
@@ -49779,6 +49837,7 @@ void main(){
49779
49837
  return stunLeft;
49780
49838
  },
49781
49839
  update(dt) {
49840
+ returnRecoil(dt);
49782
49841
  if (stunLeft > 0) {
49783
49842
  stunLeft = Math.max(0, stunLeft - dt);
49784
49843
  stunPhase += dt * 2.4;
@@ -49827,6 +49886,8 @@ void main(){
49827
49886
  },
49828
49887
  setPose(position, yw, pt2) {
49829
49888
  knocked = null;
49889
+ recoilDebt.pitch = 0;
49890
+ recoilDebt.yaw = 0;
49830
49891
  px.x = position[0];
49831
49892
  px.z = position[2];
49832
49893
  floorLevel = floorHere();
@@ -49839,6 +49900,8 @@ void main(){
49839
49900
  },
49840
49901
  look(position, target) {
49841
49902
  knocked = null;
49903
+ recoilDebt.pitch = 0;
49904
+ recoilDebt.yaw = 0;
49842
49905
  px.x = position[0];
49843
49906
  px.z = position[2];
49844
49907
  floorLevel = floorHere();
@@ -49853,8 +49916,11 @@ void main(){
49853
49916
  apply();
49854
49917
  },
49855
49918
  kick(pitchDeg, yawDeg) {
49919
+ const before = pitch;
49856
49920
  pitch = Math.max(-1.5, Math.min(1.5, pitch + pitchDeg * Math.PI / 180));
49857
49921
  yaw += yawDeg * Math.PI / 180;
49922
+ recoilDebt.pitch += (pitch - before) * recoilReturn.share;
49923
+ recoilDebt.yaw += yawDeg * Math.PI / 180 * recoilReturn.share;
49858
49924
  apply();
49859
49925
  },
49860
49926
  moveBy(dx, dz) {
@@ -51034,6 +51100,8 @@ void main(){
51034
51100
  { id: "dry_clay", strata: [{ material: "clay", thickness: 0.35 }, { material: "soil", thickness: 1.5 }, { material: "stone", thickness: 0.5 }] },
51035
51101
  { id: "snow", strata: [{ material: "snow", thickness: 0.3 }, { material: "soil", thickness: 1.5 }, { material: "stone", thickness: 0.5 }] },
51036
51102
  { id: "cobblestone", strata: [{ material: "cobble", thickness: 0.12 }, { material: "sand", thickness: 0.06 }, { material: "gravel", thickness: 0.2 }, { material: "soil", thickness: 1.2 }] },
51103
+ // rock at the surface (an outcrop, a quarry floor): a blast only spalls it and a shot chips it
51104
+ { id: "bedrock", surfacePattern: "rock", strata: [{ material: "stone", thickness: 3 }] },
51037
51105
  { id: "paver_floor", strata: [{ material: "paver", thickness: 0.08 }, { material: "sand", thickness: 0.05 }, { material: "gravel", thickness: 0.15 }, { material: "soil", thickness: 1.2 }] },
51038
51106
  { id: "shallow_water", water: { liquid: "water", depth: 0.3, drag: 0.65, blowShare: WATER_BLOW_SHARE }, strata: [{ material: "mud", thickness: 0.15 }, { material: "soil", thickness: 1.5 }, { material: "stone", thickness: 0.5 }] }
51039
51107
  ];
@@ -52268,7 +52336,7 @@ void main(){
52268
52336
  }
52269
52337
  };
52270
52338
  }
52271
- var TERRAIN_PATTERNS = ["plain", "grass", "soil", "sand", "asphalt", "concrete", "tile", "planks", "stone", "mud", "gravel", "clay", "snow", "cobble", "paver"];
52339
+ var TERRAIN_PATTERNS = ["plain", "grass", "soil", "sand", "asphalt", "concrete", "tile", "planks", "stone", "mud", "gravel", "clay", "snow", "cobble", "paver", "rock"];
52272
52340
  var STRIDE = 0.72;
52273
52341
  var FOOT_LENGTH = 0.15;
52274
52342
  var FOOT_APART = 0.11;
@@ -52316,10 +52384,10 @@ void main(){
52316
52384
  };
52317
52385
  }
52318
52386
 
52319
- // ../game-terrain-three/dist/chunk-B5EA72GU.js
52387
+ // ../game-terrain-three/dist/chunk-6YHNPX3W.js
52320
52388
  var PROFILE_NAMES = {
52321
- en: { lawn: "Lawn", dirt: "Dirt", sand: "Sand", road: "Asphalt road", concrete_slab: "Concrete slab", suspended_slab: "Slab over a void", tile_floor: "Tile floor", wood_floor: "Wood floor", stone_paving: "Stone paving" },
52322
- "pt-BR": { lawn: "Grama", dirt: "Terra", sand: "Areia", road: "Asfalto", concrete_slab: "Laje de concreto", suspended_slab: "Laje sobre v\xE3o", tile_floor: "Piso cer\xE2mico", wood_floor: "Assoalho", stone_paving: "Cal\xE7amento de pedra" }
52389
+ en: { lawn: "Lawn", dirt: "Dirt", sand: "Sand", road: "Asphalt road", concrete_slab: "Concrete slab", suspended_slab: "Slab over a void", tile_floor: "Tile floor", wood_floor: "Wood floor", stone_paving: "Stone paving", mud: "Mud", gravel_bed: "Gravel", dry_clay: "Dry clay", snow: "Snow", cobblestone: "Cobblestones", paver_floor: "Concrete pavers", shallow_water: "Shallow water", bedrock: "Bare rock" },
52390
+ "pt-BR": { lawn: "Grama", dirt: "Terra", sand: "Areia", road: "Asfalto", concrete_slab: "Laje de concreto", suspended_slab: "Laje sobre v\xE3o", tile_floor: "Piso cer\xE2mico", wood_floor: "Assoalho", stone_paving: "Cal\xE7amento de pedra", mud: "Lama", gravel_bed: "Cascalho", dry_clay: "Barro seco", snow: "Neve", cobblestone: "Paralelep\xEDpedo", paver_floor: "Bloquete", shallow_water: "\xC1gua rasa", bedrock: "Rocha" }
52323
52391
  };
52324
52392
  function terrainProfileName(profileId, localeId) {
52325
52393
  return (PROFILE_NAMES[localeId] ?? PROFILE_NAMES.en)[profileId] ?? profileId;
@@ -52329,20 +52397,20 @@ void main(){
52329
52397
  name: "Terrain",
52330
52398
  group: "Terrain",
52331
52399
  weight: "Concrete block (60 kg)",
52332
- surfaces: { dirt: "Dirt", lawn: "Lawn", sand: "Sand", road: "Asphalt", concrete_slab: "Concrete slab", suspended_slab: "Slab over a void", wood_floor: "Wood floor", tile_floor: "Tile", stone_paving: "Stone paving", mud: "Mud", gravel_bed: "Gravel", dry_clay: "Dry clay", snow: "Snow", cobblestone: "Cobblestones", paver_floor: "Concrete pavers", shallow_water: "Shallow water" }
52400
+ surfaces: { dirt: "Dirt", lawn: "Lawn", sand: "Sand", road: "Asphalt", concrete_slab: "Concrete slab", suspended_slab: "Slab over a void", wood_floor: "Wood floor", tile_floor: "Tile", stone_paving: "Stone paving", mud: "Mud", gravel_bed: "Gravel", dry_clay: "Dry clay", snow: "Snow", cobblestone: "Cobblestones", paver_floor: "Concrete pavers", shallow_water: "Shallow water", bedrock: "Bare rock" }
52333
52401
  },
52334
52402
  "pt-BR": {
52335
52403
  name: "Terreno",
52336
52404
  group: "Terreno",
52337
52405
  weight: "Bloco de concreto (60 kg)",
52338
- surfaces: { dirt: "Terra", lawn: "Grama", sand: "Areia", road: "Asfalto", concrete_slab: "Laje de concreto", suspended_slab: "Laje sobre v\xE3o", wood_floor: "Assoalho", tile_floor: "Cer\xE2mica", stone_paving: "Pedra", mud: "Lama", gravel_bed: "Cascalho", dry_clay: "Barro seco", snow: "Neve", cobblestone: "Paralelep\xEDpedo", paver_floor: "Bloquete", shallow_water: "\xC1gua rasa" }
52406
+ surfaces: { dirt: "Terra", lawn: "Grama", sand: "Areia", road: "Asfalto", concrete_slab: "Laje de concreto", suspended_slab: "Laje sobre v\xE3o", wood_floor: "Assoalho", tile_floor: "Cer\xE2mica", stone_paving: "Pedra", mud: "Lama", gravel_bed: "Cascalho", dry_clay: "Barro seco", snow: "Neve", cobblestone: "Paralelep\xEDpedo", paver_floor: "Bloquete", shallow_water: "\xC1gua rasa", bedrock: "Rocha" }
52339
52407
  }
52340
52408
  };
52341
52409
  var locale = (id) => LOCALES[id] ?? LOCALES.en;
52342
- var TERRAIN_STRIPS = ["lawn", "dirt", "sand", "mud", "dry_clay", "gravel_bed", "snow", "shallow_water", "road", "cobblestone", "paver_floor", "stone_paving", "concrete_slab", "suspended_slab", "tile_floor", "wood_floor"];
52343
- var LOT_WIDTH = 3.4;
52410
+ var TERRAIN_STRIPS = ["lawn", "dirt", "sand", "mud", "dry_clay", "gravel_bed", "snow", "shallow_water", "bedrock", "road", "cobblestone", "paver_floor", "stone_paving", "concrete_slab", "suspended_slab", "tile_floor", "wood_floor"];
52411
+ var LOT_WIDTH = 3;
52344
52412
  var LOT_DEPTH = 3.8;
52345
- var LOTS_PER_ROW = 8;
52413
+ var LOTS_PER_ROW = 9;
52346
52414
  var LOTS_Z1 = -1.4;
52347
52415
  var lotRect = (index) => {
52348
52416
  const row = Math.floor(index / LOTS_PER_ROW);
@@ -52435,11 +52503,18 @@ vec3 terrainSurface(vec3 base, vec2 xz, float kind) {
52435
52503
  float seam = max(tsLine(xz.y, 0.2, 0.014), tsLine(along, 0.3, 0.014));
52436
52504
  return base * (0.72 + 0.6 * tsHash(cell)) * (0.82 + 0.36 * tsFbm(xz * 22.0)) * (1.0 - 0.8 * seam);
52437
52505
  }
52438
- float row = floor(xz.y / 0.2);
52439
- float along = xz.x - mod(abs(row), 2.0) * 0.1;
52440
- vec2 cell = vec2(floor(along / 0.2), row);
52441
- float seam = max(tsLine(xz.y, 0.2, 0.008), tsLine(along, 0.2, 0.008));
52442
- return base * (0.86 + 0.26 * tsHash(cell)) * (0.92 + 0.16 * tsNoise(xz * 70.0)) * (1.0 - 0.7 * seam);
52506
+ if (k < 14.5) {
52507
+ float row = floor(xz.y / 0.2);
52508
+ float along = xz.x - mod(abs(row), 2.0) * 0.1;
52509
+ vec2 cell = vec2(floor(along / 0.2), row);
52510
+ float seam = max(tsLine(xz.y, 0.2, 0.008), tsLine(along, 0.2, 0.008));
52511
+ return base * (0.86 + 0.26 * tsHash(cell)) * (0.92 + 0.16 * tsNoise(xz * 70.0)) * (1.0 - 0.7 * seam);
52512
+ }
52513
+ // bare rock: weathered blotches, lichen-dark patches and a few long joints, no grid
52514
+ float blotch = tsFbm(xz * 0.9) * 0.6 + tsFbm(xz * 4.0 + 2.0) * 0.4;
52515
+ float joint = 1.0 - smoothstep(0.0, 0.035, abs(tsNoise(xz * vec2(0.7, 1.9)) - 0.5));
52516
+ float grain = tsNoise(xz * 95.0);
52517
+ return base * (0.62 + 0.75 * blotch) * (0.88 + 0.24 * grain) * (1.0 - 0.6 * joint);
52443
52518
  }
52444
52519
  `
52445
52520
  );
@@ -52468,9 +52543,9 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
52468
52543
  const index = row * terrain.stride + column;
52469
52544
  let material = terrain.vertexMaterial(column, row);
52470
52545
  if (terrain.loose[index] > LOOSE_VISIBLE) {
52471
- const profile = terrain.profileList[terrain.profileIndex[index]];
52472
- const first = profile.strata[0];
52473
- material = terrain.material(first.thickness < THIN_SKIN && profile.strata[1] ? profile.strata[1].material : first.material);
52546
+ const profile2 = terrain.profileList[terrain.profileIndex[index]];
52547
+ const first = profile2.strata[0];
52548
+ material = terrain.material(first.thickness < THIN_SKIN && profile2.strata[1] ? profile2.strata[1].material : first.material);
52474
52549
  }
52475
52550
  const broken = material.pattern === "concrete" && terrain.dug[index] > BROKEN_DEPTH;
52476
52551
  const tone = broken ? BROKEN_TONE : 1;
@@ -52479,7 +52554,9 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
52479
52554
  base[2] = material.color[2] * tone;
52480
52555
  shade[0] = Math.max(MIN_DEPTH_LIGHT, 1 - terrain.dug[index] / DEPTH_SHADE);
52481
52556
  shade[1] = terrain.scorch[index];
52482
- return patternIndex(broken ? "soil" : material.pattern);
52557
+ const profile = terrain.profileList[terrain.profileIndex[index]];
52558
+ const untouched = profile.surfacePattern !== void 0 && terrain.dug[index] <= BROKEN_DEPTH && terrain.loose[index] <= LOOSE_VISIBLE;
52559
+ return patternIndex(broken ? "soil" : untouched ? profile.surfacePattern : material.pattern);
52483
52560
  }
52484
52561
  function chunkVertexCount(terrain, chunk, step) {
52485
52562
  const lastColumn = Math.min(terrain.columns, chunk.column + chunk.cells);
@@ -55519,6 +55596,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
55519
55596
  const scene2 = options.scene ?? new THREE.Scene();
55520
55597
  const camera2 = options.camera ?? new THREE.PerspectiveCamera(72, width() / height(), 0.05, 140);
55521
55598
  if (!options.camera) scene2.add(camera2);
55599
+ camera2.layers.disable(FIRST_PERSON_LAYER);
55522
55600
  const resize = () => {
55523
55601
  const w = width();
55524
55602
  const h = height();
@@ -55625,7 +55703,9 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
55625
55703
  profile: quality,
55626
55704
  ...options.debris?.particles ? { sprites: { kinds: patchSpriteKinds(SPRITE_KINDS, options.debris.particles) } } : {},
55627
55705
  groundAt,
55628
- marklessAt: (x, z) => terrain.waterAt(x, z) !== null
55706
+ marklessAt: (x, z) => terrain.waterAt(x, z) !== null,
55707
+ // the dirt crater of the effects only where the ground is loose; rock, asphalt and a slab keep the soot and the bowl the terrain itself dug
55708
+ cratersAt: (x, z) => terrain.material(terrain.columnAt(x, z).material).reposeDegrees !== void 0
55629
55709
  })
55630
55710
  });
55631
55711
  const renderer = createDestructionRenderer(THREE, {
@@ -56610,10 +56690,21 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
56610
56690
  return { ...pose, position: pose.at(camDistance), free: camDistance };
56611
56691
  };
56612
56692
  const thirdActive = () => cameraMode === "third" && avatar !== null;
56693
+ const firstPersonArms = options.firstPersonArms ?? null;
56694
+ if (firstPersonArms) {
56695
+ firstPersonArms.root.visible = cameraMode === "first";
56696
+ scene2.add(firstPersonArms.root);
56697
+ }
56613
56698
  const syncAvatarVisible = () => {
56699
+ if (firstPersonArms) firstPersonArms.root.visible = cameraMode === "first";
56614
56700
  if (!avatar) return;
56615
56701
  avatar.root.visible = cameraMode === "third" || !!avatar.eye;
56616
- avatar.setView?.(cameraMode, camera2);
56702
+ avatar.setView?.(cameraMode, camera2, { arms: firstPersonArms !== null });
56703
+ };
56704
+ const updateFirstPersonArms = (dt) => {
56705
+ if (!firstPersonArms || cameraMode !== "first") return;
56706
+ firstPersonArms.update(dt, { camera: camera2, targets: lastHand.targets, stance: lastHand.stance, speed: player.speed, running: avatarState.running, crouch: avatarState.crouch });
56707
+ firstPersonArms.root.traverse((node) => node.layers.set(FIRST_PERSON_LAYER));
56617
56708
  };
56618
56709
  const setCameraMode = (mode) => {
56619
56710
  if (mode !== "first" && mode !== "third") return;
@@ -56697,6 +56788,39 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
56697
56788
  };
56698
56789
  let handSource = null;
56699
56790
  let muzzleSource = null;
56791
+ let viewmodelFov = null;
56792
+ const firstPersonCamera = new THREE.PerspectiveCamera(72, 1, 0.01, 30);
56793
+ firstPersonCamera.layers.set(FIRST_PERSON_LAYER);
56794
+ let firstPersonPasses = 0;
56795
+ const drawFirstPersonLayer = () => {
56796
+ if (!firstPersonArms && viewmodelFov === null) return;
56797
+ const renderer2 = gl;
56798
+ if (typeof renderer2.clearDepth !== "function" || typeof renderer2.render !== "function") return;
56799
+ if (firstPersonPasses++ % 60 === 0) {
56800
+ scene2.traverse((node) => {
56801
+ if (node.isLight) node.layers.enable(FIRST_PERSON_LAYER);
56802
+ });
56803
+ }
56804
+ firstPersonCamera.position.copy(camera2.position);
56805
+ firstPersonCamera.quaternion.copy(camera2.quaternion);
56806
+ const fov = viewmodelFov ?? camera2.fov;
56807
+ if (firstPersonCamera.fov !== fov || firstPersonCamera.aspect !== camera2.aspect) {
56808
+ firstPersonCamera.fov = fov;
56809
+ firstPersonCamera.aspect = camera2.aspect;
56810
+ firstPersonCamera.updateProjectionMatrix();
56811
+ }
56812
+ firstPersonCamera.updateMatrixWorld();
56813
+ const { autoClear } = renderer2;
56814
+ const { background, fog } = scene2;
56815
+ renderer2.autoClear = false;
56816
+ scene2.background = null;
56817
+ scene2.fog = null;
56818
+ renderer2.clearDepth();
56819
+ renderer2.render(scene2, firstPersonCamera);
56820
+ scene2.background = background;
56821
+ scene2.fog = fog;
56822
+ renderer2.autoClear = autoClear ?? true;
56823
+ };
56700
56824
  let debugView = null;
56701
56825
  let lastHand = { stance: "relaxed", triggerFinger: null, targets: null };
56702
56826
  const strikeClips = options.strikeClips ?? DEFAULT_STRIKE_CLIPS;
@@ -56844,7 +56968,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
56844
56968
  const line = new THREE.Line(g, tracerMat);
56845
56969
  line.frustumCulled = false;
56846
56970
  scene2.add(line);
56847
- tr = { line, life: 0 };
56971
+ tr = { line, life: 0, follows: false };
56848
56972
  tracers.push(tr);
56849
56973
  }
56850
56974
  const attr = tr.line.geometry.getAttribute("position");
@@ -56853,6 +56977,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
56853
56977
  attr.needsUpdate = true;
56854
56978
  tr.line.visible = true;
56855
56979
  tr.life = 0.07;
56980
+ tr.follows = muzzleSource !== null;
56856
56981
  };
56857
56982
  const fireZones = [];
56858
56983
  const burningStains = [];
@@ -58168,10 +58293,16 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58168
58293
  }
58169
58294
  };
58170
58295
  const simulateAfterModules = (dt) => {
58296
+ const drawn = tracers.some((tr) => tr.life > 0 && tr.follows) ? muzzleSource?.() ?? null : null;
58171
58297
  for (const tr of tracers) {
58172
58298
  if (tr.life > 0) {
58173
58299
  tr.life -= dt;
58174
58300
  if (tr.life <= 0) tr.line.visible = false;
58301
+ else if (tr.follows && drawn) {
58302
+ const attr = tr.line.geometry.getAttribute("position");
58303
+ attr.setXYZ(0, drawn.position[0], drawn.position[1], drawn.position[2]);
58304
+ attr.needsUpdate = true;
58305
+ }
58175
58306
  }
58176
58307
  }
58177
58308
  flush();
@@ -58205,6 +58336,7 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58205
58336
  };
58206
58337
  const updatePresent = (dt) => {
58207
58338
  reachAvatar();
58339
+ updateFirstPersonArms(dt);
58208
58340
  if (progress && simTime - progress.at > 0.25) progress = null;
58209
58341
  ui?.flushLog();
58210
58342
  hudAcc += dt;
@@ -58554,6 +58686,9 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
58554
58686
  if (handSource === source) handSource = null;
58555
58687
  };
58556
58688
  },
58689
+ setViewmodelFov(degrees) {
58690
+ viewmodelFov = degrees;
58691
+ },
58557
58692
  setMuzzleSource(source) {
58558
58693
  muzzleSource = source;
58559
58694
  return () => {
@@ -59285,7 +59420,9 @@ ${SURFACE_GLSL}`).replace("#include <color_fragment>", "#include <color_fragment
59285
59420
  for (const batcher of batchers.values()) batcher.update();
59286
59421
  const restoreCamera = room.applyCamera();
59287
59422
  graphics?.beforeRender(camera2);
59288
- gl.render(scene2, debugView ?? moduleViews.main(camera2));
59423
+ const view = debugView ?? moduleViews.main(camera2);
59424
+ gl.render(scene2, view);
59425
+ if (view === camera2 && cameraMode === "first") drawFirstPersonLayer();
59289
59426
  moduleViews.render(scene2);
59290
59427
  graphics?.afterRender();
59291
59428
  restoreCamera();