@adatechnology/game-weapons-three 0.14.2 → 0.16.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.
@@ -1,9 +1,12 @@
1
1
  // src/test-area/area.ts
2
- import { markKitOrigin as markKitOrigin5 } from "@adatechnology/game-contracts";
3
- import { createArsenal } from "@adatechnology/game-weapons";
2
+ import { markKitOrigin as markKitOrigin6 } from "@adatechnology/game-contracts";
3
+ import { createArsenal, genericWeapons } from "@adatechnology/game-weapons";
4
4
  import { furnitureSpec } from "@adatechnology/game-floorplan-three";
5
5
  import { isLightBudget, isRenderPools } from "@adatechnology/game-render-three";
6
6
 
7
+ // src/test-area/accessory-bench.ts
8
+ import { aimZoomOf } from "@adatechnology/game-weapons";
9
+
7
10
  // src/attachments-key.ts
8
11
  var attachmentsKey = (parts) => {
9
12
  if (!parts) return "";
@@ -119,11 +122,11 @@ function createAccessoryBench(options) {
119
122
  else return false;
120
123
  return true;
121
124
  };
122
- const zoomOf = (tool) => {
123
- const optic = (tool.attachments ?? []).map((part) => arsenal.attachments.get(part.id)?.optic).find((entry) => entry !== void 0);
124
- return optic?.zoom[0] ?? 1;
125
+ const zoomOf = (tool) => aimZoomOf(arsenal, tool.weapon, tool.attachments?.map((part) => part.id));
126
+ const aims = (tool) => {
127
+ const kind = arsenal.weapons.get(tool.weapon)?.action.kind;
128
+ return kind === "firearm" || kind === "launcher";
125
129
  };
126
- const isFirearm = (tool) => arsenal.weapons.get(tool.weapon)?.action.kind === "firearm";
127
130
  const setAim = (on) => {
128
131
  if (on === aiming) return;
129
132
  aiming = on;
@@ -150,7 +153,7 @@ function createAccessoryBench(options) {
150
153
  };
151
154
  const isOnRoom = (event) => Boolean(document.pointerLockElement) || event.target instanceof HTMLCanvasElement;
152
155
  const onMouseDown = (event) => {
153
- if (event.button !== SECONDARY_BUTTON || !isOnRoom(event) || !isFirearm(host.tool())) return;
156
+ if (event.button !== SECONDARY_BUTTON || !isOnRoom(event) || !aims(host.tool())) return;
154
157
  event.preventDefault();
155
158
  event.stopImmediatePropagation();
156
159
  setAim(true);
@@ -1410,6 +1413,10 @@ function computeCasingSound(input) {
1410
1413
  const surfaceName = surface === "hard" ? "concreto" : surface === "soft" ? "terra" : surface === "wood" ? "madeira" : "metal";
1411
1414
  return { surface, kind, sample: `casing_drop_${surfaceName}_${kind}`, voices };
1412
1415
  }
1416
+ function magazineDropId(material) {
1417
+ const surface = casingSurfaceOf(material);
1418
+ return `mag_drop_${surface === "hard" ? "concreto" : surface === "soft" ? "terra" : surface === "wood" ? "madeira" : "metal"}`;
1419
+ }
1413
1420
 
1414
1421
  // src/audio/flyby.ts
1415
1422
  function computeFlyby(input) {
@@ -2214,7 +2221,7 @@ var CUES_IN_SAMPLE = { mag_release: "mag_out", mag_insert: "mag_seat" };
2214
2221
  var isGunshot = (id) => GUNSHOT_SAMPLE_IDS.includes(id);
2215
2222
  function weaponSampleDefaults(sound3, overrides = {}) {
2216
2223
  if (sound3.category !== "armas") return void 0;
2217
- const base = sound3.id.startsWith("suppressed_") ? SUPPRESSED_SAMPLE : isGunshot(sound3.id) ? GUNSHOT_SAMPLE : sound3.id.startsWith("casing_drop_") ? CASING_SAMPLE : RELOAD_SAMPLE;
2224
+ const base = sound3.id.startsWith("suppressed_") ? SUPPRESSED_SAMPLE : isGunshot(sound3.id) ? GUNSHOT_SAMPLE : sound3.id.startsWith("casing_drop_") || sound3.id.startsWith("mag_drop_") ? CASING_SAMPLE : RELOAD_SAMPLE;
2218
2225
  return { ...base, ...overrides[sound3.id] };
2219
2226
  }
2220
2227
  function weaponSampleBank(options) {
@@ -2265,6 +2272,8 @@ function createWeaponAudio(volume = 0.6, options = {}) {
2265
2272
  void loadSamples();
2266
2273
  return false;
2267
2274
  };
2275
+ const fittedReady = (id) => sampleIds.has(id) && engine.registry.sound(id) === void 0 && engine.registry.set(id) !== void 0;
2276
+ const hostReady = (id) => sampleReady(id) || engine.registry.sound(id) === void 0 && engine.registry.set(id) !== void 0;
2268
2277
  const burst = (config) => {
2269
2278
  const id = config.filter === "highpass" ? BURST_IDS.highpass : BURST_IDS.lowpass;
2270
2279
  const params = { frequency: config.frequency, q: config.q ?? 0.8, gain: Math.max(config.gain, 2e-4), seconds: config.seconds };
@@ -2319,6 +2328,11 @@ function createWeaponAudio(volume = 0.6, options = {}) {
2319
2328
  options.onCasing?.(sound3, "sample");
2320
2329
  return;
2321
2330
  }
2331
+ if (recorded && fittedReady(recorded)) {
2332
+ if ((input.bounce ?? 0) === 0) channel.play(recorded, { volume: Math.min(1, Math.max(0.2, (input.speed ?? 1.5) / 2.5)) });
2333
+ options.onCasing?.(sound3, "fitted");
2334
+ return;
2335
+ }
2322
2336
  for (const voice of sound3.voices) playCasingVoice(voice);
2323
2337
  options.onCasing?.(sound3, "synth");
2324
2338
  };
@@ -2343,7 +2357,9 @@ function createWeaponAudio(volume = 0.6, options = {}) {
2343
2357
  const pitch = 1 + (channel.random() * 2 - 1) * pitchSpread;
2344
2358
  const shot = computeGunshot({ ...base, take, pitch }, options.gunshot);
2345
2359
  const recorded = shot.samples.close;
2346
- if (shot.audible && sampleReady(recorded)) {
2360
+ const sample = shot.audible && sampleReady(recorded);
2361
+ const fitted = shot.audible && !sample && fittedReady(recorded);
2362
+ if (sample || fitted) {
2347
2363
  const occlusion = Math.min(1, Math.max(0, extras.occlusion ?? 0));
2348
2364
  const handle = channel.play(recorded, {
2349
2365
  volume: (extras.volume ?? 1) * (1 - 0.5 * occlusion),
@@ -2356,7 +2372,7 @@ function createWeaponAudio(volume = 0.6, options = {}) {
2356
2372
  for (const voice of shot.voices) if (voice.group === "tail") playGunshotVoice(voice, extras);
2357
2373
  }
2358
2374
  const layers = handle?.kind === "sample" ? handle.plan.layers.map((layer) => ({ role: layer.role, take: layer.take, rate: layer.rate })) : [];
2359
- options.onGunshot?.(shot, { ...base, take, pitch }, { kind: "sample", id: recorded, layers });
2375
+ options.onGunshot?.(shot, { ...base, take, pitch }, fitted ? { kind: "fitted", id: recorded } : { kind: "sample", id: recorded, layers });
2360
2376
  return;
2361
2377
  }
2362
2378
  for (const voice of shot.voices) playGunshotVoice(voice, extras);
@@ -2377,10 +2393,11 @@ function createWeaponAudio(volume = 0.6, options = {}) {
2377
2393
  mechanical,
2378
2394
  cue(id, profile, lead = 0) {
2379
2395
  const carriedBy = CUES_IN_SAMPLE[id];
2380
- if (carriedBy && sampleReady(carriedBy)) return "carried";
2381
- if (sampleReady(id)) {
2396
+ if (carriedBy && (sampleReady(carriedBy) || fittedReady(carriedBy))) return "carried";
2397
+ const recorded = sampleReady(id);
2398
+ if (recorded || fittedReady(id)) {
2382
2399
  channel.play(id, { intensity: WEIGHT_INTENSITY[(profile ?? DEFAULT_SOUND_PROFILE).weight] });
2383
- return "sample";
2400
+ return recorded ? "sample" : "fitted";
2384
2401
  }
2385
2402
  for (const entry of reloadCueSteps(id, profile, cueTable)) step(lead > 0 ? { ...entry, at: entry.at + lead } : entry);
2386
2403
  return "synth";
@@ -2407,7 +2424,12 @@ function createWeaponAudio(volume = 0.6, options = {}) {
2407
2424
  casing,
2408
2425
  flyby(input) {
2409
2426
  const sound3 = computeFlyby({ ...input, pitch: input.pitch ?? 1 + (channel.random() * 2 - 1) * 0.05 });
2410
- for (const voice of sound3?.voices ?? []) playCasingVoice(voice, input.position);
2427
+ if (!sound3) return;
2428
+ if (hostReady(sound3.sample)) {
2429
+ channel.play(sound3.sample, { volume: Math.min(1, 1 / (1 + input.miss / 1.5)), ...input.position ? { position: [input.position[0], input.position[1], input.position[2]] } : {} });
2430
+ return;
2431
+ }
2432
+ for (const voice of sound3.voices) playCasingVoice(voice, input.position);
2411
2433
  },
2412
2434
  clink() {
2413
2435
  if (options.shotModel !== "game") {
@@ -2418,8 +2440,13 @@ function createWeaponAudio(volume = 0.6, options = {}) {
2418
2440
  const at = 0.25 + channel.random() * 0.2;
2419
2441
  sceneTone({ layer: "scene_tone", at, wave: "sine", f, f2: 3800, d: 0.06, vol: 0.06 });
2420
2442
  },
2421
- thud() {
2422
- burst({ seconds: 0.09, gain: 0.35, filter: "lowpass", frequency: 380 });
2443
+ thud(input = {}) {
2444
+ const id = magazineDropId(input.surface);
2445
+ if (hostReady(id)) {
2446
+ if ((input.bounce ?? 0) === 0) channel.play(id, { volume: Math.min(1, Math.max(0.2, (input.speed ?? 1.5) / 2.5)) });
2447
+ return;
2448
+ }
2449
+ burst({ seconds: 0.09, gain: 0.35 * (input.speed === void 0 ? 1 : Math.min(1, Math.max(0.3, input.speed / 2.5))) * ((input.bounce ?? 0) === 0 ? 1 : 0.5), filter: "lowpass", frequency: 380 });
2423
2450
  },
2424
2451
  setVolume(next) {
2425
2452
  const level = Math.max(0, Math.min(1, next));
@@ -2833,6 +2860,279 @@ function drawItemWeapon({ THREE, def, items, itemId, turn: turn2, flip }) {
2833
2860
  return group;
2834
2861
  }
2835
2862
 
2863
+ // src/models/explosives.ts
2864
+ import { markKitOrigin as markKitOrigin2 } from "@adatechnology/game-contracts";
2865
+
2866
+ // src/models/kit.ts
2867
+ import { markShared, shapeGeometries as shapeGeometries2 } from "@adatechnology/game-shapes-three";
2868
+
2869
+ // src/models/rounded-box.ts
2870
+ var uvAlong = (THREE, faceDirection, normal, uvAxis, projectionAxis, radius, sideLength) => {
2871
+ const arcLength = 2 * Math.PI * radius / 4;
2872
+ const centerLength = Math.max(sideLength - 2 * radius, 0);
2873
+ const halfArc = Math.PI / 4;
2874
+ const projected = new THREE.Vector3().copy(normal);
2875
+ projected[projectionAxis] = 0;
2876
+ projected.normalize();
2877
+ const arcUvRatio = 0.5 * arcLength / (arcLength + centerLength);
2878
+ const arcAngleRatio = 1 - projected.angleTo(faceDirection) / halfArc;
2879
+ if (Math.sign(projected[uvAxis]) === 1) return arcAngleRatio * arcUvRatio;
2880
+ const centerUv = centerLength / (arcLength + centerLength);
2881
+ return centerUv + arcUvRatio + arcUvRatio * (1 - arcAngleRatio);
2882
+ };
2883
+ var FACES = [
2884
+ { direction: [1, 0, 0], u: ["z", "y", false], v: ["y", "z", true] },
2885
+ { direction: [-1, 0, 0], u: ["z", "y", true], v: ["y", "z", true] },
2886
+ { direction: [0, 1, 0], u: ["x", "z", true], v: ["z", "x", false] },
2887
+ { direction: [0, -1, 0], u: ["x", "z", true], v: ["z", "x", true] },
2888
+ { direction: [0, 0, 1], u: ["x", "y", true], v: ["y", "x", true] },
2889
+ { direction: [0, 0, -1], u: ["x", "y", false], v: ["y", "x", true] }
2890
+ ];
2891
+ function createRoundedBoxGeometry(THREE, width, height, depth, segments, radius) {
2892
+ const count = segments * 2 + 1;
2893
+ const rounding = Math.min(width / 2, height / 2, depth / 2, radius);
2894
+ const geometry = new THREE.BoxGeometry(1, 1, 1, count, count, count);
2895
+ const flat2 = geometry.toNonIndexed();
2896
+ geometry.setIndex(null);
2897
+ geometry.setAttribute("position", flat2.attributes.position);
2898
+ geometry.setAttribute("normal", flat2.attributes.normal);
2899
+ geometry.setAttribute("uv", flat2.attributes.uv);
2900
+ const position = new THREE.Vector3();
2901
+ const normal = new THREE.Vector3();
2902
+ const box2 = new THREE.Vector3(width, height, depth).divideScalar(2).subScalar(rounding);
2903
+ const positions = geometry.attributes.position.array;
2904
+ const normals = geometry.attributes.normal.array;
2905
+ const uvs = geometry.attributes.uv.array;
2906
+ const faceTriangles = positions.length / 6;
2907
+ const halfSegment = 0.5 / count;
2908
+ const sides = { x: width, y: height, z: depth };
2909
+ for (let i = 0, j = 0; i < positions.length; i += 3, j += 2) {
2910
+ position.fromArray(positions, i);
2911
+ normal.copy(position);
2912
+ normal.x -= Math.sign(normal.x) * halfSegment;
2913
+ normal.y -= Math.sign(normal.y) * halfSegment;
2914
+ normal.z -= Math.sign(normal.z) * halfSegment;
2915
+ normal.normalize();
2916
+ positions[i] = box2.x * Math.sign(position.x) + normal.x * rounding;
2917
+ positions[i + 1] = box2.y * Math.sign(position.y) + normal.y * rounding;
2918
+ positions[i + 2] = box2.z * Math.sign(position.z) + normal.z * rounding;
2919
+ normals[i] = normal.x;
2920
+ normals[i + 1] = normal.y;
2921
+ normals[i + 2] = normal.z;
2922
+ const face = FACES[Math.floor(i / faceTriangles)];
2923
+ if (!face) continue;
2924
+ const direction = new THREE.Vector3(...face.direction);
2925
+ for (const [offset, [uvAxis, projectionAxis, mirrored]] of [[0, face.u], [1, face.v]]) {
2926
+ const value = uvAlong(THREE, direction, normal, uvAxis, projectionAxis, rounding, sides[uvAxis]);
2927
+ uvs[j + offset] = mirrored ? 1 - value : value;
2928
+ }
2929
+ }
2930
+ return geometry;
2931
+ }
2932
+
2933
+ // src/models/kit.ts
2934
+ var PI = Math.PI;
2935
+ var createMaterials = (THREE) => {
2936
+ const standard = (color, roughness = 0.7, metalness = 0.2, extra = {}) => new THREE.MeshStandardMaterial({ color, roughness, metalness, ...extra });
2937
+ const basic = (color, extra = {}) => new THREE.MeshBasicMaterial({ color, ...extra });
2938
+ const additive = { transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending, depthWrite: false };
2939
+ const materials = {
2940
+ metal: standard(3422010, 0.42, 0.75),
2941
+ steel: standard(7106418, 0.32, 0.85),
2942
+ steelL: standard(9080208, 0.28, 0.9),
2943
+ dark: standard(1711133, 0.55, 0.5),
2944
+ poly: standard(2369058, 0.8, 0.05),
2945
+ olive: standard(4936241, 0.8, 0.05),
2946
+ tan: standard(10259043, 0.8, 0.05),
2947
+ wood: standard(4860951, 0.55, 0),
2948
+ woodL: standard(6175263, 0.55, 0),
2949
+ brass: standard(12819013, 0.28, 0.9),
2950
+ copper: standard(11561531, 0.3, 0.85),
2951
+ lead: standard(7171955, 0.5, 0.5),
2952
+ shell: standard(10692380, 0.5, 0.05),
2953
+ glass: standard(8364968, 0.05, 0.1, { transparent: true, opacity: 0.42 }),
2954
+ lens: standard(2311770, 0.05, 0.3, { transparent: true, opacity: 0.9, emissive: 399392 }),
2955
+ rubber: standard(1381653, 0.85, 0),
2956
+ white: standard(13617332, 0.8, 0),
2957
+ hole: standard(526343, 1, 0),
2958
+ bake: standard(5911072, 0.55, 0),
2959
+ rust: standard(7027230, 0.95, 0.3),
2960
+ rustD: standard(4860440, 0.95, 0.25),
2961
+ worn: standard(5593178, 0.6, 0.6),
2962
+ fire: basic(16742946),
2963
+ fire2: basic(16764762),
2964
+ flashA: basic(16757575, additive),
2965
+ flashB: basic(16773568, additive),
2966
+ smokeP: standard(9079430, 1, 0, { transparent: true, opacity: 0.35, depthWrite: false }),
2967
+ /** Launcher paints. */
2968
+ yellow: standard(11570978, 0.6, 0),
2969
+ red: standard(9313300, 0.45, 0.1),
2970
+ redPaint: standard(10691354, 0.35, 0.2),
2971
+ tape: standard(9079944, 0.9, 0),
2972
+ pipe: standard(10115634, 0.35, 0.8),
2973
+ green: standard(3818536, 0.75, 0.05),
2974
+ /** Explosive finishes: the molotov's bottle, the dynamite's paper, the C4's putty and the detonator's wire. */
2975
+ bottle: standard(3103284, 0.1, 0.1, { transparent: true, opacity: 0.72 }),
2976
+ dynamite: standard(10365471, 0.7, 0),
2977
+ c4: standard(7170636, 0.95, 0),
2978
+ wire: standard(11542560, 0.5, 0),
2979
+ /** Magazine finishes named by the options. */
2980
+ bakelite: standard(6958620, 0.6, 0),
2981
+ polyMag: standard(2763302, 0.85, 0)
2982
+ };
2983
+ markShared(...Object.values(materials));
2984
+ return materials;
2985
+ };
2986
+ var kits = /* @__PURE__ */ new WeakMap();
2987
+ function modelKit(host) {
2988
+ const cached = kits.get(host);
2989
+ if (cached) return cached;
2990
+ const THREE = shapeGeometries2(host);
2991
+ const geometries = /* @__PURE__ */ new Map();
2992
+ const shadowed = (mesh) => {
2993
+ mesh.castShadow = true;
2994
+ mesh.receiveShadow = true;
2995
+ return mesh;
2996
+ };
2997
+ const place = (parent, mesh, x, y, z) => {
2998
+ mesh.position.set(x, y, z);
2999
+ parent.add(mesh);
3000
+ return mesh;
3001
+ };
3002
+ const kit = {
3003
+ THREE,
3004
+ M: createMaterials(THREE),
3005
+ roundness: null,
3006
+ group: () => new THREE.Group(),
3007
+ box(parent, w, h, d, material, x, y, z, rz = 0, ry = 0, rx = 0) {
3008
+ const radius = Math.min(w, h, d) * (kit.roundness ?? 0.2);
3009
+ const key = `${w.toFixed(4)},${h.toFixed(4)},${d.toFixed(4)},${radius.toFixed(5)}`;
3010
+ let geometry = geometries.get(key);
3011
+ if (!geometry) {
3012
+ geometry = radius > 25e-5 ? createRoundedBoxGeometry(THREE, w, h, d, 2, radius) : new THREE.BoxGeometry(w, h, d);
3013
+ markShared(geometry);
3014
+ geometries.set(key, geometry);
3015
+ }
3016
+ const mesh = place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z);
3017
+ mesh.rotation.set(rx, ry, rz);
3018
+ return mesh;
3019
+ },
3020
+ cylinderX(parent, radius, length, material, x, y, z, segments = 16, radiusEnd) {
3021
+ const mesh = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, length, segments), material));
3022
+ mesh.rotation.z = -PI / 2;
3023
+ return place(parent, mesh, x, y, z);
3024
+ },
3025
+ cylinderY(parent, radius, height, material, x, y, z, segments = 16, radiusEnd) {
3026
+ return place(parent, shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, height, segments), material)), x, y, z);
3027
+ },
3028
+ cylinderZ(parent, radius, length, material, x, y, z, segments = 16) {
3029
+ const mesh = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radius, radius, length, segments), material));
3030
+ mesh.rotation.x = PI / 2;
3031
+ return place(parent, mesh, x, y, z);
3032
+ },
3033
+ sphere(parent, radius, material, x, y, z, detail = 1) {
3034
+ return place(parent, shadowed(new THREE.Mesh(new THREE.IcosahedronGeometry(radius, detail), material)), x, y, z);
3035
+ },
3036
+ torus: (parent, geometry, material, x, y, z) => place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z),
3037
+ mark(parent, x, y, z) {
3038
+ const object = new THREE.Object3D();
3039
+ object.position.set(x, y, z);
3040
+ parent.add(object);
3041
+ return object;
3042
+ },
3043
+ pivot(parent, x = 0, y = 0, z = 0) {
3044
+ const group = new THREE.Group();
3045
+ group.position.set(x, y, z);
3046
+ parent.add(group);
3047
+ return group;
3048
+ }
3049
+ };
3050
+ kits.set(host, kit);
3051
+ return kit;
3052
+ }
3053
+ function heatify(glow, mesh) {
3054
+ mesh.material = mesh.material.clone();
3055
+ glow.push(mesh);
3056
+ return mesh;
3057
+ }
3058
+
3059
+ // src/models/explosives.ts
3060
+ var grenade = (kit, group) => {
3061
+ const { M, sphere, cylinderY, box: box2, torus, THREE } = kit;
3062
+ sphere(group, 0.032, M.olive, 0, 0.032, 0, 1);
3063
+ cylinderY(group, 0.011, 0.022, M.metal, 0, 0.072, 0);
3064
+ box2(group, 0.012, 0.07, 6e-3, M.metal, 0.016, 0.05, 0, 0.2);
3065
+ torus(group, new THREE.TorusGeometry(85e-4, 16e-4, 6, 12), M.steel, -0.012, 0.082, 0);
3066
+ kit.mark(group, 0, 0.032, 0).name = "handle";
3067
+ };
3068
+ var molotov = (kit, group) => {
3069
+ const { M, cylinderY, box: box2 } = kit;
3070
+ cylinderY(group, 0.036, 0.17, M.bottle, 0, 0.085, 0, 10);
3071
+ cylinderY(group, 0.012, 0.06, M.bottle, 0, 0.2, 0, 8, 0.036);
3072
+ box2(group, 0.03, 0.08, 0.02, M.white, 5e-3, 0.25, 0, 0.3);
3073
+ cylinderY(group, 0.02, 0.07, M.fire, 0.01, 0.31, 0, 6, 0).userData.flame = true;
3074
+ kit.mark(group, 0, 0.085, 0).name = "handle";
3075
+ };
3076
+ var pipeBomb = (kit, group) => {
3077
+ const { M, cylinderX, cylinderY } = kit;
3078
+ cylinderX(group, 0.025, 0.2, M.metal, 0, 0.03, 0, 8);
3079
+ cylinderX(group, 0.031, 0.025, M.dark, -0.1, 0.03, 0, 6);
3080
+ cylinderX(group, 0.031, 0.025, M.dark, 0.1, 0.03, 0, 6);
3081
+ cylinderY(group, 2e-3, 0.06, M.white, 0.1, 0.07, 0);
3082
+ kit.mark(group, 0, 0.03, 0).name = "handle";
3083
+ };
3084
+ var dynamite = (kit, group) => {
3085
+ const { M, cylinderX } = kit;
3086
+ for (const [z, y] of [[-0.015, 0], [0.015, 0], [0, 0.026]]) cylinderX(group, 0.014, 0.2, M.dynamite, 0, 0.016 + y, z, 8);
3087
+ cylinderX(group, 0.031, 0.03, M.dark, -0.04, 0.025, 0, 8);
3088
+ cylinderX(group, 0.031, 0.03, M.dark, 0.05, 0.025, 0, 8);
3089
+ cylinderX(group, 2e-3, 0.1, M.white, 0.14, 0.03, 0, 4);
3090
+ kit.mark(group, 0, 0.025, 0).name = "handle";
3091
+ };
3092
+ var c4 = (kit, group) => {
3093
+ const { M, box: box2, cylinderY } = kit;
3094
+ box2(group, 0.28, 0.05, 0.11, M.c4, 0, 0.025, 0);
3095
+ box2(group, 0.05, 0.03, 0.04, M.dark, 0.06, 0.065, 0);
3096
+ cylinderY(group, 4e-3, 0.05, M.metal, 0.03, 0.07, 0);
3097
+ box2(group, 0.24, 3e-3, 3e-3, M.wire, 0.18, 0.055, 0.02);
3098
+ box2(group, 0.24, 3e-3, 3e-3, M.metal, 0.18, 0.055, 0.03);
3099
+ kit.mark(group, 0, 0.025, 0).name = "handle";
3100
+ };
3101
+ var claymore = (kit, group) => {
3102
+ const { M, box: box2 } = kit;
3103
+ for (const i of [-1, 0, 1]) box2(group, 0.075, 0.083, 0.035, M.olive, i * 0.072, 0.12, Math.abs(i) * 0.012, 0, -i * 0.25);
3104
+ for (const [x, z] of [[-0.07, 0.03], [-0.07, -0.03], [0.07, 0.03], [0.07, -0.03]]) box2(group, 4e-3, 0.09, 4e-3, M.metal, x, 0.04, z, x > 0 ? -0.25 : 0.25);
3105
+ kit.mark(group, 0, 0.12, 0).name = "handle";
3106
+ };
3107
+ var flashbang = (kit, group) => {
3108
+ const { M, cylinderY, box: box2 } = kit;
3109
+ cylinderY(group, 0.022, 0.13, M.olive, 0, 0.065, 0, 10);
3110
+ for (let i = 0; i < 4; i++) cylinderY(group, 0.023, 8e-3, M.dark, 0, 0.025 + i * 0.028, 0, 10);
3111
+ cylinderY(group, 0.012, 0.02, M.metal, 0, 0.14, 0);
3112
+ box2(group, 0.01, 0.08, 5e-3, M.metal, 0.024, 0.1, 0, 0.15);
3113
+ kit.mark(group, 0, 0.065, 0).name = "handle";
3114
+ };
3115
+ var BUILDERS = {
3116
+ m67: grenade,
3117
+ molotov,
3118
+ pipe_bomb: pipeBomb,
3119
+ dynamite,
3120
+ c4,
3121
+ claymore,
3122
+ flashbang
3123
+ };
3124
+ var EXPLOSIVE_MODEL_IDS = Object.keys(BUILDERS);
3125
+ var hasExplosiveModel = (explosiveId) => explosiveId in BUILDERS;
3126
+ function buildExplosiveModel(THREE, explosiveId) {
3127
+ const builder = BUILDERS[explosiveId];
3128
+ if (!builder) return void 0;
3129
+ const kit = modelKit(THREE);
3130
+ const group = markKitOrigin2(kit.group(), { owner: "game-weapons-three", kind: "explosive", id: explosiveId });
3131
+ group.name = explosiveId;
3132
+ builder(kit, group);
3133
+ return group;
3134
+ }
3135
+
2836
3136
  // src/weapon-item-model.ts
2837
3137
  var SHIPPED_WEAPON_ITEM_MODELS = [
2838
3138
  ["wrench", { item: "adjustable_wrench" }],
@@ -2841,8 +3141,12 @@ var SHIPPED_WEAPON_ITEM_MODELS = [
2841
3141
  ["crossbow", { turn: 1 }],
2842
3142
  ["knife", { flip: true }],
2843
3143
  ["meat_cleaver", { flip: true }],
2844
- ["screwdriver", { flip: true }]
3144
+ ["screwdriver", { flip: true }],
3145
+ // the Itens do Vale Cinzento hammer is a claw hammer: the head and the two-pronged claw
3146
+ ["claw_hammer", { item: "hammer" }]
2845
3147
  ];
3148
+ var hasThrowableModel = (def) => def.action?.kind === "throwable" && def.action.explosive !== void 0 && hasExplosiveModel(def.action.explosive);
3149
+ var buildThrowableModel = (THREE, def) => def.action?.kind === "throwable" && def.action.explosive !== void 0 ? buildExplosiveModel(THREE, def.action.explosive) : void 0;
2846
3150
  var weaponPropsOver = (models) => {
2847
3151
  const itemModelOf = (weaponId, items) => {
2848
3152
  const id = models.get(weaponId)?.item ?? weaponId;
@@ -2858,7 +3162,7 @@ var weaponPropsOver = (models) => {
2858
3162
  get: (weaponId) => models.get(weaponId),
2859
3163
  itemModelOf,
2860
3164
  buildItemModel,
2861
- buildModel: (THREE, def, items) => buildItemModel(THREE, def, items) ?? buildProp(THREE, weaponProp(def))
3165
+ buildModel: (THREE, def, items) => buildItemModel(THREE, def, items) ?? buildThrowableModel(THREE, def) ?? buildProp(THREE, weaponProp(def))
2862
3166
  };
2863
3167
  };
2864
3168
  var defaultProps;
@@ -2870,6 +3174,7 @@ var buildWeaponModel = (THREE, def, items) => defaultWeaponProps().buildModel(TH
2870
3174
 
2871
3175
  // src/box-fx.ts
2872
3176
  var EJECTS_ON_SHOT = /* @__PURE__ */ new Set(["pistol", "smg", "carbine", "rifle", "sniper", "shotgun"]);
3177
+ var SPOUTS = /* @__PURE__ */ new Set(["firearm", "flame", "heat", "extinguisher"]);
2873
3178
  var DROPS_MAGAZINE = /* @__PURE__ */ new Set(["pistol", "smg", "carbine", "rifle", "sniper"]);
2874
3179
  var CYCLES = /* @__PURE__ */ new Set(["shotgun_pump_12", "rifle_bolt_308"]);
2875
3180
  var FALLBACK_ENERGY = 300;
@@ -2904,6 +3209,7 @@ function createBoxFx(options) {
2904
3209
  const q = new THREE.Quaternion();
2905
3210
  const q2 = new THREE.Quaternion();
2906
3211
  const pointForward = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2);
3212
+ const frontOf = new THREE.Vector3();
2907
3213
  const equip = (weapon) => {
2908
3214
  if (model) hand.remove(model);
2909
3215
  model = null;
@@ -2914,6 +3220,7 @@ function createBoxFx(options) {
2914
3220
  const length = new THREE.Box3().setFromObject(model).getSize(v).x;
2915
3221
  rear = new THREE.Box3().setFromObject(model).min.x;
2916
3222
  lengthMetres = length;
3223
+ frontOf.copy(new THREE.Box3().setFromObject(model).getCenter(v)).setX(rear + length);
2917
3224
  model.quaternion.copy(pointForward);
2918
3225
  hand.add(model);
2919
3226
  inHand = def.id;
@@ -3014,6 +3321,13 @@ function createBoxFx(options) {
3014
3321
  inHand = null;
3015
3322
  hold = null;
3016
3323
  },
3324
+ muzzle() {
3325
+ if (!model || !def || !SPOUTS.has(def.action.kind)) return null;
3326
+ hand.updateMatrixWorld(true);
3327
+ const at = model.localToWorld(v.copy(frontOf));
3328
+ const along = v2.set(1, 0, 0).applyQuaternion(model.getWorldQuaternion(q2)).normalize();
3329
+ return { position: [at.x, at.y, at.z], direction: [along.x, along.y, along.z], drawsFlash: false };
3330
+ },
3017
3331
  handTargets() {
3018
3332
  if (!model || !hold) return null;
3019
3333
  hand.updateMatrixWorld(true);
@@ -3065,7 +3379,7 @@ function createBoxFx(options) {
3065
3379
  }
3066
3380
 
3067
3381
  // src/models/index.ts
3068
- import { markKitOrigin as markKitOrigin2 } from "@adatechnology/game-contracts";
3382
+ import { markKitOrigin as markKitOrigin3 } from "@adatechnology/game-contracts";
3069
3383
  import { WEAPONS } from "@adatechnology/game-weapons";
3070
3384
 
3071
3385
  // src/data/models.json
@@ -4618,197 +4932,24 @@ var models_default = {
4618
4932
  options: {
4619
4933
  rpm: 3e3
4620
4934
  }
4935
+ },
4936
+ m79: {
4937
+ builder: "breakLauncher",
4938
+ caliber: "40\xD746",
4939
+ length: 730,
4940
+ barrel: 356,
4941
+ options: {
4942
+ stock: "fixed",
4943
+ wood: 1,
4944
+ mag: "none",
4945
+ hg: "wood",
4946
+ hgFrac: 0.4,
4947
+ br: 0.022,
4948
+ hR: 0.05
4949
+ }
4621
4950
  }
4622
4951
  };
4623
4952
 
4624
- // src/models/kit.ts
4625
- import { markShared, shapeGeometries as shapeGeometries2 } from "@adatechnology/game-shapes-three";
4626
-
4627
- // src/models/rounded-box.ts
4628
- var uvAlong = (THREE, faceDirection, normal, uvAxis, projectionAxis, radius, sideLength) => {
4629
- const arcLength = 2 * Math.PI * radius / 4;
4630
- const centerLength = Math.max(sideLength - 2 * radius, 0);
4631
- const halfArc = Math.PI / 4;
4632
- const projected = new THREE.Vector3().copy(normal);
4633
- projected[projectionAxis] = 0;
4634
- projected.normalize();
4635
- const arcUvRatio = 0.5 * arcLength / (arcLength + centerLength);
4636
- const arcAngleRatio = 1 - projected.angleTo(faceDirection) / halfArc;
4637
- if (Math.sign(projected[uvAxis]) === 1) return arcAngleRatio * arcUvRatio;
4638
- const centerUv = centerLength / (arcLength + centerLength);
4639
- return centerUv + arcUvRatio + arcUvRatio * (1 - arcAngleRatio);
4640
- };
4641
- var FACES = [
4642
- { direction: [1, 0, 0], u: ["z", "y", false], v: ["y", "z", true] },
4643
- { direction: [-1, 0, 0], u: ["z", "y", true], v: ["y", "z", true] },
4644
- { direction: [0, 1, 0], u: ["x", "z", true], v: ["z", "x", false] },
4645
- { direction: [0, -1, 0], u: ["x", "z", true], v: ["z", "x", true] },
4646
- { direction: [0, 0, 1], u: ["x", "y", true], v: ["y", "x", true] },
4647
- { direction: [0, 0, -1], u: ["x", "y", false], v: ["y", "x", true] }
4648
- ];
4649
- function createRoundedBoxGeometry(THREE, width, height, depth, segments, radius) {
4650
- const count = segments * 2 + 1;
4651
- const rounding = Math.min(width / 2, height / 2, depth / 2, radius);
4652
- const geometry = new THREE.BoxGeometry(1, 1, 1, count, count, count);
4653
- const flat2 = geometry.toNonIndexed();
4654
- geometry.setIndex(null);
4655
- geometry.setAttribute("position", flat2.attributes.position);
4656
- geometry.setAttribute("normal", flat2.attributes.normal);
4657
- geometry.setAttribute("uv", flat2.attributes.uv);
4658
- const position = new THREE.Vector3();
4659
- const normal = new THREE.Vector3();
4660
- const box2 = new THREE.Vector3(width, height, depth).divideScalar(2).subScalar(rounding);
4661
- const positions = geometry.attributes.position.array;
4662
- const normals = geometry.attributes.normal.array;
4663
- const uvs = geometry.attributes.uv.array;
4664
- const faceTriangles = positions.length / 6;
4665
- const halfSegment = 0.5 / count;
4666
- const sides = { x: width, y: height, z: depth };
4667
- for (let i = 0, j = 0; i < positions.length; i += 3, j += 2) {
4668
- position.fromArray(positions, i);
4669
- normal.copy(position);
4670
- normal.x -= Math.sign(normal.x) * halfSegment;
4671
- normal.y -= Math.sign(normal.y) * halfSegment;
4672
- normal.z -= Math.sign(normal.z) * halfSegment;
4673
- normal.normalize();
4674
- positions[i] = box2.x * Math.sign(position.x) + normal.x * rounding;
4675
- positions[i + 1] = box2.y * Math.sign(position.y) + normal.y * rounding;
4676
- positions[i + 2] = box2.z * Math.sign(position.z) + normal.z * rounding;
4677
- normals[i] = normal.x;
4678
- normals[i + 1] = normal.y;
4679
- normals[i + 2] = normal.z;
4680
- const face = FACES[Math.floor(i / faceTriangles)];
4681
- if (!face) continue;
4682
- const direction = new THREE.Vector3(...face.direction);
4683
- for (const [offset, [uvAxis, projectionAxis, mirrored]] of [[0, face.u], [1, face.v]]) {
4684
- const value = uvAlong(THREE, direction, normal, uvAxis, projectionAxis, rounding, sides[uvAxis]);
4685
- uvs[j + offset] = mirrored ? 1 - value : value;
4686
- }
4687
- }
4688
- return geometry;
4689
- }
4690
-
4691
- // src/models/kit.ts
4692
- var PI = Math.PI;
4693
- var createMaterials = (THREE) => {
4694
- const standard = (color, roughness = 0.7, metalness = 0.2, extra = {}) => new THREE.MeshStandardMaterial({ color, roughness, metalness, ...extra });
4695
- const basic = (color, extra = {}) => new THREE.MeshBasicMaterial({ color, ...extra });
4696
- const additive = { transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending, depthWrite: false };
4697
- const materials = {
4698
- metal: standard(3422010, 0.42, 0.75),
4699
- steel: standard(7106418, 0.32, 0.85),
4700
- steelL: standard(9080208, 0.28, 0.9),
4701
- dark: standard(1711133, 0.55, 0.5),
4702
- poly: standard(2369058, 0.8, 0.05),
4703
- olive: standard(4936241, 0.8, 0.05),
4704
- tan: standard(10259043, 0.8, 0.05),
4705
- wood: standard(4860951, 0.55, 0),
4706
- woodL: standard(6175263, 0.55, 0),
4707
- brass: standard(12819013, 0.28, 0.9),
4708
- copper: standard(11561531, 0.3, 0.85),
4709
- lead: standard(7171955, 0.5, 0.5),
4710
- shell: standard(10692380, 0.5, 0.05),
4711
- glass: standard(8364968, 0.05, 0.1, { transparent: true, opacity: 0.42 }),
4712
- lens: standard(2311770, 0.05, 0.3, { transparent: true, opacity: 0.9, emissive: 399392 }),
4713
- rubber: standard(1381653, 0.85, 0),
4714
- white: standard(13617332, 0.8, 0),
4715
- hole: standard(526343, 1, 0),
4716
- bake: standard(5911072, 0.55, 0),
4717
- rust: standard(7027230, 0.95, 0.3),
4718
- rustD: standard(4860440, 0.95, 0.25),
4719
- worn: standard(5593178, 0.6, 0.6),
4720
- fire: basic(16742946),
4721
- fire2: basic(16764762),
4722
- flashA: basic(16757575, additive),
4723
- flashB: basic(16773568, additive),
4724
- smokeP: standard(9079430, 1, 0, { transparent: true, opacity: 0.35, depthWrite: false }),
4725
- /** Launcher paints. */
4726
- yellow: standard(11570978, 0.6, 0),
4727
- red: standard(9313300, 0.45, 0.1),
4728
- redPaint: standard(10691354, 0.35, 0.2),
4729
- tape: standard(9079944, 0.9, 0),
4730
- pipe: standard(10115634, 0.35, 0.8),
4731
- green: standard(3818536, 0.75, 0.05),
4732
- /** Magazine finishes named by the options. */
4733
- bakelite: standard(6958620, 0.6, 0),
4734
- polyMag: standard(2763302, 0.85, 0)
4735
- };
4736
- markShared(...Object.values(materials));
4737
- return materials;
4738
- };
4739
- var kits = /* @__PURE__ */ new WeakMap();
4740
- function modelKit(host) {
4741
- const cached = kits.get(host);
4742
- if (cached) return cached;
4743
- const THREE = shapeGeometries2(host);
4744
- const geometries = /* @__PURE__ */ new Map();
4745
- const shadowed = (mesh) => {
4746
- mesh.castShadow = true;
4747
- mesh.receiveShadow = true;
4748
- return mesh;
4749
- };
4750
- const place = (parent, mesh, x, y, z) => {
4751
- mesh.position.set(x, y, z);
4752
- parent.add(mesh);
4753
- return mesh;
4754
- };
4755
- const kit = {
4756
- THREE,
4757
- M: createMaterials(THREE),
4758
- roundness: null,
4759
- group: () => new THREE.Group(),
4760
- box(parent, w, h, d, material, x, y, z, rz = 0, ry = 0, rx = 0) {
4761
- const radius = Math.min(w, h, d) * (kit.roundness ?? 0.2);
4762
- const key = `${w.toFixed(4)},${h.toFixed(4)},${d.toFixed(4)},${radius.toFixed(5)}`;
4763
- let geometry = geometries.get(key);
4764
- if (!geometry) {
4765
- geometry = radius > 25e-5 ? createRoundedBoxGeometry(THREE, w, h, d, 2, radius) : new THREE.BoxGeometry(w, h, d);
4766
- markShared(geometry);
4767
- geometries.set(key, geometry);
4768
- }
4769
- const mesh = place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z);
4770
- mesh.rotation.set(rx, ry, rz);
4771
- return mesh;
4772
- },
4773
- cylinderX(parent, radius, length, material, x, y, z, segments = 16, radiusEnd) {
4774
- const mesh = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, length, segments), material));
4775
- mesh.rotation.z = -PI / 2;
4776
- return place(parent, mesh, x, y, z);
4777
- },
4778
- cylinderY(parent, radius, height, material, x, y, z, segments = 16, radiusEnd) {
4779
- return place(parent, shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, height, segments), material)), x, y, z);
4780
- },
4781
- cylinderZ(parent, radius, length, material, x, y, z, segments = 16) {
4782
- const mesh = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radius, radius, length, segments), material));
4783
- mesh.rotation.x = PI / 2;
4784
- return place(parent, mesh, x, y, z);
4785
- },
4786
- sphere(parent, radius, material, x, y, z, detail = 1) {
4787
- return place(parent, shadowed(new THREE.Mesh(new THREE.IcosahedronGeometry(radius, detail), material)), x, y, z);
4788
- },
4789
- torus: (parent, geometry, material, x, y, z) => place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z),
4790
- mark(parent, x, y, z) {
4791
- const object = new THREE.Object3D();
4792
- object.position.set(x, y, z);
4793
- parent.add(object);
4794
- return object;
4795
- },
4796
- pivot(parent, x = 0, y = 0, z = 0) {
4797
- const group = new THREE.Group();
4798
- group.position.set(x, y, z);
4799
- parent.add(group);
4800
- return group;
4801
- }
4802
- };
4803
- kits.set(host, kit);
4804
- return kit;
4805
- }
4806
- function heatify(glow, mesh) {
4807
- mesh.material = mesh.material.clone();
4808
- glow.push(mesh);
4809
- return mesh;
4810
- }
4811
-
4812
4953
  // src/models/break-action.ts
4813
4954
  function buildBreakAction(kit, options) {
4814
4955
  const { M, box: box2, cylinderX, sphere } = kit;
@@ -4842,78 +4983,23 @@ function buildBreakAction(kit, options) {
4842
4983
  return { group, parts };
4843
4984
  }
4844
4985
 
4845
- // src/models/attachments/mount-frames.ts
4846
- var DEFAULT_BARREL_RADIUS = 85e-4;
4847
- var DEFAULT_RECEIVER_WIDTH = 0.034;
4848
- var RECEIVER_START_FRACTION = 0.25;
4849
- var RECEIVER_END_FRACTION = 0.55;
4850
- var RECEIVER_HEIGHT_FRACTION = 0.4;
4851
- var DEFAULT_GRIP_OFFSET = 0.07;
4852
- var HANDGUARD_FRACTION = 0.5;
4853
- function deriveFrames(THREE, model) {
4854
- const { root, inner, parts } = model;
4855
- root.updateMatrixWorld(true);
4856
- const box2 = new THREE.Box3().setFromObject(root);
4857
- const span = box2.max.x - box2.min.x;
4858
- const toInnerX = (x) => (x - inner.position.x) / inner.scale.x;
4859
- const muzzle = parts.muzzle?.position;
4860
- const length = muzzle ? muzzle.x - 0.01 : toInnerX(box2.max.x);
4861
- const receiverStart = toInnerX(box2.min.x + span * RECEIVER_START_FRACTION);
4862
- const receiverEnd = toInnerX(box2.min.x + span * RECEIVER_END_FRACTION);
4863
- return {
4864
- length,
4865
- barrelY: muzzle?.y ?? (box2.max.y + box2.min.y) / 2,
4866
- barrelRadius: DEFAULT_BARREL_RADIUS,
4867
- receiverStart,
4868
- receiverEnd,
4869
- receiverHeight: (box2.max.y - box2.min.y) * RECEIVER_HEIGHT_FRACTION,
4870
- receiverWidth: Math.min(DEFAULT_RECEIVER_WIDTH, box2.max.z - box2.min.z),
4871
- handguardEnd: receiverEnd + (length - receiverEnd) * HANDGUARD_FRACTION,
4872
- magazineX: parts.mag ? parts.mag.position.x : (receiverStart + receiverEnd) / 2,
4873
- gripX: receiverStart + DEFAULT_GRIP_OFFSET,
4874
- hasRail: false
4875
- };
4986
+ // src/models/attachments/muzzle-brake.ts
4987
+ function buildBrake(kit, parent, kind, x, y, barrelRadius, heat = () => void 0) {
4988
+ const { M, box: box2, cylinderX } = kit;
4989
+ const scale = kind === 2 ? 3.2 : 2;
4990
+ const length = kind === 2 ? 0.12 : 0.06;
4991
+ heat(cylinderX(parent, barrelRadius * scale, length, M.dark, x - length / 2, y, 0, kind === 2 ? 8 : 16));
4992
+ for (let i = 0; i < 3; i++) box2(parent, length * 0.16, barrelRadius * scale * 1.3, barrelRadius * scale * 2.2, M.hole, x - length * 0.25 - i * length * 0.25, y, 0);
4876
4993
  }
4877
- function mountFrame(model, mount) {
4878
- const frames = model.parts.frames;
4879
- if (!frames) return void 0;
4880
- const { inner } = model;
4881
- const toRoot = (x, y, z) => [inner.position.x + x * inner.scale.x, y, z];
4882
- const handguardHeight = frames.receiverHeight * 0.8;
4883
- const railTopY = frames.receiverHeight / 2 + (frames.hasRail ? 6e-3 : 0);
4884
- const forendMiddle = (frames.receiverEnd + frames.handguardEnd) / 2;
4885
- const base = { mount, barrelRadius: frames.barrelRadius, receiverWidth: frames.receiverWidth, receiverHeight: frames.receiverHeight, hasRail: frames.hasRail };
4886
- switch (mount) {
4887
- case "muzzle":
4888
- return { ...base, position: toRoot(frames.length, frames.barrelY, 0), side: 0 };
4889
- case "rail_top":
4890
- return { ...base, position: toRoot((frames.receiverStart + frames.receiverEnd) / 2 + 0.03, railTopY, 0), side: 0 };
4891
- case "rail_bottom":
4892
- return { ...base, position: toRoot(forendMiddle, frames.barrelY - handguardHeight / 2, 0), side: 0 };
4893
- case "rail_left":
4894
- return { ...base, position: toRoot(forendMiddle, frames.barrelY, -(frames.receiverWidth * 0.54 + 4e-3)), side: -1 };
4895
- case "rail_right":
4896
- return { ...base, position: toRoot(forendMiddle, frames.barrelY, frames.receiverWidth * 0.54 + 4e-3), side: 1 };
4897
- case "underbarrel": {
4898
- const [x, y] = frames.underbarrel ?? [frames.receiverEnd + (frames.handguardEnd - frames.receiverEnd) * 0.25, frames.barrelY - handguardHeight / 2 - 0.02];
4899
- return { ...base, position: toRoot(x, y, 0), side: 0 };
4900
- }
4901
- case "magazine":
4902
- return { ...base, position: toRoot(frames.magazineX, -frames.receiverHeight / 2, 0), side: 0 };
4903
- case "feed": {
4904
- const belt = model.parts.belt?.position;
4905
- return { ...base, position: belt ? toRoot(belt.x, belt.y, belt.z) : toRoot(frames.magazineX + 0.02, -frames.receiverHeight / 2 - 0.05, -0.045), side: -1 };
4906
- }
4907
- case "sling":
4908
- return {
4909
- ...base,
4910
- position: toRoot(frames.receiverEnd + (frames.length - frames.receiverEnd) * 0.45, -frames.receiverHeight / 2 - 0.03, 0),
4911
- secondary: toRoot(0.08, -0.09, 0),
4912
- side: 0
4913
- };
4914
- default:
4915
- return void 0;
4916
- }
4994
+
4995
+ // src/models/capture.ts
4996
+ function captured(parent, draw) {
4997
+ const before = parent.children.length;
4998
+ draw();
4999
+ return parent.children.slice(before);
5000
+ }
5001
+ function recordFitting(parts, id, parent, draw) {
5002
+ parts.fittings = { ...parts.fittings, [id]: captured(parent, draw) };
4917
5003
  }
4918
5004
 
4919
5005
  // src/models/details.ts
@@ -4968,1131 +5054,1209 @@ function scope(kit, parent, kind, cx, cy, cz = 0) {
4968
5054
  var buttpad = (kit, parent, x, y, height, width) => void kit.box(parent, 0.012, height, width, kit.M.rubber, x, y, 0);
4969
5055
  var swivel = (kit, parent, x, y) => void kit.torus(parent, new kit.THREE.TorusGeometry(6e-3, 15e-4, 6, 12), kit.M.metal, x, y, 0);
4970
5056
 
4971
- // src/models/bullpup.ts
4972
- function buildBullpup(kit, options) {
5057
+ // src/models/attachments/bipod.ts
5058
+ function buildBipod(kit, parent, x, y) {
4973
5059
  const { M, box: box2, cylinderX } = kit;
4974
- const group = kit.group();
4975
- const L = (options.L ?? 0) / 1e3;
4976
- const barrel = (options.B ?? 0) / 1e3;
4977
- const bodyLength = Math.max(0.32, L - barrel * 0.72);
4978
- const body = options.shot ? M.poly : M.olive;
4979
- const parts = { family: options.shot ? "pump" : "rifle", stroke: 0.06, glow: [] };
4980
- box2(group, bodyLength, 0.075, 0.042, body, bodyLength / 2, 0, 0);
4981
- box2(group, bodyLength * 0.9, 0.02, 0.044, M.dark, bodyLength * 0.5, -0.02, 0);
4982
- box2(group, 0.032, 0.13, 0.044, M.rubber, 0.016, -0.025, 0);
4983
- const gx = bodyLength * 0.8;
4984
- const grip = kit.pivot(group, gx, -0.085, 0);
4985
- grip.rotation.z = -0.25;
4986
- parts.grip = grip;
4987
- box2(grip, 0.03, 0.1, 0.03, M.poly, 0, 0, 0);
4988
- box2(group, 0.1, 6e-3, 0.03, body, gx + 0.01, -0.06, 0);
4989
- parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.02, -0.05, 0, 0.15);
4990
- heatify(parts.glow, cylinderX(group, options.shot ? 0.0105 : 85e-4, barrel, M.metal, L - barrel / 2, 0.012, 0, 16));
4991
- cylinderX(group, 6e-3, 4e-3, M.hole, L + 1e-3, 0.012, 0, 12);
4992
- box2(group, 0.05, 0.024, 4e-3, M.hole, bodyLength * 0.25, 5e-3, 0.0212);
4993
- const bolt = kit.pivot(group);
4994
- parts.bolt = bolt;
4995
- box2(bolt, 0.044, 0.02, 6e-3, M.steelL, bodyLength * 0.25, 5e-3, 0.02);
4996
- if (options.shot) {
4997
- cylinderX(group, 0.012, bodyLength * 0.75, M.metal, bodyLength * 0.55, -0.025, 0.014, 14);
4998
- cylinderX(group, 0.012, bodyLength * 0.75, M.metal, bodyLength * 0.55, -0.025, -0.014, 14);
4999
- const pump = kit.pivot(group);
5000
- parts.pump = pump;
5001
- box2(pump, 0.15, 0.045, 0.052, M.poly, bodyLength + 0.06, -0.015, 0);
5002
- for (let i = 0; i < 6; i++) box2(pump, 6e-3, 0.047, 0.054, M.dark, bodyLength + 5e-3 + i * 0.022, -0.015, 0);
5003
- rail(kit, group, bodyLength * 0.15, bodyLength * 0.8, 0.04, 0, 0.02);
5004
- parts.loadPort = kit.mark(group, bodyLength * 0.2, -0.04, 0);
5005
- } else {
5006
- const magazine = kit.pivot(group, gx - 0.11, -0.03, 0);
5007
- magazine.rotation.z = 0.14;
5008
- parts.mag = magazine;
5009
- box2(magazine, 0.05, 0.14, 0.026, M.glass, 0, -0.07, 0);
5010
- for (let i = 0; i < 6; i++) cylinderX(magazine, 3e-3, 0.04, M.brass, 0, -0.01 - i * 0.02, 0, 6);
5011
- parts.magDrop = 0.18;
5012
- scope(kit, group, 2, bodyLength * 0.5, 0.04, 0);
5013
- box2(group, 0.2, 0.03, 0.02, body, bodyLength * 0.62, 0.048, 0);
5014
- box2(group, 0.026, 0.075, 0.026, body, bodyLength + 0.05, -0.05, 0, 0.1);
5060
+ cylinderX(parent, 8e-3, 0.02, M.dark, x, y, 0, 12);
5061
+ for (const side of [-1, 1]) {
5062
+ const leg = kit.pivot(parent, x, y, side * 0.012);
5063
+ leg.rotation.set(side * 0.15, 0, -0.35);
5064
+ box2(leg, 6e-3, 0.2, 6e-3, M.dark, 0, -0.1, 0);
5065
+ box2(leg, 0.01, 6e-3, 0.01, M.rubber, 0, -0.2, 0);
5015
5066
  }
5016
- parts.muzzle = kit.mark(group, L + 0.01, 0.012, 0);
5017
- parts.eject = kit.mark(group, bodyLength * 0.25, 5e-3, 0.03);
5018
- return { group, parts };
5019
5067
  }
5020
-
5021
- // src/models/launcher-flame.ts
5022
- var hose = (kit, group, points, radius, material) => {
5023
- const { THREE } = kit;
5024
- const curve = new THREE.CatmullRomCurve3(points.map((point) => new THREE.Vector3(...point)));
5025
- const tube = new THREE.Mesh(new THREE.TubeGeometry(curve, 40, radius, 8, false), material);
5026
- tube.castShadow = tube.receiveShadow = true;
5027
- group.add(tube);
5028
- };
5029
- var pilot = (kit, group, x, y) => {
5030
- const { M, cylinderX } = kit;
5031
- cylinderX(group, 6e-3, 0.03, M.fire2, x + 0.015, y, 0, 6, 0).userData.flame = 1;
5032
- cylinderX(group, 4e-3, 0.022, M.fire, x + 0.012, y, 0, 6, 0).userData.flame = 1;
5068
+ var buildBipodModel = ({ kit }) => {
5069
+ const group = kit.group();
5070
+ buildBipod(kit, group, 0, 0);
5071
+ return group;
5033
5072
  };
5034
- function buildFlamethrower(kit, group, parts) {
5035
- const { M, box: box2, cylinderX, cylinderY, sphere } = kit;
5036
- parts.kick = 0.15;
5037
- heatify(parts.glow, cylinderX(group, 0.016, 0.62, M.steel, 0.42, 0, 0, 14));
5038
- cylinderX(group, 0.022, 0.1, M.dark, 0.78, 0, 0, 14);
5039
- heatify(parts.glow, cylinderX(group, 0.013, 0.1, M.steel, 0.88, 0, 0, 12, 9e-3));
5040
- cylinderX(group, 0.018, 0.02, M.metal, 0.73, 0, 0, 14);
5041
- cylinderX(group, 0.012, 0.06, M.metal, 0.84, -0.025, 0, 10);
5042
- pilot(kit, group, 0.92, -0.012);
5043
- const foreGrip = kit.pivot(group, 0.6, -0.02, 0);
5044
- foreGrip.rotation.z = -0.1;
5045
- box2(foreGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5046
- const lever = kit.pivot(group, 0.62, -0.02, 0);
5047
- parts.trigger = box2(lever, 8e-3, 0.05, 6e-3, M.metal, 0.02, -0.03, 0, 0.3);
5048
- const rearGrip = kit.pivot(group, 0.17, -0.02, 0);
5049
- rearGrip.rotation.z = -0.28;
5050
- box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5051
- box2(group, 0.05, 4e-3, 0.012, M.metal, 0.2, -0.035, 0);
5052
- cylinderX(group, 0.02, 0.04, M.metal, 0.1, 0, 0, 12);
5053
- hose(kit, group, [[0.08, 0, 0], [0, -0.04, 0], [-0.08, -0.2, 0.05], [-0.18, -0.32, 0.08], [-0.25, -0.33, 0.08]], 0.011, M.rubber);
5054
- for (const z of [-0.1, 0.1]) {
5055
- cylinderY(group, 0.085, 0.5, M.olive, -0.3, -0.05, z, 20);
5056
- sphere(group, 0.085, M.olive, -0.3, 0.2, z, 2).scale.y = 0.45;
5057
- sphere(group, 0.085, M.olive, -0.3, -0.3, z, 2).scale.y = 0.45;
5058
- cylinderY(group, 0.03, 0.03, M.metal, -0.3, 0.24, z, 12);
5073
+
5074
+ // src/models/rifle-fittings.ts
5075
+ var SCOPE_IDS = { 1: "red_dot", 2: "acog_4x", 3: "scope_3_9x", 4: "scope_6_24x" };
5076
+ function buildFittings(context, gripX) {
5077
+ const { kit, group, parts, options: o, furniture: furn, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
5078
+ const { M, box: box2, cylinderX, cylinderZ, sphere } = kit;
5079
+ const middle = (x0 + x1) / 2;
5080
+ if (o.scope) recordFitting(parts, SCOPE_IDS[o.scope] ?? "scope", group, () => scope(kit, group, o.scope, middle + 0.03, hR / 2 + (o.rail ? 6e-3 : 0), o.scope === 4 ? -0.02 : 0));
5081
+ if (o.carry) {
5082
+ box2(group, 0.15, 0.026, 0.014, M.metal, middle, hR / 2 + 0.03, 0);
5083
+ box2(group, 0.012, 0.03, 0.014, M.metal, middle + 0.07, hR / 2 + 0.014, 0);
5084
+ box2(group, 0.012, 0.03, 0.014, M.metal, middle - 0.07, hR / 2 + 0.014, 0);
5059
5085
  }
5060
- sphere(group, 0.075, M.olive, -0.3, 0.1, 0, 2).position.x = -0.39;
5061
- box2(group, 0.03, 0.6, 0.24, M.dark, -0.4, -0.05, 0);
5062
- cylinderY(group, 0.03, 0.012, M.metal, -0.39, 0.18, 0, 14);
5063
- const gauge = kit.pivot(group, -0.39, 0.18, 3e-3);
5064
- parts.gauge = gauge;
5065
- box2(gauge, 2e-3, 0.022, 2e-3, M.red, 0, 8e-3, 0.012);
5066
- parts.muzzle = kit.pivot(group, 0.94, 0, 0);
5067
- parts.rear = kit.pivot(group, 0.1, 0, 0);
5068
- }
5069
- function buildImprovisedFlamethrower(kit, group, parts) {
5070
- const { M, box: box2, cylinderX, cylinderY, sphere } = kit;
5071
- parts.kick = 0.12;
5072
- heatify(parts.glow, cylinderX(group, 0.011, 0.66, M.pipe, 0.38, 0, 0, 12));
5073
- heatify(parts.glow, cylinderX(group, 0.015, 0.06, M.steel, 0.74, 0, 0, 12, 0.01));
5074
- cylinderX(group, 6e-3, 0.05, M.metal, 0.735, -0.02, 0, 8);
5075
- pilot(kit, group, 0.77, -0.02);
5076
- cylinderY(group, 0.012, 0.04, M.redPaint, 0.72, -0.055, 0, 10);
5077
- box2(group, 0.02, 0.03, 0.012, M.dark, 0.72, -0.085, 0);
5078
- cylinderX(group, 0.06, 0.42, M.redPaint, 0.3, -0.09, 0, 22);
5079
- sphere(group, 0.06, M.redPaint, 0.51, -0.09, 0, 2).scale.x = 0.5;
5080
- sphere(group, 0.06, M.redPaint, 0.09, -0.09, 0, 2).scale.x = 0.5;
5081
- cylinderX(group, 0.02, 0.04, M.dark, 0.04, -0.09, 0, 12);
5082
- box2(group, 0.02, 0.06, 5e-3, M.white, 0.3, -0.09, 0.06);
5083
- for (const x of [0.15, 0.3, 0.45]) cylinderX(group, 0.063, 0.03, M.tape, x, -0.09, 0, 22);
5084
- cylinderX(group, 0.016, 0.03, M.tape, 0.3, 0, 0, 12);
5085
- hose(kit, group, [[0.04, -0.09, 0], [-0.02, -0.06, 0.02], [0, 0, 0.02], [0.05, 0, 0]], 7e-3, M.rubber);
5086
- const rearGrip = kit.pivot(group, 0.12, -0.01, 0);
5087
- rearGrip.rotation.z = -0.3;
5088
- box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5089
- const lever = kit.pivot(group, 0.17, -0.02, 0);
5090
- parts.trigger = box2(lever, 8e-3, 0.04, 6e-3, M.metal, 0, -0.02, 0, 0.2);
5091
- parts.muzzle = kit.pivot(group, 0.78, 0, 0);
5092
- parts.rear = kit.pivot(group, 0.05, 0, 0);
5086
+ if (o.bipod) recordFitting(parts, "bipod", group, () => buildBipod(kit, group, x1 + (L - x1) * 0.45 + 0.04, by - 0.012));
5087
+ if (o.bolt) {
5088
+ const handle = kit.pivot(group, x1 - 0.045, by, 0);
5089
+ parts.boltHandle = handle;
5090
+ parts.boltHandleRest = handle.position.x;
5091
+ cylinderZ(handle, 42e-4, 0.05, M.steelL, 0, 0, wR / 2 + 0.022, 12);
5092
+ sphere(handle, 95e-4, M.dark, 0, -4e-3, wR / 2 + 0.048, 1);
5093
+ cylinderX(group, hR * 0.3, 0.03, M.steel, x0 + 5e-3, by, 0, 16);
5094
+ }
5095
+ if (o.lever) {
5096
+ const lever = kit.pivot(group, gripX - 0.02, -hR / 2 - 0.01, 0);
5097
+ parts.lever = lever;
5098
+ box2(lever, 0.11, 6e-3, 0.012, M.metal, 0.055, -0.052, 0);
5099
+ box2(lever, 6e-3, 0.05, 0.012, M.metal, 3e-3, -0.028, 0);
5100
+ box2(lever, 6e-3, 0.05, 0.012, M.metal, 0.108, -0.028, 0);
5101
+ box2(lever, 0.04, 6e-3, 0.012, M.metal, 0.08, -2e-3, 0);
5102
+ box2(group, 0.03, 0.012, 3e-3, M.dark, x1 - 0.04, by, wR / 2 + 1e-3);
5103
+ }
5104
+ if (o.tube) {
5105
+ const tubeLength = (L - x1) * (o.tubeLen || 0.8);
5106
+ cylinderX(group, 0.0105, tubeLength, M.metal, x1 + tubeLength / 2, by - 0.023, 0, 16);
5107
+ cylinderX(group, 0.0125, 0.02, M.dark, x1 + tubeLength + 6e-3, by - 0.023, 0, 16);
5108
+ }
5109
+ if (o.pump) {
5110
+ const pump = kit.pivot(group);
5111
+ parts.pump = pump;
5112
+ box2(pump, 0.17, 0.042, 0.046, furn, x1 + 0.14, by - 0.022, 0);
5113
+ for (let i = 0; i < 8; i++) box2(pump, 6e-3, 0.044, 0.048, M.dark, x1 + 0.07 + i * 0.02, by - 0.022, 0);
5114
+ }
5115
+ if (o.heat) {
5116
+ cylinderX(group, r * 1.75, (L - x1) * 0.7, M.dark, x1 + (L - x1) * 0.4, by, 0, 12);
5117
+ for (let i = 0; i < 10; i++) box2(group, 8e-3, r * 1.5, 4e-3, M.hole, x1 + (L - x1) * (0.1 + i * 0.06), by + r * 0.8, r * 1.5);
5118
+ }
5119
+ if (o.bayo) box2(group, 0.025, 8e-3, 0.01, M.metal, L - 0.07, by - 0.016, 0);
5120
+ if (o.rod) cylinderX(group, 3e-3, (L - x1) * 0.85, M.steel, x1 + (L - x1) * 0.45, by - 0.014, 0, 8);
5093
5121
  }
5094
5122
 
5095
- // src/models/launcher-rounds.ts
5123
+ // src/models/rifle-forend.ts
5096
5124
  var PI3 = Math.PI;
5097
- function buildRocketGrenade(kit) {
5098
- const { M, cylinderX } = kit;
5099
- const round2 = kit.group();
5100
- cylinderX(round2, 0.018, 0.15, M.olive, -0.05, 0, 0, 14);
5101
- cylinderX(round2, 0.02, 0.07, M.olive, 0.035, 0, 0, 18, 0.0425);
5102
- cylinderX(round2, 0.0425, 0.12, M.olive, 0.13, 0, 0, 20);
5103
- cylinderX(round2, 0.0428, 0.012, M.dark, 0.09, 0, 0, 20);
5104
- cylinderX(round2, 0.0425, 0.13, M.olive, 0.255, 0, 0, 20, 0.011);
5105
- cylinderX(round2, 0.011, 0.03, M.metal, 0.335, 0, 0, 10, 4e-3);
5106
- return round2;
5107
- }
5108
- function buildRecoillessRound(kit) {
5109
- const { M, cylinderX } = kit;
5110
- const round2 = kit.group();
5111
- cylinderX(round2, 0.041, 0.32, M.green, -0.16, 0, 0, 20);
5112
- cylinderX(round2, 0.0415, 0.02, M.yellow, -0.05, 0, 0, 20);
5113
- cylinderX(round2, 0.041, 0.12, M.green, 0.06, 0, 0, 20, 0.012);
5114
- cylinderX(round2, 0.043, 0.03, M.brass, -0.33, 0, 0, 20);
5115
- return round2;
5125
+ function buildForend(context) {
5126
+ const { kit, group, options: o, furniture: furn, length: L, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
5127
+ const { M, box: box2 } = kit;
5128
+ const hasLongScope = (o.scope ?? 0) >= 3;
5129
+ if (!context.isTraditional && o.hg !== "none") {
5130
+ const handguardLength = (L - x1) * (o.hgFrac || 0.5);
5131
+ const hx = x1 + handguardLength / 2;
5132
+ const hh = hR * 0.8;
5133
+ if (o.hg === "rail") {
5134
+ box2(group, handguardLength, hh, wR * 1.04, M.dark, hx, by - 2e-3, 0);
5135
+ rail(kit, group, x1, handguardLength, by + hh / 2, 0, wR * 0.66);
5136
+ rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, wR * 0.54, wR * 0.4, PI3 / 2);
5137
+ rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, -wR * 0.54, wR * 0.4, -PI3 / 2);
5138
+ rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - hh / 2, 0, wR * 0.6, PI3);
5139
+ } else {
5140
+ const material = o.hg === "wood" ? M.wood : furn;
5141
+ if (o.ak) {
5142
+ box2(group, handguardLength, hh * 0.55, wR * 1.08, material, hx, by - hh * 0.22, 0);
5143
+ box2(group, handguardLength * 0.75, hh * 0.35, wR * 0.7, material, hx - handguardLength * 0.1, by + r * 2.2, 0);
5144
+ } else {
5145
+ box2(group, handguardLength, hh, wR * 1.08, material, hx, by - 2e-3, 0);
5146
+ vents(kit, group, x1 + 0.01, handguardLength - 0.02, by + hh * 0.15, wR * 0.54 + 1e-3, Math.round(handguardLength / 0.025), hh * 0.3);
5147
+ vents(kit, group, x1 + 0.01, handguardLength - 0.02, by + hh * 0.15, -wR * 0.54 - 1e-3, Math.round(handguardLength / 0.025), hh * 0.3);
5148
+ }
5149
+ }
5150
+ if (!hasLongScope && !o.rail && o.ak) {
5151
+ box2(group, 0.03, 0.016, wR * 0.6, M.metal, x1 + 0.02, by + r * 2.5 + 0.01, 0);
5152
+ box2(group, 0.012, 4e-3, wR * 0.5, M.dark, x1 + 0.03, by + r * 2.5 + 0.019, 0);
5153
+ }
5154
+ }
5155
+ if (hasLongScope) return;
5156
+ const sightX = L - (o.ak ? 0.06 : 0.08);
5157
+ if (o.ak) {
5158
+ box2(group, 0.022, 0.022, wR * 0.6, M.metal, sightX, by + 8e-3, 0);
5159
+ box2(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, 8e-3);
5160
+ box2(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, -8e-3);
5161
+ box2(group, 3e-3, 0.016, 3e-3, M.dark, sightX, by + 0.022, 0);
5162
+ } else if (!o.rail || o.carry) {
5163
+ box2(group, 0.018, 0.014, wR * 0.55, M.dark, sightX, by + 8e-3, 0);
5164
+ box2(group, 0.012, 0.034, 8e-3, M.dark, sightX, by + 0.03, 0, -0.25);
5165
+ } else box2(group, 0.012, 0.024, 0.012, M.dark, sightX - 0.04, by + hR * 0.4 + 0.012, 0);
5116
5166
  }
5117
- function buildFlyingRocket(kit, launcherType) {
5167
+
5168
+ // src/models/attachments/belt-box.ts
5169
+ function buildBeltBox(kit, belt) {
5118
5170
  const { M, box: box2, cylinderX } = kit;
5119
- const rocket = kit.group();
5120
- const diameter = launcherType === "at4" || launcherType === "carl" ? 0.041 : 0.032;
5121
- cylinderX(rocket, diameter * 0.5, 0.2, M.dark, -0.1, 0, 0, 10);
5122
- cylinderX(rocket, diameter, 0.3, M.olive, 0.15, 0, 0, 18);
5123
- cylinderX(rocket, diameter, 0.12, M.olive, 0.36, 0, 0, 18, diameter * 0.25);
5124
- for (let i = 0; i < 4; i++) {
5125
- const fin = box2(rocket, 0.06, 2e-3, diameter * 1.4, M.metal, -0.17, 0, 0);
5126
- fin.rotation.x = i * PI3 / 4;
5171
+ box2(belt, 0.11, 0.1, 0.08, M.olive, 0, 0, 0);
5172
+ box2(belt, 0.112, 0.012, 0.082, M.dark, 0, 0.046, 0);
5173
+ for (let i = 0; i < 7; i++) {
5174
+ cylinderX(belt, 42e-4, 0.034, M.brass, -0.04 + i * 0.012, 0.07, 0.035, 10);
5175
+ box2(belt, 6e-3, 6e-3, 0.012, M.metal, -0.04 + i * 0.012, 0.066, 0.035);
5127
5176
  }
5128
- return rocket;
5129
5177
  }
5130
5178
 
5131
- // src/models/launcher-rocket.ts
5132
- function buildRpg(kit, group, parts) {
5133
- const { M, box: box2, cylinderX } = kit;
5134
- parts.kick = 1.6;
5135
- cylinderX(group, 0.036, 0.13, M.dark, 0.065, 0, 0, 20, 0.021);
5136
- cylinderX(group, 0.038, 0.012, M.metal, 0, 0, 0, 20);
5137
- heatify(parts.glow, cylinderX(group, 0.021, 0.83, M.steel, 0.545, 0, 0, 18));
5138
- cylinderX(group, 0.03, 0.3, M.wood, 0.47, 0, 0, 16);
5139
- cylinderX(group, 0.031, 0.012, M.metal, 0.32, 0, 0, 16);
5140
- cylinderX(group, 0.031, 0.012, M.metal, 0.62, 0, 0, 16);
5141
- cylinderX(group, 0.024, 0.02, M.metal, 0.94, 0, 0, 16);
5142
- box2(group, 0.09, 0.03, 0.032, M.metal, 0.46, -0.028, 0);
5143
- const frontGrip = kit.pivot(group, 0.4, -0.04, 0);
5144
- frontGrip.rotation.z = -0.22;
5145
- box2(frontGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5146
- const rearGrip = kit.pivot(group, 0.5, -0.04, 0);
5147
- rearGrip.rotation.z = -0.18;
5148
- box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5149
- box2(group, 0.06, 4e-3, 0.012, M.metal, 0.53, -0.05, 0);
5150
- box2(group, 4e-3, 0.025, 0.012, M.metal, 0.56, -0.04, 0);
5151
- parts.trigger = box2(group, 4e-3, 0.018, 4e-3, M.dark, 0.535, -0.045, 0, 0.15);
5152
- const hammer = kit.pivot(group, 0.44, -0.012, 0);
5153
- parts.hammer = hammer;
5154
- box2(hammer, 0.012, 0.02, 0.01, M.dark, 0, 0, 0);
5155
- box2(group, 6e-3, 0.03, 6e-3, M.dark, 0.78, 0.032, 0);
5156
- box2(group, 0.02, 0.022, 0.014, M.dark, 0.46, 0.032, 0);
5157
- box2(group, 0.12, 0.042, 0.032, M.dark, 0.5, 0.04, -0.04);
5158
- cylinderX(group, 0.013, 0.05, M.rubber, 0.42, 0.048, -0.04, 14);
5159
- cylinderX(group, 0.016, 4e-3, M.lens, 0.565, 0.048, -0.04, 14);
5160
- box2(group, 0.04, 0.012, 0.012, M.metal, 0.5, 0.016, -0.03);
5161
- const round2 = buildRocketGrenade(kit);
5162
- round2.position.x = 0.95;
5163
- group.add(round2);
5164
- parts.round = round2;
5165
- parts.roundRest = 0.95;
5166
- parts.makeRound = () => buildRocketGrenade(kit);
5167
- parts.muzzle = kit.pivot(group, 0.95, 0, 0);
5168
- parts.rear = kit.pivot(group, 0, 0, 0);
5169
- }
5170
- function buildM72(kit, group, parts) {
5171
- const { M, box: box2, cylinderX, cylinderY } = kit;
5172
- parts.kick = 1.1;
5173
- parts.disposable = true;
5174
- heatify(parts.glow, cylinderX(group, 0.0365, 0.6, M.olive, 0.58, 0, 0, 20));
5175
- cylinderX(group, 0.032, 0.32, M.olive, 0.16, 0, 0, 20);
5176
- cylinderX(group, 0.039, 0.03, M.rubber, 0.866, 0, 0, 20);
5177
- cylinderX(group, 0.035, 0.025, M.rubber, 0.012, 0, 0, 20);
5178
- cylinderX(group, 0.037, 0.015, M.yellow, 0.8, 0, 0, 20);
5179
- cylinderX(group, 0.037, 0.012, M.dark, 0.3, 0, 0, 20);
5180
- box2(group, 4e-3, 0.05, 0.05, M.dark, 0.79, 0.055, 0);
5181
- box2(group, 4e-3, 6e-3, 0.04, M.dark, 0.79, 0.078, 0);
5182
- box2(group, 4e-3, 0.035, 0.024, M.dark, 0.38, 0.05, 0);
5183
- cylinderY(group, 4e-3, 6e-3, M.lens, 0.38, 0.064, 0, 8);
5184
- parts.trigger = box2(group, 0.05, 0.016, 0.024, M.rubber, 0.5, 0.042, 0);
5185
- box2(group, 0.1, 0.01, 0.03, M.dark, 0.47, 0.036, 0);
5186
- box2(group, 0.02, 0.03, 0.01, M.dark, 0.66, -0.035, 0.03);
5187
- box2(group, 8e-3, 0.03, 8e-3, M.metal, 0.25, -0.04, 0.03);
5188
- parts.muzzle = kit.pivot(group, 0.881, 0, 0);
5189
- parts.rear = kit.pivot(group, 0, 0, 0);
5190
- }
5191
- function buildAt4(kit, group, parts) {
5192
- const { M, box: box2, cylinderX } = kit;
5193
- parts.kick = 1.3;
5194
- parts.disposable = true;
5195
- heatify(parts.glow, cylinderX(group, 0.05, 0.86, M.olive, 0.51, 0, 0, 24));
5196
- cylinderX(group, 0.056, 0.08, M.dark, 0.98, 0, 0, 24);
5197
- cylinderX(group, 0.058, 0.07, M.dark, 0.04, 0, 0, 24, 0.05);
5198
- cylinderX(group, 0.051, 0.015, M.yellow, 0.86, 0, 0, 24);
5199
- box2(group, 0.16, 0.026, 0.05, M.dark, 0.42, 0.058, 0);
5200
- box2(group, 0.03, 0.012, 0.016, M.red, 0.47, 0.075, 0);
5201
- parts.trigger = box2(group, 0.022, 0.012, 0.018, M.rubber, 0.5, 0.074, 0);
5202
- box2(group, 0.012, 0.07, 0.02, M.dark, 0.35, 0.09, -0.035);
5203
- box2(group, 0.012, 0.05, 0.02, M.dark, 0.71, 0.08, -0.035);
5204
- const grip = kit.pivot(group, 0.68, -0.05, 0);
5205
- grip.rotation.z = -0.15;
5206
- parts.grip = grip;
5207
- box2(grip, 0.035, 0.11, 0.032, M.dark, 0, -0.05, 0);
5208
- box2(group, 0.14, 0.035, 0.06, M.rubber, 0.32, -0.06, 0);
5209
- box2(group, 0.3, 0.012, 0.055, M.dark, 0.5, -0.05, 0);
5210
- parts.muzzle = kit.pivot(group, 1.02, 0, 0);
5211
- parts.rear = kit.pivot(group, 0, 0, 0);
5212
- }
5213
- function buildCarlGustaf(kit, group, parts) {
5214
- const { M, box: box2, cylinderX } = kit;
5215
- parts.kick = 1.4;
5216
- heatify(parts.glow, cylinderX(group, 0.045, 0.72, M.dark, 0.59, 0, 0, 24));
5217
- cylinderX(group, 0.05, 0.04, M.rubber, 0.93, 0, 0, 24);
5218
- const breech = kit.pivot(group, 0.21, 0, 0.05);
5219
- parts.breech = [breech];
5220
- cylinderX(breech, 0.06, 0.16, M.dark, -0.1, 0, -0.05, 24, 0.046);
5221
- cylinderX(breech, 0.062, 0.015, M.metal, -0.18, 0, -0.05, 24);
5222
- box2(group, 0.025, 0.03, 0.02, M.metal, 0.22, 0, 0.052);
5223
- const foreGrip = kit.pivot(group, 0.62, -0.05, 0);
5224
- foreGrip.rotation.z = -0.2;
5225
- box2(foreGrip, 0.035, 0.11, 0.034, M.dark, 0, -0.05, 0);
5226
- parts.trigger = box2(group, 4e-3, 0.018, 4e-3, M.dark, 0.66, -0.06, 0, 0.15);
5227
- box2(group, 0.06, 4e-3, 0.012, M.metal, 0.66, -0.07, 0);
5228
- const rearGrip = kit.pivot(group, 0.4, -0.05, 0);
5229
- rearGrip.rotation.z = -0.25;
5230
- box2(rearGrip, 0.035, 0.1, 0.034, M.dark, 0, -0.05, 0);
5231
- box2(group, 0.13, 0.04, 0.07, M.rubber, 0.28, -0.065, 0);
5232
- box2(group, 0.16, 0.05, 0.045, M.dark, 0.56, 0.07, -0.035);
5233
- cylinderX(group, 0.016, 0.05, M.rubber, 0.46, 0.075, -0.035, 14);
5234
- cylinderX(group, 0.02, 4e-3, M.lens, 0.645, 0.075, -0.035, 14);
5235
- box2(group, 0.06, 0.02, 0.02, M.metal, 0.56, 0.04, -0.03);
5236
- box2(group, 0.22, 0.01, 0.02, M.dark, 0.6, 0.06, 0.03);
5237
- box2(group, 0.01, 0.03, 0.02, M.dark, 0.5, 0.05, 0.03);
5238
- box2(group, 0.01, 0.03, 0.02, M.dark, 0.7, 0.05, 0.03);
5239
- parts.makeRound = () => buildRecoillessRound(kit);
5240
- parts.muzzle = kit.pivot(group, 0.95, 0, 0);
5241
- parts.rear = kit.pivot(group, 0.05, 0, 0);
5179
+ // src/models/attachments/drum.ts
5180
+ function buildDrum(kit, magazine) {
5181
+ const { M, box: box2, cylinderZ } = kit;
5182
+ cylinderZ(magazine, 0.075, 0.07, M.dark, 0.02, -0.085, 0, 22);
5183
+ cylinderZ(magazine, 0.05, 0.074, M.metal, 0.02, -0.085, 0, 18);
5184
+ box2(magazine, 0.05, 0.04, 0.03, M.dark, 0, -0.02, 0);
5242
5185
  }
5243
- var ROCKET_BUILDERS = {
5244
- rpg: buildRpg,
5245
- m72: buildM72,
5246
- at4: buildAt4,
5247
- carl: buildCarlGustaf
5248
- };
5249
5186
 
5250
- // src/models/launcher.ts
5251
- function buildLauncher(kit, options) {
5252
- const group = kit.group();
5253
- const type = options.lt ?? "";
5254
- const isFlame = type === "flame" || type === "flameDIY";
5255
- const parts = { family: isFlame ? "flame" : "rocket", launcherType: type, glow: [] };
5256
- if (type === "flame") buildFlamethrower(kit, group, parts);
5257
- else if (type === "flameDIY") buildImprovisedFlamethrower(kit, group, parts);
5258
- else ROCKET_BUILDERS[type]?.(kit, group, parts);
5259
- parts.eject = parts.muzzle;
5260
- return { group, parts };
5187
+ // src/models/helical-magazine.ts
5188
+ var PI4 = Math.PI;
5189
+ var LENGTH = 0.26;
5190
+ var RADIUS = 0.026;
5191
+ var TURNS = 11;
5192
+ function buildHelicalMagazine(context) {
5193
+ const { kit, group, parts, receiverEnd, barrelY, barrelRadius } = context;
5194
+ const { M, cylinderX, box: box2 } = kit;
5195
+ const x = receiverEnd + LENGTH / 2 + 0.02;
5196
+ const y = barrelY - barrelRadius - RADIUS - 0.012;
5197
+ const magazine = kit.pivot(group, x, y, 0);
5198
+ parts.mag = magazine;
5199
+ cylinderX(magazine, RADIUS, LENGTH, M.dark, 0, 0, 0, 20);
5200
+ cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, -LENGTH / 2, 0, 0, 20);
5201
+ cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, LENGTH / 2, 0, 0, 20);
5202
+ for (let i = 0; i < TURNS; i++) box2(magazine, 0.012, RADIUS * 2.15, 0.014, M.metal, -LENGTH / 2 + (i + 0.5) * LENGTH / TURNS, 0, 0, 0, 0, i * PI4 / 4);
5203
+ box2(magazine, 0.05, 0.03, 0.03, M.dark, -LENGTH / 2 - 0.02, RADIUS * 0.4, 0);
5204
+ parts.magDrop = 0.16;
5205
+ return x;
5261
5206
  }
5262
5207
 
5263
- // src/models/m134.ts
5264
- var PI4 = Math.PI;
5265
- var BARRELS = 6;
5266
- var CLUSTER_RADIUS = 0.03;
5267
- var BARREL_RADIUS = 78e-4;
5268
- var BARREL_END = 0.8;
5269
- function buildBarrelCluster(kit, parts, group) {
5270
- const { M, cylinderX } = kit;
5271
- const barrels = kit.pivot(group, 0, 0, 0);
5272
- parts.barrels = barrels;
5273
- const length = BARREL_END - 0.3;
5274
- let heated;
5275
- for (let i = 0; i < BARRELS; i++) {
5276
- const angle = i * 2 * PI4 / BARRELS;
5277
- const y = Math.cos(angle) * CLUSTER_RADIUS;
5278
- const z = Math.sin(angle) * CLUSTER_RADIUS;
5279
- const barrel = cylinderX(barrels, BARREL_RADIUS, length, M.metal, 0.3 + length / 2, y, z, 14);
5280
- if (heated) barrel.material = heated;
5281
- else heated = heatify(parts.glow, barrel).material;
5282
- cylinderX(barrels, BARREL_RADIUS * 0.55, 4e-3, M.hole, BARREL_END + 5e-4, y, z, 10);
5208
+ // src/models/rifle-controls.ts
5209
+ function buildGrip(context) {
5210
+ const { kit, group, parts, options: o, furniture: furn, receiverStart: x0, receiverHeight: hR, receiverWidth: wR } = context;
5211
+ const { M, box: box2 } = kit;
5212
+ const gx = x0 + (o.gripAt || 0.07);
5213
+ if (!context.isTraditional && o.grip !== "none") {
5214
+ const grip = kit.pivot(group, gx - 0.012, -hR / 2 - 0.046, 0);
5215
+ grip.rotation.z = -0.3;
5216
+ parts.grip = grip;
5217
+ box2(grip, 0.032, 0.105, 0.028, furn, 0, 0, 0);
5218
+ for (let i = 0; i < 3; i++) box2(grip, 6e-3, 0.012, 0.026, furn, 0.017, 0.022 - i * 0.02, 0);
5219
+ box2(grip, 0.034, 8e-3, 0.03, furn, 0, -0.052, 0);
5283
5220
  }
5284
- cylinderX(barrels, 0.012, length, M.steel, 0.3 + length / 2, 0, 0, 12);
5285
- for (const x of [0.42, 0.58, 0.74]) cylinderX(barrels, CLUSTER_RADIUS + BARREL_RADIUS * 1.6, 0.014, M.steel, x, 0, 0, 24);
5286
- cylinderX(barrels, CLUSTER_RADIUS + BARREL_RADIUS * 1.8, 0.03, M.dark, BARREL_END - 0.015, 0, 0, 24);
5221
+ box2(group, 0.06, 4e-3, 0.01, M.metal, gx + 0.03, -hR / 2 - 0.024, 0);
5222
+ box2(group, 4e-3, 0.022, 0.01, M.metal, gx + 0.06, -hR / 2 - 0.013, 0);
5223
+ parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.024, -hR / 2 - 0.012, 0, 0.15);
5224
+ if (o.sel) parts.safety = box2(group, 0.016, 5e-3, 3e-3, M.dark, gx + 5e-3, -hR * 0.15, -wR / 2 - 2e-3, 0.4);
5225
+ return gx;
5287
5226
  }
5288
- function buildHousing(kit, parts, group) {
5289
- const { M, box: box2, cylinderX, cylinderZ } = kit;
5290
- box2(group, 0.3, 0.14, 0.15, M.metal, 0.18, 0, 0);
5291
- cylinderX(group, 0.052, 0.12, M.dark, 0.06, 0, 0, 20);
5292
- cylinderX(group, 0.062, 0.2, M.dark, 0.38, 0, 0, 22);
5293
- for (let i = 0; i < 4; i++) cylinderX(group, 0.064, 8e-3, M.steel, 0.3 + i * 0.05, 0, 0, 22);
5294
- box2(group, 0.04, 0.05, 0.2, M.dark, 0.1, 0.095, 0);
5295
- box2(group, 0.2, 0.012, 0.045, M.dark, 0.22, 0.158, 0);
5296
- box2(group, 0.012, 0.05, 0.045, M.dark, 0.13, 0.13, 0);
5297
- box2(group, 0.012, 0.05, 0.045, M.dark, 0.31, 0.13, 0);
5298
- for (const side of [-1, 1]) {
5299
- box2(group, 0.03, 0.03, 0.1, M.poly, 0.06, -0.01, side * 0.14);
5300
- cylinderZ(group, 0.016, 0.07, M.poly, 0.06, -0.01, side * 0.2, 14);
5227
+ function buildMagazine(context, gx) {
5228
+ const { kit, group, parts, options: o, receiverStart: x0, receiverHeight: hR, receiverWidth: wR, receiverLength: RL } = context;
5229
+ const { M, box: box2 } = kit;
5230
+ if (o.mag === "helical") return buildHelicalMagazine(context);
5231
+ const mx = o.magAt != null ? x0 + o.magAt : gx + 0.075;
5232
+ const scale = o.magLen || 1;
5233
+ const magazine = kit.pivot(group, mx, -hR / 2, 0);
5234
+ parts.mag = magazine;
5235
+ const finish = o.magMat === "bakelite" ? M.bakelite : o.magMat === "poly" ? M.polyMag : void 0;
5236
+ if (o.mag === "curved") {
5237
+ let px = 0;
5238
+ let py = 0;
5239
+ const material = finish ?? M.metal;
5240
+ for (const angle of [0.06, 0.22, 0.38]) {
5241
+ const length = 0.07 * scale;
5242
+ const cx = px + Math.sin(angle) * length / 2;
5243
+ const cy = py - Math.cos(angle) * length / 2;
5244
+ box2(magazine, 0.052, length + 4e-3, 0.024, material, cx, cy, 0, angle);
5245
+ box2(magazine, 0.04, length * 0.8, 0.026, M.dark, cx, cy, 0, angle);
5246
+ px += Math.sin(angle) * length;
5247
+ py -= Math.cos(angle) * length;
5248
+ }
5249
+ box2(magazine, 0.058, 8e-3, 0.028, material, px, py, 0, 0.38);
5250
+ parts.magDrop = 0.07 * scale * 3 + 0.04;
5251
+ } else if (o.mag === "box") {
5252
+ const length = 0.13 * scale;
5253
+ const curve = o.curve || 0;
5254
+ const segment = length / 3;
5255
+ const material = finish ?? M.dark;
5256
+ let px = 0;
5257
+ let py = 0;
5258
+ let angle = 0.08;
5259
+ for (let i = 0; i < 3; i++) {
5260
+ angle = 0.08 + curve * i;
5261
+ const cx = px + Math.sin(angle) * segment / 2;
5262
+ const cy = py - Math.cos(angle) * segment / 2;
5263
+ box2(magazine, 0.05, segment + 4e-3, 0.024, material, cx, cy, 0, angle);
5264
+ box2(magazine, 0.04, 3e-3, 0.026, M.metal, cx, cy, 0, angle);
5265
+ px += Math.sin(angle) * segment;
5266
+ py -= Math.cos(angle) * segment;
5267
+ }
5268
+ box2(magazine, 0.056, 7e-3, 0.028, material, px, py - 2e-3, 0, angle);
5269
+ parts.magDrop = length + 0.03;
5270
+ } else if (o.mag === "drum") {
5271
+ buildDrum(kit, magazine);
5272
+ parts.magDrop = 0.2;
5273
+ } else if (o.mag === "internal") {
5274
+ box2(group, 0.08, 0.025, 0.03, context.isTraditional ? context.furniture : M.metal, mx, -hR / 2 - 0.012, 0);
5275
+ parts.mag = null;
5276
+ } else parts.mag = null;
5277
+ if (o.mag === "belt") {
5278
+ const belt = kit.pivot(group, mx + 0.02, -hR / 2 - 0.05, -0.045);
5279
+ parts.belt = belt;
5280
+ buildBeltBox(kit, belt);
5281
+ const cover = kit.pivot(group, x0 + 0.01, hR / 2, 0);
5282
+ parts.cover = cover;
5283
+ box2(cover, RL - 0.03, 0.014, wR * 1.05, M.metal, (RL - 0.03) / 2, 7e-3, 0);
5284
+ box2(cover, 0.02, 0.012, wR * 0.6, M.dark, RL - 0.04, 0.016, 0);
5301
5285
  }
5302
- parts.trigger = box2(group, 0.012, 0.02, 0.016, M.dark, 0.1, 0.085, 0.14, 0.3);
5303
- parts.safety = box2(group, 0.014, 6e-3, 0.01, M.dark, 0.2, 0.073, -0.076, 0.4);
5286
+ return mx;
5304
5287
  }
5305
- function buildFeed(kit, parts, group) {
5288
+
5289
+ // src/models/rifle-stock.ts
5290
+ function buildStock(context, magazineX) {
5291
+ const { kit, group, options: o, furniture: furn, length: L, stockLength: S, receiverStart: x0, receiverEnd: x1, receiverHeight: hR } = context;
5306
5292
  const { M, box: box2, cylinderX } = kit;
5307
- const belt = kit.pivot(group, 0.2, -0.13, -0.16);
5308
- parts.belt = belt;
5309
- box2(belt, 0.16, 0.12, 0.1, M.olive, 0, 0, 0);
5310
- box2(belt, 0.162, 0.012, 0.102, M.dark, 0, 0.056, 0);
5311
- for (let i = 0; i < 8; i++) cylinderX(belt, 46e-4, 0.04, M.brass, -0.07 + i * 0.02, 0.075, 0.04, 10);
5312
- box2(group, 0.1, 0.03, 0.1, M.metal, 0.22, -0.06, -0.1, 0, 0, 0.5);
5313
- const cover = kit.pivot(group, 0.1, 0.075, 0);
5314
- parts.cover = cover;
5315
- box2(cover, 0.24, 0.012, 0.15, M.metal, 0.12, 6e-3, 0);
5316
- const bolt = kit.pivot(group);
5317
- parts.bolt = bolt;
5318
- parts.stroke = 0.03;
5319
- box2(bolt, 0.03, 0.012, 0.012, M.steelL, 0.28, 0.02, 0.078);
5320
- }
5321
- function buildM134(kit) {
5322
- const group = kit.group();
5323
- const parts = { family: "belt", kick: 0.9, glow: [] };
5324
- buildHousing(kit, parts, group);
5325
- buildBarrelCluster(kit, parts, group);
5326
- buildFeed(kit, parts, group);
5327
- parts.frames = {
5328
- length: BARREL_END,
5329
- barrelY: 0,
5330
- barrelRadius: BARREL_RADIUS,
5331
- receiverStart: 0.06,
5332
- receiverEnd: 0.48,
5333
- receiverHeight: 0.14,
5334
- receiverWidth: 0.15,
5335
- handguardEnd: 0.6,
5336
- magazineX: 0.2,
5337
- gripX: 0.06,
5338
- hasRail: false,
5339
- // the tripod head, where the gun balances (the tripod of the counters joins here too)
5340
- underbarrel: [0.4, -0.1]
5341
- };
5342
- parts.muzzle = kit.mark(group, BARREL_END + 0.01, 0, 0);
5343
- parts.eject = kit.mark(group, 0.3, -0.07, 0.08);
5344
- return { group, parts };
5293
+ if (context.isTraditional) {
5294
+ buttpad(kit, group, 6e-3, -0.04, 0.13, 0.038);
5295
+ box2(group, 0.03, 0.13, 0.038, furn, 0.024, -0.04, 0);
5296
+ box2(group, S, 0.055, 0.034, furn, S / 2, -0.012, 0, 0.06);
5297
+ box2(group, 0.09, 0.05, 0.032, furn, x0 - 0.02, -0.035, 0, 0.25);
5298
+ const forendLength = x1 - x0 + (L - x1) * (o.fore ?? 0.62);
5299
+ if (forendLength > 0) box2(group, forendLength, 0.045, 0.04, furn, x0 + forendLength / 2, -hR / 2 + 4e-3, 0);
5300
+ if (o.stock === "straight") box2(group, 0.15, 0.04, 0.035, furn, x1 + 0.08, -hR / 2, 0);
5301
+ box2(group, 0.04, 4e-3, 0.03, M.metal, magazineX, -hR / 2 - 0.022, 0);
5302
+ swivel(kit, group, 0.2, -0.08);
5303
+ swivel(kit, group, x1 + (L - x1) * 0.45, -hR / 2 - 0.03);
5304
+ if (o.bands) for (const fraction of [0.35, 0.75]) cylinderX(group, 0.024, 8e-3, M.metal, x1 + (L - x1) * fraction * (o.fore || 0.62), -4e-3, 0, 16);
5305
+ } else if (o.stock === "fixed" || o.stock === "wood") {
5306
+ box2(group, S, 0.05, 0.03, furn, S / 2, -8e-3, 0, 0.05);
5307
+ box2(group, 0.03, 0.13, 0.036, furn, 0.022, -0.035, 0);
5308
+ buttpad(kit, group, 4e-3, -0.035, 0.13, 0.036);
5309
+ if (o.stock === "wood") box2(group, S * 0.6, 0.03, 0.028, furn, S * 0.55, -0.045, 0, 0.2);
5310
+ swivel(kit, group, 0.08, -0.09);
5311
+ } else if (o.stock === "skeleton") {
5312
+ box2(group, S, 0.014, 0.026, furn, S / 2, 0.012, 0, 0.03);
5313
+ box2(group, S, 0.014, 0.026, furn, S / 2, -0.05, 0, -0.08);
5314
+ box2(group, 0.03, 0.12, 0.034, furn, 0.02, -0.025, 0);
5315
+ buttpad(kit, group, 4e-3, -0.025, 0.12, 0.034);
5316
+ } else if (o.stock === "retract") {
5317
+ cylinderX(group, 0.013, S, M.metal, S / 2, 0, 0, 16);
5318
+ for (let i = 0; i < 5; i++) box2(group, 3e-3, 3e-3, 8e-3, M.dark, S * 0.2 + i * 0.015, -0.013, 0);
5319
+ box2(group, 0.034, 0.11, 0.036, furn, 0.02, -0.025, 0);
5320
+ box2(group, S * 0.55, 0.032, 0.03, furn, S * 0.35, 0, 0);
5321
+ buttpad(kit, group, 3e-3, -0.025, 0.11, 0.036);
5322
+ } else if (o.stock === "fold") {
5323
+ box2(group, S, 6e-3, 6e-3, M.metal, S / 2, 0.01, 0.012);
5324
+ box2(group, S, 6e-3, 6e-3, M.metal, S / 2, -0.04, 0.012, -0.12);
5325
+ box2(group, 0.012, 0.1, 0.03, M.metal, 6e-3, -0.02, 0.012);
5326
+ }
5345
5327
  }
5346
5328
 
5347
- // src/models/m2.ts
5348
- function buildM2(kit) {
5349
- const { M, box: box2, cylinderX, cylinderZ } = kit;
5350
- const group = kit.group();
5351
- const parts = { family: "belt", kick: 2.2, stroke: 0.08, glow: [] };
5352
- box2(group, 0.52, 0.13, 0.12, M.metal, 0.36, 0, 0);
5353
- for (let i = 0; i < 5; i++) cylinderZ(group, 6e-3, 0.124, M.dark, 0.15 + i * 0.1, -0.03, 0, 10);
5354
- heatify(parts.glow, cylinderX(group, 0.016, 1.14, M.metal, 1.14, 0.01, 0, 16));
5355
- cylinderX(group, 0.034, 0.24, M.dark, 0.74, 0.01, 0, 16);
5356
- for (let i = 0; i < 6; i++) cylinderX(group, 0.036, 0.012, M.metal, 0.66 + i * 0.035, 0.01, 0, 16);
5357
- cylinderX(group, 0.022, 0.05, M.dark, 1.69, 0.01, 0, 16);
5358
- box2(group, 0.04, 0.09, 0.03, M.wood, 0.05, -0.02, 0.05);
5359
- box2(group, 0.04, 0.09, 0.03, M.wood, 0.05, -0.02, -0.05);
5360
- box2(group, 0.06, 0.02, 0.12, M.metal, 0.09, -0.02, 0);
5361
- const belt = kit.pivot(group, 0.38, -0.05, -0.12);
5362
- parts.belt = belt;
5363
- box2(belt, 0.16, 0.12, 0.09, M.olive, 0, 0, 0);
5364
- for (let i = 0; i < 8; i++) cylinderX(belt, 6e-3, 0.05, M.brass, -0.08 + i * 0.02, 0.08, 0.04, 8);
5365
- const cover = kit.pivot(group, 0.12, 0.065, 0);
5366
- parts.cover = cover;
5367
- box2(cover, 0.4, 0.012, 0.12, M.metal, 0.2, 6e-3, 0);
5368
- box2(group, 0.04, 0.05, 0.04, M.dark, 0.66, 0.1, 0);
5329
+ // src/models/rifle.ts
5330
+ var PI5 = Math.PI;
5331
+ function buildReceiver(context) {
5332
+ const { kit, group, parts, options: o, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, receiverLength: RL, barrelY: by } = context;
5333
+ const { M, box: box2, cylinderX, cylinderY, cylinderZ, sphere } = kit;
5334
+ const receiverMaterial = o.tan ? M.tan : M.metal;
5335
+ const middle = (x0 + x1) / 2;
5336
+ if (o.round) {
5337
+ cylinderX(group, hR * 0.36, RL, M.steel, middle, by, 0, 20);
5338
+ cylinderX(group, hR * 0.32, 0.05, M.steel, x0 - 0.01, by, 0, 18);
5339
+ } else if (o.ar) {
5340
+ box2(group, RL, hR * 0.5, wR, receiverMaterial, middle, hR * 0.25, 0);
5341
+ box2(group, RL * 0.92, hR * 0.52, wR * 0.96, receiverMaterial, middle - RL * 0.02, -hR * 0.24, 0);
5342
+ box2(group, 0.05, 0.03, wR * 1.15, receiverMaterial, x0 + RL * 0.62, -hR * 0.5 - 0.01, 0);
5343
+ cylinderZ(group, 6e-3, 0.012, M.metal, x1 - 0.03, hR * 0.28, wR / 2 + 6e-3, 12);
5344
+ box2(group, 0.012, 0.01, 0.012, M.metal, x1 - 0.05, hR * 0.12, wR / 2 + 6e-3);
5345
+ box2(group, 0.03, 8e-3, 0.012, M.dark, x0 + 0.02, hR / 2 + 4e-3, 0);
5346
+ box2(group, 0.012, 6e-3, 4e-3, M.dark, x0 + RL * 0.55, -hR * 0.15, -wR / 2 - 2e-3);
5347
+ } else box2(group, RL, hR, wR, receiverMaterial, middle, 0, 0);
5348
+ if (o.ak) {
5349
+ box2(group, RL * 0.86, hR * 0.28, wR * 0.95, M.metal, middle - 8e-3, hR / 2 + 4e-3, 0);
5350
+ for (let i = 0; i < 3; i++) box2(group, 4e-3, hR * 0.3, wR * 0.97, M.dark, x0 + RL * 0.25 + i * 0.02, hR / 2 + 4e-3, 0);
5351
+ box2(group, RL * 0.5, 0.012, 3e-3, M.metal, x0 + RL * 0.55, -hR * 0.02, wR / 2 + 15e-4, -0.05);
5352
+ box2(group, 0.012, 0.014, 3e-3, M.dark, x0 + RL * 0.32, -hR * 0.05, wR / 2 + 2e-3);
5353
+ }
5354
+ if (o.g3) {
5355
+ cylinderX(group, 9e-3, (L - x1) * 0.6, M.metal, x1 + (L - x1) * 0.28, hR * 0.48, 0, 14);
5356
+ box2(group, 0.012, 0.024, 8e-3, M.dark, x1 + (L - x1) * 0.5, hR * 0.48, -0.012);
5357
+ cylinderY(group, 0.012, 0.016, M.dark, x0 + 0.03, hR / 2 + 8e-3, 0, 12);
5358
+ }
5359
+ const ejectX = x1 - (o.ejectBack || 0.07);
5360
+ const ejectY = o.ar ? hR * 0.2 : by;
5361
+ box2(group, 0.05, hR * 0.36, 4e-3, M.hole, ejectX, ejectY, wR / 2 - 4e-4);
5369
5362
  const bolt = kit.pivot(group);
5370
5363
  parts.bolt = bolt;
5371
- box2(bolt, 0.03, 0.03, 0.02, M.metal, 0.5, 0, 0.07);
5372
- const tripod = kit.group();
5373
- tripod.position.set(0.42, -0.1, 0);
5374
- group.add(tripod);
5375
- box2(tripod, 0.03, 0.06, 0.03, M.metal, 0, 0, 0);
5376
- box2(tripod, 0.012, 0.62, 0.012, M.metal, -0.2, -0.25, 0, 0.75);
5377
- box2(tripod, 0.012, 0.62, 0.012, M.metal, 0.22, -0.25, 0.16, -0.75, 0, 0.3);
5378
- box2(tripod, 0.012, 0.62, 0.012, M.metal, 0.22, -0.25, -0.16, -0.75, 0, -0.3);
5379
- parts.muzzle = kit.mark(group, 1.72, 0.01, 0);
5380
- parts.eject = kit.mark(group, 0.3, -0.07, 0);
5381
- return { group, parts };
5364
+ parts.stroke = Math.min(0.1, RL * 0.32);
5365
+ box2(bolt, 0.044, hR * 0.26, 6e-3, M.steelL, ejectX, ejectY, wR / 2 - 12e-4);
5366
+ if (o.charge) {
5367
+ box2(bolt, 0.012, 0.01, 0.03, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.012);
5368
+ sphere(bolt, 65e-4, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.028, 1);
5369
+ }
5370
+ if (o.ar) box2(group, 0.012, 0.016, 6e-3, receiverMaterial, ejectX - 0.03, ejectY + 4e-3, wR / 2 + 4e-3);
5371
+ if (o.rail) rail(kit, group, x0 - 5e-3, RL + 0.07, hR / 2 + (o.ar ? 0 : 3e-3), 0, wR * 0.68);
5382
5372
  }
5383
-
5384
- // src/models/p90.ts
5385
- function buildP90(kit) {
5373
+ function buildBarrel(context) {
5374
+ const { kit, group, parts, options: o, length: L, barrelLength: Bl, receiverEnd: x1, barrelRadius: r, barrelY: by } = context;
5386
5375
  const { M, box: box2, cylinderX } = kit;
5376
+ heatify(parts.glow, cylinderX(group, r, Bl, M.metal, L - Bl / 2, by, 0, 16));
5377
+ if (o.jacket) {
5378
+ cylinderX(group, r * 2, (L - x1) * 0.6, M.dark, x1 + (L - x1) * 0.3, by, 0, 16);
5379
+ for (let i = 0; i < 6; i++) cylinderX(group, r * 2.15, 4e-3, M.metal, x1 + (L - x1) * (0.05 + i * 0.09), by, 0, 16);
5380
+ }
5381
+ if (o.gas) cylinderX(group, r * 0.75, (L - x1) * 0.55, M.metal, x1 + (L - x1) * 0.3, by + r * 2.2, 0, 14);
5382
+ if (o.brake) {
5383
+ const kind = o.brake === 2 ? 2 : 1;
5384
+ recordFitting(parts, kind === 2 ? "muzzle_brake_heavy" : "muzzle_brake", group, () => buildBrake(kit, group, kind, L, by, r, (body) => void heatify(parts.glow, body)));
5385
+ } else if (!o.noFH) {
5386
+ recordFitting(parts, "muzzle_default", group, () => {
5387
+ heatify(parts.glow, cylinderX(group, r * 1.45, 0.042, M.dark, L - 0.021, by, 0, 16));
5388
+ for (let i = 0; i < 4; i++) {
5389
+ const angle = i * PI5 / 2 + PI5 / 4;
5390
+ box2(group, 0.026, 25e-4, 4e-3, M.hole, L - 0.02, by + Math.cos(angle) * r * 1.42, Math.sin(angle) * r * 1.42, 0, 0, angle);
5391
+ }
5392
+ });
5393
+ }
5394
+ cylinderX(group, r * 0.55, 4e-3, M.hole, L + 5e-4, by, 0, 12);
5395
+ }
5396
+ var familyOf = (o) => {
5397
+ if (o.mag === "belt") return "belt";
5398
+ if (o.pump) return "pump";
5399
+ if (o.lever) return "lever";
5400
+ if (o.tube) return "semi12";
5401
+ if (o.bolt) return o.mag === "box" ? "boltmag" : "bolt";
5402
+ return o.mag === "internal" ? "semiint" : "rifle";
5403
+ };
5404
+ function buildRifle(kit, options) {
5405
+ const { M } = kit;
5387
5406
  const group = kit.group();
5388
- const parts = { family: "p90", stroke: 0.04, kick: 0.6, glow: [] };
5389
- box2(group, 0.4, 0.07, 0.055, M.poly, 0.2, 0, 0);
5390
- box2(group, 0.12, 0.075, 0.05, M.poly, 0.2, -0.07, 0);
5391
- box2(group, 0.08, 0.04, 0.03, M.hole, 0.21, -0.06, 0);
5392
- box2(group, 0.06, 0.13, 0.056, M.poly, 0.03, -0.03, 0);
5393
- box2(group, 0.05, 0.03, 0.056, M.poly, 0.33, -0.04, 0);
5394
- heatify(parts.glow, cylinderX(group, 8e-3, 0.09, M.metal, 0.43, 0, 0, 16));
5395
- const magazine = kit.pivot(group, 0.22, 0.047, 0);
5396
- parts.mag = magazine;
5397
- parts.magDir = 1;
5398
- parts.magDrop = 0.08;
5399
- box2(magazine, 0.3, 0.022, 0.05, M.glass, 0, 0, 0);
5400
- for (let i = 0; i < 12; i++) cylinderX(magazine, 28e-4, 0.02, M.brass, -0.12 + i * 0.022, 0, 0, 6);
5401
- scope(kit, group, 1, 0.24, 0.06, 0);
5402
- const bolt = kit.pivot(group);
5403
- parts.bolt = bolt;
5404
- box2(bolt, 0.02, 0.012, 0.012, M.dark, 0.3, 0.02, 0.03);
5405
- parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, 0.24, -0.045, 0);
5406
- parts.muzzle = kit.mark(group, 0.48, 0, 0);
5407
- parts.eject = kit.mark(group, 0.2, -0.07, 0);
5407
+ const parts = { family: "rifle", glow: [] };
5408
+ const length = (options.L ?? 0) / 1e3;
5409
+ const barrelLength = (options.B ?? 0) / 1e3;
5410
+ const isTraditional = options.stock === "trad" || options.stock === "straight";
5411
+ const stockLength = options.stock === "none" ? 0 : options.stockLen || Math.min(0.36, Math.max(0.2, 0.3 * length));
5412
+ const receiverStart = stockLength;
5413
+ const receiverEnd = Math.max(receiverStart + 0.12, length - barrelLength * 0.92);
5414
+ const receiverHeight = options.hR || 0.06;
5415
+ const context = {
5416
+ kit,
5417
+ group,
5418
+ parts,
5419
+ options,
5420
+ furniture: options.wood ? M.wood : options.olive ? M.olive : options.tan ? M.tan : M.poly,
5421
+ isTraditional,
5422
+ length,
5423
+ barrelLength,
5424
+ stockLength,
5425
+ receiverStart,
5426
+ receiverEnd,
5427
+ receiverHeight,
5428
+ receiverWidth: options.wR || 0.034,
5429
+ barrelRadius: options.br || 85e-4,
5430
+ barrelY: receiverHeight * 0.15,
5431
+ receiverLength: receiverEnd - receiverStart
5432
+ };
5433
+ buildReceiver(context);
5434
+ buildBarrel(context);
5435
+ buildForend(context);
5436
+ const gripX = buildGrip(context);
5437
+ const magazineX = buildMagazine(context, gripX);
5438
+ parts.stockMeshes = context.isTraditional ? [] : captured(group, () => buildStock(context, magazineX));
5439
+ if (context.isTraditional) buildStock(context, magazineX);
5440
+ buildFittings(context, gripX);
5441
+ const hasHandguard = !context.isTraditional && options.hg !== "none";
5442
+ parts.frames = {
5443
+ length,
5444
+ barrelY: context.barrelY,
5445
+ barrelRadius: context.barrelRadius,
5446
+ receiverStart,
5447
+ receiverEnd,
5448
+ receiverHeight,
5449
+ receiverWidth: context.receiverWidth,
5450
+ handguardEnd: receiverEnd + (length - receiverEnd) * (hasHandguard ? options.hgFrac || 0.5 : 0.4),
5451
+ magazineX,
5452
+ gripX,
5453
+ hasRail: Boolean(options.rail)
5454
+ };
5455
+ const { barrelY: by, receiverWidth: wR } = context;
5456
+ const ejectX = receiverEnd - (options.ejectBack || 0.07);
5457
+ const ejectY = options.ar ? receiverHeight * 0.2 : by;
5458
+ parts.muzzle = kit.mark(group, length + 0.01, by, 0);
5459
+ parts.eject = kit.mark(group, ejectX, ejectY, wR / 2 + 6e-3);
5460
+ parts.loadPort = kit.mark(group, receiverEnd - 0.05, -receiverHeight / 2 - 4e-3, 0);
5461
+ parts.topLoad = kit.mark(group, ejectX, receiverHeight / 2 + 4e-3, 0);
5462
+ parts.family = familyOf(options);
5408
5463
  return { group, parts };
5409
5464
  }
5410
5465
 
5411
- // src/models/part-registry.ts
5412
- var SINGLE_PARTS = ["slide", "bolt", "boltHandle", "mag", "trigger", "hammer", "lever", "pump", "belt", "cover", "crane", "cylinder", "barrels", "safety", "muzzle", "eject", "loadPort", "topLoad", "rear", "round", "gauge", "stock", "ugl"];
5413
- function partRegistry(parts) {
5414
- const registry = /* @__PURE__ */ new Map();
5415
- for (const key of SINGLE_PARTS) {
5416
- const part = parts[key];
5417
- if (part) registry.set(key, part);
5466
+ // src/models/break-launcher.ts
5467
+ function buildBreakLauncher(kit, options) {
5468
+ const built = buildRifle(kit, options);
5469
+ const { group, parts } = built;
5470
+ const frames = parts.frames;
5471
+ if (!frames) return built;
5472
+ const hingeY = frames.barrelY - frames.barrelRadius;
5473
+ const barrels = kit.pivot(group, frames.receiverEnd, hingeY, 0);
5474
+ group.updateMatrixWorld(true);
5475
+ const centre = new kit.THREE.Vector3();
5476
+ for (const child of [...group.children]) {
5477
+ if (child === barrels) continue;
5478
+ const mesh = child;
5479
+ const isAhead = mesh.isMesh ? new kit.THREE.Box3().setFromObject(mesh).getCenter(centre).x > frames.receiverEnd : child === parts.muzzle;
5480
+ if (isAhead) barrels.attach(child);
5418
5481
  }
5419
- parts.breech?.forEach((part, index) => registry.set(`breech_${index}`, part));
5420
- parts.chambers?.forEach((part, index) => registry.set(`chamber_${index}`, part));
5421
- return registry;
5482
+ parts.barrels = barrels;
5483
+ parts.breech = [kit.mark(barrels, 4e-3, frames.barrelY - hingeY, 0)];
5484
+ parts.eject = parts.breech[0];
5485
+ parts.family = "break";
5486
+ return built;
5422
5487
  }
5423
5488
 
5424
- // src/models/pistol-slide.ts
5425
- var PI5 = Math.PI;
5426
- function buildSlide(kit, slide2, parts, options, style, size, metal2) {
5427
- const { M, box: box2, cylinderX } = kit;
5428
- const { length: L, width: w } = size;
5429
- const slideHeight = style.inset ? size.height * 0.92 : size.height;
5430
- const slideWidth = style.inset ? w * 0.86 : w;
5431
- kit.roundness = style.rf;
5432
- if (style.tri) box2(slide2, L * 0.42, slideHeight, slideWidth, metal2, L * 0.21, 0, 0);
5433
- else if (style.open) {
5434
- box2(slide2, L * 0.45, slideHeight, slideWidth, metal2, L * 0.225, 0, 0);
5435
- box2(slide2, L * 0.55, slideHeight * 0.62, slideWidth, metal2, L * 0.72, -slideHeight * 0.19, 0);
5436
- } else box2(slide2, L, slideHeight, slideWidth, metal2, L / 2, 0, 0);
5437
- if (style.slope) box2(slide2, L * 0.25, slideHeight * 0.5, slideWidth * 0.98, metal2, L * 0.86, slideHeight * 0.35, 0, -0.22);
5438
- kit.roundness = null;
5439
- for (let i = 0; i < (style.flush ? 9 : 7); i++) box2(slide2, 22e-4, slideHeight * 0.72, slideWidth + 14e-4, M.dark, 7e-3 + i * 52e-4, -2e-3, 0, 0);
5440
- if (style.frontSer) for (let i = 0; i < 5; i++) box2(slide2, 22e-4, slideHeight * 0.6, slideWidth + 14e-4, M.dark, L * 0.76 + i * 52e-4, -3e-3, 0);
5441
- if (style.port) {
5442
- box2(slide2, L * style.port, 4e-3, slideWidth * 0.55, M.hole, L * 0.6, slideHeight / 2 - 1e-3, slideWidth * 0.12);
5443
- box2(slide2, L * style.port * 0.85, 3e-3, slideWidth * 0.4, M.steelL, L * 0.6, slideHeight / 2 - 4e-4, slideWidth * 0.1);
5444
- box2(slide2, 0.012, 4e-3, 2e-3, M.dark, L * 0.55, slideHeight * 0.15, slideWidth / 2 + 8e-4);
5445
- }
5446
- if (style.open) {
5447
- heatify(parts.glow, cylinderX(slide2, 62e-4, L * 0.56, M.steelL, L * 0.7, slideHeight * 0.12, 0, 18));
5448
- box2(slide2, 0.03, 4e-3, slideWidth * 0.6, M.hole, L * 0.48, slideHeight / 2 - 1e-3, 0);
5449
- }
5450
- if (style.tri) {
5451
- const tube = new kit.THREE.Mesh(new kit.THREE.CylinderGeometry(0.016, 0.016, L * 0.6, 3), metal2);
5452
- tube.castShadow = tube.receiveShadow = true;
5453
- tube.rotation.set(0, 0, -PI5 / 2);
5454
- tube.rotateY(PI5 / 6);
5455
- tube.position.set(L * 0.7, slideHeight * 0.08, 0);
5456
- slide2.add(tube);
5457
- heatify(parts.glow, tube);
5458
- box2(slide2, L * 0.6, 6e-3, 0.012, metal2, L * 0.7, slideHeight * 0.38, 0);
5459
- for (let i = 0; i < 6; i++) box2(slide2, 8e-3, 3e-3, 0.013, M.hole, L * 0.45 + i * 0.04, slideHeight * 0.41, 0);
5460
- }
5461
- if (style.bushing) {
5462
- heatify(parts.glow, cylinderX(slide2, 78e-4, 6e-3, M.steelL, L + 2e-3, 1e-3, 0, 18));
5463
- cylinderX(slide2, 45e-4, 4e-3, M.steel, L + 2e-3, -0.012, 0, 12);
5489
+ // src/models/attachments/mount-frames.ts
5490
+ var DEFAULT_BARREL_RADIUS = 85e-4;
5491
+ var DEFAULT_RECEIVER_WIDTH = 0.034;
5492
+ var RECEIVER_START_FRACTION = 0.25;
5493
+ var RECEIVER_END_FRACTION = 0.55;
5494
+ var RECEIVER_HEIGHT_FRACTION = 0.4;
5495
+ var DEFAULT_GRIP_OFFSET = 0.07;
5496
+ var HANDGUARD_FRACTION = 0.5;
5497
+ function deriveFrames(THREE, model) {
5498
+ const { root, inner, parts } = model;
5499
+ root.updateMatrixWorld(true);
5500
+ const box2 = new THREE.Box3().setFromObject(root);
5501
+ const span = box2.max.x - box2.min.x;
5502
+ const toInnerX = (x) => (x - inner.position.x) / inner.scale.x;
5503
+ const muzzle = parts.muzzle?.position;
5504
+ const length = muzzle ? muzzle.x - 0.01 : toInnerX(box2.max.x);
5505
+ const receiverStart = toInnerX(box2.min.x + span * RECEIVER_START_FRACTION);
5506
+ const receiverEnd = toInnerX(box2.min.x + span * RECEIVER_END_FRACTION);
5507
+ return {
5508
+ length,
5509
+ barrelY: muzzle?.y ?? (box2.max.y + box2.min.y) / 2,
5510
+ barrelRadius: DEFAULT_BARREL_RADIUS,
5511
+ receiverStart,
5512
+ receiverEnd,
5513
+ receiverHeight: (box2.max.y - box2.min.y) * RECEIVER_HEIGHT_FRACTION,
5514
+ receiverWidth: Math.min(DEFAULT_RECEIVER_WIDTH, box2.max.z - box2.min.z),
5515
+ handguardEnd: receiverEnd + (length - receiverEnd) * HANDGUARD_FRACTION,
5516
+ magazineX: parts.mag ? parts.mag.position.x : (receiverStart + receiverEnd) / 2,
5517
+ gripX: receiverStart + DEFAULT_GRIP_OFFSET,
5518
+ hasRail: false
5519
+ };
5520
+ }
5521
+ function mountFrame(model, mount) {
5522
+ const frames = model.parts.frames;
5523
+ if (!frames) return void 0;
5524
+ const { inner } = model;
5525
+ const toRoot = (x, y, z) => [inner.position.x + x * inner.scale.x, y, z];
5526
+ const handguardHeight = frames.receiverHeight * 0.8;
5527
+ const railTopY = frames.receiverHeight / 2 + (frames.hasRail ? 6e-3 : 0);
5528
+ const forendMiddle = (frames.receiverEnd + frames.handguardEnd) / 2;
5529
+ const base = { mount, barrelRadius: frames.barrelRadius, receiverWidth: frames.receiverWidth, receiverHeight: frames.receiverHeight, hasRail: frames.hasRail };
5530
+ switch (mount) {
5531
+ case "muzzle":
5532
+ return { ...base, position: toRoot(frames.length, frames.barrelY, 0), side: 0 };
5533
+ case "rail_top":
5534
+ return { ...base, position: toRoot((frames.receiverStart + frames.receiverEnd) / 2 + 0.03, railTopY, 0), side: 0 };
5535
+ case "rail_bottom":
5536
+ return { ...base, position: toRoot(forendMiddle, frames.barrelY - handguardHeight / 2, 0), side: 0 };
5537
+ case "rail_left":
5538
+ return { ...base, position: toRoot(forendMiddle, frames.barrelY, -(frames.receiverWidth * 0.54 + 4e-3)), side: -1 };
5539
+ case "rail_right":
5540
+ return { ...base, position: toRoot(forendMiddle, frames.barrelY, frames.receiverWidth * 0.54 + 4e-3), side: 1 };
5541
+ case "underbarrel": {
5542
+ const [x, y] = frames.underbarrel ?? [frames.receiverEnd + (frames.handguardEnd - frames.receiverEnd) * 0.25, frames.barrelY - handguardHeight / 2 - 0.02];
5543
+ return { ...base, position: toRoot(x, y, 0), side: 0 };
5544
+ }
5545
+ case "magazine":
5546
+ return { ...base, position: toRoot(frames.magazineX, -frames.receiverHeight / 2, 0), side: 0 };
5547
+ case "feed": {
5548
+ const belt = model.parts.belt?.position;
5549
+ return { ...base, position: belt ? toRoot(belt.x, belt.y, belt.z) : toRoot(frames.magazineX + 0.02, -frames.receiverHeight / 2 - 0.05, -0.045), side: -1 };
5550
+ }
5551
+ case "sling":
5552
+ return {
5553
+ ...base,
5554
+ position: toRoot(frames.receiverEnd + (frames.length - frames.receiverEnd) * 0.45, -frames.receiverHeight / 2 - 0.03, 0),
5555
+ secondary: toRoot(0.08, -0.09, 0),
5556
+ side: 0
5557
+ };
5558
+ default:
5559
+ return void 0;
5464
5560
  }
5465
- const muzzleY = style.tri ? slideHeight * 0.08 : 2e-3;
5466
- heatify(parts.glow, cylinderX(slide2, style.tri ? 72e-4 : 62e-4, 4e-3, M.dark, L + 15e-4, muzzleY, 0, 18));
5467
- cylinderX(slide2, 34e-4, 5e-3, M.hole, L + 25e-4, muzzleY, 0, 12);
5468
- const top = slideHeight / 2;
5469
- if (style.sight === "u" || style.sight === "3dot") {
5470
- box2(slide2, 4e-3, 7e-3, 4e-3, M.dark, L - 8e-3, top + 35e-4, 0);
5471
- box2(slide2, 22e-4, 22e-4, 46e-4, M.white, L - 8e-3, top + 58e-4, 0);
5472
- box2(slide2, 8e-3, 7e-3, 0.016, M.dark, 9e-3, top + 35e-4, 0);
5473
- if (style.sight === "u") box2(slide2, 4e-3, 2e-3, 0.0122, M.white, 9e-3, top + 6e-3, 0);
5474
- else box2(slide2, 22e-4, 22e-4, 0.0166, M.white, 9e-3, top + 58e-4, 0);
5561
+ }
5562
+
5563
+ // src/models/bullpup.ts
5564
+ function buildBullpup(kit, options) {
5565
+ const { M, box: box2, cylinderX } = kit;
5566
+ const group = kit.group();
5567
+ const L = (options.L ?? 0) / 1e3;
5568
+ const barrel = (options.B ?? 0) / 1e3;
5569
+ const bodyLength = Math.max(0.32, L - barrel * 0.72);
5570
+ const body = options.shot ? M.poly : M.olive;
5571
+ const parts = { family: options.shot ? "pump" : "rifle", stroke: 0.06, glow: [] };
5572
+ box2(group, bodyLength, 0.075, 0.042, body, bodyLength / 2, 0, 0);
5573
+ box2(group, bodyLength * 0.9, 0.02, 0.044, M.dark, bodyLength * 0.5, -0.02, 0);
5574
+ box2(group, 0.032, 0.13, 0.044, M.rubber, 0.016, -0.025, 0);
5575
+ const gx = bodyLength * 0.8;
5576
+ const grip = kit.pivot(group, gx, -0.085, 0);
5577
+ grip.rotation.z = -0.25;
5578
+ parts.grip = grip;
5579
+ box2(grip, 0.03, 0.1, 0.03, M.poly, 0, 0, 0);
5580
+ box2(group, 0.1, 6e-3, 0.03, body, gx + 0.01, -0.06, 0);
5581
+ parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.02, -0.05, 0, 0.15);
5582
+ heatify(parts.glow, cylinderX(group, options.shot ? 0.0105 : 85e-4, barrel, M.metal, L - barrel / 2, 0.012, 0, 16));
5583
+ cylinderX(group, 6e-3, 4e-3, M.hole, L + 1e-3, 0.012, 0, 12);
5584
+ box2(group, 0.05, 0.024, 4e-3, M.hole, bodyLength * 0.25, 5e-3, 0.0212);
5585
+ const bolt = kit.pivot(group);
5586
+ parts.bolt = bolt;
5587
+ box2(bolt, 0.044, 0.02, 6e-3, M.steelL, bodyLength * 0.25, 5e-3, 0.02);
5588
+ if (options.shot) {
5589
+ cylinderX(group, 0.012, bodyLength * 0.75, M.metal, bodyLength * 0.55, -0.025, 0.014, 14);
5590
+ cylinderX(group, 0.012, bodyLength * 0.75, M.metal, bodyLength * 0.55, -0.025, -0.014, 14);
5591
+ const pump = kit.pivot(group);
5592
+ parts.pump = pump;
5593
+ box2(pump, 0.15, 0.045, 0.052, M.poly, bodyLength + 0.06, -0.015, 0);
5594
+ for (let i = 0; i < 6; i++) box2(pump, 6e-3, 0.047, 0.054, M.dark, bodyLength + 5e-3 + i * 0.022, -0.015, 0);
5595
+ rail(kit, group, bodyLength * 0.15, bodyLength * 0.8, 0.04, 0, 0.02);
5596
+ parts.loadPort = kit.mark(group, bodyLength * 0.2, -0.04, 0);
5475
5597
  } else {
5476
- box2(slide2, 6e-3, 4e-3, 3e-3, M.dark, L - 6e-3, top + 2e-3, 0);
5477
- box2(slide2, 6e-3, 4e-3, 0.012, M.dark, 8e-3, top + 2e-3, 0);
5478
- }
5479
- if (style.decock === "slide") {
5480
- box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.02, slideHeight * 0.15, slideWidth / 2 + 2e-3, 0.3);
5481
- box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.02, slideHeight * 0.15, -slideWidth / 2 - 2e-3, 0.3);
5482
- }
5483
- if (style.safety === "slide") parts.safety = box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.018, slideHeight * 0.1, -slideWidth / 2 - 2e-3, 0.2);
5484
- if (options.sel) {
5485
- const selector = box2(slide2, 0.01, 8e-3, 3e-3, M.dark, 0.014, -slideHeight * 0.2, slideWidth / 2 + 14e-4);
5486
- parts.safety ?? (parts.safety = selector);
5598
+ const magazine = kit.pivot(group, gx - 0.11, -0.03, 0);
5599
+ magazine.rotation.z = 0.14;
5600
+ parts.mag = magazine;
5601
+ box2(magazine, 0.05, 0.14, 0.026, M.glass, 0, -0.07, 0);
5602
+ for (let i = 0; i < 6; i++) cylinderX(magazine, 3e-3, 0.04, M.brass, 0, -0.01 - i * 0.02, 0, 6);
5603
+ parts.magDrop = 0.18;
5604
+ scope(kit, group, 2, bodyLength * 0.5, 0.04, 0);
5605
+ box2(group, 0.2, 0.03, 0.02, body, bodyLength * 0.62, 0.048, 0);
5606
+ box2(group, 0.026, 0.075, 0.026, body, bodyLength + 0.05, -0.05, 0, 0.1);
5487
5607
  }
5608
+ parts.muzzle = kit.mark(group, L + 0.01, 0.012, 0);
5609
+ parts.eject = kit.mark(group, bodyLength * 0.25, 5e-3, 0.03);
5610
+ return { group, parts };
5488
5611
  }
5489
5612
 
5490
- // src/models/pistol-style.ts
5491
- var STYLES = {
5492
- glock: { rf: 0.07, ga: -0.36, grip: "finger", guard: "square", rail: 1, sight: "u", port: 0.2, slideM: "metal", frame: "poly" },
5493
- taurus: { rf: 0.16, ga: -0.3, grip: "panel", guard: "square", rail: 1, sight: "3dot", port: 0.18, frontSer: 1, frame: "poly" },
5494
- g2c: { rf: 0.2, ga: -0.3, grip: "panel", guard: "round", rail: 0, sight: "3dot", port: 0.17, frame: "poly", safety: "thumb" },
5495
- beretta: { rf: 0.28, ga: -0.27, grip: "wood", guard: "round", hammer: "spur", sight: "3dot", open: 1, decock: "slide", port: 0, frame: "metal" },
5496
- pt100: { rf: 0.25, ga: -0.27, grip: "rubber", guard: "round", hammer: "spur", sight: "3dot", port: 0.17, decock: "frame", frame: "metal" },
5497
- sig: { rf: 0.14, ga: -0.3, grip: "rubber", guard: "hook", hammer: "spur", sight: "3dot", port: 0.19, decock: "frame", rail: 1, tall: 1.12, frame: "metal" },
5498
- cz: { rf: 0.32, ga: -0.3, grip: "rubber", guard: "round", hammer: "spur", sight: "3dot", port: 0.15, inset: 1, safety: "thumb", frame: "metal" },
5499
- m1911: { rf: 0.24, ga: -0.2, grip: "wood", guard: "round", hammer: "ring", sight: "small", port: 0.12, bushing: 1, beaver: 1, safety: "thumb", slideStop: 1, frame: "metal" },
5500
- makarov: { rf: 0.48, ga: -0.24, grip: "star", guard: "round", hammer: "ring", sight: "small", port: 0.14, flush: 1, safety: "slide", lanyard: 1, frame: "metal" },
5501
- fiveseven: { rf: 0.2, ga: -0.4, grip: "chunky", guard: "square", rail: 1, sight: "3dot", port: 0.18, slope: 1, frame: "poly" },
5502
- deagle: { rf: 0.08, ga: -0.28, grip: "rubber", guard: "round", hammer: "big", sight: "small", port: 0.16, tri: 1, safety: "slide", frame: "metal" }
5613
+ // src/models/launcher-flame.ts
5614
+ var hose = (kit, group, points, radius, material) => {
5615
+ const { THREE } = kit;
5616
+ const curve = new THREE.CatmullRomCurve3(points.map((point) => new THREE.Vector3(...point)));
5617
+ const tube = new THREE.Mesh(new THREE.TubeGeometry(curve, 40, radius, 8, false), material);
5618
+ tube.castShadow = tube.receiveShadow = true;
5619
+ group.add(tube);
5503
5620
  };
5504
-
5505
- // src/models/pistol.ts
5506
- var PI6 = Math.PI;
5507
- function buildFrame(kit, group, parts, options, style, size, materials) {
5508
- const { M, box: box2, cylinderZ, sphere } = kit;
5509
- const { length: L, height: h, width: w } = size;
5510
- const fm = materials.frame;
5511
- if (style.inset) {
5512
- box2(group, L * 0.9, h * 0.35, w * 0.12, fm, L * 0.47, -h * 0.32, w * 0.47);
5513
- box2(group, L * 0.9, h * 0.35, w * 0.12, fm, L * 0.47, -h * 0.32, -w * 0.47);
5514
- }
5515
- const frameLength = style.tri ? L * 0.66 : L * 0.8;
5516
- kit.roundness = style.rf;
5517
- box2(group, frameLength, 0.016, w * 0.92, fm, frameLength / 2 + L * 0.06, -h / 2 - 8e-3, 0);
5518
- kit.roundness = null;
5519
- if (style.rail) for (let i = 0; i < (style.slope ? 5 : 3); i++) box2(group, 4e-3, 3e-3, w * 0.96, M.dark, L * 0.62 + i * 9e-3, -h / 2 - 0.017, 0);
5520
- const isGlock = options.st === "glock" || !options.st;
5521
- if (isGlock) {
5522
- box2(group, 8e-3, 4e-3, 3e-3, M.dark, L * 0.5, -h / 2 - 3e-3, -w / 2 - 12e-4);
5523
- box2(group, 8e-3, 4e-3, 3e-3, M.dark, L * 0.5, -h / 2 - 3e-3, w / 2 + 12e-4);
5524
- }
5525
- if (style.slideStop) {
5526
- box2(group, 0.03, 6e-3, 3e-3, M.dark, L * 0.42, -h / 2 - 2e-3, -w / 2 - 14e-4);
5527
- sphere(group, 28e-4, M.dark, L * 0.55, -h / 2 - 3e-3, w / 2 + 1e-3, 1);
5528
- } else box2(group, 0.012, 4e-3, 3e-3, M.dark, L * 0.45, -h / 2 - 3e-3, -w / 2 - 12e-4);
5529
- if (style.decock === "frame") box2(group, 0.01, 0.012, 3e-3, M.dark, L * 0.38, -h / 2 - 0.012, -w / 2 - 14e-4, 0.6);
5530
- if (style.safety === "thumb") parts.safety = box2(group, 0.014, 6e-3, 4e-3, M.dark, 0.02, -h / 2 - 2e-3, -w / 2 - 16e-4);
5531
- const gripLength = options.gl ?? 0.105;
5532
- const gx = 0.034 + (L - 0.2) * 0.15;
5533
- const gy = -h / 2 - gripLength / 2 + 6e-3;
5534
- const grip = kit.pivot(group, gx, gy, 0);
5535
- grip.rotation.z = style.ga;
5536
- parts.grip = grip;
5537
- const gripWidth = style.grip === "chunky" ? w * 1.18 : w * 1.06;
5538
- const gripDepth = style.grip === "chunky" ? 0.036 : 0.032;
5539
- kit.roundness = 0.3;
5540
- if (style.grip === "wood" || style.grip === "star") {
5541
- box2(grip, gripDepth * 0.86, gripLength, w * 0.9, fm, 0, 0, 0);
5542
- box2(grip, gripDepth * 0.8, gripLength * 0.86, 4e-3, materials.grip, 1e-3, -2e-3, w * 0.47);
5543
- box2(grip, gripDepth * 0.8, gripLength * 0.86, 4e-3, materials.grip, 1e-3, -2e-3, -w * 0.47);
5544
- if (style.grip === "wood") {
5545
- for (let i = 0; i < 6; i++) for (let j = 0; j < 2; j++) box2(grip, 3e-3, 3e-3, 12e-4, M.woodL, -6e-3 + j * 0.012, -gripLength * 0.3 + i * 0.012, w * 0.495, 0, 0, 0);
5546
- cylinderZ(grip, 35e-4, 15e-4, M.brass, 0, gripLength * 0.25, w * 0.5, 10);
5547
- cylinderZ(grip, 35e-4, 15e-4, M.brass, 0, -gripLength * 0.3, w * 0.5, 10);
5548
- } else cylinderZ(grip, 8e-3, 15e-4, M.dark, 0, gripLength * 0.1, w * 0.5, 5);
5549
- } else box2(grip, gripDepth, gripLength, gripWidth, materials.grip, 0, 0, 0);
5550
- kit.roundness = null;
5551
- if (style.grip === "finger") {
5552
- for (let i = 0; i < 3; i++) box2(grip, 6e-3, 0.012, gripWidth * 0.98, materials.grip, gripDepth / 2 + 2e-3, gripLength * 0.18 - i * 0.022, 0);
5553
- for (let i = 0; i < 5; i++) for (let j = 0; j < 3; j++) box2(grip, 3e-3, 3e-3, 12e-4, M.dark, -8e-3 + j * 8e-3, -gripLength * 0.25 + i * 0.012, gripWidth / 2 + 4e-4);
5554
- }
5555
- if (style.grip === "panel" || style.grip === "rubber" || style.grip === "chunky") for (let i = 0; i < 6; i++) box2(grip, gripDepth * 0.75, 25e-4, gripWidth + 1e-3, M.dark, 2e-3, -gripLength / 2 + 0.018 + i * 0.013, 0);
5556
- if (style.grip === "chunky") box2(grip, 8e-3, gripLength * 0.8, gripWidth * 0.9, materials.grip, gripDepth / 2 + 2e-3, -4e-3, 0);
5557
- if (style.beaver) {
5558
- box2(group, 0.026, 8e-3, w * 0.9, fm, -4e-3, -h / 2 - 4e-3, 0, 0.25);
5559
- box2(grip, 6e-3, gripLength * 0.55, w * 0.9, fm, -gripDepth * 0.5 - 2e-3, gripLength * 0.18, 0);
5560
- } else box2(group, 0.02, 9e-3, w * 0.9, fm, 4e-3, -h / 2 - 6e-3, 0);
5561
- if (style.lanyard) {
5562
- const ring = kit.torus(grip, new kit.THREE.TorusGeometry(5e-3, 13e-4, 6, 12), M.dark, -0.012, -gripLength / 2 + 4e-3, 0);
5563
- ring.position.set(-0.012, -gripLength / 2 + 4e-3, 0);
5621
+ var pilot = (kit, group, x, y) => {
5622
+ const { M, cylinderX } = kit;
5623
+ cylinderX(group, 6e-3, 0.03, M.fire2, x + 0.015, y, 0, 6, 0).userData.flame = 1;
5624
+ cylinderX(group, 4e-3, 0.022, M.fire, x + 0.012, y, 0, 6, 0).userData.flame = 1;
5625
+ };
5626
+ function buildFlamethrower(kit, group, parts) {
5627
+ const { M, box: box2, cylinderX, cylinderY, sphere } = kit;
5628
+ parts.kick = 0.15;
5629
+ heatify(parts.glow, cylinderX(group, 0.016, 0.62, M.steel, 0.42, 0, 0, 14));
5630
+ cylinderX(group, 0.022, 0.1, M.dark, 0.78, 0, 0, 14);
5631
+ heatify(parts.glow, cylinderX(group, 0.013, 0.1, M.steel, 0.88, 0, 0, 12, 9e-3));
5632
+ cylinderX(group, 0.018, 0.02, M.metal, 0.73, 0, 0, 14);
5633
+ cylinderX(group, 0.012, 0.06, M.metal, 0.84, -0.025, 0, 10);
5634
+ pilot(kit, group, 0.92, -0.012);
5635
+ const foreGrip = kit.pivot(group, 0.6, -0.02, 0);
5636
+ foreGrip.rotation.z = -0.1;
5637
+ box2(foreGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5638
+ const lever = kit.pivot(group, 0.62, -0.02, 0);
5639
+ parts.trigger = box2(lever, 8e-3, 0.05, 6e-3, M.metal, 0.02, -0.03, 0, 0.3);
5640
+ const rearGrip = kit.pivot(group, 0.17, -0.02, 0);
5641
+ rearGrip.rotation.z = -0.28;
5642
+ box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5643
+ box2(group, 0.05, 4e-3, 0.012, M.metal, 0.2, -0.035, 0);
5644
+ cylinderX(group, 0.02, 0.04, M.metal, 0.1, 0, 0, 12);
5645
+ hose(kit, group, [[0.08, 0, 0], [0, -0.04, 0], [-0.08, -0.2, 0.05], [-0.18, -0.32, 0.08], [-0.25, -0.33, 0.08]], 0.011, M.rubber);
5646
+ for (const z of [-0.1, 0.1]) {
5647
+ cylinderY(group, 0.085, 0.5, M.olive, -0.3, -0.05, z, 20);
5648
+ sphere(group, 0.085, M.olive, -0.3, 0.2, z, 2).scale.y = 0.45;
5649
+ sphere(group, 0.085, M.olive, -0.3, -0.3, z, 2).scale.y = 0.45;
5650
+ cylinderY(group, 0.03, 0.03, M.metal, -0.3, 0.24, z, 12);
5564
5651
  }
5565
- const magazine = kit.pivot(grip);
5566
- parts.mag = magazine;
5567
- parts.magDrop = gripLength * 1.15;
5568
- box2(magazine, 0.022, gripLength * 0.94, w * 0.72, M.dark, 3e-3, -4e-3, 0);
5569
- if (style.grip === "chunky") box2(magazine, 0.04, 0.01, gripWidth * 1.02, M.poly, 3e-3, -gripLength / 2 - 4e-3, 0);
5570
- else if (style.flush || style.grip === "wood") box2(magazine, 0.028, 4e-3, w * 0.9, M.steel, 3e-3, -gripLength / 2 - 1e-3, 0);
5571
- else box2(magazine, 0.031, 6e-3, gripWidth * 1.02, M.dark, 3e-3, -gripLength / 2 - 2e-3, 0);
5572
- return { gx, ty: -h / 2 };
5652
+ sphere(group, 0.075, M.olive, -0.3, 0.1, 0, 2).position.x = -0.39;
5653
+ box2(group, 0.03, 0.6, 0.24, M.dark, -0.4, -0.05, 0);
5654
+ cylinderY(group, 0.03, 0.012, M.metal, -0.39, 0.18, 0, 14);
5655
+ const gauge = kit.pivot(group, -0.39, 0.18, 3e-3);
5656
+ parts.gauge = gauge;
5657
+ box2(gauge, 2e-3, 0.022, 2e-3, M.red, 0, 8e-3, 0.012);
5658
+ parts.muzzle = kit.pivot(group, 0.94, 0, 0);
5659
+ parts.rear = kit.pivot(group, 0.1, 0, 0);
5573
5660
  }
5574
- function buildPistol(kit, options) {
5575
- const { M, box: box2 } = kit;
5576
- const style = { ...STYLES[options.st ?? "glock"] };
5577
- const group = kit.group();
5578
- const parts = { family: "pistol", kick: options.deagle ? 1.8 : 0.6, glow: [] };
5579
- const size = { length: (options.L ?? 0) / 1e3 * 0.97, height: (options.h ?? 0.031) * (style.tall ?? 1), width: options.deagle ? 0.032 : style.flush ? 0.024 : 0.025 };
5580
- const { length: L, height: h, width: w } = size;
5581
- const slideMetal = options.steel ? M.steel : options.stain ? M.steelL : M.metal;
5582
- const frameMetal = style.frame === "poly" ? M.poly : slideMetal;
5583
- const gripMaterial = style.grip === "wood" ? M.wood : style.grip === "star" ? M.bake : style.grip === "rubber" ? M.rubber : M.poly;
5584
- const slide2 = kit.pivot(group);
5585
- parts.slide = slide2;
5586
- parts.stroke = L * 0.15;
5587
- buildSlide(kit, slide2, parts, options, style, size, slideMetal);
5588
- const { gx, ty } = buildFrame(kit, group, parts, options, style, size, { frame: frameMetal, grip: gripMaterial });
5589
- if (style.guard === "square") {
5590
- box2(group, 0.046, 4e-3, 9e-3, frameMetal, gx + 0.05, ty - 0.034, 0);
5591
- box2(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.074, ty - 0.02, 0);
5592
- box2(group, 6e-3, 4e-3, 9e-3, frameMetal, gx + 0.075, ty - 0.034, 0);
5593
- } else if (style.guard === "hook") {
5594
- box2(group, 0.046, 4e-3, 9e-3, frameMetal, gx + 0.05, ty - 0.034, 0);
5595
- box2(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.073, ty - 0.02, 0, 0.2);
5596
- box2(group, 8e-3, 6e-3, 9e-3, frameMetal, gx + 0.074, ty - 0.037, 0, 0.4);
5597
- } else {
5598
- const guard = kit.torus(group, new kit.THREE.TorusGeometry(0.019, 22e-4, 6, 16, PI6), frameMetal, gx + 0.05, ty - 0.014, 0);
5599
- guard.rotation.z = PI6;
5600
- guard.scale.set(1.2, 1, 1);
5601
- }
5602
- const isGlock = options.st === "glock" || !options.st;
5603
- parts.trigger = box2(group, 4e-3, 0.014, 4e-3, isGlock ? M.dark : M.steel, gx + 0.042, ty - 0.022, 0, 0.2);
5604
- if (isGlock) box2(parts.trigger, 15e-4, 8e-3, 45e-4, M.metal, 0, -1e-3, 0);
5605
- if (style.hammer) {
5606
- const hammer = kit.pivot(group, 3e-3, h / 2 - 8e-3, 0);
5607
- parts.hammer = hammer;
5608
- if (style.hammer === "ring") {
5609
- box2(hammer, 6e-3, 0.012, 6e-3, M.dark, -2e-3, 4e-3, 0, 0.3);
5610
- kit.torus(hammer, new kit.THREE.TorusGeometry(45e-4, 16e-4, 6, 12), M.dark, -4e-3, 0.012, 0);
5611
- } else if (style.hammer === "big") box2(hammer, 0.014, 0.02, 9e-3, M.dark, -4e-3, 7e-3, 0, 0.35);
5612
- else {
5613
- box2(hammer, 8e-3, 0.016, 7e-3, M.dark, -2e-3, 6e-3, 0, 0.3);
5614
- box2(hammer, 8e-3, 4e-3, 8e-3, M.dark, -6e-3, 0.013, 0, 0.3);
5615
- }
5616
- }
5617
- parts.muzzle = kit.mark(group, L + 6e-3, style.tri ? h * 0.08 : 2e-3, 0);
5618
- parts.eject = kit.mark(group, L * 0.6, h / 2, w * 0.25);
5619
- return { group, parts };
5661
+ function buildImprovisedFlamethrower(kit, group, parts) {
5662
+ const { M, box: box2, cylinderX, cylinderY, sphere } = kit;
5663
+ parts.kick = 0.12;
5664
+ heatify(parts.glow, cylinderX(group, 0.011, 0.66, M.pipe, 0.38, 0, 0, 12));
5665
+ heatify(parts.glow, cylinderX(group, 0.015, 0.06, M.steel, 0.74, 0, 0, 12, 0.01));
5666
+ cylinderX(group, 6e-3, 0.05, M.metal, 0.735, -0.02, 0, 8);
5667
+ pilot(kit, group, 0.77, -0.02);
5668
+ cylinderY(group, 0.012, 0.04, M.redPaint, 0.72, -0.055, 0, 10);
5669
+ box2(group, 0.02, 0.03, 0.012, M.dark, 0.72, -0.085, 0);
5670
+ cylinderX(group, 0.06, 0.42, M.redPaint, 0.3, -0.09, 0, 22);
5671
+ sphere(group, 0.06, M.redPaint, 0.51, -0.09, 0, 2).scale.x = 0.5;
5672
+ sphere(group, 0.06, M.redPaint, 0.09, -0.09, 0, 2).scale.x = 0.5;
5673
+ cylinderX(group, 0.02, 0.04, M.dark, 0.04, -0.09, 0, 12);
5674
+ box2(group, 0.02, 0.06, 5e-3, M.white, 0.3, -0.09, 0.06);
5675
+ for (const x of [0.15, 0.3, 0.45]) cylinderX(group, 0.063, 0.03, M.tape, x, -0.09, 0, 22);
5676
+ cylinderX(group, 0.016, 0.03, M.tape, 0.3, 0, 0, 12);
5677
+ hose(kit, group, [[0.04, -0.09, 0], [-0.02, -0.06, 0.02], [0, 0, 0.02], [0.05, 0, 0]], 7e-3, M.rubber);
5678
+ const rearGrip = kit.pivot(group, 0.12, -0.01, 0);
5679
+ rearGrip.rotation.z = -0.3;
5680
+ box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5681
+ const lever = kit.pivot(group, 0.17, -0.02, 0);
5682
+ parts.trigger = box2(lever, 8e-3, 0.04, 6e-3, M.metal, 0, -0.02, 0, 0.2);
5683
+ parts.muzzle = kit.pivot(group, 0.78, 0, 0);
5684
+ parts.rear = kit.pivot(group, 0.05, 0, 0);
5620
5685
  }
5621
5686
 
5622
- // src/models/revolver.ts
5623
- var PI7 = Math.PI;
5624
- var DEFAULT_CHAMBERS = 6;
5625
- function buildRevolver(kit, options) {
5687
+ // src/models/launcher-rounds.ts
5688
+ var PI6 = Math.PI;
5689
+ function buildRocketGrenade(kit) {
5690
+ const { M, cylinderX } = kit;
5691
+ const round2 = kit.group();
5692
+ cylinderX(round2, 0.018, 0.15, M.olive, -0.05, 0, 0, 14);
5693
+ cylinderX(round2, 0.02, 0.07, M.olive, 0.035, 0, 0, 18, 0.0425);
5694
+ cylinderX(round2, 0.0425, 0.12, M.olive, 0.13, 0, 0, 20);
5695
+ cylinderX(round2, 0.0428, 0.012, M.dark, 0.09, 0, 0, 20);
5696
+ cylinderX(round2, 0.0425, 0.13, M.olive, 0.255, 0, 0, 20, 0.011);
5697
+ cylinderX(round2, 0.011, 0.03, M.metal, 0.335, 0, 0, 10, 4e-3);
5698
+ return round2;
5699
+ }
5700
+ function buildRecoillessRound(kit) {
5701
+ const { M, cylinderX } = kit;
5702
+ const round2 = kit.group();
5703
+ cylinderX(round2, 0.041, 0.32, M.green, -0.16, 0, 0, 20);
5704
+ cylinderX(round2, 0.0415, 0.02, M.yellow, -0.05, 0, 0, 20);
5705
+ cylinderX(round2, 0.041, 0.12, M.green, 0.06, 0, 0, 20, 0.012);
5706
+ cylinderX(round2, 0.043, 0.03, M.brass, -0.33, 0, 0, 20);
5707
+ return round2;
5708
+ }
5709
+ function buildFlyingRocket(kit, launcherType) {
5626
5710
  const { M, box: box2, cylinderX } = kit;
5627
- const group = kit.group();
5628
- const parts = { family: "revolver", kick: options.cal === ".44 Magnum" ? 1.5 : 0.9, glow: [] };
5629
- const barrel = (options.B ?? 0) / 1e3;
5630
- const chambers = options.chambers ?? DEFAULT_CHAMBERS;
5631
- const metal2 = options.steel ? M.steel : M.metal;
5632
- box2(group, 0.08, 0.044, 0.024, metal2, 0.07, 0, 0);
5633
- box2(group, 0.036, 0.012, 0.022, metal2, 0.096, 0.022, 0);
5634
- box2(group, 0.024, 0.012, 4e-3, M.dark, 0.055, 6e-3, -0.013);
5635
- const crane = kit.pivot(group, 0.077, -0.024, 0);
5636
- parts.crane = crane;
5637
- const cylinder = kit.pivot(crane, 0, 0.022, 0);
5638
- parts.cylinder = cylinder;
5639
- cylinderX(cylinder, 0.0215, 0.042, metal2, 0, 0, 0, 24);
5640
- for (let i = 0; i < chambers; i++) {
5641
- const angle = i * 2 * PI7 / chambers;
5642
- box2(cylinder, 0.026, 5e-3, 5e-3, M.dark, 0, Math.cos(angle) * 0.0205, Math.sin(angle) * 0.0205, 0, 0, angle);
5643
- }
5644
- parts.chambers = [];
5645
- for (let i = 0; i < chambers; i++) {
5646
- const angle = i * 2 * PI7 / chambers + PI7 / chambers;
5647
- parts.chambers.push(kit.mark(cylinder, -0.022, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013));
5648
- cylinderX(cylinder, 46e-4, 2e-3, M.brass, -0.0215, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013, 12);
5711
+ const rocket = kit.group();
5712
+ const diameter = launcherType === "at4" || launcherType === "carl" ? 0.041 : 0.032;
5713
+ cylinderX(rocket, diameter * 0.5, 0.2, M.dark, -0.1, 0, 0, 10);
5714
+ cylinderX(rocket, diameter, 0.3, M.olive, 0.15, 0, 0, 18);
5715
+ cylinderX(rocket, diameter, 0.12, M.olive, 0.36, 0, 0, 18, diameter * 0.25);
5716
+ for (let i = 0; i < 4; i++) {
5717
+ const fin = box2(rocket, 0.06, 2e-3, diameter * 1.4, M.metal, -0.17, 0, 0);
5718
+ fin.rotation.x = i * PI6 / 4;
5649
5719
  }
5650
- heatify(parts.glow, cylinderX(group, 88e-4, barrel, metal2, 0.112 + barrel / 2, 9e-3, 0, 18));
5651
- box2(group, barrel * 0.92, 0.015, 0.013, metal2, 0.112 + barrel * 0.46, -5e-3, 0);
5652
- cylinderX(group, 4e-3, 5e-3, M.hole, 0.112 + barrel + 15e-4, 9e-3, 0, 12);
5653
- box2(group, 0.012, 9e-3, 4e-3, M.dark, 0.112 + barrel - 8e-3, 0.021, 0, -0.3);
5654
- box2(group, 0.012, 6e-3, 0.012, M.dark, 0.105, 0.031, 0);
5655
- const hammer = kit.pivot(group, 0.036, 0.018, 0);
5656
- box2(hammer, 0.01, 0.02, 8e-3, M.dark, -4e-3, 8e-3, 0, 0.45);
5657
- parts.hammer = hammer;
5658
- const grip = kit.pivot(group, 0.024, -0.062, 0);
5659
- grip.rotation.z = -0.38;
5660
- parts.grip = grip;
5661
- box2(grip, 0.034, 0.1, 0.031, options.wood ? M.wood : M.rubber, 0, 0, 0);
5662
- if (!options.wood) for (let i = 0; i < 4; i++) box2(grip, 6e-3, 0.07, 0.033, M.dark, -0.012 + i * 8e-3, 0, 0);
5663
- box2(group, 0.038, 4e-3, 8e-3, metal2, 0.062, -0.036, 0);
5664
- box2(group, 4e-3, 0.022, 8e-3, metal2, 0.08, -0.026, 0, 0.2);
5665
- parts.trigger = box2(group, 4e-3, 0.015, 4e-3, M.dark, 0.06, -0.027, 0, 0.2);
5666
- parts.muzzle = kit.mark(group, 0.112 + barrel + 6e-3, 9e-3, 0);
5667
- parts.eject = kit.mark(group, 0.077, 0, 0);
5668
- return { group, parts };
5720
+ return rocket;
5669
5721
  }
5670
5722
 
5671
- // src/models/attachments/muzzle-brake.ts
5672
- function buildBrake(kit, parent, kind, x, y, barrelRadius, heat = () => void 0) {
5723
+ // src/models/launcher-rocket.ts
5724
+ function buildRpg(kit, group, parts) {
5673
5725
  const { M, box: box2, cylinderX } = kit;
5674
- const scale = kind === 2 ? 3.2 : 2;
5675
- const length = kind === 2 ? 0.12 : 0.06;
5676
- heat(cylinderX(parent, barrelRadius * scale, length, M.dark, x - length / 2, y, 0, kind === 2 ? 8 : 16));
5677
- for (let i = 0; i < 3; i++) box2(parent, length * 0.16, barrelRadius * scale * 1.3, barrelRadius * scale * 2.2, M.hole, x - length * 0.25 - i * length * 0.25, y, 0);
5678
- }
5679
-
5680
- // src/models/capture.ts
5681
- function captured(parent, draw) {
5682
- const before = parent.children.length;
5683
- draw();
5684
- return parent.children.slice(before);
5726
+ parts.kick = 1.6;
5727
+ cylinderX(group, 0.036, 0.13, M.dark, 0.065, 0, 0, 20, 0.021);
5728
+ cylinderX(group, 0.038, 0.012, M.metal, 0, 0, 0, 20);
5729
+ heatify(parts.glow, cylinderX(group, 0.021, 0.83, M.steel, 0.545, 0, 0, 18));
5730
+ cylinderX(group, 0.03, 0.3, M.wood, 0.47, 0, 0, 16);
5731
+ cylinderX(group, 0.031, 0.012, M.metal, 0.32, 0, 0, 16);
5732
+ cylinderX(group, 0.031, 0.012, M.metal, 0.62, 0, 0, 16);
5733
+ cylinderX(group, 0.024, 0.02, M.metal, 0.94, 0, 0, 16);
5734
+ box2(group, 0.09, 0.03, 0.032, M.metal, 0.46, -0.028, 0);
5735
+ const frontGrip = kit.pivot(group, 0.4, -0.04, 0);
5736
+ frontGrip.rotation.z = -0.22;
5737
+ box2(frontGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5738
+ const rearGrip = kit.pivot(group, 0.5, -0.04, 0);
5739
+ rearGrip.rotation.z = -0.18;
5740
+ box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5741
+ box2(group, 0.06, 4e-3, 0.012, M.metal, 0.53, -0.05, 0);
5742
+ box2(group, 4e-3, 0.025, 0.012, M.metal, 0.56, -0.04, 0);
5743
+ parts.trigger = box2(group, 4e-3, 0.018, 4e-3, M.dark, 0.535, -0.045, 0, 0.15);
5744
+ const hammer = kit.pivot(group, 0.44, -0.012, 0);
5745
+ parts.hammer = hammer;
5746
+ box2(hammer, 0.012, 0.02, 0.01, M.dark, 0, 0, 0);
5747
+ box2(group, 6e-3, 0.03, 6e-3, M.dark, 0.78, 0.032, 0);
5748
+ box2(group, 0.02, 0.022, 0.014, M.dark, 0.46, 0.032, 0);
5749
+ box2(group, 0.12, 0.042, 0.032, M.dark, 0.5, 0.04, -0.04);
5750
+ cylinderX(group, 0.013, 0.05, M.rubber, 0.42, 0.048, -0.04, 14);
5751
+ cylinderX(group, 0.016, 4e-3, M.lens, 0.565, 0.048, -0.04, 14);
5752
+ box2(group, 0.04, 0.012, 0.012, M.metal, 0.5, 0.016, -0.03);
5753
+ const round2 = buildRocketGrenade(kit);
5754
+ round2.position.x = 0.95;
5755
+ group.add(round2);
5756
+ parts.round = round2;
5757
+ parts.roundRest = 0.95;
5758
+ parts.makeRound = () => buildRocketGrenade(kit);
5759
+ parts.muzzle = kit.pivot(group, 0.95, 0, 0);
5760
+ parts.rear = kit.pivot(group, 0, 0, 0);
5685
5761
  }
5686
- function recordFitting(parts, id, parent, draw) {
5687
- parts.fittings = { ...parts.fittings, [id]: captured(parent, draw) };
5762
+ function buildM72(kit, group, parts) {
5763
+ const { M, box: box2, cylinderX, cylinderY } = kit;
5764
+ parts.kick = 1.1;
5765
+ parts.disposable = true;
5766
+ heatify(parts.glow, cylinderX(group, 0.0365, 0.6, M.olive, 0.58, 0, 0, 20));
5767
+ cylinderX(group, 0.032, 0.32, M.olive, 0.16, 0, 0, 20);
5768
+ cylinderX(group, 0.039, 0.03, M.rubber, 0.866, 0, 0, 20);
5769
+ cylinderX(group, 0.035, 0.025, M.rubber, 0.012, 0, 0, 20);
5770
+ cylinderX(group, 0.037, 0.015, M.yellow, 0.8, 0, 0, 20);
5771
+ cylinderX(group, 0.037, 0.012, M.dark, 0.3, 0, 0, 20);
5772
+ box2(group, 4e-3, 0.05, 0.05, M.dark, 0.79, 0.055, 0);
5773
+ box2(group, 4e-3, 6e-3, 0.04, M.dark, 0.79, 0.078, 0);
5774
+ box2(group, 4e-3, 0.035, 0.024, M.dark, 0.38, 0.05, 0);
5775
+ cylinderY(group, 4e-3, 6e-3, M.lens, 0.38, 0.064, 0, 8);
5776
+ parts.trigger = box2(group, 0.05, 0.016, 0.024, M.rubber, 0.5, 0.042, 0);
5777
+ box2(group, 0.1, 0.01, 0.03, M.dark, 0.47, 0.036, 0);
5778
+ box2(group, 0.02, 0.03, 0.01, M.dark, 0.66, -0.035, 0.03);
5779
+ box2(group, 8e-3, 0.03, 8e-3, M.metal, 0.25, -0.04, 0.03);
5780
+ parts.muzzle = kit.pivot(group, 0.881, 0, 0);
5781
+ parts.rear = kit.pivot(group, 0, 0, 0);
5688
5782
  }
5689
-
5690
- // src/models/attachments/bipod.ts
5691
- function buildBipod(kit, parent, x, y) {
5783
+ function buildAt4(kit, group, parts) {
5692
5784
  const { M, box: box2, cylinderX } = kit;
5693
- cylinderX(parent, 8e-3, 0.02, M.dark, x, y, 0, 12);
5694
- for (const side of [-1, 1]) {
5695
- const leg = kit.pivot(parent, x, y, side * 0.012);
5696
- leg.rotation.set(side * 0.15, 0, -0.35);
5697
- box2(leg, 6e-3, 0.2, 6e-3, M.dark, 0, -0.1, 0);
5698
- box2(leg, 0.01, 6e-3, 0.01, M.rubber, 0, -0.2, 0);
5699
- }
5785
+ parts.kick = 1.3;
5786
+ parts.disposable = true;
5787
+ heatify(parts.glow, cylinderX(group, 0.05, 0.86, M.olive, 0.51, 0, 0, 24));
5788
+ cylinderX(group, 0.056, 0.08, M.dark, 0.98, 0, 0, 24);
5789
+ cylinderX(group, 0.058, 0.07, M.dark, 0.04, 0, 0, 24, 0.05);
5790
+ cylinderX(group, 0.051, 0.015, M.yellow, 0.86, 0, 0, 24);
5791
+ box2(group, 0.16, 0.026, 0.05, M.dark, 0.42, 0.058, 0);
5792
+ box2(group, 0.03, 0.012, 0.016, M.red, 0.47, 0.075, 0);
5793
+ parts.trigger = box2(group, 0.022, 0.012, 0.018, M.rubber, 0.5, 0.074, 0);
5794
+ box2(group, 0.012, 0.07, 0.02, M.dark, 0.35, 0.09, -0.035);
5795
+ box2(group, 0.012, 0.05, 0.02, M.dark, 0.71, 0.08, -0.035);
5796
+ const grip = kit.pivot(group, 0.68, -0.05, 0);
5797
+ grip.rotation.z = -0.15;
5798
+ parts.grip = grip;
5799
+ box2(grip, 0.035, 0.11, 0.032, M.dark, 0, -0.05, 0);
5800
+ box2(group, 0.14, 0.035, 0.06, M.rubber, 0.32, -0.06, 0);
5801
+ box2(group, 0.3, 0.012, 0.055, M.dark, 0.5, -0.05, 0);
5802
+ parts.muzzle = kit.pivot(group, 1.02, 0, 0);
5803
+ parts.rear = kit.pivot(group, 0, 0, 0);
5700
5804
  }
5701
- var buildBipodModel = ({ kit }) => {
5702
- const group = kit.group();
5703
- buildBipod(kit, group, 0, 0);
5704
- return group;
5705
- };
5706
-
5707
- // src/models/rifle-fittings.ts
5708
- var SCOPE_IDS = { 1: "red_dot", 2: "acog_4x", 3: "scope_3_9x", 4: "scope_6_24x" };
5709
- function buildFittings(context, gripX) {
5710
- const { kit, group, parts, options: o, furniture: furn, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
5711
- const { M, box: box2, cylinderX, cylinderZ, sphere } = kit;
5712
- const middle = (x0 + x1) / 2;
5713
- if (o.scope) recordFitting(parts, SCOPE_IDS[o.scope] ?? "scope", group, () => scope(kit, group, o.scope, middle + 0.03, hR / 2 + (o.rail ? 6e-3 : 0), o.scope === 4 ? -0.02 : 0));
5714
- if (o.carry) {
5715
- box2(group, 0.15, 0.026, 0.014, M.metal, middle, hR / 2 + 0.03, 0);
5716
- box2(group, 0.012, 0.03, 0.014, M.metal, middle + 0.07, hR / 2 + 0.014, 0);
5717
- box2(group, 0.012, 0.03, 0.014, M.metal, middle - 0.07, hR / 2 + 0.014, 0);
5718
- }
5719
- if (o.bipod) recordFitting(parts, "bipod", group, () => buildBipod(kit, group, x1 + (L - x1) * 0.45 + 0.04, by - 0.012));
5720
- if (o.bolt) {
5721
- const handle = kit.pivot(group, x1 - 0.045, by, 0);
5722
- parts.boltHandle = handle;
5723
- parts.boltHandleRest = handle.position.x;
5724
- cylinderZ(handle, 42e-4, 0.05, M.steelL, 0, 0, wR / 2 + 0.022, 12);
5725
- sphere(handle, 95e-4, M.dark, 0, -4e-3, wR / 2 + 0.048, 1);
5726
- cylinderX(group, hR * 0.3, 0.03, M.steel, x0 + 5e-3, by, 0, 16);
5727
- }
5728
- if (o.lever) {
5729
- const lever = kit.pivot(group, gripX - 0.02, -hR / 2 - 0.01, 0);
5730
- parts.lever = lever;
5731
- box2(lever, 0.11, 6e-3, 0.012, M.metal, 0.055, -0.052, 0);
5732
- box2(lever, 6e-3, 0.05, 0.012, M.metal, 3e-3, -0.028, 0);
5733
- box2(lever, 6e-3, 0.05, 0.012, M.metal, 0.108, -0.028, 0);
5734
- box2(lever, 0.04, 6e-3, 0.012, M.metal, 0.08, -2e-3, 0);
5735
- box2(group, 0.03, 0.012, 3e-3, M.dark, x1 - 0.04, by, wR / 2 + 1e-3);
5736
- }
5737
- if (o.tube) {
5738
- const tubeLength = (L - x1) * (o.tubeLen || 0.8);
5739
- cylinderX(group, 0.0105, tubeLength, M.metal, x1 + tubeLength / 2, by - 0.023, 0, 16);
5740
- cylinderX(group, 0.0125, 0.02, M.dark, x1 + tubeLength + 6e-3, by - 0.023, 0, 16);
5741
- }
5742
- if (o.pump) {
5743
- const pump = kit.pivot(group);
5744
- parts.pump = pump;
5745
- box2(pump, 0.17, 0.042, 0.046, furn, x1 + 0.14, by - 0.022, 0);
5746
- for (let i = 0; i < 8; i++) box2(pump, 6e-3, 0.044, 0.048, M.dark, x1 + 0.07 + i * 0.02, by - 0.022, 0);
5747
- }
5748
- if (o.heat) {
5749
- cylinderX(group, r * 1.75, (L - x1) * 0.7, M.dark, x1 + (L - x1) * 0.4, by, 0, 12);
5750
- for (let i = 0; i < 10; i++) box2(group, 8e-3, r * 1.5, 4e-3, M.hole, x1 + (L - x1) * (0.1 + i * 0.06), by + r * 0.8, r * 1.5);
5751
- }
5752
- if (o.bayo) box2(group, 0.025, 8e-3, 0.01, M.metal, L - 0.07, by - 0.016, 0);
5753
- if (o.rod) cylinderX(group, 3e-3, (L - x1) * 0.85, M.steel, x1 + (L - x1) * 0.45, by - 0.014, 0, 8);
5805
+ function buildCarlGustaf(kit, group, parts) {
5806
+ const { M, box: box2, cylinderX } = kit;
5807
+ parts.kick = 1.4;
5808
+ heatify(parts.glow, cylinderX(group, 0.045, 0.72, M.dark, 0.59, 0, 0, 24));
5809
+ cylinderX(group, 0.05, 0.04, M.rubber, 0.93, 0, 0, 24);
5810
+ const breech = kit.pivot(group, 0.21, 0, 0.05);
5811
+ parts.breech = [breech];
5812
+ cylinderX(breech, 0.06, 0.16, M.dark, -0.1, 0, -0.05, 24, 0.046);
5813
+ cylinderX(breech, 0.062, 0.015, M.metal, -0.18, 0, -0.05, 24);
5814
+ box2(group, 0.025, 0.03, 0.02, M.metal, 0.22, 0, 0.052);
5815
+ const foreGrip = kit.pivot(group, 0.62, -0.05, 0);
5816
+ foreGrip.rotation.z = -0.2;
5817
+ box2(foreGrip, 0.035, 0.11, 0.034, M.dark, 0, -0.05, 0);
5818
+ parts.trigger = box2(group, 4e-3, 0.018, 4e-3, M.dark, 0.66, -0.06, 0, 0.15);
5819
+ box2(group, 0.06, 4e-3, 0.012, M.metal, 0.66, -0.07, 0);
5820
+ const rearGrip = kit.pivot(group, 0.4, -0.05, 0);
5821
+ rearGrip.rotation.z = -0.25;
5822
+ box2(rearGrip, 0.035, 0.1, 0.034, M.dark, 0, -0.05, 0);
5823
+ box2(group, 0.13, 0.04, 0.07, M.rubber, 0.28, -0.065, 0);
5824
+ box2(group, 0.16, 0.05, 0.045, M.dark, 0.56, 0.07, -0.035);
5825
+ cylinderX(group, 0.016, 0.05, M.rubber, 0.46, 0.075, -0.035, 14);
5826
+ cylinderX(group, 0.02, 4e-3, M.lens, 0.645, 0.075, -0.035, 14);
5827
+ box2(group, 0.06, 0.02, 0.02, M.metal, 0.56, 0.04, -0.03);
5828
+ box2(group, 0.22, 0.01, 0.02, M.dark, 0.6, 0.06, 0.03);
5829
+ box2(group, 0.01, 0.03, 0.02, M.dark, 0.5, 0.05, 0.03);
5830
+ box2(group, 0.01, 0.03, 0.02, M.dark, 0.7, 0.05, 0.03);
5831
+ parts.makeRound = () => buildRecoillessRound(kit);
5832
+ parts.muzzle = kit.pivot(group, 0.95, 0, 0);
5833
+ parts.rear = kit.pivot(group, 0.05, 0, 0);
5754
5834
  }
5835
+ var ROCKET_BUILDERS = {
5836
+ rpg: buildRpg,
5837
+ m72: buildM72,
5838
+ at4: buildAt4,
5839
+ carl: buildCarlGustaf
5840
+ };
5755
5841
 
5756
- // src/models/rifle-forend.ts
5757
- var PI8 = Math.PI;
5758
- function buildForend(context) {
5759
- const { kit, group, options: o, furniture: furn, length: L, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
5760
- const { M, box: box2 } = kit;
5761
- const hasLongScope = (o.scope ?? 0) >= 3;
5762
- if (!context.isTraditional && o.hg !== "none") {
5763
- const handguardLength = (L - x1) * (o.hgFrac || 0.5);
5764
- const hx = x1 + handguardLength / 2;
5765
- const hh = hR * 0.8;
5766
- if (o.hg === "rail") {
5767
- box2(group, handguardLength, hh, wR * 1.04, M.dark, hx, by - 2e-3, 0);
5768
- rail(kit, group, x1, handguardLength, by + hh / 2, 0, wR * 0.66);
5769
- rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, wR * 0.54, wR * 0.4, PI8 / 2);
5770
- rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, -wR * 0.54, wR * 0.4, -PI8 / 2);
5771
- rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - hh / 2, 0, wR * 0.6, PI8);
5772
- } else {
5773
- const material = o.hg === "wood" ? M.wood : furn;
5774
- if (o.ak) {
5775
- box2(group, handguardLength, hh * 0.55, wR * 1.08, material, hx, by - hh * 0.22, 0);
5776
- box2(group, handguardLength * 0.75, hh * 0.35, wR * 0.7, material, hx - handguardLength * 0.1, by + r * 2.2, 0);
5777
- } else {
5778
- box2(group, handguardLength, hh, wR * 1.08, material, hx, by - 2e-3, 0);
5779
- vents(kit, group, x1 + 0.01, handguardLength - 0.02, by + hh * 0.15, wR * 0.54 + 1e-3, Math.round(handguardLength / 0.025), hh * 0.3);
5780
- vents(kit, group, x1 + 0.01, handguardLength - 0.02, by + hh * 0.15, -wR * 0.54 - 1e-3, Math.round(handguardLength / 0.025), hh * 0.3);
5781
- }
5782
- }
5783
- if (!hasLongScope && !o.rail && o.ak) {
5784
- box2(group, 0.03, 0.016, wR * 0.6, M.metal, x1 + 0.02, by + r * 2.5 + 0.01, 0);
5785
- box2(group, 0.012, 4e-3, wR * 0.5, M.dark, x1 + 0.03, by + r * 2.5 + 0.019, 0);
5786
- }
5787
- }
5788
- if (hasLongScope) return;
5789
- const sightX = L - (o.ak ? 0.06 : 0.08);
5790
- if (o.ak) {
5791
- box2(group, 0.022, 0.022, wR * 0.6, M.metal, sightX, by + 8e-3, 0);
5792
- box2(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, 8e-3);
5793
- box2(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, -8e-3);
5794
- box2(group, 3e-3, 0.016, 3e-3, M.dark, sightX, by + 0.022, 0);
5795
- } else if (!o.rail || o.carry) {
5796
- box2(group, 0.018, 0.014, wR * 0.55, M.dark, sightX, by + 8e-3, 0);
5797
- box2(group, 0.012, 0.034, 8e-3, M.dark, sightX, by + 0.03, 0, -0.25);
5798
- } else box2(group, 0.012, 0.024, 0.012, M.dark, sightX - 0.04, by + hR * 0.4 + 0.012, 0);
5842
+ // src/models/launcher.ts
5843
+ function buildLauncher(kit, options) {
5844
+ const group = kit.group();
5845
+ const type = options.lt ?? "";
5846
+ const isFlame = type === "flame" || type === "flameDIY";
5847
+ const parts = { family: isFlame ? "flame" : "rocket", launcherType: type, glow: [] };
5848
+ if (type === "flame") buildFlamethrower(kit, group, parts);
5849
+ else if (type === "flameDIY") buildImprovisedFlamethrower(kit, group, parts);
5850
+ else ROCKET_BUILDERS[type]?.(kit, group, parts);
5851
+ parts.eject = parts.muzzle;
5852
+ return { group, parts };
5799
5853
  }
5800
5854
 
5801
- // src/models/attachments/belt-box.ts
5802
- function buildBeltBox(kit, belt) {
5803
- const { M, box: box2, cylinderX } = kit;
5804
- box2(belt, 0.11, 0.1, 0.08, M.olive, 0, 0, 0);
5805
- box2(belt, 0.112, 0.012, 0.082, M.dark, 0, 0.046, 0);
5806
- for (let i = 0; i < 7; i++) {
5807
- cylinderX(belt, 42e-4, 0.034, M.brass, -0.04 + i * 0.012, 0.07, 0.035, 10);
5808
- box2(belt, 6e-3, 6e-3, 0.012, M.metal, -0.04 + i * 0.012, 0.066, 0.035);
5855
+ // src/models/m134.ts
5856
+ var PI7 = Math.PI;
5857
+ var BARRELS = 6;
5858
+ var CLUSTER_RADIUS = 0.03;
5859
+ var BARREL_RADIUS = 78e-4;
5860
+ var BARREL_END = 0.8;
5861
+ function buildBarrelCluster(kit, parts, group) {
5862
+ const { M, cylinderX } = kit;
5863
+ const barrels = kit.pivot(group, 0, 0, 0);
5864
+ parts.barrels = barrels;
5865
+ const length = BARREL_END - 0.3;
5866
+ let heated;
5867
+ for (let i = 0; i < BARRELS; i++) {
5868
+ const angle = i * 2 * PI7 / BARRELS;
5869
+ const y = Math.cos(angle) * CLUSTER_RADIUS;
5870
+ const z = Math.sin(angle) * CLUSTER_RADIUS;
5871
+ const barrel = cylinderX(barrels, BARREL_RADIUS, length, M.metal, 0.3 + length / 2, y, z, 14);
5872
+ if (heated) barrel.material = heated;
5873
+ else heated = heatify(parts.glow, barrel).material;
5874
+ cylinderX(barrels, BARREL_RADIUS * 0.55, 4e-3, M.hole, BARREL_END + 5e-4, y, z, 10);
5809
5875
  }
5876
+ cylinderX(barrels, 0.012, length, M.steel, 0.3 + length / 2, 0, 0, 12);
5877
+ for (const x of [0.42, 0.58, 0.74]) cylinderX(barrels, CLUSTER_RADIUS + BARREL_RADIUS * 1.6, 0.014, M.steel, x, 0, 0, 24);
5878
+ cylinderX(barrels, CLUSTER_RADIUS + BARREL_RADIUS * 1.8, 0.03, M.dark, BARREL_END - 0.015, 0, 0, 24);
5810
5879
  }
5811
-
5812
- // src/models/attachments/drum.ts
5813
- function buildDrum(kit, magazine) {
5814
- const { M, box: box2, cylinderZ } = kit;
5815
- cylinderZ(magazine, 0.075, 0.07, M.dark, 0.02, -0.085, 0, 22);
5816
- cylinderZ(magazine, 0.05, 0.074, M.metal, 0.02, -0.085, 0, 18);
5817
- box2(magazine, 0.05, 0.04, 0.03, M.dark, 0, -0.02, 0);
5880
+ function buildHousing(kit, parts, group) {
5881
+ const { M, box: box2, cylinderX, cylinderZ } = kit;
5882
+ box2(group, 0.3, 0.14, 0.15, M.metal, 0.18, 0, 0);
5883
+ cylinderX(group, 0.052, 0.12, M.dark, 0.06, 0, 0, 20);
5884
+ cylinderX(group, 0.062, 0.2, M.dark, 0.38, 0, 0, 22);
5885
+ for (let i = 0; i < 4; i++) cylinderX(group, 0.064, 8e-3, M.steel, 0.3 + i * 0.05, 0, 0, 22);
5886
+ box2(group, 0.04, 0.05, 0.2, M.dark, 0.1, 0.095, 0);
5887
+ box2(group, 0.2, 0.012, 0.045, M.dark, 0.22, 0.158, 0);
5888
+ box2(group, 0.012, 0.05, 0.045, M.dark, 0.13, 0.13, 0);
5889
+ box2(group, 0.012, 0.05, 0.045, M.dark, 0.31, 0.13, 0);
5890
+ for (const side of [-1, 1]) {
5891
+ box2(group, 0.03, 0.03, 0.1, M.poly, 0.06, -0.01, side * 0.14);
5892
+ cylinderZ(group, 0.016, 0.07, M.poly, 0.06, -0.01, side * 0.2, 14);
5893
+ }
5894
+ parts.trigger = box2(group, 0.012, 0.02, 0.016, M.dark, 0.1, 0.085, 0.14, 0.3);
5895
+ parts.safety = box2(group, 0.014, 6e-3, 0.01, M.dark, 0.2, 0.073, -0.076, 0.4);
5818
5896
  }
5819
-
5820
- // src/models/helical-magazine.ts
5821
- var PI9 = Math.PI;
5822
- var LENGTH = 0.26;
5823
- var RADIUS = 0.026;
5824
- var TURNS = 11;
5825
- function buildHelicalMagazine(context) {
5826
- const { kit, group, parts, receiverEnd, barrelY, barrelRadius } = context;
5827
- const { M, cylinderX, box: box2 } = kit;
5828
- const x = receiverEnd + LENGTH / 2 + 0.02;
5829
- const y = barrelY - barrelRadius - RADIUS - 0.012;
5830
- const magazine = kit.pivot(group, x, y, 0);
5831
- parts.mag = magazine;
5832
- cylinderX(magazine, RADIUS, LENGTH, M.dark, 0, 0, 0, 20);
5833
- cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, -LENGTH / 2, 0, 0, 20);
5834
- cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, LENGTH / 2, 0, 0, 20);
5835
- for (let i = 0; i < TURNS; i++) box2(magazine, 0.012, RADIUS * 2.15, 0.014, M.metal, -LENGTH / 2 + (i + 0.5) * LENGTH / TURNS, 0, 0, 0, 0, i * PI9 / 4);
5836
- box2(magazine, 0.05, 0.03, 0.03, M.dark, -LENGTH / 2 - 0.02, RADIUS * 0.4, 0);
5837
- parts.magDrop = 0.16;
5838
- return x;
5897
+ function buildFeed(kit, parts, group) {
5898
+ const { M, box: box2, cylinderX } = kit;
5899
+ const belt = kit.pivot(group, 0.2, -0.13, -0.16);
5900
+ parts.belt = belt;
5901
+ box2(belt, 0.16, 0.12, 0.1, M.olive, 0, 0, 0);
5902
+ box2(belt, 0.162, 0.012, 0.102, M.dark, 0, 0.056, 0);
5903
+ for (let i = 0; i < 8; i++) cylinderX(belt, 46e-4, 0.04, M.brass, -0.07 + i * 0.02, 0.075, 0.04, 10);
5904
+ box2(group, 0.1, 0.03, 0.1, M.metal, 0.22, -0.06, -0.1, 0, 0, 0.5);
5905
+ const cover = kit.pivot(group, 0.1, 0.075, 0);
5906
+ parts.cover = cover;
5907
+ box2(cover, 0.24, 0.012, 0.15, M.metal, 0.12, 6e-3, 0);
5908
+ const bolt = kit.pivot(group);
5909
+ parts.bolt = bolt;
5910
+ parts.stroke = 0.03;
5911
+ box2(bolt, 0.03, 0.012, 0.012, M.steelL, 0.28, 0.02, 0.078);
5912
+ }
5913
+ function buildM134(kit) {
5914
+ const group = kit.group();
5915
+ const parts = { family: "belt", kick: 0.9, glow: [] };
5916
+ buildHousing(kit, parts, group);
5917
+ buildBarrelCluster(kit, parts, group);
5918
+ buildFeed(kit, parts, group);
5919
+ parts.frames = {
5920
+ length: BARREL_END,
5921
+ barrelY: 0,
5922
+ barrelRadius: BARREL_RADIUS,
5923
+ receiverStart: 0.06,
5924
+ receiverEnd: 0.48,
5925
+ receiverHeight: 0.14,
5926
+ receiverWidth: 0.15,
5927
+ handguardEnd: 0.6,
5928
+ magazineX: 0.2,
5929
+ gripX: 0.06,
5930
+ hasRail: false,
5931
+ // the tripod head, where the gun balances (the tripod of the counters joins here too)
5932
+ underbarrel: [0.4, -0.1]
5933
+ };
5934
+ parts.muzzle = kit.mark(group, BARREL_END + 0.01, 0, 0);
5935
+ parts.eject = kit.mark(group, 0.3, -0.07, 0.08);
5936
+ return { group, parts };
5839
5937
  }
5840
5938
 
5841
- // src/models/rifle-controls.ts
5842
- function buildGrip(context) {
5843
- const { kit, group, parts, options: o, furniture: furn, receiverStart: x0, receiverHeight: hR, receiverWidth: wR } = context;
5844
- const { M, box: box2 } = kit;
5845
- const gx = x0 + (o.gripAt || 0.07);
5846
- if (!context.isTraditional && o.grip !== "none") {
5847
- const grip = kit.pivot(group, gx - 0.012, -hR / 2 - 0.046, 0);
5848
- grip.rotation.z = -0.3;
5849
- parts.grip = grip;
5850
- box2(grip, 0.032, 0.105, 0.028, furn, 0, 0, 0);
5851
- for (let i = 0; i < 3; i++) box2(grip, 6e-3, 0.012, 0.026, furn, 0.017, 0.022 - i * 0.02, 0);
5852
- box2(grip, 0.034, 8e-3, 0.03, furn, 0, -0.052, 0);
5853
- }
5854
- box2(group, 0.06, 4e-3, 0.01, M.metal, gx + 0.03, -hR / 2 - 0.024, 0);
5855
- box2(group, 4e-3, 0.022, 0.01, M.metal, gx + 0.06, -hR / 2 - 0.013, 0);
5856
- parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.024, -hR / 2 - 0.012, 0, 0.15);
5857
- if (o.sel) parts.safety = box2(group, 0.016, 5e-3, 3e-3, M.dark, gx + 5e-3, -hR * 0.15, -wR / 2 - 2e-3, 0.4);
5858
- return gx;
5939
+ // src/models/m2.ts
5940
+ function buildM2(kit) {
5941
+ const { M, box: box2, cylinderX, cylinderZ } = kit;
5942
+ const group = kit.group();
5943
+ const parts = { family: "belt", kick: 2.2, stroke: 0.08, glow: [] };
5944
+ box2(group, 0.52, 0.13, 0.12, M.metal, 0.36, 0, 0);
5945
+ for (let i = 0; i < 5; i++) cylinderZ(group, 6e-3, 0.124, M.dark, 0.15 + i * 0.1, -0.03, 0, 10);
5946
+ heatify(parts.glow, cylinderX(group, 0.016, 1.14, M.metal, 1.14, 0.01, 0, 16));
5947
+ cylinderX(group, 0.034, 0.24, M.dark, 0.74, 0.01, 0, 16);
5948
+ for (let i = 0; i < 6; i++) cylinderX(group, 0.036, 0.012, M.metal, 0.66 + i * 0.035, 0.01, 0, 16);
5949
+ cylinderX(group, 0.022, 0.05, M.dark, 1.69, 0.01, 0, 16);
5950
+ box2(group, 0.04, 0.09, 0.03, M.wood, 0.05, -0.02, 0.05);
5951
+ box2(group, 0.04, 0.09, 0.03, M.wood, 0.05, -0.02, -0.05);
5952
+ box2(group, 0.06, 0.02, 0.12, M.metal, 0.09, -0.02, 0);
5953
+ const belt = kit.pivot(group, 0.38, -0.05, -0.12);
5954
+ parts.belt = belt;
5955
+ box2(belt, 0.16, 0.12, 0.09, M.olive, 0, 0, 0);
5956
+ for (let i = 0; i < 8; i++) cylinderX(belt, 6e-3, 0.05, M.brass, -0.08 + i * 0.02, 0.08, 0.04, 8);
5957
+ const cover = kit.pivot(group, 0.12, 0.065, 0);
5958
+ parts.cover = cover;
5959
+ box2(cover, 0.4, 0.012, 0.12, M.metal, 0.2, 6e-3, 0);
5960
+ box2(group, 0.04, 0.05, 0.04, M.dark, 0.66, 0.1, 0);
5961
+ const bolt = kit.pivot(group);
5962
+ parts.bolt = bolt;
5963
+ box2(bolt, 0.03, 0.03, 0.02, M.metal, 0.5, 0, 0.07);
5964
+ const tripod = kit.group();
5965
+ tripod.position.set(0.42, -0.1, 0);
5966
+ group.add(tripod);
5967
+ box2(tripod, 0.03, 0.06, 0.03, M.metal, 0, 0, 0);
5968
+ box2(tripod, 0.012, 0.62, 0.012, M.metal, -0.2, -0.25, 0, 0.75);
5969
+ box2(tripod, 0.012, 0.62, 0.012, M.metal, 0.22, -0.25, 0.16, -0.75, 0, 0.3);
5970
+ box2(tripod, 0.012, 0.62, 0.012, M.metal, 0.22, -0.25, -0.16, -0.75, 0, -0.3);
5971
+ parts.muzzle = kit.mark(group, 1.72, 0.01, 0);
5972
+ parts.eject = kit.mark(group, 0.3, -0.07, 0);
5973
+ return { group, parts };
5859
5974
  }
5860
- function buildMagazine(context, gx) {
5861
- const { kit, group, parts, options: o, receiverStart: x0, receiverHeight: hR, receiverWidth: wR, receiverLength: RL } = context;
5862
- const { M, box: box2 } = kit;
5863
- if (o.mag === "helical") return buildHelicalMagazine(context);
5864
- const mx = o.magAt != null ? x0 + o.magAt : gx + 0.075;
5865
- const scale = o.magLen || 1;
5866
- const magazine = kit.pivot(group, mx, -hR / 2, 0);
5975
+
5976
+ // src/models/p90.ts
5977
+ function buildP90(kit) {
5978
+ const { M, box: box2, cylinderX } = kit;
5979
+ const group = kit.group();
5980
+ const parts = { family: "p90", stroke: 0.04, kick: 0.6, glow: [] };
5981
+ box2(group, 0.4, 0.07, 0.055, M.poly, 0.2, 0, 0);
5982
+ box2(group, 0.12, 0.075, 0.05, M.poly, 0.2, -0.07, 0);
5983
+ box2(group, 0.08, 0.04, 0.03, M.hole, 0.21, -0.06, 0);
5984
+ box2(group, 0.06, 0.13, 0.056, M.poly, 0.03, -0.03, 0);
5985
+ box2(group, 0.05, 0.03, 0.056, M.poly, 0.33, -0.04, 0);
5986
+ heatify(parts.glow, cylinderX(group, 8e-3, 0.09, M.metal, 0.43, 0, 0, 16));
5987
+ const magazine = kit.pivot(group, 0.22, 0.047, 0);
5867
5988
  parts.mag = magazine;
5868
- const finish = o.magMat === "bakelite" ? M.bakelite : o.magMat === "poly" ? M.polyMag : void 0;
5869
- if (o.mag === "curved") {
5870
- let px = 0;
5871
- let py = 0;
5872
- const material = finish ?? M.metal;
5873
- for (const angle of [0.06, 0.22, 0.38]) {
5874
- const length = 0.07 * scale;
5875
- const cx = px + Math.sin(angle) * length / 2;
5876
- const cy = py - Math.cos(angle) * length / 2;
5877
- box2(magazine, 0.052, length + 4e-3, 0.024, material, cx, cy, 0, angle);
5878
- box2(magazine, 0.04, length * 0.8, 0.026, M.dark, cx, cy, 0, angle);
5879
- px += Math.sin(angle) * length;
5880
- py -= Math.cos(angle) * length;
5881
- }
5882
- box2(magazine, 0.058, 8e-3, 0.028, material, px, py, 0, 0.38);
5883
- parts.magDrop = 0.07 * scale * 3 + 0.04;
5884
- } else if (o.mag === "box") {
5885
- const length = 0.13 * scale;
5886
- const curve = o.curve || 0;
5887
- const segment = length / 3;
5888
- const material = finish ?? M.dark;
5889
- let px = 0;
5890
- let py = 0;
5891
- let angle = 0.08;
5892
- for (let i = 0; i < 3; i++) {
5893
- angle = 0.08 + curve * i;
5894
- const cx = px + Math.sin(angle) * segment / 2;
5895
- const cy = py - Math.cos(angle) * segment / 2;
5896
- box2(magazine, 0.05, segment + 4e-3, 0.024, material, cx, cy, 0, angle);
5897
- box2(magazine, 0.04, 3e-3, 0.026, M.metal, cx, cy, 0, angle);
5898
- px += Math.sin(angle) * segment;
5899
- py -= Math.cos(angle) * segment;
5900
- }
5901
- box2(magazine, 0.056, 7e-3, 0.028, material, px, py - 2e-3, 0, angle);
5902
- parts.magDrop = length + 0.03;
5903
- } else if (o.mag === "drum") {
5904
- buildDrum(kit, magazine);
5905
- parts.magDrop = 0.2;
5906
- } else if (o.mag === "internal") {
5907
- box2(group, 0.08, 0.025, 0.03, context.isTraditional ? context.furniture : M.metal, mx, -hR / 2 - 0.012, 0);
5908
- parts.mag = null;
5909
- } else parts.mag = null;
5910
- if (o.mag === "belt") {
5911
- const belt = kit.pivot(group, mx + 0.02, -hR / 2 - 0.05, -0.045);
5912
- parts.belt = belt;
5913
- buildBeltBox(kit, belt);
5914
- const cover = kit.pivot(group, x0 + 0.01, hR / 2, 0);
5915
- parts.cover = cover;
5916
- box2(cover, RL - 0.03, 0.014, wR * 1.05, M.metal, (RL - 0.03) / 2, 7e-3, 0);
5917
- box2(cover, 0.02, 0.012, wR * 0.6, M.dark, RL - 0.04, 0.016, 0);
5989
+ parts.magDir = 1;
5990
+ parts.magDrop = 0.08;
5991
+ box2(magazine, 0.3, 0.022, 0.05, M.glass, 0, 0, 0);
5992
+ for (let i = 0; i < 12; i++) cylinderX(magazine, 28e-4, 0.02, M.brass, -0.12 + i * 0.022, 0, 0, 6);
5993
+ scope(kit, group, 1, 0.24, 0.06, 0);
5994
+ const bolt = kit.pivot(group);
5995
+ parts.bolt = bolt;
5996
+ box2(bolt, 0.02, 0.012, 0.012, M.dark, 0.3, 0.02, 0.03);
5997
+ parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, 0.24, -0.045, 0);
5998
+ parts.muzzle = kit.mark(group, 0.48, 0, 0);
5999
+ parts.eject = kit.mark(group, 0.2, -0.07, 0);
6000
+ return { group, parts };
6001
+ }
6002
+
6003
+ // src/models/part-registry.ts
6004
+ var SINGLE_PARTS = ["slide", "bolt", "boltHandle", "mag", "trigger", "hammer", "lever", "pump", "belt", "cover", "crane", "cylinder", "barrels", "safety", "muzzle", "eject", "loadPort", "topLoad", "rear", "round", "gauge", "stock", "ugl"];
6005
+ function partRegistry(parts) {
6006
+ const registry = /* @__PURE__ */ new Map();
6007
+ for (const key of SINGLE_PARTS) {
6008
+ const part = parts[key];
6009
+ if (part) registry.set(key, part);
5918
6010
  }
5919
- return mx;
6011
+ parts.breech?.forEach((part, index) => registry.set(`breech_${index}`, part));
6012
+ parts.chambers?.forEach((part, index) => registry.set(`chamber_${index}`, part));
6013
+ return registry;
5920
6014
  }
5921
6015
 
5922
- // src/models/rifle-stock.ts
5923
- function buildStock(context, magazineX) {
5924
- const { kit, group, options: o, furniture: furn, length: L, stockLength: S, receiverStart: x0, receiverEnd: x1, receiverHeight: hR } = context;
6016
+ // src/models/pistol-slide.ts
6017
+ var PI8 = Math.PI;
6018
+ function buildSlide(kit, slide2, parts, options, style, size, metal2) {
5925
6019
  const { M, box: box2, cylinderX } = kit;
5926
- if (context.isTraditional) {
5927
- buttpad(kit, group, 6e-3, -0.04, 0.13, 0.038);
5928
- box2(group, 0.03, 0.13, 0.038, furn, 0.024, -0.04, 0);
5929
- box2(group, S, 0.055, 0.034, furn, S / 2, -0.012, 0, 0.06);
5930
- box2(group, 0.09, 0.05, 0.032, furn, x0 - 0.02, -0.035, 0, 0.25);
5931
- const forendLength = x1 - x0 + (L - x1) * (o.fore ?? 0.62);
5932
- if (forendLength > 0) box2(group, forendLength, 0.045, 0.04, furn, x0 + forendLength / 2, -hR / 2 + 4e-3, 0);
5933
- if (o.stock === "straight") box2(group, 0.15, 0.04, 0.035, furn, x1 + 0.08, -hR / 2, 0);
5934
- box2(group, 0.04, 4e-3, 0.03, M.metal, magazineX, -hR / 2 - 0.022, 0);
5935
- swivel(kit, group, 0.2, -0.08);
5936
- swivel(kit, group, x1 + (L - x1) * 0.45, -hR / 2 - 0.03);
5937
- if (o.bands) for (const fraction of [0.35, 0.75]) cylinderX(group, 0.024, 8e-3, M.metal, x1 + (L - x1) * fraction * (o.fore || 0.62), -4e-3, 0, 16);
5938
- } else if (o.stock === "fixed" || o.stock === "wood") {
5939
- box2(group, S, 0.05, 0.03, furn, S / 2, -8e-3, 0, 0.05);
5940
- box2(group, 0.03, 0.13, 0.036, furn, 0.022, -0.035, 0);
5941
- buttpad(kit, group, 4e-3, -0.035, 0.13, 0.036);
5942
- if (o.stock === "wood") box2(group, S * 0.6, 0.03, 0.028, furn, S * 0.55, -0.045, 0, 0.2);
5943
- swivel(kit, group, 0.08, -0.09);
5944
- } else if (o.stock === "skeleton") {
5945
- box2(group, S, 0.014, 0.026, furn, S / 2, 0.012, 0, 0.03);
5946
- box2(group, S, 0.014, 0.026, furn, S / 2, -0.05, 0, -0.08);
5947
- box2(group, 0.03, 0.12, 0.034, furn, 0.02, -0.025, 0);
5948
- buttpad(kit, group, 4e-3, -0.025, 0.12, 0.034);
5949
- } else if (o.stock === "retract") {
5950
- cylinderX(group, 0.013, S, M.metal, S / 2, 0, 0, 16);
5951
- for (let i = 0; i < 5; i++) box2(group, 3e-3, 3e-3, 8e-3, M.dark, S * 0.2 + i * 0.015, -0.013, 0);
5952
- box2(group, 0.034, 0.11, 0.036, furn, 0.02, -0.025, 0);
5953
- box2(group, S * 0.55, 0.032, 0.03, furn, S * 0.35, 0, 0);
5954
- buttpad(kit, group, 3e-3, -0.025, 0.11, 0.036);
5955
- } else if (o.stock === "fold") {
5956
- box2(group, S, 6e-3, 6e-3, M.metal, S / 2, 0.01, 0.012);
5957
- box2(group, S, 6e-3, 6e-3, M.metal, S / 2, -0.04, 0.012, -0.12);
5958
- box2(group, 0.012, 0.1, 0.03, M.metal, 6e-3, -0.02, 0.012);
6020
+ const { length: L, width: w } = size;
6021
+ const slideHeight = style.inset ? size.height * 0.92 : size.height;
6022
+ const slideWidth = style.inset ? w * 0.86 : w;
6023
+ kit.roundness = style.rf;
6024
+ if (style.tri) box2(slide2, L * 0.42, slideHeight, slideWidth, metal2, L * 0.21, 0, 0);
6025
+ else if (style.open) {
6026
+ box2(slide2, L * 0.45, slideHeight, slideWidth, metal2, L * 0.225, 0, 0);
6027
+ box2(slide2, L * 0.55, slideHeight * 0.62, slideWidth, metal2, L * 0.72, -slideHeight * 0.19, 0);
6028
+ } else box2(slide2, L, slideHeight, slideWidth, metal2, L / 2, 0, 0);
6029
+ if (style.slope) box2(slide2, L * 0.25, slideHeight * 0.5, slideWidth * 0.98, metal2, L * 0.86, slideHeight * 0.35, 0, -0.22);
6030
+ kit.roundness = null;
6031
+ for (let i = 0; i < (style.flush ? 9 : 7); i++) box2(slide2, 22e-4, slideHeight * 0.72, slideWidth + 14e-4, M.dark, 7e-3 + i * 52e-4, -2e-3, 0, 0);
6032
+ if (style.frontSer) for (let i = 0; i < 5; i++) box2(slide2, 22e-4, slideHeight * 0.6, slideWidth + 14e-4, M.dark, L * 0.76 + i * 52e-4, -3e-3, 0);
6033
+ if (style.port) {
6034
+ box2(slide2, L * style.port, 4e-3, slideWidth * 0.55, M.hole, L * 0.6, slideHeight / 2 - 1e-3, slideWidth * 0.12);
6035
+ box2(slide2, L * style.port * 0.85, 3e-3, slideWidth * 0.4, M.steelL, L * 0.6, slideHeight / 2 - 4e-4, slideWidth * 0.1);
6036
+ box2(slide2, 0.012, 4e-3, 2e-3, M.dark, L * 0.55, slideHeight * 0.15, slideWidth / 2 + 8e-4);
6037
+ }
6038
+ if (style.open) {
6039
+ heatify(parts.glow, cylinderX(slide2, 62e-4, L * 0.56, M.steelL, L * 0.7, slideHeight * 0.12, 0, 18));
6040
+ box2(slide2, 0.03, 4e-3, slideWidth * 0.6, M.hole, L * 0.48, slideHeight / 2 - 1e-3, 0);
6041
+ }
6042
+ if (style.tri) {
6043
+ const tube = new kit.THREE.Mesh(new kit.THREE.CylinderGeometry(0.016, 0.016, L * 0.6, 3), metal2);
6044
+ tube.castShadow = tube.receiveShadow = true;
6045
+ tube.rotation.set(0, 0, -PI8 / 2);
6046
+ tube.rotateY(PI8 / 6);
6047
+ tube.position.set(L * 0.7, slideHeight * 0.08, 0);
6048
+ slide2.add(tube);
6049
+ heatify(parts.glow, tube);
6050
+ box2(slide2, L * 0.6, 6e-3, 0.012, metal2, L * 0.7, slideHeight * 0.38, 0);
6051
+ for (let i = 0; i < 6; i++) box2(slide2, 8e-3, 3e-3, 0.013, M.hole, L * 0.45 + i * 0.04, slideHeight * 0.41, 0);
6052
+ }
6053
+ if (style.bushing) {
6054
+ heatify(parts.glow, cylinderX(slide2, 78e-4, 6e-3, M.steelL, L + 2e-3, 1e-3, 0, 18));
6055
+ cylinderX(slide2, 45e-4, 4e-3, M.steel, L + 2e-3, -0.012, 0, 12);
6056
+ }
6057
+ const muzzleY = style.tri ? slideHeight * 0.08 : 2e-3;
6058
+ heatify(parts.glow, cylinderX(slide2, style.tri ? 72e-4 : 62e-4, 4e-3, M.dark, L + 15e-4, muzzleY, 0, 18));
6059
+ cylinderX(slide2, 34e-4, 5e-3, M.hole, L + 25e-4, muzzleY, 0, 12);
6060
+ const top = slideHeight / 2;
6061
+ if (style.sight === "u" || style.sight === "3dot") {
6062
+ box2(slide2, 4e-3, 7e-3, 4e-3, M.dark, L - 8e-3, top + 35e-4, 0);
6063
+ box2(slide2, 22e-4, 22e-4, 46e-4, M.white, L - 8e-3, top + 58e-4, 0);
6064
+ box2(slide2, 8e-3, 7e-3, 0.016, M.dark, 9e-3, top + 35e-4, 0);
6065
+ if (style.sight === "u") box2(slide2, 4e-3, 2e-3, 0.0122, M.white, 9e-3, top + 6e-3, 0);
6066
+ else box2(slide2, 22e-4, 22e-4, 0.0166, M.white, 9e-3, top + 58e-4, 0);
6067
+ } else {
6068
+ box2(slide2, 6e-3, 4e-3, 3e-3, M.dark, L - 6e-3, top + 2e-3, 0);
6069
+ box2(slide2, 6e-3, 4e-3, 0.012, M.dark, 8e-3, top + 2e-3, 0);
6070
+ }
6071
+ if (style.decock === "slide") {
6072
+ box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.02, slideHeight * 0.15, slideWidth / 2 + 2e-3, 0.3);
6073
+ box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.02, slideHeight * 0.15, -slideWidth / 2 - 2e-3, 0.3);
6074
+ }
6075
+ if (style.safety === "slide") parts.safety = box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.018, slideHeight * 0.1, -slideWidth / 2 - 2e-3, 0.2);
6076
+ if (options.sel) {
6077
+ const selector = box2(slide2, 0.01, 8e-3, 3e-3, M.dark, 0.014, -slideHeight * 0.2, slideWidth / 2 + 14e-4);
6078
+ parts.safety ?? (parts.safety = selector);
5959
6079
  }
5960
6080
  }
5961
6081
 
5962
- // src/models/rifle.ts
5963
- var PI10 = Math.PI;
5964
- function buildReceiver(context) {
5965
- const { kit, group, parts, options: o, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, receiverLength: RL, barrelY: by } = context;
5966
- const { M, box: box2, cylinderX, cylinderY, cylinderZ, sphere } = kit;
5967
- const receiverMaterial = o.tan ? M.tan : M.metal;
5968
- const middle = (x0 + x1) / 2;
5969
- if (o.round) {
5970
- cylinderX(group, hR * 0.36, RL, M.steel, middle, by, 0, 20);
5971
- cylinderX(group, hR * 0.32, 0.05, M.steel, x0 - 0.01, by, 0, 18);
5972
- } else if (o.ar) {
5973
- box2(group, RL, hR * 0.5, wR, receiverMaterial, middle, hR * 0.25, 0);
5974
- box2(group, RL * 0.92, hR * 0.52, wR * 0.96, receiverMaterial, middle - RL * 0.02, -hR * 0.24, 0);
5975
- box2(group, 0.05, 0.03, wR * 1.15, receiverMaterial, x0 + RL * 0.62, -hR * 0.5 - 0.01, 0);
5976
- cylinderZ(group, 6e-3, 0.012, M.metal, x1 - 0.03, hR * 0.28, wR / 2 + 6e-3, 12);
5977
- box2(group, 0.012, 0.01, 0.012, M.metal, x1 - 0.05, hR * 0.12, wR / 2 + 6e-3);
5978
- box2(group, 0.03, 8e-3, 0.012, M.dark, x0 + 0.02, hR / 2 + 4e-3, 0);
5979
- box2(group, 0.012, 6e-3, 4e-3, M.dark, x0 + RL * 0.55, -hR * 0.15, -wR / 2 - 2e-3);
5980
- } else box2(group, RL, hR, wR, receiverMaterial, middle, 0, 0);
5981
- if (o.ak) {
5982
- box2(group, RL * 0.86, hR * 0.28, wR * 0.95, M.metal, middle - 8e-3, hR / 2 + 4e-3, 0);
5983
- for (let i = 0; i < 3; i++) box2(group, 4e-3, hR * 0.3, wR * 0.97, M.dark, x0 + RL * 0.25 + i * 0.02, hR / 2 + 4e-3, 0);
5984
- box2(group, RL * 0.5, 0.012, 3e-3, M.metal, x0 + RL * 0.55, -hR * 0.02, wR / 2 + 15e-4, -0.05);
5985
- box2(group, 0.012, 0.014, 3e-3, M.dark, x0 + RL * 0.32, -hR * 0.05, wR / 2 + 2e-3);
6082
+ // src/models/pistol-style.ts
6083
+ var STYLES = {
6084
+ glock: { rf: 0.07, ga: -0.36, grip: "finger", guard: "square", rail: 1, sight: "u", port: 0.2, slideM: "metal", frame: "poly" },
6085
+ taurus: { rf: 0.16, ga: -0.3, grip: "panel", guard: "square", rail: 1, sight: "3dot", port: 0.18, frontSer: 1, frame: "poly" },
6086
+ g2c: { rf: 0.2, ga: -0.3, grip: "panel", guard: "round", rail: 0, sight: "3dot", port: 0.17, frame: "poly", safety: "thumb" },
6087
+ beretta: { rf: 0.28, ga: -0.27, grip: "wood", guard: "round", hammer: "spur", sight: "3dot", open: 1, decock: "slide", port: 0, frame: "metal" },
6088
+ pt100: { rf: 0.25, ga: -0.27, grip: "rubber", guard: "round", hammer: "spur", sight: "3dot", port: 0.17, decock: "frame", frame: "metal" },
6089
+ sig: { rf: 0.14, ga: -0.3, grip: "rubber", guard: "hook", hammer: "spur", sight: "3dot", port: 0.19, decock: "frame", rail: 1, tall: 1.12, frame: "metal" },
6090
+ cz: { rf: 0.32, ga: -0.3, grip: "rubber", guard: "round", hammer: "spur", sight: "3dot", port: 0.15, inset: 1, safety: "thumb", frame: "metal" },
6091
+ m1911: { rf: 0.24, ga: -0.2, grip: "wood", guard: "round", hammer: "ring", sight: "small", port: 0.12, bushing: 1, beaver: 1, safety: "thumb", slideStop: 1, frame: "metal" },
6092
+ makarov: { rf: 0.48, ga: -0.24, grip: "star", guard: "round", hammer: "ring", sight: "small", port: 0.14, flush: 1, safety: "slide", lanyard: 1, frame: "metal" },
6093
+ fiveseven: { rf: 0.2, ga: -0.4, grip: "chunky", guard: "square", rail: 1, sight: "3dot", port: 0.18, slope: 1, frame: "poly" },
6094
+ deagle: { rf: 0.08, ga: -0.28, grip: "rubber", guard: "round", hammer: "big", sight: "small", port: 0.16, tri: 1, safety: "slide", frame: "metal" }
6095
+ };
6096
+
6097
+ // src/models/pistol.ts
6098
+ var PI9 = Math.PI;
6099
+ function buildFrame(kit, group, parts, options, style, size, materials) {
6100
+ const { M, box: box2, cylinderZ, sphere } = kit;
6101
+ const { length: L, height: h, width: w } = size;
6102
+ const fm = materials.frame;
6103
+ if (style.inset) {
6104
+ box2(group, L * 0.9, h * 0.35, w * 0.12, fm, L * 0.47, -h * 0.32, w * 0.47);
6105
+ box2(group, L * 0.9, h * 0.35, w * 0.12, fm, L * 0.47, -h * 0.32, -w * 0.47);
5986
6106
  }
5987
- if (o.g3) {
5988
- cylinderX(group, 9e-3, (L - x1) * 0.6, M.metal, x1 + (L - x1) * 0.28, hR * 0.48, 0, 14);
5989
- box2(group, 0.012, 0.024, 8e-3, M.dark, x1 + (L - x1) * 0.5, hR * 0.48, -0.012);
5990
- cylinderY(group, 0.012, 0.016, M.dark, x0 + 0.03, hR / 2 + 8e-3, 0, 12);
6107
+ const frameLength = style.tri ? L * 0.66 : L * 0.8;
6108
+ kit.roundness = style.rf;
6109
+ box2(group, frameLength, 0.016, w * 0.92, fm, frameLength / 2 + L * 0.06, -h / 2 - 8e-3, 0);
6110
+ kit.roundness = null;
6111
+ if (style.rail) for (let i = 0; i < (style.slope ? 5 : 3); i++) box2(group, 4e-3, 3e-3, w * 0.96, M.dark, L * 0.62 + i * 9e-3, -h / 2 - 0.017, 0);
6112
+ const isGlock = options.st === "glock" || !options.st;
6113
+ if (isGlock) {
6114
+ box2(group, 8e-3, 4e-3, 3e-3, M.dark, L * 0.5, -h / 2 - 3e-3, -w / 2 - 12e-4);
6115
+ box2(group, 8e-3, 4e-3, 3e-3, M.dark, L * 0.5, -h / 2 - 3e-3, w / 2 + 12e-4);
6116
+ }
6117
+ if (style.slideStop) {
6118
+ box2(group, 0.03, 6e-3, 3e-3, M.dark, L * 0.42, -h / 2 - 2e-3, -w / 2 - 14e-4);
6119
+ sphere(group, 28e-4, M.dark, L * 0.55, -h / 2 - 3e-3, w / 2 + 1e-3, 1);
6120
+ } else box2(group, 0.012, 4e-3, 3e-3, M.dark, L * 0.45, -h / 2 - 3e-3, -w / 2 - 12e-4);
6121
+ if (style.decock === "frame") box2(group, 0.01, 0.012, 3e-3, M.dark, L * 0.38, -h / 2 - 0.012, -w / 2 - 14e-4, 0.6);
6122
+ if (style.safety === "thumb") parts.safety = box2(group, 0.014, 6e-3, 4e-3, M.dark, 0.02, -h / 2 - 2e-3, -w / 2 - 16e-4);
6123
+ const gripLength = options.gl ?? 0.105;
6124
+ const gx = 0.034 + (L - 0.2) * 0.15;
6125
+ const gy = -h / 2 - gripLength / 2 + 6e-3;
6126
+ const grip = kit.pivot(group, gx, gy, 0);
6127
+ grip.rotation.z = style.ga;
6128
+ parts.grip = grip;
6129
+ const gripWidth = style.grip === "chunky" ? w * 1.18 : w * 1.06;
6130
+ const gripDepth = style.grip === "chunky" ? 0.036 : 0.032;
6131
+ kit.roundness = 0.3;
6132
+ if (style.grip === "wood" || style.grip === "star") {
6133
+ box2(grip, gripDepth * 0.86, gripLength, w * 0.9, fm, 0, 0, 0);
6134
+ box2(grip, gripDepth * 0.8, gripLength * 0.86, 4e-3, materials.grip, 1e-3, -2e-3, w * 0.47);
6135
+ box2(grip, gripDepth * 0.8, gripLength * 0.86, 4e-3, materials.grip, 1e-3, -2e-3, -w * 0.47);
6136
+ if (style.grip === "wood") {
6137
+ for (let i = 0; i < 6; i++) for (let j = 0; j < 2; j++) box2(grip, 3e-3, 3e-3, 12e-4, M.woodL, -6e-3 + j * 0.012, -gripLength * 0.3 + i * 0.012, w * 0.495, 0, 0, 0);
6138
+ cylinderZ(grip, 35e-4, 15e-4, M.brass, 0, gripLength * 0.25, w * 0.5, 10);
6139
+ cylinderZ(grip, 35e-4, 15e-4, M.brass, 0, -gripLength * 0.3, w * 0.5, 10);
6140
+ } else cylinderZ(grip, 8e-3, 15e-4, M.dark, 0, gripLength * 0.1, w * 0.5, 5);
6141
+ } else box2(grip, gripDepth, gripLength, gripWidth, materials.grip, 0, 0, 0);
6142
+ kit.roundness = null;
6143
+ if (style.grip === "finger") {
6144
+ for (let i = 0; i < 3; i++) box2(grip, 6e-3, 0.012, gripWidth * 0.98, materials.grip, gripDepth / 2 + 2e-3, gripLength * 0.18 - i * 0.022, 0);
6145
+ for (let i = 0; i < 5; i++) for (let j = 0; j < 3; j++) box2(grip, 3e-3, 3e-3, 12e-4, M.dark, -8e-3 + j * 8e-3, -gripLength * 0.25 + i * 0.012, gripWidth / 2 + 4e-4);
5991
6146
  }
5992
- const ejectX = x1 - (o.ejectBack || 0.07);
5993
- const ejectY = o.ar ? hR * 0.2 : by;
5994
- box2(group, 0.05, hR * 0.36, 4e-3, M.hole, ejectX, ejectY, wR / 2 - 4e-4);
5995
- const bolt = kit.pivot(group);
5996
- parts.bolt = bolt;
5997
- parts.stroke = Math.min(0.1, RL * 0.32);
5998
- box2(bolt, 0.044, hR * 0.26, 6e-3, M.steelL, ejectX, ejectY, wR / 2 - 12e-4);
5999
- if (o.charge) {
6000
- box2(bolt, 0.012, 0.01, 0.03, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.012);
6001
- sphere(bolt, 65e-4, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.028, 1);
6147
+ if (style.grip === "panel" || style.grip === "rubber" || style.grip === "chunky") for (let i = 0; i < 6; i++) box2(grip, gripDepth * 0.75, 25e-4, gripWidth + 1e-3, M.dark, 2e-3, -gripLength / 2 + 0.018 + i * 0.013, 0);
6148
+ if (style.grip === "chunky") box2(grip, 8e-3, gripLength * 0.8, gripWidth * 0.9, materials.grip, gripDepth / 2 + 2e-3, -4e-3, 0);
6149
+ if (style.beaver) {
6150
+ box2(group, 0.026, 8e-3, w * 0.9, fm, -4e-3, -h / 2 - 4e-3, 0, 0.25);
6151
+ box2(grip, 6e-3, gripLength * 0.55, w * 0.9, fm, -gripDepth * 0.5 - 2e-3, gripLength * 0.18, 0);
6152
+ } else box2(group, 0.02, 9e-3, w * 0.9, fm, 4e-3, -h / 2 - 6e-3, 0);
6153
+ if (style.lanyard) {
6154
+ const ring = kit.torus(grip, new kit.THREE.TorusGeometry(5e-3, 13e-4, 6, 12), M.dark, -0.012, -gripLength / 2 + 4e-3, 0);
6155
+ ring.position.set(-0.012, -gripLength / 2 + 4e-3, 0);
6002
6156
  }
6003
- if (o.ar) box2(group, 0.012, 0.016, 6e-3, receiverMaterial, ejectX - 0.03, ejectY + 4e-3, wR / 2 + 4e-3);
6004
- if (o.rail) rail(kit, group, x0 - 5e-3, RL + 0.07, hR / 2 + (o.ar ? 0 : 3e-3), 0, wR * 0.68);
6157
+ const magazine = kit.pivot(grip);
6158
+ parts.mag = magazine;
6159
+ parts.magDrop = gripLength * 1.15;
6160
+ box2(magazine, 0.022, gripLength * 0.94, w * 0.72, M.dark, 3e-3, -4e-3, 0);
6161
+ if (style.grip === "chunky") box2(magazine, 0.04, 0.01, gripWidth * 1.02, M.poly, 3e-3, -gripLength / 2 - 4e-3, 0);
6162
+ else if (style.flush || style.grip === "wood") box2(magazine, 0.028, 4e-3, w * 0.9, M.steel, 3e-3, -gripLength / 2 - 1e-3, 0);
6163
+ else box2(magazine, 0.031, 6e-3, gripWidth * 1.02, M.dark, 3e-3, -gripLength / 2 - 2e-3, 0);
6164
+ return { gx, ty: -h / 2 };
6005
6165
  }
6006
- function buildBarrel(context) {
6007
- const { kit, group, parts, options: o, length: L, barrelLength: Bl, receiverEnd: x1, barrelRadius: r, barrelY: by } = context;
6008
- const { M, box: box2, cylinderX } = kit;
6009
- heatify(parts.glow, cylinderX(group, r, Bl, M.metal, L - Bl / 2, by, 0, 16));
6010
- if (o.jacket) {
6011
- cylinderX(group, r * 2, (L - x1) * 0.6, M.dark, x1 + (L - x1) * 0.3, by, 0, 16);
6012
- for (let i = 0; i < 6; i++) cylinderX(group, r * 2.15, 4e-3, M.metal, x1 + (L - x1) * (0.05 + i * 0.09), by, 0, 16);
6166
+ function buildPistol(kit, options) {
6167
+ const { M, box: box2 } = kit;
6168
+ const style = { ...STYLES[options.st ?? "glock"] };
6169
+ const group = kit.group();
6170
+ const parts = { family: "pistol", kick: options.deagle ? 1.8 : 0.6, glow: [] };
6171
+ const size = { length: (options.L ?? 0) / 1e3 * 0.97, height: (options.h ?? 0.031) * (style.tall ?? 1), width: options.deagle ? 0.032 : style.flush ? 0.024 : 0.025 };
6172
+ const { length: L, height: h, width: w } = size;
6173
+ const slideMetal = options.steel ? M.steel : options.stain ? M.steelL : M.metal;
6174
+ const frameMetal = style.frame === "poly" ? M.poly : slideMetal;
6175
+ const gripMaterial = style.grip === "wood" ? M.wood : style.grip === "star" ? M.bake : style.grip === "rubber" ? M.rubber : M.poly;
6176
+ const slide2 = kit.pivot(group);
6177
+ parts.slide = slide2;
6178
+ parts.stroke = L * 0.15;
6179
+ buildSlide(kit, slide2, parts, options, style, size, slideMetal);
6180
+ const { gx, ty } = buildFrame(kit, group, parts, options, style, size, { frame: frameMetal, grip: gripMaterial });
6181
+ if (style.guard === "square") {
6182
+ box2(group, 0.046, 4e-3, 9e-3, frameMetal, gx + 0.05, ty - 0.034, 0);
6183
+ box2(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.074, ty - 0.02, 0);
6184
+ box2(group, 6e-3, 4e-3, 9e-3, frameMetal, gx + 0.075, ty - 0.034, 0);
6185
+ } else if (style.guard === "hook") {
6186
+ box2(group, 0.046, 4e-3, 9e-3, frameMetal, gx + 0.05, ty - 0.034, 0);
6187
+ box2(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.073, ty - 0.02, 0, 0.2);
6188
+ box2(group, 8e-3, 6e-3, 9e-3, frameMetal, gx + 0.074, ty - 0.037, 0, 0.4);
6189
+ } else {
6190
+ const guard = kit.torus(group, new kit.THREE.TorusGeometry(0.019, 22e-4, 6, 16, PI9), frameMetal, gx + 0.05, ty - 0.014, 0);
6191
+ guard.rotation.z = PI9;
6192
+ guard.scale.set(1.2, 1, 1);
6013
6193
  }
6014
- if (o.gas) cylinderX(group, r * 0.75, (L - x1) * 0.55, M.metal, x1 + (L - x1) * 0.3, by + r * 2.2, 0, 14);
6015
- if (o.brake) {
6016
- const kind = o.brake === 2 ? 2 : 1;
6017
- recordFitting(parts, kind === 2 ? "muzzle_brake_heavy" : "muzzle_brake", group, () => buildBrake(kit, group, kind, L, by, r, (body) => void heatify(parts.glow, body)));
6018
- } else if (!o.noFH) {
6019
- recordFitting(parts, "muzzle_default", group, () => {
6020
- heatify(parts.glow, cylinderX(group, r * 1.45, 0.042, M.dark, L - 0.021, by, 0, 16));
6021
- for (let i = 0; i < 4; i++) {
6022
- const angle = i * PI10 / 2 + PI10 / 4;
6023
- box2(group, 0.026, 25e-4, 4e-3, M.hole, L - 0.02, by + Math.cos(angle) * r * 1.42, Math.sin(angle) * r * 1.42, 0, 0, angle);
6024
- }
6025
- });
6194
+ const isGlock = options.st === "glock" || !options.st;
6195
+ parts.trigger = box2(group, 4e-3, 0.014, 4e-3, isGlock ? M.dark : M.steel, gx + 0.042, ty - 0.022, 0, 0.2);
6196
+ if (isGlock) box2(parts.trigger, 15e-4, 8e-3, 45e-4, M.metal, 0, -1e-3, 0);
6197
+ if (style.hammer) {
6198
+ const hammer = kit.pivot(group, 3e-3, h / 2 - 8e-3, 0);
6199
+ parts.hammer = hammer;
6200
+ if (style.hammer === "ring") {
6201
+ box2(hammer, 6e-3, 0.012, 6e-3, M.dark, -2e-3, 4e-3, 0, 0.3);
6202
+ kit.torus(hammer, new kit.THREE.TorusGeometry(45e-4, 16e-4, 6, 12), M.dark, -4e-3, 0.012, 0);
6203
+ } else if (style.hammer === "big") box2(hammer, 0.014, 0.02, 9e-3, M.dark, -4e-3, 7e-3, 0, 0.35);
6204
+ else {
6205
+ box2(hammer, 8e-3, 0.016, 7e-3, M.dark, -2e-3, 6e-3, 0, 0.3);
6206
+ box2(hammer, 8e-3, 4e-3, 8e-3, M.dark, -6e-3, 0.013, 0, 0.3);
6207
+ }
6026
6208
  }
6027
- cylinderX(group, r * 0.55, 4e-3, M.hole, L + 5e-4, by, 0, 12);
6209
+ parts.muzzle = kit.mark(group, L + 6e-3, style.tri ? h * 0.08 : 2e-3, 0);
6210
+ parts.eject = kit.mark(group, L * 0.6, h / 2, w * 0.25);
6211
+ return { group, parts };
6028
6212
  }
6029
- var familyOf = (o) => {
6030
- if (o.mag === "belt") return "belt";
6031
- if (o.pump) return "pump";
6032
- if (o.lever) return "lever";
6033
- if (o.tube) return "semi12";
6034
- if (o.bolt) return o.mag === "box" ? "boltmag" : "bolt";
6035
- return o.mag === "internal" ? "semiint" : "rifle";
6036
- };
6037
- function buildRifle(kit, options) {
6038
- const { M } = kit;
6213
+
6214
+ // src/models/revolver.ts
6215
+ var PI10 = Math.PI;
6216
+ var DEFAULT_CHAMBERS = 6;
6217
+ function buildRevolver(kit, options) {
6218
+ const { M, box: box2, cylinderX } = kit;
6039
6219
  const group = kit.group();
6040
- const parts = { family: "rifle", glow: [] };
6041
- const length = (options.L ?? 0) / 1e3;
6042
- const barrelLength = (options.B ?? 0) / 1e3;
6043
- const isTraditional = options.stock === "trad" || options.stock === "straight";
6044
- const stockLength = options.stock === "none" ? 0 : options.stockLen || Math.min(0.36, Math.max(0.2, 0.3 * length));
6045
- const receiverStart = stockLength;
6046
- const receiverEnd = Math.max(receiverStart + 0.12, length - barrelLength * 0.92);
6047
- const receiverHeight = options.hR || 0.06;
6048
- const context = {
6049
- kit,
6050
- group,
6051
- parts,
6052
- options,
6053
- furniture: options.wood ? M.wood : options.olive ? M.olive : options.tan ? M.tan : M.poly,
6054
- isTraditional,
6055
- length,
6056
- barrelLength,
6057
- stockLength,
6058
- receiverStart,
6059
- receiverEnd,
6060
- receiverHeight,
6061
- receiverWidth: options.wR || 0.034,
6062
- barrelRadius: options.br || 85e-4,
6063
- barrelY: receiverHeight * 0.15,
6064
- receiverLength: receiverEnd - receiverStart
6065
- };
6066
- buildReceiver(context);
6067
- buildBarrel(context);
6068
- buildForend(context);
6069
- const gripX = buildGrip(context);
6070
- const magazineX = buildMagazine(context, gripX);
6071
- parts.stockMeshes = context.isTraditional ? [] : captured(group, () => buildStock(context, magazineX));
6072
- if (context.isTraditional) buildStock(context, magazineX);
6073
- buildFittings(context, gripX);
6074
- const hasHandguard = !context.isTraditional && options.hg !== "none";
6075
- parts.frames = {
6076
- length,
6077
- barrelY: context.barrelY,
6078
- barrelRadius: context.barrelRadius,
6079
- receiverStart,
6080
- receiverEnd,
6081
- receiverHeight,
6082
- receiverWidth: context.receiverWidth,
6083
- handguardEnd: receiverEnd + (length - receiverEnd) * (hasHandguard ? options.hgFrac || 0.5 : 0.4),
6084
- magazineX,
6085
- gripX,
6086
- hasRail: Boolean(options.rail)
6087
- };
6088
- const { barrelY: by, receiverWidth: wR } = context;
6089
- const ejectX = receiverEnd - (options.ejectBack || 0.07);
6090
- const ejectY = options.ar ? receiverHeight * 0.2 : by;
6091
- parts.muzzle = kit.mark(group, length + 0.01, by, 0);
6092
- parts.eject = kit.mark(group, ejectX, ejectY, wR / 2 + 6e-3);
6093
- parts.loadPort = kit.mark(group, receiverEnd - 0.05, -receiverHeight / 2 - 4e-3, 0);
6094
- parts.topLoad = kit.mark(group, ejectX, receiverHeight / 2 + 4e-3, 0);
6095
- parts.family = familyOf(options);
6220
+ const parts = { family: "revolver", kick: options.cal === ".44 Magnum" ? 1.5 : 0.9, glow: [] };
6221
+ const barrel = (options.B ?? 0) / 1e3;
6222
+ const chambers = options.chambers ?? DEFAULT_CHAMBERS;
6223
+ const metal2 = options.steel ? M.steel : M.metal;
6224
+ box2(group, 0.08, 0.044, 0.024, metal2, 0.07, 0, 0);
6225
+ box2(group, 0.036, 0.012, 0.022, metal2, 0.096, 0.022, 0);
6226
+ box2(group, 0.024, 0.012, 4e-3, M.dark, 0.055, 6e-3, -0.013);
6227
+ const crane = kit.pivot(group, 0.077, -0.024, 0);
6228
+ parts.crane = crane;
6229
+ const cylinder = kit.pivot(crane, 0, 0.022, 0);
6230
+ parts.cylinder = cylinder;
6231
+ cylinderX(cylinder, 0.0215, 0.042, metal2, 0, 0, 0, 24);
6232
+ for (let i = 0; i < chambers; i++) {
6233
+ const angle = i * 2 * PI10 / chambers;
6234
+ box2(cylinder, 0.026, 5e-3, 5e-3, M.dark, 0, Math.cos(angle) * 0.0205, Math.sin(angle) * 0.0205, 0, 0, angle);
6235
+ }
6236
+ parts.chambers = [];
6237
+ for (let i = 0; i < chambers; i++) {
6238
+ const angle = i * 2 * PI10 / chambers + PI10 / chambers;
6239
+ parts.chambers.push(kit.mark(cylinder, -0.022, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013));
6240
+ cylinderX(cylinder, 46e-4, 2e-3, M.brass, -0.0215, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013, 12);
6241
+ }
6242
+ heatify(parts.glow, cylinderX(group, 88e-4, barrel, metal2, 0.112 + barrel / 2, 9e-3, 0, 18));
6243
+ box2(group, barrel * 0.92, 0.015, 0.013, metal2, 0.112 + barrel * 0.46, -5e-3, 0);
6244
+ cylinderX(group, 4e-3, 5e-3, M.hole, 0.112 + barrel + 15e-4, 9e-3, 0, 12);
6245
+ box2(group, 0.012, 9e-3, 4e-3, M.dark, 0.112 + barrel - 8e-3, 0.021, 0, -0.3);
6246
+ box2(group, 0.012, 6e-3, 0.012, M.dark, 0.105, 0.031, 0);
6247
+ const hammer = kit.pivot(group, 0.036, 0.018, 0);
6248
+ box2(hammer, 0.01, 0.02, 8e-3, M.dark, -4e-3, 8e-3, 0, 0.45);
6249
+ parts.hammer = hammer;
6250
+ const grip = kit.pivot(group, 0.024, -0.062, 0);
6251
+ grip.rotation.z = -0.38;
6252
+ parts.grip = grip;
6253
+ box2(grip, 0.034, 0.1, 0.031, options.wood ? M.wood : M.rubber, 0, 0, 0);
6254
+ if (!options.wood) for (let i = 0; i < 4; i++) box2(grip, 6e-3, 0.07, 0.033, M.dark, -0.012 + i * 8e-3, 0, 0);
6255
+ box2(group, 0.038, 4e-3, 8e-3, metal2, 0.062, -0.036, 0);
6256
+ box2(group, 4e-3, 0.022, 8e-3, metal2, 0.08, -0.026, 0, 0.2);
6257
+ parts.trigger = box2(group, 4e-3, 0.015, 4e-3, M.dark, 0.06, -0.027, 0, 0.2);
6258
+ parts.muzzle = kit.mark(group, 0.112 + barrel + 6e-3, 9e-3, 0);
6259
+ parts.eject = kit.mark(group, 0.077, 0, 0);
6096
6260
  return { group, parts };
6097
6261
  }
6098
6262
 
@@ -6108,13 +6272,14 @@ var ARCHETYPE_MODELS = (() => {
6108
6272
  }
6109
6273
  return chosen;
6110
6274
  })();
6111
- var BUILDERS = {
6275
+ var BUILDERS2 = {
6112
6276
  pistol: buildPistol,
6113
6277
  revolver: buildRevolver,
6114
6278
  rifle: buildRifle,
6115
6279
  bullpup: buildBullpup,
6116
6280
  p90: (kit) => buildP90(kit),
6117
6281
  break: buildBreakAction,
6282
+ breakLauncher: buildBreakLauncher,
6118
6283
  m2: (kit) => buildM2(kit),
6119
6284
  m134: (kit) => buildM134(kit),
6120
6285
  launcher: buildLauncher
@@ -6138,7 +6303,7 @@ function buildWeaponModelById(THREE, id) {
6138
6303
  const kit = modelKit(THREE);
6139
6304
  const options = { L: entry.length, B: entry.barrel, cal: entry.caliber, ...entry.options };
6140
6305
  if (id in ARCHETYPE_MODELS) delete options.chambers;
6141
- const built = BUILDERS[entry.builder](kit, options);
6306
+ const built = BUILDERS2[entry.builder](kit, options);
6142
6307
  nameParts(built.parts);
6143
6308
  let root;
6144
6309
  if (entry.builder === "launcher") {
@@ -6147,7 +6312,7 @@ function buildWeaponModelById(THREE, id) {
6147
6312
  built.group.position.x = -new THREE.Box3().setFromObject(built.group).min.x;
6148
6313
  } else root = fitLength(kit, built.group, entry.length / 1e3);
6149
6314
  root.name = id;
6150
- markKitOrigin2(root, { owner: "game-weapons-three", kind: "weapon", id });
6315
+ markKitOrigin3(root, { owner: "game-weapons-three", kind: "weapon", id });
6151
6316
  const model = { id, root, inner: built.group, parts: built.parts };
6152
6317
  if (!built.parts.frames) built.parts.frames = deriveFrames(THREE, model);
6153
6318
  return model;
@@ -6380,9 +6545,17 @@ function revolverReload(kind) {
6380
6545
  if (kind !== "one_by_one") events.push(cue(0.7, "move_in", { from: "chambers" }), sound2(0.7, "speedloader"));
6381
6546
  return { tweens: [turn("crane", "x", 0, 0.22, 0, -1.25), turn("crane", "x", 1.05, 0.2, -1.25, 0)], events, end: 1.3 };
6382
6547
  }
6383
- var breakReload = () => ({
6548
+ var breakReload = (chambers) => ({
6384
6549
  tweens: [turn("barrels", "z", 0, 0.2, 0, -0.5), turn("barrels", "z", 1, 0.15, -0.5, 0)],
6385
- events: [sound2(0, "barrels_open"), cue(0.25, "eject_casing", { from: "breech", down: false }), sound2(0.25, "casings_eject"), cue(0.65, "move_in", { from: "breech" }), sound2(0.65, "shell_insert"), sound2(0.8, "shell_insert"), sound2(1.13, "barrels_close")],
6550
+ events: [
6551
+ sound2(0, "barrels_open"),
6552
+ cue(0.25, "eject_casing", { from: "breech", down: false }),
6553
+ sound2(0.25, "casings_eject"),
6554
+ cue(0.65, "move_in", { from: "breech" }),
6555
+ sound2(0.65, "shell_insert"),
6556
+ ...chambers > 1 ? [sound2(0.8, "shell_insert")] : [],
6557
+ sound2(1.13, "barrels_close")
6558
+ ],
6386
6559
  end: 1.2
6387
6560
  });
6388
6561
  var beltReload = (context) => ({
@@ -6455,7 +6628,7 @@ function draftFor(context, kind) {
6455
6628
  if (family === "rocket" || family === "flame") return heavyReload(context);
6456
6629
  if (parts.mag) return magazineReload(context, kind);
6457
6630
  if (family === "revolver" && parts.crane) return revolverReload(kind);
6458
- if (family === "break" && parts.barrels) return breakReload();
6631
+ if (family === "break" && parts.barrels) return breakReload(parts.breech?.length ?? 2);
6459
6632
  if (family === "belt" && parts.cover && parts.belt) return beltReload(context);
6460
6633
  if (isTube(context)) return tubeReload(context, kind);
6461
6634
  if (family === "bolt") return boltReload(context);
@@ -7155,7 +7328,7 @@ function applyCondition(kit, model, condition) {
7155
7328
  }
7156
7329
 
7157
7330
  // src/models/cartridge.ts
7158
- import { markKitOrigin as markKitOrigin3 } from "@adatechnology/game-contracts";
7331
+ import { markKitOrigin as markKitOrigin4 } from "@adatechnology/game-contracts";
7159
7332
  var BOTTLENECK_RATIO = 0.8;
7160
7333
  function cartridgeShape(caliber, dims) {
7161
7334
  if (caliber.caseType === "shell" || caliber.rim === "shell") return "shell";
@@ -7167,7 +7340,7 @@ function cartridgeShape(caliber, dims) {
7167
7340
  }
7168
7341
  function buildCartridge(kit, dims, shape) {
7169
7342
  const { M, cylinderY } = kit;
7170
- const group = markKitOrigin3(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7343
+ const group = markKitOrigin4(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7171
7344
  const radius = dims.caseDiameter / 2;
7172
7345
  const bulletRadius = dims.projectileDiameter / 2;
7173
7346
  const caseLength = dims.caseLength;
@@ -7199,7 +7372,7 @@ function buildCartridge(kit, dims, shape) {
7199
7372
  }
7200
7373
  function buildCasing(kit, dims, shape) {
7201
7374
  const { M, cylinderX } = kit;
7202
- const group = markKitOrigin3(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7375
+ const group = markKitOrigin4(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7203
7376
  const radius = dims.caseDiameter / 2e3;
7204
7377
  const length = dims.caseLength / 1e3;
7205
7378
  if (shape === "shell") {
@@ -7215,7 +7388,7 @@ function buildCasing(kit, dims, shape) {
7215
7388
  return { group, radius, length };
7216
7389
  }
7217
7390
  function buildRound(kit, dims, shape) {
7218
- const group = markKitOrigin3(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7391
+ const group = markKitOrigin4(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7219
7392
  const cartridge = buildCartridge(kit, dims, shape);
7220
7393
  cartridge.scale.setScalar(1e-3);
7221
7394
  cartridge.rotation.z = -Math.PI / 2;
@@ -7252,6 +7425,38 @@ function createEffects(THREE, sink, random) {
7252
7425
  };
7253
7426
  }
7254
7427
 
7428
+ // src/fx/view-hold.ts
7429
+ var VIEW_HOLD = {
7430
+ /** The right hand's grip, from the eye. */
7431
+ grip: [0.2, -0.2, -0.3],
7432
+ /** A handgun's grip: higher and nearer the middle, the arms out in front. */
7433
+ handgunGrip: [0.13, -0.15, -0.33],
7434
+ /** A rocket launcher's grip: its tube rides on the shoulder, so the hands are higher. */
7435
+ launcherGrip: [0.2, -0.14, -0.3],
7436
+ /** A gun with no grip anchor: the rear of its bore, from the eye. */
7437
+ rear: [0.2, -0.24, -0.12]
7438
+ };
7439
+ var scratch2 = { grip: null };
7440
+ function placeInView(placement) {
7441
+ const { THREE, hand, holder, camera, right } = placement;
7442
+ holder.scale.setScalar(1);
7443
+ hand.position.copy(camera.position);
7444
+ hand.quaternion.copy(camera.quaternion);
7445
+ hand.scale.setScalar(1);
7446
+ holder.position.set(0, 0, 0);
7447
+ hand.updateMatrixWorld(true);
7448
+ if (!right) {
7449
+ holder.position.set(VIEW_HOLD.rear[0], VIEW_HOLD.rear[1], VIEW_HOLD.rear[2] + placement.rear);
7450
+ hand.updateMatrixWorld(true);
7451
+ return;
7452
+ }
7453
+ scratch2.grip ?? (scratch2.grip = new THREE.Vector3());
7454
+ const grip = hand.worldToLocal(right.part.localToWorld(scratch2.grip.copy(right.offset)));
7455
+ const [x, y, z] = placement.kind === "handgun" ? VIEW_HOLD.handgunGrip : placement.kind === "launcher" ? VIEW_HOLD.launcherGrip : VIEW_HOLD.grip;
7456
+ holder.position.set(x - grip.x, y - grip.y, z - grip.z);
7457
+ hand.updateMatrixWorld(true);
7458
+ }
7459
+
7255
7460
  // src/fx/heavy.ts
7256
7461
  import { LIGHT_PRIORITY as LIGHT_PRIORITY2, geometryKey, materialKey } from "@adatechnology/game-render-three";
7257
7462
  import { discardObject3D } from "@adatechnology/game-shapes-three";
@@ -8212,10 +8417,13 @@ function createViewmodel(options) {
8212
8417
  const distance = () => options.soundDistance?.() ?? 0;
8213
8418
  const particles = createParticles(THREE, scene, groundAt, options.particleLimit);
8214
8419
  particles.onBounce = (kind, hit) => {
8215
- if (kind === "magazine") audio?.thud();
8216
- if (kind !== "casing" || !audio) return;
8217
- const caseType = hit?.object.userData["caseType"];
8420
+ if (!audio || kind !== "casing" && kind !== "magazine") return;
8218
8421
  const surface = hit ? options.surfaceAt?.(hit.object.position.x, hit.object.position.z) : void 0;
8422
+ if (kind === "magazine") {
8423
+ audio.thud({ ...surface ? { surface } : {}, ...hit ? { speed: hit.speed, bounce: hit.bounce } : {} });
8424
+ return;
8425
+ }
8426
+ const caseType = hit?.object.userData["caseType"];
8219
8427
  audio.casing({ ...caseType ? { caseType } : {}, ...surface ? { surface } : {}, ...hit ? { speed: hit.speed, bounce: hit.bounce } : {} });
8220
8428
  };
8221
8429
  if (!options.effects) warnOnce2("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");
@@ -8244,6 +8452,8 @@ function createViewmodel(options) {
8244
8452
  let triggerRest = 0;
8245
8453
  let feedCued = false;
8246
8454
  const rootQuaternion = new THREE.Quaternion();
8455
+ const muzzleAt = new THREE.Vector3();
8456
+ const muzzleAlong = new THREE.Vector3();
8247
8457
  const world = {
8248
8458
  kit,
8249
8459
  scene,
@@ -8343,6 +8553,7 @@ function createViewmodel(options) {
8343
8553
  if (model.parts.stockMove && onTool?.stock === "collapsed") rig.setPartPose("stock", collapsedStockPose(model.parts.stockMove));
8344
8554
  applyCondition(kit, model, condition);
8345
8555
  lastAmmo = options.tool?.().ammo ?? 0;
8556
+ void audio?.loadSamples();
8346
8557
  hand.updateMatrixWorld(true);
8347
8558
  hold = holdOfModel(THREE, model, 1);
8348
8559
  hold = { ...hold, works: handWorkOf(THREE, model, weapon, hold.right) };
@@ -8395,12 +8606,22 @@ function createViewmodel(options) {
8395
8606
  if (!session) return;
8396
8607
  const family = session.model.parts.family;
8397
8608
  session.holderScale = 1;
8609
+ if (options.firstPerson?.()) {
8610
+ placeInView({ THREE, hand, holder, camera, right: hold?.right ?? null, rear: modelRear, kind: family === "pistol" || family === "revolver" ? "handgun" : family === "rocket" ? "launcher" : "long" });
8611
+ lift = 0;
8612
+ pullFromWall(dt);
8613
+ return;
8614
+ }
8615
+ hand.scale.set(1, 1, 1);
8398
8616
  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 });
8399
8617
  hand.updateMatrixWorld(true);
8400
8618
  lift += (liftToKeepInView() - lift) * Math.min(1, dt * HAND_IN_VIEW.rate);
8401
8619
  if (lift > 1e-4) hand.position.addScaledVector(up.set(0, 1, 0).applyQuaternion(camera.quaternion), lift);
8402
8620
  hand.updateMatrixWorld(true);
8403
- if (!options.contact) return;
8621
+ pullFromWall(dt);
8622
+ };
8623
+ const pullFromWall = (dt) => {
8624
+ if (!options.contact || !session) return;
8404
8625
  ahead.set(0, 0, -1).applyQuaternion(camera.quaternion);
8405
8626
  if (reach === 0) reach = Math.max(0.01, measureReach(session));
8406
8627
  const free = options.contact(camera.position, ahead, WALL_PULL.probe);
@@ -8431,7 +8652,7 @@ function createViewmodel(options) {
8431
8652
  }
8432
8653
  const action = current.def.action;
8433
8654
  if (action.kind === "launcher") {
8434
- launchRocket(heavy(current), options.flyRocket !== false);
8655
+ if (current.model.parts.family === "rocket") launchRocket(heavy(current), options.flyRocket !== false);
8435
8656
  audio?.play("explosion", LAUNCH_VOLUME, distance());
8436
8657
  }
8437
8658
  if (action.kind !== "firearm") return;
@@ -8508,6 +8729,15 @@ function createViewmodel(options) {
8508
8729
  condition = next;
8509
8730
  if (session) applyCondition(kit, session.model, condition);
8510
8731
  },
8732
+ muzzle() {
8733
+ const mark = session?.model.parts.muzzle;
8734
+ if (!session || !mark) return null;
8735
+ hand.updateMatrixWorld(true);
8736
+ const at = worldPositionOf(session, mark, muzzleAt);
8737
+ const along = muzzleAlong.set(1, 0, 0).applyQuaternion(session.model.root.getWorldQuaternion(rootQuaternion)).normalize();
8738
+ const kind = session.def.action.kind;
8739
+ return { position: [at.x, at.y, at.z], direction: [along.x, along.y, along.z], drawsFlash: kind === "firearm" || kind === "launcher" };
8740
+ },
8511
8741
  handTargets() {
8512
8742
  if (!session || !hold) return null;
8513
8743
  hand.updateMatrixWorld(true);
@@ -8562,6 +8792,7 @@ function createWeaponFx(options) {
8562
8792
  ...options.raycast ? { raycast: options.raycast } : {},
8563
8793
  ...options.contact ? { contact: options.contact } : {},
8564
8794
  ...options.bodyHold ? { bodyHold: options.bodyHold } : {},
8795
+ ...options.firstPerson ? { firstPerson: options.firstPerson } : {},
8565
8796
  ...options.effects ? { effects: options.effects } : {},
8566
8797
  ...options.flyRocket === void 0 ? {} : { flyRocket: options.flyRocket },
8567
8798
  ...options.particleLimit ? { particleLimit: options.particleLimit } : {},
@@ -8584,6 +8815,7 @@ function createWeaponFx(options) {
8584
8815
  },
8585
8816
  setCondition: (condition) => model.setCondition(condition),
8586
8817
  handTargets: () => model.handTargets() ?? box2.handTargets(),
8818
+ muzzle: () => model.inHand !== null ? model.muzzle() : box2.muzzle(),
8587
8819
  layers,
8588
8820
  get inHand() {
8589
8821
  return model.inHand ?? box2.inHand;
@@ -8605,17 +8837,17 @@ function createWeaponFx(options) {
8605
8837
  }
8606
8838
 
8607
8839
  // src/modeled-prop.ts
8608
- import { markKitOrigin as markKitOrigin4 } from "@adatechnology/game-contracts";
8840
+ import { markKitOrigin as markKitOrigin5 } from "@adatechnology/game-contracts";
8609
8841
  import { mergeByMaterial } from "@adatechnology/game-shapes-three";
8610
8842
  var MODEL_BODY = "body";
8611
8843
  function buildModeledProp(THREE, def, name, decorate, items, props = defaultWeaponProps()) {
8612
8844
  const model = buildWeaponModelById(THREE, def.id) ?? (() => {
8613
- const root = props.buildItemModel(THREE, def, items);
8845
+ const root = props.buildItemModel(THREE, def, items) ?? buildThrowableModel(THREE, def);
8614
8846
  return root ? { root } : void 0;
8615
8847
  })();
8616
8848
  if (!model) return void 0;
8617
8849
  decorate?.(model.root);
8618
- const group = markKitOrigin4(new THREE.Group(), { owner: "game-weapons-three", kind: "weapon", id: def.id });
8850
+ const group = markKitOrigin5(new THREE.Group(), { owner: "game-weapons-three", kind: "weapon", id: def.id });
8619
8851
  group.name = name;
8620
8852
  const box2 = new THREE.Box3().setFromObject(model.root);
8621
8853
  const center = box2.getCenter(new THREE.Vector3());
@@ -9653,6 +9885,7 @@ var TEXT = {
9653
9885
  reloadClip: "Reload of {weapon}: {kind}, clip {clip} ({seconds} s)",
9654
9886
  soundCue: "Sound: {cue} ({weapon})",
9655
9887
  cueRecorded: " \xB7 recorded",
9888
+ cueFitted: " \xB7 fitted copy of the recording",
9656
9889
  cueCarried: " \xB7 inside the next recording",
9657
9890
  gunshotSound: "Shot sound: {profile}, take {take}/4, pitch {pitch}, {space}, {distance} m{far}",
9658
9891
  gunshotRecorded: " \xB7 recorded take {takes}",
@@ -9688,6 +9921,7 @@ var TEXT = {
9688
9921
  reloadClip: "Recarga de {weapon}: {kind}, clipe {clip} ({seconds} s)",
9689
9922
  soundCue: "Som: {cue} ({weapon})",
9690
9923
  cueRecorded: " \xB7 grava\xE7\xE3o",
9924
+ cueFitted: " \xB7 c\xF3pia medida da grava\xE7\xE3o",
9691
9925
  cueCarried: " \xB7 dentro da grava\xE7\xE3o seguinte",
9692
9926
  gunshotSound: "Som do tiro: {profile}, tomada {take}/4, tom {pitch}, {space}, {distance} m{far}",
9693
9927
  gunshotRecorded: " \xB7 grava\xE7\xE3o, tomada {takes}",
@@ -9753,7 +9987,7 @@ var targetProp = (id, name) => {
9753
9987
  };
9754
9988
  var weaponItem = (def, name, items, props = defaultWeaponProps()) => {
9755
9989
  const id = `${WEAPON_ITEM_PREFIX}${def.id}`;
9756
- if (!hasWeaponModel(def.id) && !props.itemModelOf(def.id, items)) return asItem(id, name, weaponProp(def, name), { weapon: def.id, movable: true });
9990
+ if (!hasWeaponModel(def.id) && !props.itemModelOf(def.id, items) && !hasThrowableModel(def)) return asItem(id, name, weaponProp(def, name), { weapon: def.id, movable: true });
9757
9991
  return {
9758
9992
  id,
9759
9993
  name,
@@ -9773,7 +10007,7 @@ var mountedM134Item = (def, name, items, props) => ({
9773
10007
  });
9774
10008
  var targetItem = (id, name) => {
9775
10009
  const prop = targetProp(id, name);
9776
- return { id, name, build: (THREE) => markKitOrigin5(buildProp(THREE, prop), { owner: "game-weapons-three", kind: "target", id }), spec: prop.spec, movable: false };
10010
+ return { id, name, build: (THREE) => markKitOrigin6(buildProp(THREE, prop), { owner: "game-weapons-three", kind: "target", id }), spec: prop.spec, movable: false };
9777
10011
  };
9778
10012
  var asItem = (id, name, prop, extra = {}) => ({
9779
10013
  id,
@@ -9813,7 +10047,8 @@ function createWeaponsTestModule(options = {}) {
9813
10047
  const seedOption = options.seed === void 0 ? {} : { seed: options.seed };
9814
10048
  let audio;
9815
10049
  const ownAudio = () => audio === void 0 ? audio = options.sound === false ? null : createWeaponAudio(void 0, { ...seedOption, ...options.samples ? { samples: options.samples } : {} }) : audio;
9816
- const weaponDefs = [...arsenal.weapons.values()].filter((def) => !BODY_ACTIONS.has(def.id));
10050
+ const generics = genericWeapons(arsenal);
10051
+ const weaponDefs = [...arsenal.weapons.values()].filter((def) => !BODY_ACTIONS.has(def.id) && !generics.has(def.id));
9817
10052
  const thumbnailBase = options.thumbnailBase ?? DEFAULT_THUMBNAIL_BASE;
9818
10053
  const withThumbnail = (item, key) => {
9819
10054
  const thumbnail = thumbnailBase === false ? void 0 : weaponThumbnailUrl(key, thumbnailBase);
@@ -9901,20 +10136,20 @@ function createWeaponsTestModule(options = {}) {
9901
10136
  engine: lentEngine,
9902
10137
  ...options.samples ? { samples: options.samples } : {},
9903
10138
  onGunshot: (shot, input, played) => {
9904
- const recorded = played.kind === "sample" && played.layers.length > 0 ? fill(text.gunshotRecorded, { takes: played.layers.map((layer) => `${layer.role} ${layer.take + 1}`).join(", ") }) : "";
10139
+ const recorded = played.kind === "sample" && played.layers.length > 0 ? fill(text.gunshotRecorded, { takes: played.layers.map((layer) => `${layer.role} ${layer.take + 1}`).join(", ") }) : played.kind === "fitted" ? text.cueFitted : "";
9905
10140
  lent.log(
9906
10141
  fill(text.gunshotSound, { profile: shot.profile, take: String(shot.take + 1), pitch: `${shot.pitch >= 1 ? "+" : ""}${((shot.pitch - 1) * 100).toFixed(1)} %`, space: text.spaces[input.space ?? "outdoor"], distance: String(Math.round(input.distance ?? 0)), far: shot.closeMix < 1 ? ` (${Math.round((1 - shot.closeMix) * 100)} % far)` : "" }) + recorded,
9907
10142
  { profile: shot.profile, take: shot.take, pitch: shot.pitch, space: input.space ?? "outdoor", distance: input.distance ?? 0, closeMix: shot.closeMix, delay: shot.delay, samples: shot.samples, played },
9908
10143
  "sound"
9909
10144
  );
9910
10145
  },
9911
- onCasing: (sound3, played) => lent.log(fill(text.casingSound, { case: sound3.kind, surface: text.surfaces[sound3.surface] }) + (played === "sample" ? text.cueRecorded : ""), { case: sound3.kind, surface: sound3.surface, sample: sound3.sample, played }, "sound")
10146
+ onCasing: (sound3, played) => lent.log(fill(text.casingSound, { case: sound3.kind, surface: text.surfaces[sound3.surface] }) + (played === "sample" ? text.cueRecorded : played === "fitted" ? text.cueFitted : ""), { case: sound3.kind, surface: sound3.surface, sample: sound3.sample, played }, "sound")
9912
10147
  });
9913
10148
  }
9914
10149
  let simulatedMs = 0;
9915
10150
  const offClock = lent.onFrame((dt) => simulatedMs += dt * 1e3);
9916
10151
  const created = createWeaponFx({
9917
- onSound: ({ weapon, cue: cue2, profile, played }) => lent.log(fill(text.soundCue, { weapon: arsenal.name(weapon), cue: text.soundCues[cue2] ?? cue2 }) + (played === "sample" ? text.cueRecorded : played === "carried" ? text.cueCarried : ""), { weapon, cue: cue2, weight: profile.weight, material: profile.material, ...played ? { played } : {} }, "sound"),
10152
+ onSound: ({ weapon, cue: cue2, profile, played }) => lent.log(fill(text.soundCue, { weapon: arsenal.name(weapon), cue: text.soundCues[cue2] ?? cue2 }) + (played === "sample" ? text.cueRecorded : played === "fitted" ? text.cueFitted : played === "carried" ? text.cueCarried : ""), { weapon, cue: cue2, weight: profile.weight, material: profile.material, ...played ? { played } : {} }, "sound"),
9918
10153
  THREE: lent.THREE,
9919
10154
  scene: lent.scene,
9920
10155
  camera: lent.camera,
@@ -9930,6 +10165,7 @@ function createWeaponsTestModule(options = {}) {
9930
10165
  // the weapon against a wall asks as often as the laser, on the clock of the simulation (so a fast-forwarded room measures as often per frame)
9931
10166
  contact: createLaserRaycast(lent.THREE, lent.scene, lent.camera, { now: () => simulatedMs }),
9932
10167
  ...lent.holdFrame ? { bodyHold: () => lent.holdFrame?.() ?? null } : {},
10168
+ firstPerson: () => lent.player().cameraMode === "first",
9933
10169
  ...lent.effects ? { effects: lent.effects } : {},
9934
10170
  ...lent.particleLimit ? { particleLimit: lent.particleLimit } : {},
9935
10171
  ...isLightBudget(lent.lights) ? { lights: lent.lights } : {},
@@ -9963,6 +10199,7 @@ function createWeaponsTestModule(options = {}) {
9963
10199
  const offEvents = lent.on((event) => {
9964
10200
  worldEffects.handle(event, lent.effects);
9965
10201
  if (event.type === "explosion") {
10202
+ if (lentEngine) return;
9966
10203
  const away = Math.hypot(event.point[0] - camera.position.x, event.point[1] - camera.position.y, event.point[2] - camera.position.z);
9967
10204
  ownAudio()?.play("explosion", Math.min(1, EXPLOSION_VOLUME_BASE + event.radius * EXPLOSION_VOLUME_PER_METRE), away);
9968
10205
  return;
@@ -9980,6 +10217,7 @@ function createWeaponsTestModule(options = {}) {
9980
10217
  });
9981
10218
  const offFrame = lent.onFrame((dt) => created.update(dt));
9982
10219
  const offHands = lent.setHandTargets?.(() => created.handTargets());
10220
+ const offMuzzle = lent.setMuzzleSource?.(() => created.muzzle());
9983
10221
  created.handle({ type: "tool", weapon: lent.tool().weapon }, lent.tool());
9984
10222
  return () => {
9985
10223
  accessories.dispose();
@@ -9988,6 +10226,7 @@ function createWeaponsTestModule(options = {}) {
9988
10226
  offFrame();
9989
10227
  offClock();
9990
10228
  offHands?.();
10229
+ offMuzzle?.();
9991
10230
  created.dispose();
9992
10231
  audio?.dispose();
9993
10232
  fx = null;
@@ -10003,7 +10242,7 @@ import { createArsenal as createArsenal2 } from "@adatechnology/game-weapons";
10003
10242
 
10004
10243
  // src/test-area/explosives-bench.ts
10005
10244
  import { discardObject3D as discardObject3D4 } from "@adatechnology/game-shapes-three";
10006
- import { markKitOrigin as markKitOrigin6 } from "@adatechnology/game-contracts";
10245
+ import { markKitOrigin as markKitOrigin7 } from "@adatechnology/game-contracts";
10007
10246
 
10008
10247
  // src/test-area/explosives-text.ts
10009
10248
  var EXPLOSIVES_TEXT = {
@@ -10266,7 +10505,7 @@ function createExplosivesBench(options) {
10266
10505
  return new (T()).LineLoop(geometry, new (T()).LineBasicMaterial({ color: colour }));
10267
10506
  };
10268
10507
  const drawRings = (vessel) => {
10269
- const group = markKitOrigin6(new (T()).Group(), { owner: "game-weapons-three", kind: "aid" });
10508
+ const group = markKitOrigin7(new (T()).Group(), { owner: "game-weapons-three", kind: "aid" });
10270
10509
  group.name = "blast_rings";
10271
10510
  const lethal = lethalOf(vessel);
10272
10511
  const injury = injuryOf(vessel);
@@ -10726,6 +10965,7 @@ var SHELTERED = 0.5;
10726
10965
  var SHELTERED_MOST = 1;
10727
10966
  var CHEST = 1.1;
10728
10967
  var MIDDLE = 0.3;
10968
+ var AIM_SEARCH = 1.5;
10729
10969
  var HEAVY_ITEMS = ["camp_table"];
10730
10970
  var PERSON_MASS = 70;
10731
10971
  var GROUND_FRICTION = 7;
@@ -10785,6 +11025,27 @@ function pushScenario(all, bench, mode) {
10785
11025
  }
10786
11026
  const partsBefore = new Map(placed.map((body) => [body.objectId, partsOf(body.objectId)]));
10787
11027
  expect(placed.length > 0, fillText2(text.push_found, { n: placed.length, kinds: [...new Set(placed.map((body) => body.kind))].join(", ") }), { placed: placed.map((body) => body.itemId) });
11028
+ const aimOf = new Map(
11029
+ placed.map((body) => {
11030
+ const from = origin.get(body.objectId) ?? body.from;
11031
+ if (body.kind === "zombie" || body.kind === "survivor") return [body.objectId, [from[0], from[1] + CHEST, from[2]]];
11032
+ const boxes = room.renderer.solidsNear?.(from[0], from[2], AIM_SEARCH).filter((box2) => box2.objectId === body.objectId) ?? [];
11033
+ if (boxes.length === 0) return [body.objectId, [from[0], from[1] + MIDDLE, from[2]]];
11034
+ const low = [0, 1, 2].map((axis) => Math.min(...boxes.map((box2) => box2.min[axis])));
11035
+ const high = [0, 1, 2].map((axis) => Math.max(...boxes.map((box2) => box2.max[axis])));
11036
+ return [body.objectId, [0, 1, 2].map((axis) => (low[axis] + high[axis]) / 2)];
11037
+ })
11038
+ );
11039
+ const coverAt = (point) => new Map(
11040
+ placed.map((body) => {
11041
+ const ignore = source ? [source, body.objectId] : [body.objectId];
11042
+ return [body.objectId, room.world.blastReach?.(point, aimOf.get(body.objectId) ?? body.from, { ignore }) ?? 1];
11043
+ })
11044
+ );
11045
+ let coverOf;
11046
+ const stopListening = room.world.on("*", (event) => {
11047
+ if ((event.type === "explode" || event.type === "blast") && !coverOf) coverOf = coverAt(event.point);
11048
+ });
10788
11049
  if (mode === "p13") bench.actions.shoot();
10789
11050
  else {
10790
11051
  room.setTool("frag_grenade");
@@ -10797,17 +11058,11 @@ function pushScenario(all, bench, mode) {
10797
11058
  step(0.05);
10798
11059
  waited += 0.05;
10799
11060
  }
11061
+ stopListening();
10800
11062
  const blast = bench.lastBlast();
10801
11063
  expect(!!blast, text.push_told, { waited });
10802
11064
  if (!blast) return;
10803
- const coverOf = new Map(
10804
- placed.map((body) => {
10805
- const from = origin.get(body.objectId) ?? body.from;
10806
- const person = body.kind === "zombie" || body.kind === "survivor";
10807
- const ignore = source ? [source, body.objectId] : [body.objectId];
10808
- return [body.objectId, room.world.blastReach?.(blast.point, [from[0], from[1] + (person ? CHEST : MIDDLE), from[2]], { ignore }) ?? 1];
10809
- })
10810
- );
11065
+ const cover = coverOf ?? coverAt(blast.point);
10811
11066
  step(PUSH_READ_AT);
10812
11067
  const rows = placed.map((body) => {
10813
11068
  const from = origin.get(body.objectId) ?? body.from;
@@ -10815,16 +11070,18 @@ function pushScenario(all, bench, mode) {
10815
11070
  const distance = flat(from, blast.point);
10816
11071
  const away = Math.max(1e-6, distance);
10817
11072
  const outward = ((now[0] - from[0]) * (from[0] - blast.point[0]) + (now[2] - from[2]) * (from[2] - blast.point[2])) / away;
10818
- return { ...body, distance, outward, moved: flat(now, from), cover: coverOf.get(body.objectId) ?? 1 };
11073
+ return { ...body, distance, outward, moved: flat(now, from), cover: cover.get(body.objectId) ?? 1 };
10819
11074
  });
10820
11075
  const torn = (row) => (PUSH_KINDS.some((entry) => entry.kind === row.kind) || HEAVY_ITEMS.includes(row.kind)) && partsOf(row.objectId) < (partsBefore.get(row.objectId) ?? 0);
10821
11076
  const inReach = rows.filter((row) => row.distance < blast.radius * 0.85 && !torn(row));
11077
+ const isPerson = (row) => row.kind === "survivor" || row.kind === "zombie";
11078
+ const reached = inReach.filter((row) => row.cover >= SHELTERED && !isPerson(row));
10822
11079
  const sheltered = inReach.filter((row) => row.cover < SHELTERED);
10823
11080
  for (const row of sheltered) {
10824
- expect(row.outward < SHELTERED_MOST, fillText2(text.push_sheltered, { what: things[row.kind] ?? row.kind, distance: row.distance.toFixed(1), cover: (row.cover * 100).toFixed(0), moved: Math.max(0, row.outward).toFixed(2) }), { row: { kind: row.kind, distance: row.distance, outward: row.outward, cover: row.cover } });
11081
+ const open = reached.filter((other) => other.kind === row.kind && other.distance <= row.distance).map((other) => other.outward);
11082
+ const most = Math.max(SHELTERED_MOST, row.cover * Math.max(0, ...open));
11083
+ expect(row.outward < most, fillText2(text.push_sheltered, { what: things[row.kind] ?? row.kind, distance: row.distance.toFixed(1), cover: (row.cover * 100).toFixed(0), moved: Math.max(0, row.outward).toFixed(2) }), { row: { kind: row.kind, distance: row.distance, outward: row.outward, cover: row.cover, most } });
10825
11084
  }
10826
- const isPerson = (row) => row.kind === "survivor" || row.kind === "zombie";
10827
- const reached = inReach.filter((row) => row.cover >= SHELTERED && !isPerson(row));
10828
11085
  const movedAway = reached.filter((row) => row.outward >= MOVED);
10829
11086
  const least = reached.length === 0 ? 0 : Math.min(...reached.map((row) => row.outward));
10830
11087
  expect(reached.length > 0 && movedAway.length === reached.length, fillText2(text.push_outward, { moved: movedAway.length, n: reached.length, least: least.toFixed(2) }), { rows: reached.map((row) => ({ kind: row.kind, distance: row.distance, outward: row.outward })), torn: rows.filter(torn).map((row) => row.objectId) });
@@ -11014,14 +11271,19 @@ export {
11014
11271
  BODY_ACTIONS,
11015
11272
  weaponProp,
11016
11273
  buildProp,
11274
+ modelKit,
11275
+ EXPLOSIVE_MODEL_IDS,
11276
+ hasExplosiveModel,
11277
+ buildExplosiveModel,
11017
11278
  SHIPPED_WEAPON_ITEM_MODELS,
11279
+ hasThrowableModel,
11280
+ buildThrowableModel,
11018
11281
  weaponPropsOver,
11019
11282
  defaultWeaponProps,
11020
11283
  registerWeaponItemModel,
11021
11284
  itemModelOfWeapon,
11022
11285
  buildItemWeaponModel,
11023
11286
  buildWeaponModel,
11024
- modelKit,
11025
11287
  deriveFrames,
11026
11288
  mountFrame,
11027
11289
  buildFlyingRocket,
@@ -11067,4 +11329,4 @@ export {
11067
11329
  DEFAULT_EXPLOSIVES_SCENE,
11068
11330
  createExplosivesTestModule
11069
11331
  };
11070
- //# sourceMappingURL=chunk-XMVSA7UT.js.map
11332
+ //# sourceMappingURL=chunk-KWJQO6JS.js.map