@adatechnology/game-weapons-three 0.14.1 → 0.15.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;
@@ -141,6 +144,7 @@ function createAccessoryBench(options) {
141
144
  camera.updateProjectionMatrix();
142
145
  };
143
146
  const onKeyDown = (event) => {
147
+ if (host.playerHeld?.()) return;
144
148
  if (event.repeat || event.ctrlKey || event.metaKey || event.altKey || TEXT_FIELDS.has(event.target?.tagName ?? "")) return;
145
149
  const handled2 = event.code === ACCESSORY_KEYS.light ? act(ACTION_IDS.light) : event.code === ACCESSORY_KEYS.laser ? act(ACTION_IDS.laser) : event.code === ACCESSORY_KEYS.underbarrel ? act(ACTION_IDS.underbarrel) : event.code === ACCESSORY_KEYS.stock && toggleStock();
146
150
  if (!handled2) return;
@@ -149,7 +153,7 @@ function createAccessoryBench(options) {
149
153
  };
150
154
  const isOnRoom = (event) => Boolean(document.pointerLockElement) || event.target instanceof HTMLCanvasElement;
151
155
  const onMouseDown = (event) => {
152
- if (event.button !== SECONDARY_BUTTON || !isOnRoom(event) || !isFirearm(host.tool())) return;
156
+ if (event.button !== SECONDARY_BUTTON || !isOnRoom(event) || !aims(host.tool())) return;
153
157
  event.preventDefault();
154
158
  event.stopImmediatePropagation();
155
159
  setAim(true);
@@ -2832,6 +2836,279 @@ function drawItemWeapon({ THREE, def, items, itemId, turn: turn2, flip }) {
2832
2836
  return group;
2833
2837
  }
2834
2838
 
2839
+ // src/models/explosives.ts
2840
+ import { markKitOrigin as markKitOrigin2 } from "@adatechnology/game-contracts";
2841
+
2842
+ // src/models/kit.ts
2843
+ import { markShared, shapeGeometries as shapeGeometries2 } from "@adatechnology/game-shapes-three";
2844
+
2845
+ // src/models/rounded-box.ts
2846
+ var uvAlong = (THREE, faceDirection, normal, uvAxis, projectionAxis, radius, sideLength) => {
2847
+ const arcLength = 2 * Math.PI * radius / 4;
2848
+ const centerLength = Math.max(sideLength - 2 * radius, 0);
2849
+ const halfArc = Math.PI / 4;
2850
+ const projected = new THREE.Vector3().copy(normal);
2851
+ projected[projectionAxis] = 0;
2852
+ projected.normalize();
2853
+ const arcUvRatio = 0.5 * arcLength / (arcLength + centerLength);
2854
+ const arcAngleRatio = 1 - projected.angleTo(faceDirection) / halfArc;
2855
+ if (Math.sign(projected[uvAxis]) === 1) return arcAngleRatio * arcUvRatio;
2856
+ const centerUv = centerLength / (arcLength + centerLength);
2857
+ return centerUv + arcUvRatio + arcUvRatio * (1 - arcAngleRatio);
2858
+ };
2859
+ var FACES = [
2860
+ { direction: [1, 0, 0], u: ["z", "y", false], v: ["y", "z", true] },
2861
+ { direction: [-1, 0, 0], u: ["z", "y", true], v: ["y", "z", true] },
2862
+ { direction: [0, 1, 0], u: ["x", "z", true], v: ["z", "x", false] },
2863
+ { direction: [0, -1, 0], u: ["x", "z", true], v: ["z", "x", true] },
2864
+ { direction: [0, 0, 1], u: ["x", "y", true], v: ["y", "x", true] },
2865
+ { direction: [0, 0, -1], u: ["x", "y", false], v: ["y", "x", true] }
2866
+ ];
2867
+ function createRoundedBoxGeometry(THREE, width, height, depth, segments, radius) {
2868
+ const count = segments * 2 + 1;
2869
+ const rounding = Math.min(width / 2, height / 2, depth / 2, radius);
2870
+ const geometry = new THREE.BoxGeometry(1, 1, 1, count, count, count);
2871
+ const flat2 = geometry.toNonIndexed();
2872
+ geometry.setIndex(null);
2873
+ geometry.setAttribute("position", flat2.attributes.position);
2874
+ geometry.setAttribute("normal", flat2.attributes.normal);
2875
+ geometry.setAttribute("uv", flat2.attributes.uv);
2876
+ const position = new THREE.Vector3();
2877
+ const normal = new THREE.Vector3();
2878
+ const box2 = new THREE.Vector3(width, height, depth).divideScalar(2).subScalar(rounding);
2879
+ const positions = geometry.attributes.position.array;
2880
+ const normals = geometry.attributes.normal.array;
2881
+ const uvs = geometry.attributes.uv.array;
2882
+ const faceTriangles = positions.length / 6;
2883
+ const halfSegment = 0.5 / count;
2884
+ const sides = { x: width, y: height, z: depth };
2885
+ for (let i = 0, j = 0; i < positions.length; i += 3, j += 2) {
2886
+ position.fromArray(positions, i);
2887
+ normal.copy(position);
2888
+ normal.x -= Math.sign(normal.x) * halfSegment;
2889
+ normal.y -= Math.sign(normal.y) * halfSegment;
2890
+ normal.z -= Math.sign(normal.z) * halfSegment;
2891
+ normal.normalize();
2892
+ positions[i] = box2.x * Math.sign(position.x) + normal.x * rounding;
2893
+ positions[i + 1] = box2.y * Math.sign(position.y) + normal.y * rounding;
2894
+ positions[i + 2] = box2.z * Math.sign(position.z) + normal.z * rounding;
2895
+ normals[i] = normal.x;
2896
+ normals[i + 1] = normal.y;
2897
+ normals[i + 2] = normal.z;
2898
+ const face = FACES[Math.floor(i / faceTriangles)];
2899
+ if (!face) continue;
2900
+ const direction = new THREE.Vector3(...face.direction);
2901
+ for (const [offset, [uvAxis, projectionAxis, mirrored]] of [[0, face.u], [1, face.v]]) {
2902
+ const value = uvAlong(THREE, direction, normal, uvAxis, projectionAxis, rounding, sides[uvAxis]);
2903
+ uvs[j + offset] = mirrored ? 1 - value : value;
2904
+ }
2905
+ }
2906
+ return geometry;
2907
+ }
2908
+
2909
+ // src/models/kit.ts
2910
+ var PI = Math.PI;
2911
+ var createMaterials = (THREE) => {
2912
+ const standard = (color, roughness = 0.7, metalness = 0.2, extra = {}) => new THREE.MeshStandardMaterial({ color, roughness, metalness, ...extra });
2913
+ const basic = (color, extra = {}) => new THREE.MeshBasicMaterial({ color, ...extra });
2914
+ const additive = { transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending, depthWrite: false };
2915
+ const materials = {
2916
+ metal: standard(3422010, 0.42, 0.75),
2917
+ steel: standard(7106418, 0.32, 0.85),
2918
+ steelL: standard(9080208, 0.28, 0.9),
2919
+ dark: standard(1711133, 0.55, 0.5),
2920
+ poly: standard(2369058, 0.8, 0.05),
2921
+ olive: standard(4936241, 0.8, 0.05),
2922
+ tan: standard(10259043, 0.8, 0.05),
2923
+ wood: standard(4860951, 0.55, 0),
2924
+ woodL: standard(6175263, 0.55, 0),
2925
+ brass: standard(12819013, 0.28, 0.9),
2926
+ copper: standard(11561531, 0.3, 0.85),
2927
+ lead: standard(7171955, 0.5, 0.5),
2928
+ shell: standard(10692380, 0.5, 0.05),
2929
+ glass: standard(8364968, 0.05, 0.1, { transparent: true, opacity: 0.42 }),
2930
+ lens: standard(2311770, 0.05, 0.3, { transparent: true, opacity: 0.9, emissive: 399392 }),
2931
+ rubber: standard(1381653, 0.85, 0),
2932
+ white: standard(13617332, 0.8, 0),
2933
+ hole: standard(526343, 1, 0),
2934
+ bake: standard(5911072, 0.55, 0),
2935
+ rust: standard(7027230, 0.95, 0.3),
2936
+ rustD: standard(4860440, 0.95, 0.25),
2937
+ worn: standard(5593178, 0.6, 0.6),
2938
+ fire: basic(16742946),
2939
+ fire2: basic(16764762),
2940
+ flashA: basic(16757575, additive),
2941
+ flashB: basic(16773568, additive),
2942
+ smokeP: standard(9079430, 1, 0, { transparent: true, opacity: 0.35, depthWrite: false }),
2943
+ /** Launcher paints. */
2944
+ yellow: standard(11570978, 0.6, 0),
2945
+ red: standard(9313300, 0.45, 0.1),
2946
+ redPaint: standard(10691354, 0.35, 0.2),
2947
+ tape: standard(9079944, 0.9, 0),
2948
+ pipe: standard(10115634, 0.35, 0.8),
2949
+ green: standard(3818536, 0.75, 0.05),
2950
+ /** Explosive finishes: the molotov's bottle, the dynamite's paper, the C4's putty and the detonator's wire. */
2951
+ bottle: standard(3103284, 0.1, 0.1, { transparent: true, opacity: 0.72 }),
2952
+ dynamite: standard(10365471, 0.7, 0),
2953
+ c4: standard(7170636, 0.95, 0),
2954
+ wire: standard(11542560, 0.5, 0),
2955
+ /** Magazine finishes named by the options. */
2956
+ bakelite: standard(6958620, 0.6, 0),
2957
+ polyMag: standard(2763302, 0.85, 0)
2958
+ };
2959
+ markShared(...Object.values(materials));
2960
+ return materials;
2961
+ };
2962
+ var kits = /* @__PURE__ */ new WeakMap();
2963
+ function modelKit(host) {
2964
+ const cached = kits.get(host);
2965
+ if (cached) return cached;
2966
+ const THREE = shapeGeometries2(host);
2967
+ const geometries = /* @__PURE__ */ new Map();
2968
+ const shadowed = (mesh) => {
2969
+ mesh.castShadow = true;
2970
+ mesh.receiveShadow = true;
2971
+ return mesh;
2972
+ };
2973
+ const place = (parent, mesh, x, y, z) => {
2974
+ mesh.position.set(x, y, z);
2975
+ parent.add(mesh);
2976
+ return mesh;
2977
+ };
2978
+ const kit = {
2979
+ THREE,
2980
+ M: createMaterials(THREE),
2981
+ roundness: null,
2982
+ group: () => new THREE.Group(),
2983
+ box(parent, w, h, d, material, x, y, z, rz = 0, ry = 0, rx = 0) {
2984
+ const radius = Math.min(w, h, d) * (kit.roundness ?? 0.2);
2985
+ const key = `${w.toFixed(4)},${h.toFixed(4)},${d.toFixed(4)},${radius.toFixed(5)}`;
2986
+ let geometry = geometries.get(key);
2987
+ if (!geometry) {
2988
+ geometry = radius > 25e-5 ? createRoundedBoxGeometry(THREE, w, h, d, 2, radius) : new THREE.BoxGeometry(w, h, d);
2989
+ markShared(geometry);
2990
+ geometries.set(key, geometry);
2991
+ }
2992
+ const mesh = place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z);
2993
+ mesh.rotation.set(rx, ry, rz);
2994
+ return mesh;
2995
+ },
2996
+ cylinderX(parent, radius, length, material, x, y, z, segments = 16, radiusEnd) {
2997
+ const mesh = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, length, segments), material));
2998
+ mesh.rotation.z = -PI / 2;
2999
+ return place(parent, mesh, x, y, z);
3000
+ },
3001
+ cylinderY(parent, radius, height, material, x, y, z, segments = 16, radiusEnd) {
3002
+ return place(parent, shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, height, segments), material)), x, y, z);
3003
+ },
3004
+ cylinderZ(parent, radius, length, material, x, y, z, segments = 16) {
3005
+ const mesh = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radius, radius, length, segments), material));
3006
+ mesh.rotation.x = PI / 2;
3007
+ return place(parent, mesh, x, y, z);
3008
+ },
3009
+ sphere(parent, radius, material, x, y, z, detail = 1) {
3010
+ return place(parent, shadowed(new THREE.Mesh(new THREE.IcosahedronGeometry(radius, detail), material)), x, y, z);
3011
+ },
3012
+ torus: (parent, geometry, material, x, y, z) => place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z),
3013
+ mark(parent, x, y, z) {
3014
+ const object = new THREE.Object3D();
3015
+ object.position.set(x, y, z);
3016
+ parent.add(object);
3017
+ return object;
3018
+ },
3019
+ pivot(parent, x = 0, y = 0, z = 0) {
3020
+ const group = new THREE.Group();
3021
+ group.position.set(x, y, z);
3022
+ parent.add(group);
3023
+ return group;
3024
+ }
3025
+ };
3026
+ kits.set(host, kit);
3027
+ return kit;
3028
+ }
3029
+ function heatify(glow, mesh) {
3030
+ mesh.material = mesh.material.clone();
3031
+ glow.push(mesh);
3032
+ return mesh;
3033
+ }
3034
+
3035
+ // src/models/explosives.ts
3036
+ var grenade = (kit, group) => {
3037
+ const { M, sphere, cylinderY, box: box2, torus, THREE } = kit;
3038
+ sphere(group, 0.032, M.olive, 0, 0.032, 0, 1);
3039
+ cylinderY(group, 0.011, 0.022, M.metal, 0, 0.072, 0);
3040
+ box2(group, 0.012, 0.07, 6e-3, M.metal, 0.016, 0.05, 0, 0.2);
3041
+ torus(group, new THREE.TorusGeometry(85e-4, 16e-4, 6, 12), M.steel, -0.012, 0.082, 0);
3042
+ kit.mark(group, 0, 0.032, 0).name = "handle";
3043
+ };
3044
+ var molotov = (kit, group) => {
3045
+ const { M, cylinderY, box: box2 } = kit;
3046
+ cylinderY(group, 0.036, 0.17, M.bottle, 0, 0.085, 0, 10);
3047
+ cylinderY(group, 0.012, 0.06, M.bottle, 0, 0.2, 0, 8, 0.036);
3048
+ box2(group, 0.03, 0.08, 0.02, M.white, 5e-3, 0.25, 0, 0.3);
3049
+ cylinderY(group, 0.02, 0.07, M.fire, 0.01, 0.31, 0, 6, 0).userData.flame = true;
3050
+ kit.mark(group, 0, 0.085, 0).name = "handle";
3051
+ };
3052
+ var pipeBomb = (kit, group) => {
3053
+ const { M, cylinderX, cylinderY } = kit;
3054
+ cylinderX(group, 0.025, 0.2, M.metal, 0, 0.03, 0, 8);
3055
+ cylinderX(group, 0.031, 0.025, M.dark, -0.1, 0.03, 0, 6);
3056
+ cylinderX(group, 0.031, 0.025, M.dark, 0.1, 0.03, 0, 6);
3057
+ cylinderY(group, 2e-3, 0.06, M.white, 0.1, 0.07, 0);
3058
+ kit.mark(group, 0, 0.03, 0).name = "handle";
3059
+ };
3060
+ var dynamite = (kit, group) => {
3061
+ const { M, cylinderX } = kit;
3062
+ 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);
3063
+ cylinderX(group, 0.031, 0.03, M.dark, -0.04, 0.025, 0, 8);
3064
+ cylinderX(group, 0.031, 0.03, M.dark, 0.05, 0.025, 0, 8);
3065
+ cylinderX(group, 2e-3, 0.1, M.white, 0.14, 0.03, 0, 4);
3066
+ kit.mark(group, 0, 0.025, 0).name = "handle";
3067
+ };
3068
+ var c4 = (kit, group) => {
3069
+ const { M, box: box2, cylinderY } = kit;
3070
+ box2(group, 0.28, 0.05, 0.11, M.c4, 0, 0.025, 0);
3071
+ box2(group, 0.05, 0.03, 0.04, M.dark, 0.06, 0.065, 0);
3072
+ cylinderY(group, 4e-3, 0.05, M.metal, 0.03, 0.07, 0);
3073
+ box2(group, 0.24, 3e-3, 3e-3, M.wire, 0.18, 0.055, 0.02);
3074
+ box2(group, 0.24, 3e-3, 3e-3, M.metal, 0.18, 0.055, 0.03);
3075
+ kit.mark(group, 0, 0.025, 0).name = "handle";
3076
+ };
3077
+ var claymore = (kit, group) => {
3078
+ const { M, box: box2 } = kit;
3079
+ 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);
3080
+ 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);
3081
+ kit.mark(group, 0, 0.12, 0).name = "handle";
3082
+ };
3083
+ var flashbang = (kit, group) => {
3084
+ const { M, cylinderY, box: box2 } = kit;
3085
+ cylinderY(group, 0.022, 0.13, M.olive, 0, 0.065, 0, 10);
3086
+ for (let i = 0; i < 4; i++) cylinderY(group, 0.023, 8e-3, M.dark, 0, 0.025 + i * 0.028, 0, 10);
3087
+ cylinderY(group, 0.012, 0.02, M.metal, 0, 0.14, 0);
3088
+ box2(group, 0.01, 0.08, 5e-3, M.metal, 0.024, 0.1, 0, 0.15);
3089
+ kit.mark(group, 0, 0.065, 0).name = "handle";
3090
+ };
3091
+ var BUILDERS = {
3092
+ m67: grenade,
3093
+ molotov,
3094
+ pipe_bomb: pipeBomb,
3095
+ dynamite,
3096
+ c4,
3097
+ claymore,
3098
+ flashbang
3099
+ };
3100
+ var EXPLOSIVE_MODEL_IDS = Object.keys(BUILDERS);
3101
+ var hasExplosiveModel = (explosiveId) => explosiveId in BUILDERS;
3102
+ function buildExplosiveModel(THREE, explosiveId) {
3103
+ const builder = BUILDERS[explosiveId];
3104
+ if (!builder) return void 0;
3105
+ const kit = modelKit(THREE);
3106
+ const group = markKitOrigin2(kit.group(), { owner: "game-weapons-three", kind: "explosive", id: explosiveId });
3107
+ group.name = explosiveId;
3108
+ builder(kit, group);
3109
+ return group;
3110
+ }
3111
+
2835
3112
  // src/weapon-item-model.ts
2836
3113
  var SHIPPED_WEAPON_ITEM_MODELS = [
2837
3114
  ["wrench", { item: "adjustable_wrench" }],
@@ -2840,8 +3117,12 @@ var SHIPPED_WEAPON_ITEM_MODELS = [
2840
3117
  ["crossbow", { turn: 1 }],
2841
3118
  ["knife", { flip: true }],
2842
3119
  ["meat_cleaver", { flip: true }],
2843
- ["screwdriver", { flip: true }]
3120
+ ["screwdriver", { flip: true }],
3121
+ // the Itens do Vale Cinzento hammer is a claw hammer: the head and the two-pronged claw
3122
+ ["claw_hammer", { item: "hammer" }]
2844
3123
  ];
3124
+ var hasThrowableModel = (def) => def.action?.kind === "throwable" && def.action.explosive !== void 0 && hasExplosiveModel(def.action.explosive);
3125
+ var buildThrowableModel = (THREE, def) => def.action?.kind === "throwable" && def.action.explosive !== void 0 ? buildExplosiveModel(THREE, def.action.explosive) : void 0;
2845
3126
  var weaponPropsOver = (models) => {
2846
3127
  const itemModelOf = (weaponId, items) => {
2847
3128
  const id = models.get(weaponId)?.item ?? weaponId;
@@ -2857,7 +3138,7 @@ var weaponPropsOver = (models) => {
2857
3138
  get: (weaponId) => models.get(weaponId),
2858
3139
  itemModelOf,
2859
3140
  buildItemModel,
2860
- buildModel: (THREE, def, items) => buildItemModel(THREE, def, items) ?? buildProp(THREE, weaponProp(def))
3141
+ buildModel: (THREE, def, items) => buildItemModel(THREE, def, items) ?? buildThrowableModel(THREE, def) ?? buildProp(THREE, weaponProp(def))
2861
3142
  };
2862
3143
  };
2863
3144
  var defaultProps;
@@ -2869,6 +3150,7 @@ var buildWeaponModel = (THREE, def, items) => defaultWeaponProps().buildModel(TH
2869
3150
 
2870
3151
  // src/box-fx.ts
2871
3152
  var EJECTS_ON_SHOT = /* @__PURE__ */ new Set(["pistol", "smg", "carbine", "rifle", "sniper", "shotgun"]);
3153
+ var SPOUTS = /* @__PURE__ */ new Set(["firearm", "flame", "heat", "extinguisher"]);
2872
3154
  var DROPS_MAGAZINE = /* @__PURE__ */ new Set(["pistol", "smg", "carbine", "rifle", "sniper"]);
2873
3155
  var CYCLES = /* @__PURE__ */ new Set(["shotgun_pump_12", "rifle_bolt_308"]);
2874
3156
  var FALLBACK_ENERGY = 300;
@@ -2903,6 +3185,7 @@ function createBoxFx(options) {
2903
3185
  const q = new THREE.Quaternion();
2904
3186
  const q2 = new THREE.Quaternion();
2905
3187
  const pointForward = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2);
3188
+ const frontOf = new THREE.Vector3();
2906
3189
  const equip = (weapon) => {
2907
3190
  if (model) hand.remove(model);
2908
3191
  model = null;
@@ -2913,6 +3196,7 @@ function createBoxFx(options) {
2913
3196
  const length = new THREE.Box3().setFromObject(model).getSize(v).x;
2914
3197
  rear = new THREE.Box3().setFromObject(model).min.x;
2915
3198
  lengthMetres = length;
3199
+ frontOf.copy(new THREE.Box3().setFromObject(model).getCenter(v)).setX(rear + length);
2916
3200
  model.quaternion.copy(pointForward);
2917
3201
  hand.add(model);
2918
3202
  inHand = def.id;
@@ -3013,6 +3297,13 @@ function createBoxFx(options) {
3013
3297
  inHand = null;
3014
3298
  hold = null;
3015
3299
  },
3300
+ muzzle() {
3301
+ if (!model || !def || !SPOUTS.has(def.action.kind)) return null;
3302
+ hand.updateMatrixWorld(true);
3303
+ const at = model.localToWorld(v.copy(frontOf));
3304
+ const along = v2.set(1, 0, 0).applyQuaternion(model.getWorldQuaternion(q2)).normalize();
3305
+ return { position: [at.x, at.y, at.z], direction: [along.x, along.y, along.z], drawsFlash: false };
3306
+ },
3016
3307
  handTargets() {
3017
3308
  if (!model || !hold) return null;
3018
3309
  hand.updateMatrixWorld(true);
@@ -3064,7 +3355,7 @@ function createBoxFx(options) {
3064
3355
  }
3065
3356
 
3066
3357
  // src/models/index.ts
3067
- import { markKitOrigin as markKitOrigin2 } from "@adatechnology/game-contracts";
3358
+ import { markKitOrigin as markKitOrigin3 } from "@adatechnology/game-contracts";
3068
3359
  import { WEAPONS } from "@adatechnology/game-weapons";
3069
3360
 
3070
3361
  // src/data/models.json
@@ -4617,302 +4908,74 @@ var models_default = {
4617
4908
  options: {
4618
4909
  rpm: 3e3
4619
4910
  }
4911
+ },
4912
+ m79: {
4913
+ builder: "breakLauncher",
4914
+ caliber: "40\xD746",
4915
+ length: 730,
4916
+ barrel: 356,
4917
+ options: {
4918
+ stock: "fixed",
4919
+ wood: 1,
4920
+ mag: "none",
4921
+ hg: "wood",
4922
+ hgFrac: 0.4,
4923
+ br: 0.022,
4924
+ hR: 0.05
4925
+ }
4620
4926
  }
4621
4927
  };
4622
4928
 
4623
- // src/models/kit.ts
4624
- import { markShared, shapeGeometries as shapeGeometries2 } from "@adatechnology/game-shapes-three";
4929
+ // src/models/break-action.ts
4930
+ function buildBreakAction(kit, options) {
4931
+ const { M, box: box2, cylinderX, sphere } = kit;
4932
+ const group = kit.group();
4933
+ const L = (options.L ?? 0) / 1e3;
4934
+ const barrel = (options.B ?? 0) / 1e3;
4935
+ const parts = { family: "break", kick: 1.6, glow: [] };
4936
+ box2(group, 0.09, 0.05, 0.042, M.steel, L - barrel - 0.04, -5e-3, 0);
4937
+ for (let i = 0; i < 4; i++) box2(group, 0.05, 2e-3, 0.043, M.dark, L - barrel - 0.04, 0.01 - i * 8e-3, 0);
4938
+ const barrels = kit.pivot(group, L - barrel, -0.01, 0);
4939
+ parts.barrels = barrels;
4940
+ heatify(parts.glow, cylinderX(barrels, 0.0105, barrel, M.steel, barrel / 2, 0.01, 0.011, 18));
4941
+ heatify(parts.glow, cylinderX(barrels, 0.0105, barrel, M.steel, barrel / 2, 0.01, -0.011, 18));
4942
+ box2(barrels, barrel, 4e-3, 8e-3, M.metal, barrel / 2, 0.022, 0);
4943
+ sphere(barrels, 2e-3, M.white, barrel - 0.01, 0.026, 0, 1);
4944
+ cylinderX(barrels, 75e-4, 4e-3, M.hole, barrel + 1e-3, 0.01, 0.011, 12);
4945
+ cylinderX(barrels, 75e-4, 4e-3, M.hole, barrel + 1e-3, 0.01, -0.011, 12);
4946
+ box2(barrels, 0.22, 0.03, 0.038, M.wood, 0.12, -0.01, 0);
4947
+ parts.breech = [kit.mark(barrels, 4e-3, 0.01, 0.011), kit.mark(barrels, 4e-3, 0.01, -0.011)];
4948
+ box2(group, 0.12, 0.042, 0.032, M.wood, L - barrel - 0.14, -0.022, 0, 0.14);
4949
+ box2(group, 0.28, 0.07, 0.036, M.wood, 0.15, -0.05, 0, 0.08);
4950
+ box2(group, 0.012, 0.13, 0.04, M.rubber, 6e-3, -0.06, 0);
4951
+ box2(group, 4e-3, 0.014, 4e-3, M.dark, L - barrel - 0.05, -0.04, 0);
4952
+ parts.trigger = box2(group, 4e-3, 0.014, 4e-3, M.dark, L - barrel - 0.035, -0.04, 0);
4953
+ box2(group, 0.04, 4e-3, 0.01, M.steel, L - barrel - 0.045, -0.05, 0);
4954
+ box2(group, 0.012, 0.016, 6e-3, M.dark, L - barrel - 0.08, 0.022, 0.012, 0.5);
4955
+ box2(group, 0.012, 0.016, 6e-3, M.dark, L - barrel - 0.08, 0.022, -0.012, 0.5);
4956
+ parts.safety = box2(group, 0.02, 6e-3, 8e-3, M.steel, L - barrel - 0.07, 0.025, 0);
4957
+ parts.muzzle = kit.mark(barrels, barrel + 0.01, 0.01, 0);
4958
+ parts.eject = parts.breech[0];
4959
+ return { group, parts };
4960
+ }
4625
4961
 
4626
- // src/models/rounded-box.ts
4627
- var uvAlong = (THREE, faceDirection, normal, uvAxis, projectionAxis, radius, sideLength) => {
4628
- const arcLength = 2 * Math.PI * radius / 4;
4629
- const centerLength = Math.max(sideLength - 2 * radius, 0);
4630
- const halfArc = Math.PI / 4;
4631
- const projected = new THREE.Vector3().copy(normal);
4632
- projected[projectionAxis] = 0;
4633
- projected.normalize();
4634
- const arcUvRatio = 0.5 * arcLength / (arcLength + centerLength);
4635
- const arcAngleRatio = 1 - projected.angleTo(faceDirection) / halfArc;
4636
- if (Math.sign(projected[uvAxis]) === 1) return arcAngleRatio * arcUvRatio;
4637
- const centerUv = centerLength / (arcLength + centerLength);
4638
- return centerUv + arcUvRatio + arcUvRatio * (1 - arcAngleRatio);
4639
- };
4640
- var FACES = [
4641
- { direction: [1, 0, 0], u: ["z", "y", false], v: ["y", "z", true] },
4642
- { direction: [-1, 0, 0], u: ["z", "y", true], v: ["y", "z", true] },
4643
- { direction: [0, 1, 0], u: ["x", "z", true], v: ["z", "x", false] },
4644
- { direction: [0, -1, 0], u: ["x", "z", true], v: ["z", "x", true] },
4645
- { direction: [0, 0, 1], u: ["x", "y", true], v: ["y", "x", true] },
4646
- { direction: [0, 0, -1], u: ["x", "y", false], v: ["y", "x", true] }
4647
- ];
4648
- function createRoundedBoxGeometry(THREE, width, height, depth, segments, radius) {
4649
- const count = segments * 2 + 1;
4650
- const rounding = Math.min(width / 2, height / 2, depth / 2, radius);
4651
- const geometry = new THREE.BoxGeometry(1, 1, 1, count, count, count);
4652
- const flat2 = geometry.toNonIndexed();
4653
- geometry.setIndex(null);
4654
- geometry.setAttribute("position", flat2.attributes.position);
4655
- geometry.setAttribute("normal", flat2.attributes.normal);
4656
- geometry.setAttribute("uv", flat2.attributes.uv);
4657
- const position = new THREE.Vector3();
4658
- const normal = new THREE.Vector3();
4659
- const box2 = new THREE.Vector3(width, height, depth).divideScalar(2).subScalar(rounding);
4660
- const positions = geometry.attributes.position.array;
4661
- const normals = geometry.attributes.normal.array;
4662
- const uvs = geometry.attributes.uv.array;
4663
- const faceTriangles = positions.length / 6;
4664
- const halfSegment = 0.5 / count;
4665
- const sides = { x: width, y: height, z: depth };
4666
- for (let i = 0, j = 0; i < positions.length; i += 3, j += 2) {
4667
- position.fromArray(positions, i);
4668
- normal.copy(position);
4669
- normal.x -= Math.sign(normal.x) * halfSegment;
4670
- normal.y -= Math.sign(normal.y) * halfSegment;
4671
- normal.z -= Math.sign(normal.z) * halfSegment;
4672
- normal.normalize();
4673
- positions[i] = box2.x * Math.sign(position.x) + normal.x * rounding;
4674
- positions[i + 1] = box2.y * Math.sign(position.y) + normal.y * rounding;
4675
- positions[i + 2] = box2.z * Math.sign(position.z) + normal.z * rounding;
4676
- normals[i] = normal.x;
4677
- normals[i + 1] = normal.y;
4678
- normals[i + 2] = normal.z;
4679
- const face = FACES[Math.floor(i / faceTriangles)];
4680
- if (!face) continue;
4681
- const direction = new THREE.Vector3(...face.direction);
4682
- for (const [offset, [uvAxis, projectionAxis, mirrored]] of [[0, face.u], [1, face.v]]) {
4683
- const value = uvAlong(THREE, direction, normal, uvAxis, projectionAxis, rounding, sides[uvAxis]);
4684
- uvs[j + offset] = mirrored ? 1 - value : value;
4685
- }
4686
- }
4687
- return geometry;
4688
- }
4689
-
4690
- // src/models/kit.ts
4691
- var PI = Math.PI;
4692
- var createMaterials = (THREE) => {
4693
- const standard = (color, roughness = 0.7, metalness = 0.2, extra = {}) => new THREE.MeshStandardMaterial({ color, roughness, metalness, ...extra });
4694
- const basic = (color, extra = {}) => new THREE.MeshBasicMaterial({ color, ...extra });
4695
- const additive = { transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending, depthWrite: false };
4696
- const materials = {
4697
- metal: standard(3422010, 0.42, 0.75),
4698
- steel: standard(7106418, 0.32, 0.85),
4699
- steelL: standard(9080208, 0.28, 0.9),
4700
- dark: standard(1711133, 0.55, 0.5),
4701
- poly: standard(2369058, 0.8, 0.05),
4702
- olive: standard(4936241, 0.8, 0.05),
4703
- tan: standard(10259043, 0.8, 0.05),
4704
- wood: standard(4860951, 0.55, 0),
4705
- woodL: standard(6175263, 0.55, 0),
4706
- brass: standard(12819013, 0.28, 0.9),
4707
- copper: standard(11561531, 0.3, 0.85),
4708
- lead: standard(7171955, 0.5, 0.5),
4709
- shell: standard(10692380, 0.5, 0.05),
4710
- glass: standard(8364968, 0.05, 0.1, { transparent: true, opacity: 0.42 }),
4711
- lens: standard(2311770, 0.05, 0.3, { transparent: true, opacity: 0.9, emissive: 399392 }),
4712
- rubber: standard(1381653, 0.85, 0),
4713
- white: standard(13617332, 0.8, 0),
4714
- hole: standard(526343, 1, 0),
4715
- bake: standard(5911072, 0.55, 0),
4716
- rust: standard(7027230, 0.95, 0.3),
4717
- rustD: standard(4860440, 0.95, 0.25),
4718
- worn: standard(5593178, 0.6, 0.6),
4719
- fire: basic(16742946),
4720
- fire2: basic(16764762),
4721
- flashA: basic(16757575, additive),
4722
- flashB: basic(16773568, additive),
4723
- smokeP: standard(9079430, 1, 0, { transparent: true, opacity: 0.35, depthWrite: false }),
4724
- /** Launcher paints. */
4725
- yellow: standard(11570978, 0.6, 0),
4726
- red: standard(9313300, 0.45, 0.1),
4727
- redPaint: standard(10691354, 0.35, 0.2),
4728
- tape: standard(9079944, 0.9, 0),
4729
- pipe: standard(10115634, 0.35, 0.8),
4730
- green: standard(3818536, 0.75, 0.05),
4731
- /** Magazine finishes named by the options. */
4732
- bakelite: standard(6958620, 0.6, 0),
4733
- polyMag: standard(2763302, 0.85, 0)
4734
- };
4735
- markShared(...Object.values(materials));
4736
- return materials;
4737
- };
4738
- var kits = /* @__PURE__ */ new WeakMap();
4739
- function modelKit(host) {
4740
- const cached = kits.get(host);
4741
- if (cached) return cached;
4742
- const THREE = shapeGeometries2(host);
4743
- const geometries = /* @__PURE__ */ new Map();
4744
- const shadowed = (mesh) => {
4745
- mesh.castShadow = true;
4746
- mesh.receiveShadow = true;
4747
- return mesh;
4748
- };
4749
- const place = (parent, mesh, x, y, z) => {
4750
- mesh.position.set(x, y, z);
4751
- parent.add(mesh);
4752
- return mesh;
4753
- };
4754
- const kit = {
4755
- THREE,
4756
- M: createMaterials(THREE),
4757
- roundness: null,
4758
- group: () => new THREE.Group(),
4759
- box(parent, w, h, d, material, x, y, z, rz = 0, ry = 0, rx = 0) {
4760
- const radius = Math.min(w, h, d) * (kit.roundness ?? 0.2);
4761
- const key = `${w.toFixed(4)},${h.toFixed(4)},${d.toFixed(4)},${radius.toFixed(5)}`;
4762
- let geometry = geometries.get(key);
4763
- if (!geometry) {
4764
- geometry = radius > 25e-5 ? createRoundedBoxGeometry(THREE, w, h, d, 2, radius) : new THREE.BoxGeometry(w, h, d);
4765
- markShared(geometry);
4766
- geometries.set(key, geometry);
4767
- }
4768
- const mesh = place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z);
4769
- mesh.rotation.set(rx, ry, rz);
4770
- return mesh;
4771
- },
4772
- cylinderX(parent, radius, length, material, x, y, z, segments = 16, radiusEnd) {
4773
- const mesh = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, length, segments), material));
4774
- mesh.rotation.z = -PI / 2;
4775
- return place(parent, mesh, x, y, z);
4776
- },
4777
- cylinderY(parent, radius, height, material, x, y, z, segments = 16, radiusEnd) {
4778
- return place(parent, shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, height, segments), material)), x, y, z);
4779
- },
4780
- cylinderZ(parent, radius, length, material, x, y, z, segments = 16) {
4781
- const mesh = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radius, radius, length, segments), material));
4782
- mesh.rotation.x = PI / 2;
4783
- return place(parent, mesh, x, y, z);
4784
- },
4785
- sphere(parent, radius, material, x, y, z, detail = 1) {
4786
- return place(parent, shadowed(new THREE.Mesh(new THREE.IcosahedronGeometry(radius, detail), material)), x, y, z);
4787
- },
4788
- torus: (parent, geometry, material, x, y, z) => place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z),
4789
- mark(parent, x, y, z) {
4790
- const object = new THREE.Object3D();
4791
- object.position.set(x, y, z);
4792
- parent.add(object);
4793
- return object;
4794
- },
4795
- pivot(parent, x = 0, y = 0, z = 0) {
4796
- const group = new THREE.Group();
4797
- group.position.set(x, y, z);
4798
- parent.add(group);
4799
- return group;
4800
- }
4801
- };
4802
- kits.set(host, kit);
4803
- return kit;
4804
- }
4805
- function heatify(glow, mesh) {
4806
- mesh.material = mesh.material.clone();
4807
- glow.push(mesh);
4808
- return mesh;
4809
- }
4810
-
4811
- // src/models/break-action.ts
4812
- function buildBreakAction(kit, options) {
4813
- const { M, box: box2, cylinderX, sphere } = kit;
4814
- const group = kit.group();
4815
- const L = (options.L ?? 0) / 1e3;
4816
- const barrel = (options.B ?? 0) / 1e3;
4817
- const parts = { family: "break", kick: 1.6, glow: [] };
4818
- box2(group, 0.09, 0.05, 0.042, M.steel, L - barrel - 0.04, -5e-3, 0);
4819
- for (let i = 0; i < 4; i++) box2(group, 0.05, 2e-3, 0.043, M.dark, L - barrel - 0.04, 0.01 - i * 8e-3, 0);
4820
- const barrels = kit.pivot(group, L - barrel, -0.01, 0);
4821
- parts.barrels = barrels;
4822
- heatify(parts.glow, cylinderX(barrels, 0.0105, barrel, M.steel, barrel / 2, 0.01, 0.011, 18));
4823
- heatify(parts.glow, cylinderX(barrels, 0.0105, barrel, M.steel, barrel / 2, 0.01, -0.011, 18));
4824
- box2(barrels, barrel, 4e-3, 8e-3, M.metal, barrel / 2, 0.022, 0);
4825
- sphere(barrels, 2e-3, M.white, barrel - 0.01, 0.026, 0, 1);
4826
- cylinderX(barrels, 75e-4, 4e-3, M.hole, barrel + 1e-3, 0.01, 0.011, 12);
4827
- cylinderX(barrels, 75e-4, 4e-3, M.hole, barrel + 1e-3, 0.01, -0.011, 12);
4828
- box2(barrels, 0.22, 0.03, 0.038, M.wood, 0.12, -0.01, 0);
4829
- parts.breech = [kit.mark(barrels, 4e-3, 0.01, 0.011), kit.mark(barrels, 4e-3, 0.01, -0.011)];
4830
- box2(group, 0.12, 0.042, 0.032, M.wood, L - barrel - 0.14, -0.022, 0, 0.14);
4831
- box2(group, 0.28, 0.07, 0.036, M.wood, 0.15, -0.05, 0, 0.08);
4832
- box2(group, 0.012, 0.13, 0.04, M.rubber, 6e-3, -0.06, 0);
4833
- box2(group, 4e-3, 0.014, 4e-3, M.dark, L - barrel - 0.05, -0.04, 0);
4834
- parts.trigger = box2(group, 4e-3, 0.014, 4e-3, M.dark, L - barrel - 0.035, -0.04, 0);
4835
- box2(group, 0.04, 4e-3, 0.01, M.steel, L - barrel - 0.045, -0.05, 0);
4836
- box2(group, 0.012, 0.016, 6e-3, M.dark, L - barrel - 0.08, 0.022, 0.012, 0.5);
4837
- box2(group, 0.012, 0.016, 6e-3, M.dark, L - barrel - 0.08, 0.022, -0.012, 0.5);
4838
- parts.safety = box2(group, 0.02, 6e-3, 8e-3, M.steel, L - barrel - 0.07, 0.025, 0);
4839
- parts.muzzle = kit.mark(barrels, barrel + 0.01, 0.01, 0);
4840
- parts.eject = parts.breech[0];
4841
- return { group, parts };
4962
+ // src/models/attachments/muzzle-brake.ts
4963
+ function buildBrake(kit, parent, kind, x, y, barrelRadius, heat = () => void 0) {
4964
+ const { M, box: box2, cylinderX } = kit;
4965
+ const scale = kind === 2 ? 3.2 : 2;
4966
+ const length = kind === 2 ? 0.12 : 0.06;
4967
+ heat(cylinderX(parent, barrelRadius * scale, length, M.dark, x - length / 2, y, 0, kind === 2 ? 8 : 16));
4968
+ 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);
4842
4969
  }
4843
4970
 
4844
- // src/models/attachments/mount-frames.ts
4845
- var DEFAULT_BARREL_RADIUS = 85e-4;
4846
- var DEFAULT_RECEIVER_WIDTH = 0.034;
4847
- var RECEIVER_START_FRACTION = 0.25;
4848
- var RECEIVER_END_FRACTION = 0.55;
4849
- var RECEIVER_HEIGHT_FRACTION = 0.4;
4850
- var DEFAULT_GRIP_OFFSET = 0.07;
4851
- var HANDGUARD_FRACTION = 0.5;
4852
- function deriveFrames(THREE, model) {
4853
- const { root, inner, parts } = model;
4854
- root.updateMatrixWorld(true);
4855
- const box2 = new THREE.Box3().setFromObject(root);
4856
- const span = box2.max.x - box2.min.x;
4857
- const toInnerX = (x) => (x - inner.position.x) / inner.scale.x;
4858
- const muzzle = parts.muzzle?.position;
4859
- const length = muzzle ? muzzle.x - 0.01 : toInnerX(box2.max.x);
4860
- const receiverStart = toInnerX(box2.min.x + span * RECEIVER_START_FRACTION);
4861
- const receiverEnd = toInnerX(box2.min.x + span * RECEIVER_END_FRACTION);
4862
- return {
4863
- length,
4864
- barrelY: muzzle?.y ?? (box2.max.y + box2.min.y) / 2,
4865
- barrelRadius: DEFAULT_BARREL_RADIUS,
4866
- receiverStart,
4867
- receiverEnd,
4868
- receiverHeight: (box2.max.y - box2.min.y) * RECEIVER_HEIGHT_FRACTION,
4869
- receiverWidth: Math.min(DEFAULT_RECEIVER_WIDTH, box2.max.z - box2.min.z),
4870
- handguardEnd: receiverEnd + (length - receiverEnd) * HANDGUARD_FRACTION,
4871
- magazineX: parts.mag ? parts.mag.position.x : (receiverStart + receiverEnd) / 2,
4872
- gripX: receiverStart + DEFAULT_GRIP_OFFSET,
4873
- hasRail: false
4874
- };
4971
+ // src/models/capture.ts
4972
+ function captured(parent, draw) {
4973
+ const before = parent.children.length;
4974
+ draw();
4975
+ return parent.children.slice(before);
4875
4976
  }
4876
- function mountFrame(model, mount) {
4877
- const frames = model.parts.frames;
4878
- if (!frames) return void 0;
4879
- const { inner } = model;
4880
- const toRoot = (x, y, z) => [inner.position.x + x * inner.scale.x, y, z];
4881
- const handguardHeight = frames.receiverHeight * 0.8;
4882
- const railTopY = frames.receiverHeight / 2 + (frames.hasRail ? 6e-3 : 0);
4883
- const forendMiddle = (frames.receiverEnd + frames.handguardEnd) / 2;
4884
- const base = { mount, barrelRadius: frames.barrelRadius, receiverWidth: frames.receiverWidth, receiverHeight: frames.receiverHeight, hasRail: frames.hasRail };
4885
- switch (mount) {
4886
- case "muzzle":
4887
- return { ...base, position: toRoot(frames.length, frames.barrelY, 0), side: 0 };
4888
- case "rail_top":
4889
- return { ...base, position: toRoot((frames.receiverStart + frames.receiverEnd) / 2 + 0.03, railTopY, 0), side: 0 };
4890
- case "rail_bottom":
4891
- return { ...base, position: toRoot(forendMiddle, frames.barrelY - handguardHeight / 2, 0), side: 0 };
4892
- case "rail_left":
4893
- return { ...base, position: toRoot(forendMiddle, frames.barrelY, -(frames.receiverWidth * 0.54 + 4e-3)), side: -1 };
4894
- case "rail_right":
4895
- return { ...base, position: toRoot(forendMiddle, frames.barrelY, frames.receiverWidth * 0.54 + 4e-3), side: 1 };
4896
- case "underbarrel": {
4897
- const [x, y] = frames.underbarrel ?? [frames.receiverEnd + (frames.handguardEnd - frames.receiverEnd) * 0.25, frames.barrelY - handguardHeight / 2 - 0.02];
4898
- return { ...base, position: toRoot(x, y, 0), side: 0 };
4899
- }
4900
- case "magazine":
4901
- return { ...base, position: toRoot(frames.magazineX, -frames.receiverHeight / 2, 0), side: 0 };
4902
- case "feed": {
4903
- const belt = model.parts.belt?.position;
4904
- 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 };
4905
- }
4906
- case "sling":
4907
- return {
4908
- ...base,
4909
- position: toRoot(frames.receiverEnd + (frames.length - frames.receiverEnd) * 0.45, -frames.receiverHeight / 2 - 0.03, 0),
4910
- secondary: toRoot(0.08, -0.09, 0),
4911
- side: 0
4912
- };
4913
- default:
4914
- return void 0;
4915
- }
4977
+ function recordFitting(parts, id, parent, draw) {
4978
+ parts.fittings = { ...parts.fittings, [id]: captured(parent, draw) };
4916
4979
  }
4917
4980
 
4918
4981
  // src/models/details.ts
@@ -4945,1153 +5008,1231 @@ function scope(kit, parent, kind, cx, cy, cz = 0) {
4945
5008
  cylinderX(parent, 0.017, 2e-3, M.lens, cx + 0.073, cy + 0.032, cz, 18);
4946
5009
  cylinderY(parent, 7e-3, 0.012, M.dark, cx, cy + 0.052, cz, 12);
4947
5010
  box2(parent, 0.03, 0.01, 0.012, M.dark, cx - 0.01, cy + 0.054, cz);
4948
- return;
4949
- }
4950
- const sleeve = kind === 4 ? 0.2 : 0.32;
4951
- cylinderX(parent, 0.0135, sleeve * 0.55, M.dark, cx, cy + 0.045, cz, 18);
4952
- cylinderX(parent, 0.0135, sleeve * 0.25, M.dark, cx + sleeve * 0.36, cy + 0.045, cz, 18, 0.026);
4953
- cylinderX(parent, 0.026, 0.05, M.dark, cx + sleeve * 0.5, cy + 0.045, cz, 18);
4954
- cylinderX(parent, 0.024, 2e-3, M.lens, cx + sleeve * 0.5 + 0.026, cy + 0.045, cz, 18);
4955
- cylinderX(parent, 0.019, 0.06, M.dark, cx - sleeve * 0.38, cy + 0.045, cz, 18, 0.0135);
4956
- cylinderX(parent, 0.019, 0.035, M.rubber, cx - sleeve * 0.38 - 0.045, cy + 0.045, cz, 18);
4957
- cylinderX(parent, 0.017, 2e-3, M.lens, cx - sleeve * 0.38 - 0.063, cy + 0.045, cz, 18);
4958
- cylinderY(parent, 0.01, 0.022, M.dark, cx, cy + 0.066, cz, 14);
4959
- cylinderY(parent, 0.011, 4e-3, M.metal, cx, cy + 0.078, cz, 14);
4960
- cylinderZ(parent, 0.01, 0.022, M.dark, cx, cy + 0.045, cz + 0.022, 14);
4961
- for (const offset of [-0.07, 0.07]) {
4962
- const ring = kit.torus(parent, new THREE.TorusGeometry(0.0155, 3e-3, 8, 20), M.dark, cx + offset, cy + 0.045, cz);
4963
- ring.rotation.y = PI2 / 2;
4964
- box2(parent, 0.016, 0.03, 0.022, M.dark, cx + offset, cy + 0.018, cz);
4965
- }
4966
- }
4967
- var buttpad = (kit, parent, x, y, height, width) => void kit.box(parent, 0.012, height, width, kit.M.rubber, x, y, 0);
4968
- 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);
4969
-
4970
- // src/models/bullpup.ts
4971
- function buildBullpup(kit, options) {
4972
- const { M, box: box2, cylinderX } = kit;
4973
- const group = kit.group();
4974
- const L = (options.L ?? 0) / 1e3;
4975
- const barrel = (options.B ?? 0) / 1e3;
4976
- const bodyLength = Math.max(0.32, L - barrel * 0.72);
4977
- const body = options.shot ? M.poly : M.olive;
4978
- const parts = { family: options.shot ? "pump" : "rifle", stroke: 0.06, glow: [] };
4979
- box2(group, bodyLength, 0.075, 0.042, body, bodyLength / 2, 0, 0);
4980
- box2(group, bodyLength * 0.9, 0.02, 0.044, M.dark, bodyLength * 0.5, -0.02, 0);
4981
- box2(group, 0.032, 0.13, 0.044, M.rubber, 0.016, -0.025, 0);
4982
- const gx = bodyLength * 0.8;
4983
- const grip = kit.pivot(group, gx, -0.085, 0);
4984
- grip.rotation.z = -0.25;
4985
- parts.grip = grip;
4986
- box2(grip, 0.03, 0.1, 0.03, M.poly, 0, 0, 0);
4987
- box2(group, 0.1, 6e-3, 0.03, body, gx + 0.01, -0.06, 0);
4988
- parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.02, -0.05, 0, 0.15);
4989
- heatify(parts.glow, cylinderX(group, options.shot ? 0.0105 : 85e-4, barrel, M.metal, L - barrel / 2, 0.012, 0, 16));
4990
- cylinderX(group, 6e-3, 4e-3, M.hole, L + 1e-3, 0.012, 0, 12);
4991
- box2(group, 0.05, 0.024, 4e-3, M.hole, bodyLength * 0.25, 5e-3, 0.0212);
4992
- const bolt = kit.pivot(group);
4993
- parts.bolt = bolt;
4994
- box2(bolt, 0.044, 0.02, 6e-3, M.steelL, bodyLength * 0.25, 5e-3, 0.02);
4995
- if (options.shot) {
4996
- cylinderX(group, 0.012, bodyLength * 0.75, M.metal, bodyLength * 0.55, -0.025, 0.014, 14);
4997
- cylinderX(group, 0.012, bodyLength * 0.75, M.metal, bodyLength * 0.55, -0.025, -0.014, 14);
4998
- const pump = kit.pivot(group);
4999
- parts.pump = pump;
5000
- box2(pump, 0.15, 0.045, 0.052, M.poly, bodyLength + 0.06, -0.015, 0);
5001
- 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);
5002
- rail(kit, group, bodyLength * 0.15, bodyLength * 0.8, 0.04, 0, 0.02);
5003
- parts.loadPort = kit.mark(group, bodyLength * 0.2, -0.04, 0);
5004
- } else {
5005
- const magazine = kit.pivot(group, gx - 0.11, -0.03, 0);
5006
- magazine.rotation.z = 0.14;
5007
- parts.mag = magazine;
5008
- box2(magazine, 0.05, 0.14, 0.026, M.glass, 0, -0.07, 0);
5009
- for (let i = 0; i < 6; i++) cylinderX(magazine, 3e-3, 0.04, M.brass, 0, -0.01 - i * 0.02, 0, 6);
5010
- parts.magDrop = 0.18;
5011
- scope(kit, group, 2, bodyLength * 0.5, 0.04, 0);
5012
- box2(group, 0.2, 0.03, 0.02, body, bodyLength * 0.62, 0.048, 0);
5013
- box2(group, 0.026, 0.075, 0.026, body, bodyLength + 0.05, -0.05, 0, 0.1);
5014
- }
5015
- parts.muzzle = kit.mark(group, L + 0.01, 0.012, 0);
5016
- parts.eject = kit.mark(group, bodyLength * 0.25, 5e-3, 0.03);
5017
- return { group, parts };
5018
- }
5019
-
5020
- // src/models/launcher-flame.ts
5021
- var hose = (kit, group, points, radius, material) => {
5022
- const { THREE } = kit;
5023
- const curve = new THREE.CatmullRomCurve3(points.map((point) => new THREE.Vector3(...point)));
5024
- const tube = new THREE.Mesh(new THREE.TubeGeometry(curve, 40, radius, 8, false), material);
5025
- tube.castShadow = tube.receiveShadow = true;
5026
- group.add(tube);
5027
- };
5028
- var pilot = (kit, group, x, y) => {
5029
- const { M, cylinderX } = kit;
5030
- cylinderX(group, 6e-3, 0.03, M.fire2, x + 0.015, y, 0, 6, 0).userData.flame = 1;
5031
- cylinderX(group, 4e-3, 0.022, M.fire, x + 0.012, y, 0, 6, 0).userData.flame = 1;
5032
- };
5033
- function buildFlamethrower(kit, group, parts) {
5034
- const { M, box: box2, cylinderX, cylinderY, sphere } = kit;
5035
- parts.kick = 0.15;
5036
- heatify(parts.glow, cylinderX(group, 0.016, 0.62, M.steel, 0.42, 0, 0, 14));
5037
- cylinderX(group, 0.022, 0.1, M.dark, 0.78, 0, 0, 14);
5038
- heatify(parts.glow, cylinderX(group, 0.013, 0.1, M.steel, 0.88, 0, 0, 12, 9e-3));
5039
- cylinderX(group, 0.018, 0.02, M.metal, 0.73, 0, 0, 14);
5040
- cylinderX(group, 0.012, 0.06, M.metal, 0.84, -0.025, 0, 10);
5041
- pilot(kit, group, 0.92, -0.012);
5042
- const foreGrip = kit.pivot(group, 0.6, -0.02, 0);
5043
- foreGrip.rotation.z = -0.1;
5044
- box2(foreGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5045
- const lever = kit.pivot(group, 0.62, -0.02, 0);
5046
- parts.trigger = box2(lever, 8e-3, 0.05, 6e-3, M.metal, 0.02, -0.03, 0, 0.3);
5047
- const rearGrip = kit.pivot(group, 0.17, -0.02, 0);
5048
- rearGrip.rotation.z = -0.28;
5049
- box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5050
- box2(group, 0.05, 4e-3, 0.012, M.metal, 0.2, -0.035, 0);
5051
- cylinderX(group, 0.02, 0.04, M.metal, 0.1, 0, 0, 12);
5052
- 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);
5053
- for (const z of [-0.1, 0.1]) {
5054
- cylinderY(group, 0.085, 0.5, M.olive, -0.3, -0.05, z, 20);
5055
- sphere(group, 0.085, M.olive, -0.3, 0.2, z, 2).scale.y = 0.45;
5056
- sphere(group, 0.085, M.olive, -0.3, -0.3, z, 2).scale.y = 0.45;
5057
- cylinderY(group, 0.03, 0.03, M.metal, -0.3, 0.24, z, 12);
5058
- }
5059
- sphere(group, 0.075, M.olive, -0.3, 0.1, 0, 2).position.x = -0.39;
5060
- box2(group, 0.03, 0.6, 0.24, M.dark, -0.4, -0.05, 0);
5061
- cylinderY(group, 0.03, 0.012, M.metal, -0.39, 0.18, 0, 14);
5062
- const gauge = kit.pivot(group, -0.39, 0.18, 3e-3);
5063
- parts.gauge = gauge;
5064
- box2(gauge, 2e-3, 0.022, 2e-3, M.red, 0, 8e-3, 0.012);
5065
- parts.muzzle = kit.pivot(group, 0.94, 0, 0);
5066
- parts.rear = kit.pivot(group, 0.1, 0, 0);
5067
- }
5068
- function buildImprovisedFlamethrower(kit, group, parts) {
5069
- const { M, box: box2, cylinderX, cylinderY, sphere } = kit;
5070
- parts.kick = 0.12;
5071
- heatify(parts.glow, cylinderX(group, 0.011, 0.66, M.pipe, 0.38, 0, 0, 12));
5072
- heatify(parts.glow, cylinderX(group, 0.015, 0.06, M.steel, 0.74, 0, 0, 12, 0.01));
5073
- cylinderX(group, 6e-3, 0.05, M.metal, 0.735, -0.02, 0, 8);
5074
- pilot(kit, group, 0.77, -0.02);
5075
- cylinderY(group, 0.012, 0.04, M.redPaint, 0.72, -0.055, 0, 10);
5076
- box2(group, 0.02, 0.03, 0.012, M.dark, 0.72, -0.085, 0);
5077
- cylinderX(group, 0.06, 0.42, M.redPaint, 0.3, -0.09, 0, 22);
5078
- sphere(group, 0.06, M.redPaint, 0.51, -0.09, 0, 2).scale.x = 0.5;
5079
- sphere(group, 0.06, M.redPaint, 0.09, -0.09, 0, 2).scale.x = 0.5;
5080
- cylinderX(group, 0.02, 0.04, M.dark, 0.04, -0.09, 0, 12);
5081
- box2(group, 0.02, 0.06, 5e-3, M.white, 0.3, -0.09, 0.06);
5082
- for (const x of [0.15, 0.3, 0.45]) cylinderX(group, 0.063, 0.03, M.tape, x, -0.09, 0, 22);
5083
- cylinderX(group, 0.016, 0.03, M.tape, 0.3, 0, 0, 12);
5084
- 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);
5085
- const rearGrip = kit.pivot(group, 0.12, -0.01, 0);
5086
- rearGrip.rotation.z = -0.3;
5087
- box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5088
- const lever = kit.pivot(group, 0.17, -0.02, 0);
5089
- parts.trigger = box2(lever, 8e-3, 0.04, 6e-3, M.metal, 0, -0.02, 0, 0.2);
5090
- parts.muzzle = kit.pivot(group, 0.78, 0, 0);
5091
- parts.rear = kit.pivot(group, 0.05, 0, 0);
5092
- }
5093
-
5094
- // src/models/launcher-rounds.ts
5095
- var PI3 = Math.PI;
5096
- function buildRocketGrenade(kit) {
5097
- const { M, cylinderX } = kit;
5098
- const round2 = kit.group();
5099
- cylinderX(round2, 0.018, 0.15, M.olive, -0.05, 0, 0, 14);
5100
- cylinderX(round2, 0.02, 0.07, M.olive, 0.035, 0, 0, 18, 0.0425);
5101
- cylinderX(round2, 0.0425, 0.12, M.olive, 0.13, 0, 0, 20);
5102
- cylinderX(round2, 0.0428, 0.012, M.dark, 0.09, 0, 0, 20);
5103
- cylinderX(round2, 0.0425, 0.13, M.olive, 0.255, 0, 0, 20, 0.011);
5104
- cylinderX(round2, 0.011, 0.03, M.metal, 0.335, 0, 0, 10, 4e-3);
5105
- return round2;
5106
- }
5107
- function buildRecoillessRound(kit) {
5108
- const { M, cylinderX } = kit;
5109
- const round2 = kit.group();
5110
- cylinderX(round2, 0.041, 0.32, M.green, -0.16, 0, 0, 20);
5111
- cylinderX(round2, 0.0415, 0.02, M.yellow, -0.05, 0, 0, 20);
5112
- cylinderX(round2, 0.041, 0.12, M.green, 0.06, 0, 0, 20, 0.012);
5113
- cylinderX(round2, 0.043, 0.03, M.brass, -0.33, 0, 0, 20);
5114
- return round2;
5115
- }
5116
- function buildFlyingRocket(kit, launcherType) {
5117
- const { M, box: box2, cylinderX } = kit;
5118
- const rocket = kit.group();
5119
- const diameter = launcherType === "at4" || launcherType === "carl" ? 0.041 : 0.032;
5120
- cylinderX(rocket, diameter * 0.5, 0.2, M.dark, -0.1, 0, 0, 10);
5121
- cylinderX(rocket, diameter, 0.3, M.olive, 0.15, 0, 0, 18);
5122
- cylinderX(rocket, diameter, 0.12, M.olive, 0.36, 0, 0, 18, diameter * 0.25);
5123
- for (let i = 0; i < 4; i++) {
5124
- const fin = box2(rocket, 0.06, 2e-3, diameter * 1.4, M.metal, -0.17, 0, 0);
5125
- fin.rotation.x = i * PI3 / 4;
5011
+ return;
5012
+ }
5013
+ const sleeve = kind === 4 ? 0.2 : 0.32;
5014
+ cylinderX(parent, 0.0135, sleeve * 0.55, M.dark, cx, cy + 0.045, cz, 18);
5015
+ cylinderX(parent, 0.0135, sleeve * 0.25, M.dark, cx + sleeve * 0.36, cy + 0.045, cz, 18, 0.026);
5016
+ cylinderX(parent, 0.026, 0.05, M.dark, cx + sleeve * 0.5, cy + 0.045, cz, 18);
5017
+ cylinderX(parent, 0.024, 2e-3, M.lens, cx + sleeve * 0.5 + 0.026, cy + 0.045, cz, 18);
5018
+ cylinderX(parent, 0.019, 0.06, M.dark, cx - sleeve * 0.38, cy + 0.045, cz, 18, 0.0135);
5019
+ cylinderX(parent, 0.019, 0.035, M.rubber, cx - sleeve * 0.38 - 0.045, cy + 0.045, cz, 18);
5020
+ cylinderX(parent, 0.017, 2e-3, M.lens, cx - sleeve * 0.38 - 0.063, cy + 0.045, cz, 18);
5021
+ cylinderY(parent, 0.01, 0.022, M.dark, cx, cy + 0.066, cz, 14);
5022
+ cylinderY(parent, 0.011, 4e-3, M.metal, cx, cy + 0.078, cz, 14);
5023
+ cylinderZ(parent, 0.01, 0.022, M.dark, cx, cy + 0.045, cz + 0.022, 14);
5024
+ for (const offset of [-0.07, 0.07]) {
5025
+ const ring = kit.torus(parent, new THREE.TorusGeometry(0.0155, 3e-3, 8, 20), M.dark, cx + offset, cy + 0.045, cz);
5026
+ ring.rotation.y = PI2 / 2;
5027
+ box2(parent, 0.016, 0.03, 0.022, M.dark, cx + offset, cy + 0.018, cz);
5126
5028
  }
5127
- return rocket;
5128
5029
  }
5030
+ var buttpad = (kit, parent, x, y, height, width) => void kit.box(parent, 0.012, height, width, kit.M.rubber, x, y, 0);
5031
+ 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);
5129
5032
 
5130
- // src/models/launcher-rocket.ts
5131
- function buildRpg(kit, group, parts) {
5033
+ // src/models/attachments/bipod.ts
5034
+ function buildBipod(kit, parent, x, y) {
5132
5035
  const { M, box: box2, cylinderX } = kit;
5133
- parts.kick = 1.6;
5134
- cylinderX(group, 0.036, 0.13, M.dark, 0.065, 0, 0, 20, 0.021);
5135
- cylinderX(group, 0.038, 0.012, M.metal, 0, 0, 0, 20);
5136
- heatify(parts.glow, cylinderX(group, 0.021, 0.83, M.steel, 0.545, 0, 0, 18));
5137
- cylinderX(group, 0.03, 0.3, M.wood, 0.47, 0, 0, 16);
5138
- cylinderX(group, 0.031, 0.012, M.metal, 0.32, 0, 0, 16);
5139
- cylinderX(group, 0.031, 0.012, M.metal, 0.62, 0, 0, 16);
5140
- cylinderX(group, 0.024, 0.02, M.metal, 0.94, 0, 0, 16);
5141
- box2(group, 0.09, 0.03, 0.032, M.metal, 0.46, -0.028, 0);
5142
- const frontGrip = kit.pivot(group, 0.4, -0.04, 0);
5143
- frontGrip.rotation.z = -0.22;
5144
- box2(frontGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5145
- const rearGrip = kit.pivot(group, 0.5, -0.04, 0);
5146
- rearGrip.rotation.z = -0.18;
5147
- box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5148
- box2(group, 0.06, 4e-3, 0.012, M.metal, 0.53, -0.05, 0);
5149
- box2(group, 4e-3, 0.025, 0.012, M.metal, 0.56, -0.04, 0);
5150
- parts.trigger = box2(group, 4e-3, 0.018, 4e-3, M.dark, 0.535, -0.045, 0, 0.15);
5151
- const hammer = kit.pivot(group, 0.44, -0.012, 0);
5152
- parts.hammer = hammer;
5153
- box2(hammer, 0.012, 0.02, 0.01, M.dark, 0, 0, 0);
5154
- box2(group, 6e-3, 0.03, 6e-3, M.dark, 0.78, 0.032, 0);
5155
- box2(group, 0.02, 0.022, 0.014, M.dark, 0.46, 0.032, 0);
5156
- box2(group, 0.12, 0.042, 0.032, M.dark, 0.5, 0.04, -0.04);
5157
- cylinderX(group, 0.013, 0.05, M.rubber, 0.42, 0.048, -0.04, 14);
5158
- cylinderX(group, 0.016, 4e-3, M.lens, 0.565, 0.048, -0.04, 14);
5159
- box2(group, 0.04, 0.012, 0.012, M.metal, 0.5, 0.016, -0.03);
5160
- const round2 = buildRocketGrenade(kit);
5161
- round2.position.x = 0.95;
5162
- group.add(round2);
5163
- parts.round = round2;
5164
- parts.roundRest = 0.95;
5165
- parts.makeRound = () => buildRocketGrenade(kit);
5166
- parts.muzzle = kit.pivot(group, 0.95, 0, 0);
5167
- parts.rear = kit.pivot(group, 0, 0, 0);
5168
- }
5169
- function buildM72(kit, group, parts) {
5170
- const { M, box: box2, cylinderX, cylinderY } = kit;
5171
- parts.kick = 1.1;
5172
- parts.disposable = true;
5173
- heatify(parts.glow, cylinderX(group, 0.0365, 0.6, M.olive, 0.58, 0, 0, 20));
5174
- cylinderX(group, 0.032, 0.32, M.olive, 0.16, 0, 0, 20);
5175
- cylinderX(group, 0.039, 0.03, M.rubber, 0.866, 0, 0, 20);
5176
- cylinderX(group, 0.035, 0.025, M.rubber, 0.012, 0, 0, 20);
5177
- cylinderX(group, 0.037, 0.015, M.yellow, 0.8, 0, 0, 20);
5178
- cylinderX(group, 0.037, 0.012, M.dark, 0.3, 0, 0, 20);
5179
- box2(group, 4e-3, 0.05, 0.05, M.dark, 0.79, 0.055, 0);
5180
- box2(group, 4e-3, 6e-3, 0.04, M.dark, 0.79, 0.078, 0);
5181
- box2(group, 4e-3, 0.035, 0.024, M.dark, 0.38, 0.05, 0);
5182
- cylinderY(group, 4e-3, 6e-3, M.lens, 0.38, 0.064, 0, 8);
5183
- parts.trigger = box2(group, 0.05, 0.016, 0.024, M.rubber, 0.5, 0.042, 0);
5184
- box2(group, 0.1, 0.01, 0.03, M.dark, 0.47, 0.036, 0);
5185
- box2(group, 0.02, 0.03, 0.01, M.dark, 0.66, -0.035, 0.03);
5186
- box2(group, 8e-3, 0.03, 8e-3, M.metal, 0.25, -0.04, 0.03);
5187
- parts.muzzle = kit.pivot(group, 0.881, 0, 0);
5188
- parts.rear = kit.pivot(group, 0, 0, 0);
5036
+ cylinderX(parent, 8e-3, 0.02, M.dark, x, y, 0, 12);
5037
+ for (const side of [-1, 1]) {
5038
+ const leg = kit.pivot(parent, x, y, side * 0.012);
5039
+ leg.rotation.set(side * 0.15, 0, -0.35);
5040
+ box2(leg, 6e-3, 0.2, 6e-3, M.dark, 0, -0.1, 0);
5041
+ box2(leg, 0.01, 6e-3, 0.01, M.rubber, 0, -0.2, 0);
5042
+ }
5189
5043
  }
5190
- function buildAt4(kit, group, parts) {
5191
- const { M, box: box2, cylinderX } = kit;
5192
- parts.kick = 1.3;
5193
- parts.disposable = true;
5194
- heatify(parts.glow, cylinderX(group, 0.05, 0.86, M.olive, 0.51, 0, 0, 24));
5195
- cylinderX(group, 0.056, 0.08, M.dark, 0.98, 0, 0, 24);
5196
- cylinderX(group, 0.058, 0.07, M.dark, 0.04, 0, 0, 24, 0.05);
5197
- cylinderX(group, 0.051, 0.015, M.yellow, 0.86, 0, 0, 24);
5198
- box2(group, 0.16, 0.026, 0.05, M.dark, 0.42, 0.058, 0);
5199
- box2(group, 0.03, 0.012, 0.016, M.red, 0.47, 0.075, 0);
5200
- parts.trigger = box2(group, 0.022, 0.012, 0.018, M.rubber, 0.5, 0.074, 0);
5201
- box2(group, 0.012, 0.07, 0.02, M.dark, 0.35, 0.09, -0.035);
5202
- box2(group, 0.012, 0.05, 0.02, M.dark, 0.71, 0.08, -0.035);
5203
- const grip = kit.pivot(group, 0.68, -0.05, 0);
5204
- grip.rotation.z = -0.15;
5205
- parts.grip = grip;
5206
- box2(grip, 0.035, 0.11, 0.032, M.dark, 0, -0.05, 0);
5207
- box2(group, 0.14, 0.035, 0.06, M.rubber, 0.32, -0.06, 0);
5208
- box2(group, 0.3, 0.012, 0.055, M.dark, 0.5, -0.05, 0);
5209
- parts.muzzle = kit.pivot(group, 1.02, 0, 0);
5210
- parts.rear = kit.pivot(group, 0, 0, 0);
5044
+ var buildBipodModel = ({ kit }) => {
5045
+ const group = kit.group();
5046
+ buildBipod(kit, group, 0, 0);
5047
+ return group;
5048
+ };
5049
+
5050
+ // src/models/rifle-fittings.ts
5051
+ var SCOPE_IDS = { 1: "red_dot", 2: "acog_4x", 3: "scope_3_9x", 4: "scope_6_24x" };
5052
+ function buildFittings(context, gripX) {
5053
+ const { kit, group, parts, options: o, furniture: furn, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
5054
+ const { M, box: box2, cylinderX, cylinderZ, sphere } = kit;
5055
+ const middle = (x0 + x1) / 2;
5056
+ 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));
5057
+ if (o.carry) {
5058
+ box2(group, 0.15, 0.026, 0.014, M.metal, middle, hR / 2 + 0.03, 0);
5059
+ box2(group, 0.012, 0.03, 0.014, M.metal, middle + 0.07, hR / 2 + 0.014, 0);
5060
+ box2(group, 0.012, 0.03, 0.014, M.metal, middle - 0.07, hR / 2 + 0.014, 0);
5061
+ }
5062
+ if (o.bipod) recordFitting(parts, "bipod", group, () => buildBipod(kit, group, x1 + (L - x1) * 0.45 + 0.04, by - 0.012));
5063
+ if (o.bolt) {
5064
+ const handle = kit.pivot(group, x1 - 0.045, by, 0);
5065
+ parts.boltHandle = handle;
5066
+ parts.boltHandleRest = handle.position.x;
5067
+ cylinderZ(handle, 42e-4, 0.05, M.steelL, 0, 0, wR / 2 + 0.022, 12);
5068
+ sphere(handle, 95e-4, M.dark, 0, -4e-3, wR / 2 + 0.048, 1);
5069
+ cylinderX(group, hR * 0.3, 0.03, M.steel, x0 + 5e-3, by, 0, 16);
5070
+ }
5071
+ if (o.lever) {
5072
+ const lever = kit.pivot(group, gripX - 0.02, -hR / 2 - 0.01, 0);
5073
+ parts.lever = lever;
5074
+ box2(lever, 0.11, 6e-3, 0.012, M.metal, 0.055, -0.052, 0);
5075
+ box2(lever, 6e-3, 0.05, 0.012, M.metal, 3e-3, -0.028, 0);
5076
+ box2(lever, 6e-3, 0.05, 0.012, M.metal, 0.108, -0.028, 0);
5077
+ box2(lever, 0.04, 6e-3, 0.012, M.metal, 0.08, -2e-3, 0);
5078
+ box2(group, 0.03, 0.012, 3e-3, M.dark, x1 - 0.04, by, wR / 2 + 1e-3);
5079
+ }
5080
+ if (o.tube) {
5081
+ const tubeLength = (L - x1) * (o.tubeLen || 0.8);
5082
+ cylinderX(group, 0.0105, tubeLength, M.metal, x1 + tubeLength / 2, by - 0.023, 0, 16);
5083
+ cylinderX(group, 0.0125, 0.02, M.dark, x1 + tubeLength + 6e-3, by - 0.023, 0, 16);
5084
+ }
5085
+ if (o.pump) {
5086
+ const pump = kit.pivot(group);
5087
+ parts.pump = pump;
5088
+ box2(pump, 0.17, 0.042, 0.046, furn, x1 + 0.14, by - 0.022, 0);
5089
+ 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);
5090
+ }
5091
+ if (o.heat) {
5092
+ cylinderX(group, r * 1.75, (L - x1) * 0.7, M.dark, x1 + (L - x1) * 0.4, by, 0, 12);
5093
+ 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);
5094
+ }
5095
+ if (o.bayo) box2(group, 0.025, 8e-3, 0.01, M.metal, L - 0.07, by - 0.016, 0);
5096
+ if (o.rod) cylinderX(group, 3e-3, (L - x1) * 0.85, M.steel, x1 + (L - x1) * 0.45, by - 0.014, 0, 8);
5211
5097
  }
5212
- function buildCarlGustaf(kit, group, parts) {
5213
- const { M, box: box2, cylinderX } = kit;
5214
- parts.kick = 1.4;
5215
- heatify(parts.glow, cylinderX(group, 0.045, 0.72, M.dark, 0.59, 0, 0, 24));
5216
- cylinderX(group, 0.05, 0.04, M.rubber, 0.93, 0, 0, 24);
5217
- const breech = kit.pivot(group, 0.21, 0, 0.05);
5218
- parts.breech = [breech];
5219
- cylinderX(breech, 0.06, 0.16, M.dark, -0.1, 0, -0.05, 24, 0.046);
5220
- cylinderX(breech, 0.062, 0.015, M.metal, -0.18, 0, -0.05, 24);
5221
- box2(group, 0.025, 0.03, 0.02, M.metal, 0.22, 0, 0.052);
5222
- const foreGrip = kit.pivot(group, 0.62, -0.05, 0);
5223
- foreGrip.rotation.z = -0.2;
5224
- box2(foreGrip, 0.035, 0.11, 0.034, M.dark, 0, -0.05, 0);
5225
- parts.trigger = box2(group, 4e-3, 0.018, 4e-3, M.dark, 0.66, -0.06, 0, 0.15);
5226
- box2(group, 0.06, 4e-3, 0.012, M.metal, 0.66, -0.07, 0);
5227
- const rearGrip = kit.pivot(group, 0.4, -0.05, 0);
5228
- rearGrip.rotation.z = -0.25;
5229
- box2(rearGrip, 0.035, 0.1, 0.034, M.dark, 0, -0.05, 0);
5230
- box2(group, 0.13, 0.04, 0.07, M.rubber, 0.28, -0.065, 0);
5231
- box2(group, 0.16, 0.05, 0.045, M.dark, 0.56, 0.07, -0.035);
5232
- cylinderX(group, 0.016, 0.05, M.rubber, 0.46, 0.075, -0.035, 14);
5233
- cylinderX(group, 0.02, 4e-3, M.lens, 0.645, 0.075, -0.035, 14);
5234
- box2(group, 0.06, 0.02, 0.02, M.metal, 0.56, 0.04, -0.03);
5235
- box2(group, 0.22, 0.01, 0.02, M.dark, 0.6, 0.06, 0.03);
5236
- box2(group, 0.01, 0.03, 0.02, M.dark, 0.5, 0.05, 0.03);
5237
- box2(group, 0.01, 0.03, 0.02, M.dark, 0.7, 0.05, 0.03);
5238
- parts.makeRound = () => buildRecoillessRound(kit);
5239
- parts.muzzle = kit.pivot(group, 0.95, 0, 0);
5240
- parts.rear = kit.pivot(group, 0.05, 0, 0);
5098
+
5099
+ // src/models/rifle-forend.ts
5100
+ var PI3 = Math.PI;
5101
+ function buildForend(context) {
5102
+ const { kit, group, options: o, furniture: furn, length: L, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
5103
+ const { M, box: box2 } = kit;
5104
+ const hasLongScope = (o.scope ?? 0) >= 3;
5105
+ if (!context.isTraditional && o.hg !== "none") {
5106
+ const handguardLength = (L - x1) * (o.hgFrac || 0.5);
5107
+ const hx = x1 + handguardLength / 2;
5108
+ const hh = hR * 0.8;
5109
+ if (o.hg === "rail") {
5110
+ box2(group, handguardLength, hh, wR * 1.04, M.dark, hx, by - 2e-3, 0);
5111
+ rail(kit, group, x1, handguardLength, by + hh / 2, 0, wR * 0.66);
5112
+ rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, wR * 0.54, wR * 0.4, PI3 / 2);
5113
+ rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, -wR * 0.54, wR * 0.4, -PI3 / 2);
5114
+ rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - hh / 2, 0, wR * 0.6, PI3);
5115
+ } else {
5116
+ const material = o.hg === "wood" ? M.wood : furn;
5117
+ if (o.ak) {
5118
+ box2(group, handguardLength, hh * 0.55, wR * 1.08, material, hx, by - hh * 0.22, 0);
5119
+ box2(group, handguardLength * 0.75, hh * 0.35, wR * 0.7, material, hx - handguardLength * 0.1, by + r * 2.2, 0);
5120
+ } else {
5121
+ box2(group, handguardLength, hh, wR * 1.08, material, hx, by - 2e-3, 0);
5122
+ 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);
5123
+ 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);
5124
+ }
5125
+ }
5126
+ if (!hasLongScope && !o.rail && o.ak) {
5127
+ box2(group, 0.03, 0.016, wR * 0.6, M.metal, x1 + 0.02, by + r * 2.5 + 0.01, 0);
5128
+ box2(group, 0.012, 4e-3, wR * 0.5, M.dark, x1 + 0.03, by + r * 2.5 + 0.019, 0);
5129
+ }
5130
+ }
5131
+ if (hasLongScope) return;
5132
+ const sightX = L - (o.ak ? 0.06 : 0.08);
5133
+ if (o.ak) {
5134
+ box2(group, 0.022, 0.022, wR * 0.6, M.metal, sightX, by + 8e-3, 0);
5135
+ box2(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, 8e-3);
5136
+ box2(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, -8e-3);
5137
+ box2(group, 3e-3, 0.016, 3e-3, M.dark, sightX, by + 0.022, 0);
5138
+ } else if (!o.rail || o.carry) {
5139
+ box2(group, 0.018, 0.014, wR * 0.55, M.dark, sightX, by + 8e-3, 0);
5140
+ box2(group, 0.012, 0.034, 8e-3, M.dark, sightX, by + 0.03, 0, -0.25);
5141
+ } else box2(group, 0.012, 0.024, 0.012, M.dark, sightX - 0.04, by + hR * 0.4 + 0.012, 0);
5241
5142
  }
5242
- var ROCKET_BUILDERS = {
5243
- rpg: buildRpg,
5244
- m72: buildM72,
5245
- at4: buildAt4,
5246
- carl: buildCarlGustaf
5247
- };
5248
5143
 
5249
- // src/models/launcher.ts
5250
- function buildLauncher(kit, options) {
5251
- const group = kit.group();
5252
- const type = options.lt ?? "";
5253
- const isFlame = type === "flame" || type === "flameDIY";
5254
- const parts = { family: isFlame ? "flame" : "rocket", launcherType: type, glow: [] };
5255
- if (type === "flame") buildFlamethrower(kit, group, parts);
5256
- else if (type === "flameDIY") buildImprovisedFlamethrower(kit, group, parts);
5257
- else ROCKET_BUILDERS[type]?.(kit, group, parts);
5258
- parts.eject = parts.muzzle;
5259
- return { group, parts };
5144
+ // src/models/attachments/belt-box.ts
5145
+ function buildBeltBox(kit, belt) {
5146
+ const { M, box: box2, cylinderX } = kit;
5147
+ box2(belt, 0.11, 0.1, 0.08, M.olive, 0, 0, 0);
5148
+ box2(belt, 0.112, 0.012, 0.082, M.dark, 0, 0.046, 0);
5149
+ for (let i = 0; i < 7; i++) {
5150
+ cylinderX(belt, 42e-4, 0.034, M.brass, -0.04 + i * 0.012, 0.07, 0.035, 10);
5151
+ box2(belt, 6e-3, 6e-3, 0.012, M.metal, -0.04 + i * 0.012, 0.066, 0.035);
5152
+ }
5260
5153
  }
5261
5154
 
5262
- // src/models/m134.ts
5155
+ // src/models/attachments/drum.ts
5156
+ function buildDrum(kit, magazine) {
5157
+ const { M, box: box2, cylinderZ } = kit;
5158
+ cylinderZ(magazine, 0.075, 0.07, M.dark, 0.02, -0.085, 0, 22);
5159
+ cylinderZ(magazine, 0.05, 0.074, M.metal, 0.02, -0.085, 0, 18);
5160
+ box2(magazine, 0.05, 0.04, 0.03, M.dark, 0, -0.02, 0);
5161
+ }
5162
+
5163
+ // src/models/helical-magazine.ts
5263
5164
  var PI4 = Math.PI;
5264
- var BARRELS = 6;
5265
- var CLUSTER_RADIUS = 0.03;
5266
- var BARREL_RADIUS = 78e-4;
5267
- var BARREL_END = 0.8;
5268
- function buildBarrelCluster(kit, parts, group) {
5269
- const { M, cylinderX } = kit;
5270
- const barrels = kit.pivot(group, 0, 0, 0);
5271
- parts.barrels = barrels;
5272
- const length = BARREL_END - 0.3;
5273
- let heated;
5274
- for (let i = 0; i < BARRELS; i++) {
5275
- const angle = i * 2 * PI4 / BARRELS;
5276
- const y = Math.cos(angle) * CLUSTER_RADIUS;
5277
- const z = Math.sin(angle) * CLUSTER_RADIUS;
5278
- const barrel = cylinderX(barrels, BARREL_RADIUS, length, M.metal, 0.3 + length / 2, y, z, 14);
5279
- if (heated) barrel.material = heated;
5280
- else heated = heatify(parts.glow, barrel).material;
5281
- cylinderX(barrels, BARREL_RADIUS * 0.55, 4e-3, M.hole, BARREL_END + 5e-4, y, z, 10);
5165
+ var LENGTH = 0.26;
5166
+ var RADIUS = 0.026;
5167
+ var TURNS = 11;
5168
+ function buildHelicalMagazine(context) {
5169
+ const { kit, group, parts, receiverEnd, barrelY, barrelRadius } = context;
5170
+ const { M, cylinderX, box: box2 } = kit;
5171
+ const x = receiverEnd + LENGTH / 2 + 0.02;
5172
+ const y = barrelY - barrelRadius - RADIUS - 0.012;
5173
+ const magazine = kit.pivot(group, x, y, 0);
5174
+ parts.mag = magazine;
5175
+ cylinderX(magazine, RADIUS, LENGTH, M.dark, 0, 0, 0, 20);
5176
+ cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, -LENGTH / 2, 0, 0, 20);
5177
+ cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, LENGTH / 2, 0, 0, 20);
5178
+ 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);
5179
+ box2(magazine, 0.05, 0.03, 0.03, M.dark, -LENGTH / 2 - 0.02, RADIUS * 0.4, 0);
5180
+ parts.magDrop = 0.16;
5181
+ return x;
5182
+ }
5183
+
5184
+ // src/models/rifle-controls.ts
5185
+ function buildGrip(context) {
5186
+ const { kit, group, parts, options: o, furniture: furn, receiverStart: x0, receiverHeight: hR, receiverWidth: wR } = context;
5187
+ const { M, box: box2 } = kit;
5188
+ const gx = x0 + (o.gripAt || 0.07);
5189
+ if (!context.isTraditional && o.grip !== "none") {
5190
+ const grip = kit.pivot(group, gx - 0.012, -hR / 2 - 0.046, 0);
5191
+ grip.rotation.z = -0.3;
5192
+ parts.grip = grip;
5193
+ box2(grip, 0.032, 0.105, 0.028, furn, 0, 0, 0);
5194
+ for (let i = 0; i < 3; i++) box2(grip, 6e-3, 0.012, 0.026, furn, 0.017, 0.022 - i * 0.02, 0);
5195
+ box2(grip, 0.034, 8e-3, 0.03, furn, 0, -0.052, 0);
5282
5196
  }
5283
- cylinderX(barrels, 0.012, length, M.steel, 0.3 + length / 2, 0, 0, 12);
5284
- 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);
5285
- cylinderX(barrels, CLUSTER_RADIUS + BARREL_RADIUS * 1.8, 0.03, M.dark, BARREL_END - 0.015, 0, 0, 24);
5197
+ box2(group, 0.06, 4e-3, 0.01, M.metal, gx + 0.03, -hR / 2 - 0.024, 0);
5198
+ box2(group, 4e-3, 0.022, 0.01, M.metal, gx + 0.06, -hR / 2 - 0.013, 0);
5199
+ parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.024, -hR / 2 - 0.012, 0, 0.15);
5200
+ 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);
5201
+ return gx;
5286
5202
  }
5287
- function buildHousing(kit, parts, group) {
5288
- const { M, box: box2, cylinderX, cylinderZ } = kit;
5289
- box2(group, 0.3, 0.14, 0.15, M.metal, 0.18, 0, 0);
5290
- cylinderX(group, 0.052, 0.12, M.dark, 0.06, 0, 0, 20);
5291
- cylinderX(group, 0.062, 0.2, M.dark, 0.38, 0, 0, 22);
5292
- for (let i = 0; i < 4; i++) cylinderX(group, 0.064, 8e-3, M.steel, 0.3 + i * 0.05, 0, 0, 22);
5293
- box2(group, 0.04, 0.05, 0.2, M.dark, 0.1, 0.095, 0);
5294
- box2(group, 0.2, 0.012, 0.045, M.dark, 0.22, 0.158, 0);
5295
- box2(group, 0.012, 0.05, 0.045, M.dark, 0.13, 0.13, 0);
5296
- box2(group, 0.012, 0.05, 0.045, M.dark, 0.31, 0.13, 0);
5297
- for (const side of [-1, 1]) {
5298
- box2(group, 0.03, 0.03, 0.1, M.poly, 0.06, -0.01, side * 0.14);
5299
- cylinderZ(group, 0.016, 0.07, M.poly, 0.06, -0.01, side * 0.2, 14);
5203
+ function buildMagazine(context, gx) {
5204
+ const { kit, group, parts, options: o, receiverStart: x0, receiverHeight: hR, receiverWidth: wR, receiverLength: RL } = context;
5205
+ const { M, box: box2 } = kit;
5206
+ if (o.mag === "helical") return buildHelicalMagazine(context);
5207
+ const mx = o.magAt != null ? x0 + o.magAt : gx + 0.075;
5208
+ const scale = o.magLen || 1;
5209
+ const magazine = kit.pivot(group, mx, -hR / 2, 0);
5210
+ parts.mag = magazine;
5211
+ const finish = o.magMat === "bakelite" ? M.bakelite : o.magMat === "poly" ? M.polyMag : void 0;
5212
+ if (o.mag === "curved") {
5213
+ let px = 0;
5214
+ let py = 0;
5215
+ const material = finish ?? M.metal;
5216
+ for (const angle of [0.06, 0.22, 0.38]) {
5217
+ const length = 0.07 * scale;
5218
+ const cx = px + Math.sin(angle) * length / 2;
5219
+ const cy = py - Math.cos(angle) * length / 2;
5220
+ box2(magazine, 0.052, length + 4e-3, 0.024, material, cx, cy, 0, angle);
5221
+ box2(magazine, 0.04, length * 0.8, 0.026, M.dark, cx, cy, 0, angle);
5222
+ px += Math.sin(angle) * length;
5223
+ py -= Math.cos(angle) * length;
5224
+ }
5225
+ box2(magazine, 0.058, 8e-3, 0.028, material, px, py, 0, 0.38);
5226
+ parts.magDrop = 0.07 * scale * 3 + 0.04;
5227
+ } else if (o.mag === "box") {
5228
+ const length = 0.13 * scale;
5229
+ const curve = o.curve || 0;
5230
+ const segment = length / 3;
5231
+ const material = finish ?? M.dark;
5232
+ let px = 0;
5233
+ let py = 0;
5234
+ let angle = 0.08;
5235
+ for (let i = 0; i < 3; i++) {
5236
+ angle = 0.08 + curve * i;
5237
+ const cx = px + Math.sin(angle) * segment / 2;
5238
+ const cy = py - Math.cos(angle) * segment / 2;
5239
+ box2(magazine, 0.05, segment + 4e-3, 0.024, material, cx, cy, 0, angle);
5240
+ box2(magazine, 0.04, 3e-3, 0.026, M.metal, cx, cy, 0, angle);
5241
+ px += Math.sin(angle) * segment;
5242
+ py -= Math.cos(angle) * segment;
5243
+ }
5244
+ box2(magazine, 0.056, 7e-3, 0.028, material, px, py - 2e-3, 0, angle);
5245
+ parts.magDrop = length + 0.03;
5246
+ } else if (o.mag === "drum") {
5247
+ buildDrum(kit, magazine);
5248
+ parts.magDrop = 0.2;
5249
+ } else if (o.mag === "internal") {
5250
+ box2(group, 0.08, 0.025, 0.03, context.isTraditional ? context.furniture : M.metal, mx, -hR / 2 - 0.012, 0);
5251
+ parts.mag = null;
5252
+ } else parts.mag = null;
5253
+ if (o.mag === "belt") {
5254
+ const belt = kit.pivot(group, mx + 0.02, -hR / 2 - 0.05, -0.045);
5255
+ parts.belt = belt;
5256
+ buildBeltBox(kit, belt);
5257
+ const cover = kit.pivot(group, x0 + 0.01, hR / 2, 0);
5258
+ parts.cover = cover;
5259
+ box2(cover, RL - 0.03, 0.014, wR * 1.05, M.metal, (RL - 0.03) / 2, 7e-3, 0);
5260
+ box2(cover, 0.02, 0.012, wR * 0.6, M.dark, RL - 0.04, 0.016, 0);
5300
5261
  }
5301
- parts.trigger = box2(group, 0.012, 0.02, 0.016, M.dark, 0.1, 0.085, 0.14, 0.3);
5302
- parts.safety = box2(group, 0.014, 6e-3, 0.01, M.dark, 0.2, 0.073, -0.076, 0.4);
5262
+ return mx;
5303
5263
  }
5304
- function buildFeed(kit, parts, group) {
5264
+
5265
+ // src/models/rifle-stock.ts
5266
+ function buildStock(context, magazineX) {
5267
+ const { kit, group, options: o, furniture: furn, length: L, stockLength: S, receiverStart: x0, receiverEnd: x1, receiverHeight: hR } = context;
5305
5268
  const { M, box: box2, cylinderX } = kit;
5306
- const belt = kit.pivot(group, 0.2, -0.13, -0.16);
5307
- parts.belt = belt;
5308
- box2(belt, 0.16, 0.12, 0.1, M.olive, 0, 0, 0);
5309
- box2(belt, 0.162, 0.012, 0.102, M.dark, 0, 0.056, 0);
5310
- 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);
5311
- box2(group, 0.1, 0.03, 0.1, M.metal, 0.22, -0.06, -0.1, 0, 0, 0.5);
5312
- const cover = kit.pivot(group, 0.1, 0.075, 0);
5313
- parts.cover = cover;
5314
- box2(cover, 0.24, 0.012, 0.15, M.metal, 0.12, 6e-3, 0);
5315
- const bolt = kit.pivot(group);
5316
- parts.bolt = bolt;
5317
- parts.stroke = 0.03;
5318
- box2(bolt, 0.03, 0.012, 0.012, M.steelL, 0.28, 0.02, 0.078);
5319
- }
5320
- function buildM134(kit) {
5321
- const group = kit.group();
5322
- const parts = { family: "belt", kick: 0.9, glow: [] };
5323
- buildHousing(kit, parts, group);
5324
- buildBarrelCluster(kit, parts, group);
5325
- buildFeed(kit, parts, group);
5326
- parts.frames = {
5327
- length: BARREL_END,
5328
- barrelY: 0,
5329
- barrelRadius: BARREL_RADIUS,
5330
- receiverStart: 0.06,
5331
- receiverEnd: 0.48,
5332
- receiverHeight: 0.14,
5333
- receiverWidth: 0.15,
5334
- handguardEnd: 0.6,
5335
- magazineX: 0.2,
5336
- gripX: 0.06,
5337
- hasRail: false,
5338
- // the tripod head, where the gun balances (the tripod of the counters joins here too)
5339
- underbarrel: [0.4, -0.1]
5340
- };
5341
- parts.muzzle = kit.mark(group, BARREL_END + 0.01, 0, 0);
5342
- parts.eject = kit.mark(group, 0.3, -0.07, 0.08);
5343
- return { group, parts };
5269
+ if (context.isTraditional) {
5270
+ buttpad(kit, group, 6e-3, -0.04, 0.13, 0.038);
5271
+ box2(group, 0.03, 0.13, 0.038, furn, 0.024, -0.04, 0);
5272
+ box2(group, S, 0.055, 0.034, furn, S / 2, -0.012, 0, 0.06);
5273
+ box2(group, 0.09, 0.05, 0.032, furn, x0 - 0.02, -0.035, 0, 0.25);
5274
+ const forendLength = x1 - x0 + (L - x1) * (o.fore ?? 0.62);
5275
+ if (forendLength > 0) box2(group, forendLength, 0.045, 0.04, furn, x0 + forendLength / 2, -hR / 2 + 4e-3, 0);
5276
+ if (o.stock === "straight") box2(group, 0.15, 0.04, 0.035, furn, x1 + 0.08, -hR / 2, 0);
5277
+ box2(group, 0.04, 4e-3, 0.03, M.metal, magazineX, -hR / 2 - 0.022, 0);
5278
+ swivel(kit, group, 0.2, -0.08);
5279
+ swivel(kit, group, x1 + (L - x1) * 0.45, -hR / 2 - 0.03);
5280
+ 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);
5281
+ } else if (o.stock === "fixed" || o.stock === "wood") {
5282
+ box2(group, S, 0.05, 0.03, furn, S / 2, -8e-3, 0, 0.05);
5283
+ box2(group, 0.03, 0.13, 0.036, furn, 0.022, -0.035, 0);
5284
+ buttpad(kit, group, 4e-3, -0.035, 0.13, 0.036);
5285
+ if (o.stock === "wood") box2(group, S * 0.6, 0.03, 0.028, furn, S * 0.55, -0.045, 0, 0.2);
5286
+ swivel(kit, group, 0.08, -0.09);
5287
+ } else if (o.stock === "skeleton") {
5288
+ box2(group, S, 0.014, 0.026, furn, S / 2, 0.012, 0, 0.03);
5289
+ box2(group, S, 0.014, 0.026, furn, S / 2, -0.05, 0, -0.08);
5290
+ box2(group, 0.03, 0.12, 0.034, furn, 0.02, -0.025, 0);
5291
+ buttpad(kit, group, 4e-3, -0.025, 0.12, 0.034);
5292
+ } else if (o.stock === "retract") {
5293
+ cylinderX(group, 0.013, S, M.metal, S / 2, 0, 0, 16);
5294
+ 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);
5295
+ box2(group, 0.034, 0.11, 0.036, furn, 0.02, -0.025, 0);
5296
+ box2(group, S * 0.55, 0.032, 0.03, furn, S * 0.35, 0, 0);
5297
+ buttpad(kit, group, 3e-3, -0.025, 0.11, 0.036);
5298
+ } else if (o.stock === "fold") {
5299
+ box2(group, S, 6e-3, 6e-3, M.metal, S / 2, 0.01, 0.012);
5300
+ box2(group, S, 6e-3, 6e-3, M.metal, S / 2, -0.04, 0.012, -0.12);
5301
+ box2(group, 0.012, 0.1, 0.03, M.metal, 6e-3, -0.02, 0.012);
5302
+ }
5344
5303
  }
5345
5304
 
5346
- // src/models/m2.ts
5347
- function buildM2(kit) {
5348
- const { M, box: box2, cylinderX, cylinderZ } = kit;
5349
- const group = kit.group();
5350
- const parts = { family: "belt", kick: 2.2, stroke: 0.08, glow: [] };
5351
- box2(group, 0.52, 0.13, 0.12, M.metal, 0.36, 0, 0);
5352
- for (let i = 0; i < 5; i++) cylinderZ(group, 6e-3, 0.124, M.dark, 0.15 + i * 0.1, -0.03, 0, 10);
5353
- heatify(parts.glow, cylinderX(group, 0.016, 1.14, M.metal, 1.14, 0.01, 0, 16));
5354
- cylinderX(group, 0.034, 0.24, M.dark, 0.74, 0.01, 0, 16);
5355
- for (let i = 0; i < 6; i++) cylinderX(group, 0.036, 0.012, M.metal, 0.66 + i * 0.035, 0.01, 0, 16);
5356
- cylinderX(group, 0.022, 0.05, M.dark, 1.69, 0.01, 0, 16);
5357
- box2(group, 0.04, 0.09, 0.03, M.wood, 0.05, -0.02, 0.05);
5358
- box2(group, 0.04, 0.09, 0.03, M.wood, 0.05, -0.02, -0.05);
5359
- box2(group, 0.06, 0.02, 0.12, M.metal, 0.09, -0.02, 0);
5360
- const belt = kit.pivot(group, 0.38, -0.05, -0.12);
5361
- parts.belt = belt;
5362
- box2(belt, 0.16, 0.12, 0.09, M.olive, 0, 0, 0);
5363
- 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);
5364
- const cover = kit.pivot(group, 0.12, 0.065, 0);
5365
- parts.cover = cover;
5366
- box2(cover, 0.4, 0.012, 0.12, M.metal, 0.2, 6e-3, 0);
5367
- box2(group, 0.04, 0.05, 0.04, M.dark, 0.66, 0.1, 0);
5305
+ // src/models/rifle.ts
5306
+ var PI5 = Math.PI;
5307
+ function buildReceiver(context) {
5308
+ const { kit, group, parts, options: o, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, receiverLength: RL, barrelY: by } = context;
5309
+ const { M, box: box2, cylinderX, cylinderY, cylinderZ, sphere } = kit;
5310
+ const receiverMaterial = o.tan ? M.tan : M.metal;
5311
+ const middle = (x0 + x1) / 2;
5312
+ if (o.round) {
5313
+ cylinderX(group, hR * 0.36, RL, M.steel, middle, by, 0, 20);
5314
+ cylinderX(group, hR * 0.32, 0.05, M.steel, x0 - 0.01, by, 0, 18);
5315
+ } else if (o.ar) {
5316
+ box2(group, RL, hR * 0.5, wR, receiverMaterial, middle, hR * 0.25, 0);
5317
+ box2(group, RL * 0.92, hR * 0.52, wR * 0.96, receiverMaterial, middle - RL * 0.02, -hR * 0.24, 0);
5318
+ box2(group, 0.05, 0.03, wR * 1.15, receiverMaterial, x0 + RL * 0.62, -hR * 0.5 - 0.01, 0);
5319
+ cylinderZ(group, 6e-3, 0.012, M.metal, x1 - 0.03, hR * 0.28, wR / 2 + 6e-3, 12);
5320
+ box2(group, 0.012, 0.01, 0.012, M.metal, x1 - 0.05, hR * 0.12, wR / 2 + 6e-3);
5321
+ box2(group, 0.03, 8e-3, 0.012, M.dark, x0 + 0.02, hR / 2 + 4e-3, 0);
5322
+ box2(group, 0.012, 6e-3, 4e-3, M.dark, x0 + RL * 0.55, -hR * 0.15, -wR / 2 - 2e-3);
5323
+ } else box2(group, RL, hR, wR, receiverMaterial, middle, 0, 0);
5324
+ if (o.ak) {
5325
+ box2(group, RL * 0.86, hR * 0.28, wR * 0.95, M.metal, middle - 8e-3, hR / 2 + 4e-3, 0);
5326
+ 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);
5327
+ box2(group, RL * 0.5, 0.012, 3e-3, M.metal, x0 + RL * 0.55, -hR * 0.02, wR / 2 + 15e-4, -0.05);
5328
+ box2(group, 0.012, 0.014, 3e-3, M.dark, x0 + RL * 0.32, -hR * 0.05, wR / 2 + 2e-3);
5329
+ }
5330
+ if (o.g3) {
5331
+ cylinderX(group, 9e-3, (L - x1) * 0.6, M.metal, x1 + (L - x1) * 0.28, hR * 0.48, 0, 14);
5332
+ box2(group, 0.012, 0.024, 8e-3, M.dark, x1 + (L - x1) * 0.5, hR * 0.48, -0.012);
5333
+ cylinderY(group, 0.012, 0.016, M.dark, x0 + 0.03, hR / 2 + 8e-3, 0, 12);
5334
+ }
5335
+ const ejectX = x1 - (o.ejectBack || 0.07);
5336
+ const ejectY = o.ar ? hR * 0.2 : by;
5337
+ box2(group, 0.05, hR * 0.36, 4e-3, M.hole, ejectX, ejectY, wR / 2 - 4e-4);
5368
5338
  const bolt = kit.pivot(group);
5369
5339
  parts.bolt = bolt;
5370
- box2(bolt, 0.03, 0.03, 0.02, M.metal, 0.5, 0, 0.07);
5371
- const tripod = kit.group();
5372
- tripod.position.set(0.42, -0.1, 0);
5373
- group.add(tripod);
5374
- box2(tripod, 0.03, 0.06, 0.03, M.metal, 0, 0, 0);
5375
- box2(tripod, 0.012, 0.62, 0.012, M.metal, -0.2, -0.25, 0, 0.75);
5376
- box2(tripod, 0.012, 0.62, 0.012, M.metal, 0.22, -0.25, 0.16, -0.75, 0, 0.3);
5377
- box2(tripod, 0.012, 0.62, 0.012, M.metal, 0.22, -0.25, -0.16, -0.75, 0, -0.3);
5378
- parts.muzzle = kit.mark(group, 1.72, 0.01, 0);
5379
- parts.eject = kit.mark(group, 0.3, -0.07, 0);
5380
- return { group, parts };
5340
+ parts.stroke = Math.min(0.1, RL * 0.32);
5341
+ box2(bolt, 0.044, hR * 0.26, 6e-3, M.steelL, ejectX, ejectY, wR / 2 - 12e-4);
5342
+ if (o.charge) {
5343
+ box2(bolt, 0.012, 0.01, 0.03, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.012);
5344
+ sphere(bolt, 65e-4, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.028, 1);
5345
+ }
5346
+ if (o.ar) box2(group, 0.012, 0.016, 6e-3, receiverMaterial, ejectX - 0.03, ejectY + 4e-3, wR / 2 + 4e-3);
5347
+ if (o.rail) rail(kit, group, x0 - 5e-3, RL + 0.07, hR / 2 + (o.ar ? 0 : 3e-3), 0, wR * 0.68);
5381
5348
  }
5382
-
5383
- // src/models/p90.ts
5384
- function buildP90(kit) {
5349
+ function buildBarrel(context) {
5350
+ const { kit, group, parts, options: o, length: L, barrelLength: Bl, receiverEnd: x1, barrelRadius: r, barrelY: by } = context;
5385
5351
  const { M, box: box2, cylinderX } = kit;
5352
+ heatify(parts.glow, cylinderX(group, r, Bl, M.metal, L - Bl / 2, by, 0, 16));
5353
+ if (o.jacket) {
5354
+ cylinderX(group, r * 2, (L - x1) * 0.6, M.dark, x1 + (L - x1) * 0.3, by, 0, 16);
5355
+ 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);
5356
+ }
5357
+ 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);
5358
+ if (o.brake) {
5359
+ const kind = o.brake === 2 ? 2 : 1;
5360
+ recordFitting(parts, kind === 2 ? "muzzle_brake_heavy" : "muzzle_brake", group, () => buildBrake(kit, group, kind, L, by, r, (body) => void heatify(parts.glow, body)));
5361
+ } else if (!o.noFH) {
5362
+ recordFitting(parts, "muzzle_default", group, () => {
5363
+ heatify(parts.glow, cylinderX(group, r * 1.45, 0.042, M.dark, L - 0.021, by, 0, 16));
5364
+ for (let i = 0; i < 4; i++) {
5365
+ const angle = i * PI5 / 2 + PI5 / 4;
5366
+ 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);
5367
+ }
5368
+ });
5369
+ }
5370
+ cylinderX(group, r * 0.55, 4e-3, M.hole, L + 5e-4, by, 0, 12);
5371
+ }
5372
+ var familyOf = (o) => {
5373
+ if (o.mag === "belt") return "belt";
5374
+ if (o.pump) return "pump";
5375
+ if (o.lever) return "lever";
5376
+ if (o.tube) return "semi12";
5377
+ if (o.bolt) return o.mag === "box" ? "boltmag" : "bolt";
5378
+ return o.mag === "internal" ? "semiint" : "rifle";
5379
+ };
5380
+ function buildRifle(kit, options) {
5381
+ const { M } = kit;
5386
5382
  const group = kit.group();
5387
- const parts = { family: "p90", stroke: 0.04, kick: 0.6, glow: [] };
5388
- box2(group, 0.4, 0.07, 0.055, M.poly, 0.2, 0, 0);
5389
- box2(group, 0.12, 0.075, 0.05, M.poly, 0.2, -0.07, 0);
5390
- box2(group, 0.08, 0.04, 0.03, M.hole, 0.21, -0.06, 0);
5391
- box2(group, 0.06, 0.13, 0.056, M.poly, 0.03, -0.03, 0);
5392
- box2(group, 0.05, 0.03, 0.056, M.poly, 0.33, -0.04, 0);
5393
- heatify(parts.glow, cylinderX(group, 8e-3, 0.09, M.metal, 0.43, 0, 0, 16));
5394
- const magazine = kit.pivot(group, 0.22, 0.047, 0);
5395
- parts.mag = magazine;
5396
- parts.magDir = 1;
5397
- parts.magDrop = 0.08;
5398
- box2(magazine, 0.3, 0.022, 0.05, M.glass, 0, 0, 0);
5399
- for (let i = 0; i < 12; i++) cylinderX(magazine, 28e-4, 0.02, M.brass, -0.12 + i * 0.022, 0, 0, 6);
5400
- scope(kit, group, 1, 0.24, 0.06, 0);
5401
- const bolt = kit.pivot(group);
5402
- parts.bolt = bolt;
5403
- box2(bolt, 0.02, 0.012, 0.012, M.dark, 0.3, 0.02, 0.03);
5404
- parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, 0.24, -0.045, 0);
5405
- parts.muzzle = kit.mark(group, 0.48, 0, 0);
5406
- parts.eject = kit.mark(group, 0.2, -0.07, 0);
5383
+ const parts = { family: "rifle", glow: [] };
5384
+ const length = (options.L ?? 0) / 1e3;
5385
+ const barrelLength = (options.B ?? 0) / 1e3;
5386
+ const isTraditional = options.stock === "trad" || options.stock === "straight";
5387
+ const stockLength = options.stock === "none" ? 0 : options.stockLen || Math.min(0.36, Math.max(0.2, 0.3 * length));
5388
+ const receiverStart = stockLength;
5389
+ const receiverEnd = Math.max(receiverStart + 0.12, length - barrelLength * 0.92);
5390
+ const receiverHeight = options.hR || 0.06;
5391
+ const context = {
5392
+ kit,
5393
+ group,
5394
+ parts,
5395
+ options,
5396
+ furniture: options.wood ? M.wood : options.olive ? M.olive : options.tan ? M.tan : M.poly,
5397
+ isTraditional,
5398
+ length,
5399
+ barrelLength,
5400
+ stockLength,
5401
+ receiverStart,
5402
+ receiverEnd,
5403
+ receiverHeight,
5404
+ receiverWidth: options.wR || 0.034,
5405
+ barrelRadius: options.br || 85e-4,
5406
+ barrelY: receiverHeight * 0.15,
5407
+ receiverLength: receiverEnd - receiverStart
5408
+ };
5409
+ buildReceiver(context);
5410
+ buildBarrel(context);
5411
+ buildForend(context);
5412
+ const gripX = buildGrip(context);
5413
+ const magazineX = buildMagazine(context, gripX);
5414
+ parts.stockMeshes = context.isTraditional ? [] : captured(group, () => buildStock(context, magazineX));
5415
+ if (context.isTraditional) buildStock(context, magazineX);
5416
+ buildFittings(context, gripX);
5417
+ const hasHandguard = !context.isTraditional && options.hg !== "none";
5418
+ parts.frames = {
5419
+ length,
5420
+ barrelY: context.barrelY,
5421
+ barrelRadius: context.barrelRadius,
5422
+ receiverStart,
5423
+ receiverEnd,
5424
+ receiverHeight,
5425
+ receiverWidth: context.receiverWidth,
5426
+ handguardEnd: receiverEnd + (length - receiverEnd) * (hasHandguard ? options.hgFrac || 0.5 : 0.4),
5427
+ magazineX,
5428
+ gripX,
5429
+ hasRail: Boolean(options.rail)
5430
+ };
5431
+ const { barrelY: by, receiverWidth: wR } = context;
5432
+ const ejectX = receiverEnd - (options.ejectBack || 0.07);
5433
+ const ejectY = options.ar ? receiverHeight * 0.2 : by;
5434
+ parts.muzzle = kit.mark(group, length + 0.01, by, 0);
5435
+ parts.eject = kit.mark(group, ejectX, ejectY, wR / 2 + 6e-3);
5436
+ parts.loadPort = kit.mark(group, receiverEnd - 0.05, -receiverHeight / 2 - 4e-3, 0);
5437
+ parts.topLoad = kit.mark(group, ejectX, receiverHeight / 2 + 4e-3, 0);
5438
+ parts.family = familyOf(options);
5407
5439
  return { group, parts };
5408
5440
  }
5409
5441
 
5410
- // src/models/part-registry.ts
5411
- 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"];
5412
- function partRegistry(parts) {
5413
- const registry = /* @__PURE__ */ new Map();
5414
- for (const key of SINGLE_PARTS) {
5415
- const part = parts[key];
5416
- if (part) registry.set(key, part);
5442
+ // src/models/break-launcher.ts
5443
+ function buildBreakLauncher(kit, options) {
5444
+ const built = buildRifle(kit, options);
5445
+ const { group, parts } = built;
5446
+ const frames = parts.frames;
5447
+ if (!frames) return built;
5448
+ const hingeY = frames.barrelY - frames.barrelRadius;
5449
+ const barrels = kit.pivot(group, frames.receiverEnd, hingeY, 0);
5450
+ group.updateMatrixWorld(true);
5451
+ const centre = new kit.THREE.Vector3();
5452
+ for (const child of [...group.children]) {
5453
+ if (child === barrels) continue;
5454
+ const mesh = child;
5455
+ const isAhead = mesh.isMesh ? new kit.THREE.Box3().setFromObject(mesh).getCenter(centre).x > frames.receiverEnd : child === parts.muzzle;
5456
+ if (isAhead) barrels.attach(child);
5417
5457
  }
5418
- parts.breech?.forEach((part, index) => registry.set(`breech_${index}`, part));
5419
- parts.chambers?.forEach((part, index) => registry.set(`chamber_${index}`, part));
5420
- return registry;
5458
+ parts.barrels = barrels;
5459
+ parts.breech = [kit.mark(barrels, 4e-3, frames.barrelY - hingeY, 0)];
5460
+ parts.eject = parts.breech[0];
5461
+ parts.family = "break";
5462
+ return built;
5421
5463
  }
5422
5464
 
5423
- // src/models/pistol-slide.ts
5424
- var PI5 = Math.PI;
5425
- function buildSlide(kit, slide2, parts, options, style, size, metal2) {
5426
- const { M, box: box2, cylinderX } = kit;
5427
- const { length: L, width: w } = size;
5428
- const slideHeight = style.inset ? size.height * 0.92 : size.height;
5429
- const slideWidth = style.inset ? w * 0.86 : w;
5430
- kit.roundness = style.rf;
5431
- if (style.tri) box2(slide2, L * 0.42, slideHeight, slideWidth, metal2, L * 0.21, 0, 0);
5432
- else if (style.open) {
5433
- box2(slide2, L * 0.45, slideHeight, slideWidth, metal2, L * 0.225, 0, 0);
5434
- box2(slide2, L * 0.55, slideHeight * 0.62, slideWidth, metal2, L * 0.72, -slideHeight * 0.19, 0);
5435
- } else box2(slide2, L, slideHeight, slideWidth, metal2, L / 2, 0, 0);
5436
- if (style.slope) box2(slide2, L * 0.25, slideHeight * 0.5, slideWidth * 0.98, metal2, L * 0.86, slideHeight * 0.35, 0, -0.22);
5437
- kit.roundness = null;
5438
- 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);
5439
- 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);
5440
- if (style.port) {
5441
- box2(slide2, L * style.port, 4e-3, slideWidth * 0.55, M.hole, L * 0.6, slideHeight / 2 - 1e-3, slideWidth * 0.12);
5442
- box2(slide2, L * style.port * 0.85, 3e-3, slideWidth * 0.4, M.steelL, L * 0.6, slideHeight / 2 - 4e-4, slideWidth * 0.1);
5443
- box2(slide2, 0.012, 4e-3, 2e-3, M.dark, L * 0.55, slideHeight * 0.15, slideWidth / 2 + 8e-4);
5444
- }
5445
- if (style.open) {
5446
- heatify(parts.glow, cylinderX(slide2, 62e-4, L * 0.56, M.steelL, L * 0.7, slideHeight * 0.12, 0, 18));
5447
- box2(slide2, 0.03, 4e-3, slideWidth * 0.6, M.hole, L * 0.48, slideHeight / 2 - 1e-3, 0);
5448
- }
5449
- if (style.tri) {
5450
- const tube = new kit.THREE.Mesh(new kit.THREE.CylinderGeometry(0.016, 0.016, L * 0.6, 3), metal2);
5451
- tube.castShadow = tube.receiveShadow = true;
5452
- tube.rotation.set(0, 0, -PI5 / 2);
5453
- tube.rotateY(PI5 / 6);
5454
- tube.position.set(L * 0.7, slideHeight * 0.08, 0);
5455
- slide2.add(tube);
5456
- heatify(parts.glow, tube);
5457
- box2(slide2, L * 0.6, 6e-3, 0.012, metal2, L * 0.7, slideHeight * 0.38, 0);
5458
- 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);
5459
- }
5460
- if (style.bushing) {
5461
- heatify(parts.glow, cylinderX(slide2, 78e-4, 6e-3, M.steelL, L + 2e-3, 1e-3, 0, 18));
5462
- cylinderX(slide2, 45e-4, 4e-3, M.steel, L + 2e-3, -0.012, 0, 12);
5465
+ // src/models/attachments/mount-frames.ts
5466
+ var DEFAULT_BARREL_RADIUS = 85e-4;
5467
+ var DEFAULT_RECEIVER_WIDTH = 0.034;
5468
+ var RECEIVER_START_FRACTION = 0.25;
5469
+ var RECEIVER_END_FRACTION = 0.55;
5470
+ var RECEIVER_HEIGHT_FRACTION = 0.4;
5471
+ var DEFAULT_GRIP_OFFSET = 0.07;
5472
+ var HANDGUARD_FRACTION = 0.5;
5473
+ function deriveFrames(THREE, model) {
5474
+ const { root, inner, parts } = model;
5475
+ root.updateMatrixWorld(true);
5476
+ const box2 = new THREE.Box3().setFromObject(root);
5477
+ const span = box2.max.x - box2.min.x;
5478
+ const toInnerX = (x) => (x - inner.position.x) / inner.scale.x;
5479
+ const muzzle = parts.muzzle?.position;
5480
+ const length = muzzle ? muzzle.x - 0.01 : toInnerX(box2.max.x);
5481
+ const receiverStart = toInnerX(box2.min.x + span * RECEIVER_START_FRACTION);
5482
+ const receiverEnd = toInnerX(box2.min.x + span * RECEIVER_END_FRACTION);
5483
+ return {
5484
+ length,
5485
+ barrelY: muzzle?.y ?? (box2.max.y + box2.min.y) / 2,
5486
+ barrelRadius: DEFAULT_BARREL_RADIUS,
5487
+ receiverStart,
5488
+ receiverEnd,
5489
+ receiverHeight: (box2.max.y - box2.min.y) * RECEIVER_HEIGHT_FRACTION,
5490
+ receiverWidth: Math.min(DEFAULT_RECEIVER_WIDTH, box2.max.z - box2.min.z),
5491
+ handguardEnd: receiverEnd + (length - receiverEnd) * HANDGUARD_FRACTION,
5492
+ magazineX: parts.mag ? parts.mag.position.x : (receiverStart + receiverEnd) / 2,
5493
+ gripX: receiverStart + DEFAULT_GRIP_OFFSET,
5494
+ hasRail: false
5495
+ };
5496
+ }
5497
+ function mountFrame(model, mount) {
5498
+ const frames = model.parts.frames;
5499
+ if (!frames) return void 0;
5500
+ const { inner } = model;
5501
+ const toRoot = (x, y, z) => [inner.position.x + x * inner.scale.x, y, z];
5502
+ const handguardHeight = frames.receiverHeight * 0.8;
5503
+ const railTopY = frames.receiverHeight / 2 + (frames.hasRail ? 6e-3 : 0);
5504
+ const forendMiddle = (frames.receiverEnd + frames.handguardEnd) / 2;
5505
+ const base = { mount, barrelRadius: frames.barrelRadius, receiverWidth: frames.receiverWidth, receiverHeight: frames.receiverHeight, hasRail: frames.hasRail };
5506
+ switch (mount) {
5507
+ case "muzzle":
5508
+ return { ...base, position: toRoot(frames.length, frames.barrelY, 0), side: 0 };
5509
+ case "rail_top":
5510
+ return { ...base, position: toRoot((frames.receiverStart + frames.receiverEnd) / 2 + 0.03, railTopY, 0), side: 0 };
5511
+ case "rail_bottom":
5512
+ return { ...base, position: toRoot(forendMiddle, frames.barrelY - handguardHeight / 2, 0), side: 0 };
5513
+ case "rail_left":
5514
+ return { ...base, position: toRoot(forendMiddle, frames.barrelY, -(frames.receiverWidth * 0.54 + 4e-3)), side: -1 };
5515
+ case "rail_right":
5516
+ return { ...base, position: toRoot(forendMiddle, frames.barrelY, frames.receiverWidth * 0.54 + 4e-3), side: 1 };
5517
+ case "underbarrel": {
5518
+ const [x, y] = frames.underbarrel ?? [frames.receiverEnd + (frames.handguardEnd - frames.receiverEnd) * 0.25, frames.barrelY - handguardHeight / 2 - 0.02];
5519
+ return { ...base, position: toRoot(x, y, 0), side: 0 };
5520
+ }
5521
+ case "magazine":
5522
+ return { ...base, position: toRoot(frames.magazineX, -frames.receiverHeight / 2, 0), side: 0 };
5523
+ case "feed": {
5524
+ const belt = model.parts.belt?.position;
5525
+ 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 };
5526
+ }
5527
+ case "sling":
5528
+ return {
5529
+ ...base,
5530
+ position: toRoot(frames.receiverEnd + (frames.length - frames.receiverEnd) * 0.45, -frames.receiverHeight / 2 - 0.03, 0),
5531
+ secondary: toRoot(0.08, -0.09, 0),
5532
+ side: 0
5533
+ };
5534
+ default:
5535
+ return void 0;
5463
5536
  }
5464
- const muzzleY = style.tri ? slideHeight * 0.08 : 2e-3;
5465
- heatify(parts.glow, cylinderX(slide2, style.tri ? 72e-4 : 62e-4, 4e-3, M.dark, L + 15e-4, muzzleY, 0, 18));
5466
- cylinderX(slide2, 34e-4, 5e-3, M.hole, L + 25e-4, muzzleY, 0, 12);
5467
- const top = slideHeight / 2;
5468
- if (style.sight === "u" || style.sight === "3dot") {
5469
- box2(slide2, 4e-3, 7e-3, 4e-3, M.dark, L - 8e-3, top + 35e-4, 0);
5470
- box2(slide2, 22e-4, 22e-4, 46e-4, M.white, L - 8e-3, top + 58e-4, 0);
5471
- box2(slide2, 8e-3, 7e-3, 0.016, M.dark, 9e-3, top + 35e-4, 0);
5472
- if (style.sight === "u") box2(slide2, 4e-3, 2e-3, 0.0122, M.white, 9e-3, top + 6e-3, 0);
5473
- else box2(slide2, 22e-4, 22e-4, 0.0166, M.white, 9e-3, top + 58e-4, 0);
5537
+ }
5538
+
5539
+ // src/models/bullpup.ts
5540
+ function buildBullpup(kit, options) {
5541
+ const { M, box: box2, cylinderX } = kit;
5542
+ const group = kit.group();
5543
+ const L = (options.L ?? 0) / 1e3;
5544
+ const barrel = (options.B ?? 0) / 1e3;
5545
+ const bodyLength = Math.max(0.32, L - barrel * 0.72);
5546
+ const body = options.shot ? M.poly : M.olive;
5547
+ const parts = { family: options.shot ? "pump" : "rifle", stroke: 0.06, glow: [] };
5548
+ box2(group, bodyLength, 0.075, 0.042, body, bodyLength / 2, 0, 0);
5549
+ box2(group, bodyLength * 0.9, 0.02, 0.044, M.dark, bodyLength * 0.5, -0.02, 0);
5550
+ box2(group, 0.032, 0.13, 0.044, M.rubber, 0.016, -0.025, 0);
5551
+ const gx = bodyLength * 0.8;
5552
+ const grip = kit.pivot(group, gx, -0.085, 0);
5553
+ grip.rotation.z = -0.25;
5554
+ parts.grip = grip;
5555
+ box2(grip, 0.03, 0.1, 0.03, M.poly, 0, 0, 0);
5556
+ box2(group, 0.1, 6e-3, 0.03, body, gx + 0.01, -0.06, 0);
5557
+ parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.02, -0.05, 0, 0.15);
5558
+ heatify(parts.glow, cylinderX(group, options.shot ? 0.0105 : 85e-4, barrel, M.metal, L - barrel / 2, 0.012, 0, 16));
5559
+ cylinderX(group, 6e-3, 4e-3, M.hole, L + 1e-3, 0.012, 0, 12);
5560
+ box2(group, 0.05, 0.024, 4e-3, M.hole, bodyLength * 0.25, 5e-3, 0.0212);
5561
+ const bolt = kit.pivot(group);
5562
+ parts.bolt = bolt;
5563
+ box2(bolt, 0.044, 0.02, 6e-3, M.steelL, bodyLength * 0.25, 5e-3, 0.02);
5564
+ if (options.shot) {
5565
+ cylinderX(group, 0.012, bodyLength * 0.75, M.metal, bodyLength * 0.55, -0.025, 0.014, 14);
5566
+ cylinderX(group, 0.012, bodyLength * 0.75, M.metal, bodyLength * 0.55, -0.025, -0.014, 14);
5567
+ const pump = kit.pivot(group);
5568
+ parts.pump = pump;
5569
+ box2(pump, 0.15, 0.045, 0.052, M.poly, bodyLength + 0.06, -0.015, 0);
5570
+ 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);
5571
+ rail(kit, group, bodyLength * 0.15, bodyLength * 0.8, 0.04, 0, 0.02);
5572
+ parts.loadPort = kit.mark(group, bodyLength * 0.2, -0.04, 0);
5474
5573
  } else {
5475
- box2(slide2, 6e-3, 4e-3, 3e-3, M.dark, L - 6e-3, top + 2e-3, 0);
5476
- box2(slide2, 6e-3, 4e-3, 0.012, M.dark, 8e-3, top + 2e-3, 0);
5477
- }
5478
- if (style.decock === "slide") {
5479
- box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.02, slideHeight * 0.15, slideWidth / 2 + 2e-3, 0.3);
5480
- box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.02, slideHeight * 0.15, -slideWidth / 2 - 2e-3, 0.3);
5481
- }
5482
- 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);
5483
- if (options.sel) {
5484
- const selector = box2(slide2, 0.01, 8e-3, 3e-3, M.dark, 0.014, -slideHeight * 0.2, slideWidth / 2 + 14e-4);
5485
- parts.safety ?? (parts.safety = selector);
5574
+ const magazine = kit.pivot(group, gx - 0.11, -0.03, 0);
5575
+ magazine.rotation.z = 0.14;
5576
+ parts.mag = magazine;
5577
+ box2(magazine, 0.05, 0.14, 0.026, M.glass, 0, -0.07, 0);
5578
+ for (let i = 0; i < 6; i++) cylinderX(magazine, 3e-3, 0.04, M.brass, 0, -0.01 - i * 0.02, 0, 6);
5579
+ parts.magDrop = 0.18;
5580
+ scope(kit, group, 2, bodyLength * 0.5, 0.04, 0);
5581
+ box2(group, 0.2, 0.03, 0.02, body, bodyLength * 0.62, 0.048, 0);
5582
+ box2(group, 0.026, 0.075, 0.026, body, bodyLength + 0.05, -0.05, 0, 0.1);
5486
5583
  }
5584
+ parts.muzzle = kit.mark(group, L + 0.01, 0.012, 0);
5585
+ parts.eject = kit.mark(group, bodyLength * 0.25, 5e-3, 0.03);
5586
+ return { group, parts };
5487
5587
  }
5488
5588
 
5489
- // src/models/pistol-style.ts
5490
- var STYLES = {
5491
- glock: { rf: 0.07, ga: -0.36, grip: "finger", guard: "square", rail: 1, sight: "u", port: 0.2, slideM: "metal", frame: "poly" },
5492
- taurus: { rf: 0.16, ga: -0.3, grip: "panel", guard: "square", rail: 1, sight: "3dot", port: 0.18, frontSer: 1, frame: "poly" },
5493
- g2c: { rf: 0.2, ga: -0.3, grip: "panel", guard: "round", rail: 0, sight: "3dot", port: 0.17, frame: "poly", safety: "thumb" },
5494
- beretta: { rf: 0.28, ga: -0.27, grip: "wood", guard: "round", hammer: "spur", sight: "3dot", open: 1, decock: "slide", port: 0, frame: "metal" },
5495
- pt100: { rf: 0.25, ga: -0.27, grip: "rubber", guard: "round", hammer: "spur", sight: "3dot", port: 0.17, decock: "frame", frame: "metal" },
5496
- 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" },
5497
- cz: { rf: 0.32, ga: -0.3, grip: "rubber", guard: "round", hammer: "spur", sight: "3dot", port: 0.15, inset: 1, safety: "thumb", frame: "metal" },
5498
- 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" },
5499
- 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" },
5500
- fiveseven: { rf: 0.2, ga: -0.4, grip: "chunky", guard: "square", rail: 1, sight: "3dot", port: 0.18, slope: 1, frame: "poly" },
5501
- deagle: { rf: 0.08, ga: -0.28, grip: "rubber", guard: "round", hammer: "big", sight: "small", port: 0.16, tri: 1, safety: "slide", frame: "metal" }
5589
+ // src/models/launcher-flame.ts
5590
+ var hose = (kit, group, points, radius, material) => {
5591
+ const { THREE } = kit;
5592
+ const curve = new THREE.CatmullRomCurve3(points.map((point) => new THREE.Vector3(...point)));
5593
+ const tube = new THREE.Mesh(new THREE.TubeGeometry(curve, 40, radius, 8, false), material);
5594
+ tube.castShadow = tube.receiveShadow = true;
5595
+ group.add(tube);
5502
5596
  };
5503
-
5504
- // src/models/pistol.ts
5505
- var PI6 = Math.PI;
5506
- function buildFrame(kit, group, parts, options, style, size, materials) {
5507
- const { M, box: box2, cylinderZ, sphere } = kit;
5508
- const { length: L, height: h, width: w } = size;
5509
- const fm = materials.frame;
5510
- if (style.inset) {
5511
- box2(group, L * 0.9, h * 0.35, w * 0.12, fm, L * 0.47, -h * 0.32, w * 0.47);
5512
- box2(group, L * 0.9, h * 0.35, w * 0.12, fm, L * 0.47, -h * 0.32, -w * 0.47);
5513
- }
5514
- const frameLength = style.tri ? L * 0.66 : L * 0.8;
5515
- kit.roundness = style.rf;
5516
- box2(group, frameLength, 0.016, w * 0.92, fm, frameLength / 2 + L * 0.06, -h / 2 - 8e-3, 0);
5517
- kit.roundness = null;
5518
- 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);
5519
- const isGlock = options.st === "glock" || !options.st;
5520
- if (isGlock) {
5521
- box2(group, 8e-3, 4e-3, 3e-3, M.dark, L * 0.5, -h / 2 - 3e-3, -w / 2 - 12e-4);
5522
- box2(group, 8e-3, 4e-3, 3e-3, M.dark, L * 0.5, -h / 2 - 3e-3, w / 2 + 12e-4);
5523
- }
5524
- if (style.slideStop) {
5525
- box2(group, 0.03, 6e-3, 3e-3, M.dark, L * 0.42, -h / 2 - 2e-3, -w / 2 - 14e-4);
5526
- sphere(group, 28e-4, M.dark, L * 0.55, -h / 2 - 3e-3, w / 2 + 1e-3, 1);
5527
- } else box2(group, 0.012, 4e-3, 3e-3, M.dark, L * 0.45, -h / 2 - 3e-3, -w / 2 - 12e-4);
5528
- 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);
5529
- 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);
5530
- const gripLength = options.gl ?? 0.105;
5531
- const gx = 0.034 + (L - 0.2) * 0.15;
5532
- const gy = -h / 2 - gripLength / 2 + 6e-3;
5533
- const grip = kit.pivot(group, gx, gy, 0);
5534
- grip.rotation.z = style.ga;
5535
- parts.grip = grip;
5536
- const gripWidth = style.grip === "chunky" ? w * 1.18 : w * 1.06;
5537
- const gripDepth = style.grip === "chunky" ? 0.036 : 0.032;
5538
- kit.roundness = 0.3;
5539
- if (style.grip === "wood" || style.grip === "star") {
5540
- box2(grip, gripDepth * 0.86, gripLength, w * 0.9, fm, 0, 0, 0);
5541
- box2(grip, gripDepth * 0.8, gripLength * 0.86, 4e-3, materials.grip, 1e-3, -2e-3, w * 0.47);
5542
- box2(grip, gripDepth * 0.8, gripLength * 0.86, 4e-3, materials.grip, 1e-3, -2e-3, -w * 0.47);
5543
- if (style.grip === "wood") {
5544
- 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);
5545
- cylinderZ(grip, 35e-4, 15e-4, M.brass, 0, gripLength * 0.25, w * 0.5, 10);
5546
- cylinderZ(grip, 35e-4, 15e-4, M.brass, 0, -gripLength * 0.3, w * 0.5, 10);
5547
- } else cylinderZ(grip, 8e-3, 15e-4, M.dark, 0, gripLength * 0.1, w * 0.5, 5);
5548
- } else box2(grip, gripDepth, gripLength, gripWidth, materials.grip, 0, 0, 0);
5549
- kit.roundness = null;
5550
- if (style.grip === "finger") {
5551
- 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);
5552
- 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);
5553
- }
5554
- 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);
5555
- if (style.grip === "chunky") box2(grip, 8e-3, gripLength * 0.8, gripWidth * 0.9, materials.grip, gripDepth / 2 + 2e-3, -4e-3, 0);
5556
- if (style.beaver) {
5557
- box2(group, 0.026, 8e-3, w * 0.9, fm, -4e-3, -h / 2 - 4e-3, 0, 0.25);
5558
- box2(grip, 6e-3, gripLength * 0.55, w * 0.9, fm, -gripDepth * 0.5 - 2e-3, gripLength * 0.18, 0);
5559
- } else box2(group, 0.02, 9e-3, w * 0.9, fm, 4e-3, -h / 2 - 6e-3, 0);
5560
- if (style.lanyard) {
5561
- const ring = kit.torus(grip, new kit.THREE.TorusGeometry(5e-3, 13e-4, 6, 12), M.dark, -0.012, -gripLength / 2 + 4e-3, 0);
5562
- ring.position.set(-0.012, -gripLength / 2 + 4e-3, 0);
5597
+ var pilot = (kit, group, x, y) => {
5598
+ const { M, cylinderX } = kit;
5599
+ cylinderX(group, 6e-3, 0.03, M.fire2, x + 0.015, y, 0, 6, 0).userData.flame = 1;
5600
+ cylinderX(group, 4e-3, 0.022, M.fire, x + 0.012, y, 0, 6, 0).userData.flame = 1;
5601
+ };
5602
+ function buildFlamethrower(kit, group, parts) {
5603
+ const { M, box: box2, cylinderX, cylinderY, sphere } = kit;
5604
+ parts.kick = 0.15;
5605
+ heatify(parts.glow, cylinderX(group, 0.016, 0.62, M.steel, 0.42, 0, 0, 14));
5606
+ cylinderX(group, 0.022, 0.1, M.dark, 0.78, 0, 0, 14);
5607
+ heatify(parts.glow, cylinderX(group, 0.013, 0.1, M.steel, 0.88, 0, 0, 12, 9e-3));
5608
+ cylinderX(group, 0.018, 0.02, M.metal, 0.73, 0, 0, 14);
5609
+ cylinderX(group, 0.012, 0.06, M.metal, 0.84, -0.025, 0, 10);
5610
+ pilot(kit, group, 0.92, -0.012);
5611
+ const foreGrip = kit.pivot(group, 0.6, -0.02, 0);
5612
+ foreGrip.rotation.z = -0.1;
5613
+ box2(foreGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5614
+ const lever = kit.pivot(group, 0.62, -0.02, 0);
5615
+ parts.trigger = box2(lever, 8e-3, 0.05, 6e-3, M.metal, 0.02, -0.03, 0, 0.3);
5616
+ const rearGrip = kit.pivot(group, 0.17, -0.02, 0);
5617
+ rearGrip.rotation.z = -0.28;
5618
+ box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5619
+ box2(group, 0.05, 4e-3, 0.012, M.metal, 0.2, -0.035, 0);
5620
+ cylinderX(group, 0.02, 0.04, M.metal, 0.1, 0, 0, 12);
5621
+ 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);
5622
+ for (const z of [-0.1, 0.1]) {
5623
+ cylinderY(group, 0.085, 0.5, M.olive, -0.3, -0.05, z, 20);
5624
+ sphere(group, 0.085, M.olive, -0.3, 0.2, z, 2).scale.y = 0.45;
5625
+ sphere(group, 0.085, M.olive, -0.3, -0.3, z, 2).scale.y = 0.45;
5626
+ cylinderY(group, 0.03, 0.03, M.metal, -0.3, 0.24, z, 12);
5563
5627
  }
5564
- const magazine = kit.pivot(grip);
5565
- parts.mag = magazine;
5566
- parts.magDrop = gripLength * 1.15;
5567
- box2(magazine, 0.022, gripLength * 0.94, w * 0.72, M.dark, 3e-3, -4e-3, 0);
5568
- if (style.grip === "chunky") box2(magazine, 0.04, 0.01, gripWidth * 1.02, M.poly, 3e-3, -gripLength / 2 - 4e-3, 0);
5569
- 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);
5570
- else box2(magazine, 0.031, 6e-3, gripWidth * 1.02, M.dark, 3e-3, -gripLength / 2 - 2e-3, 0);
5571
- return { gx, ty: -h / 2 };
5628
+ sphere(group, 0.075, M.olive, -0.3, 0.1, 0, 2).position.x = -0.39;
5629
+ box2(group, 0.03, 0.6, 0.24, M.dark, -0.4, -0.05, 0);
5630
+ cylinderY(group, 0.03, 0.012, M.metal, -0.39, 0.18, 0, 14);
5631
+ const gauge = kit.pivot(group, -0.39, 0.18, 3e-3);
5632
+ parts.gauge = gauge;
5633
+ box2(gauge, 2e-3, 0.022, 2e-3, M.red, 0, 8e-3, 0.012);
5634
+ parts.muzzle = kit.pivot(group, 0.94, 0, 0);
5635
+ parts.rear = kit.pivot(group, 0.1, 0, 0);
5572
5636
  }
5573
- function buildPistol(kit, options) {
5574
- const { M, box: box2 } = kit;
5575
- const style = { ...STYLES[options.st ?? "glock"] };
5576
- const group = kit.group();
5577
- const parts = { family: "pistol", kick: options.deagle ? 1.8 : 0.6, glow: [] };
5578
- 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 };
5579
- const { length: L, height: h, width: w } = size;
5580
- const slideMetal = options.steel ? M.steel : options.stain ? M.steelL : M.metal;
5581
- const frameMetal = style.frame === "poly" ? M.poly : slideMetal;
5582
- const gripMaterial = style.grip === "wood" ? M.wood : style.grip === "star" ? M.bake : style.grip === "rubber" ? M.rubber : M.poly;
5583
- const slide2 = kit.pivot(group);
5584
- parts.slide = slide2;
5585
- parts.stroke = L * 0.15;
5586
- buildSlide(kit, slide2, parts, options, style, size, slideMetal);
5587
- const { gx, ty } = buildFrame(kit, group, parts, options, style, size, { frame: frameMetal, grip: gripMaterial });
5588
- if (style.guard === "square") {
5589
- box2(group, 0.046, 4e-3, 9e-3, frameMetal, gx + 0.05, ty - 0.034, 0);
5590
- box2(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.074, ty - 0.02, 0);
5591
- box2(group, 6e-3, 4e-3, 9e-3, frameMetal, gx + 0.075, ty - 0.034, 0);
5592
- } else if (style.guard === "hook") {
5593
- box2(group, 0.046, 4e-3, 9e-3, frameMetal, gx + 0.05, ty - 0.034, 0);
5594
- box2(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.073, ty - 0.02, 0, 0.2);
5595
- box2(group, 8e-3, 6e-3, 9e-3, frameMetal, gx + 0.074, ty - 0.037, 0, 0.4);
5596
- } else {
5597
- const guard = kit.torus(group, new kit.THREE.TorusGeometry(0.019, 22e-4, 6, 16, PI6), frameMetal, gx + 0.05, ty - 0.014, 0);
5598
- guard.rotation.z = PI6;
5599
- guard.scale.set(1.2, 1, 1);
5600
- }
5601
- const isGlock = options.st === "glock" || !options.st;
5602
- parts.trigger = box2(group, 4e-3, 0.014, 4e-3, isGlock ? M.dark : M.steel, gx + 0.042, ty - 0.022, 0, 0.2);
5603
- if (isGlock) box2(parts.trigger, 15e-4, 8e-3, 45e-4, M.metal, 0, -1e-3, 0);
5604
- if (style.hammer) {
5605
- const hammer = kit.pivot(group, 3e-3, h / 2 - 8e-3, 0);
5606
- parts.hammer = hammer;
5607
- if (style.hammer === "ring") {
5608
- box2(hammer, 6e-3, 0.012, 6e-3, M.dark, -2e-3, 4e-3, 0, 0.3);
5609
- kit.torus(hammer, new kit.THREE.TorusGeometry(45e-4, 16e-4, 6, 12), M.dark, -4e-3, 0.012, 0);
5610
- } else if (style.hammer === "big") box2(hammer, 0.014, 0.02, 9e-3, M.dark, -4e-3, 7e-3, 0, 0.35);
5611
- else {
5612
- box2(hammer, 8e-3, 0.016, 7e-3, M.dark, -2e-3, 6e-3, 0, 0.3);
5613
- box2(hammer, 8e-3, 4e-3, 8e-3, M.dark, -6e-3, 0.013, 0, 0.3);
5614
- }
5615
- }
5616
- parts.muzzle = kit.mark(group, L + 6e-3, style.tri ? h * 0.08 : 2e-3, 0);
5617
- parts.eject = kit.mark(group, L * 0.6, h / 2, w * 0.25);
5618
- return { group, parts };
5637
+ function buildImprovisedFlamethrower(kit, group, parts) {
5638
+ const { M, box: box2, cylinderX, cylinderY, sphere } = kit;
5639
+ parts.kick = 0.12;
5640
+ heatify(parts.glow, cylinderX(group, 0.011, 0.66, M.pipe, 0.38, 0, 0, 12));
5641
+ heatify(parts.glow, cylinderX(group, 0.015, 0.06, M.steel, 0.74, 0, 0, 12, 0.01));
5642
+ cylinderX(group, 6e-3, 0.05, M.metal, 0.735, -0.02, 0, 8);
5643
+ pilot(kit, group, 0.77, -0.02);
5644
+ cylinderY(group, 0.012, 0.04, M.redPaint, 0.72, -0.055, 0, 10);
5645
+ box2(group, 0.02, 0.03, 0.012, M.dark, 0.72, -0.085, 0);
5646
+ cylinderX(group, 0.06, 0.42, M.redPaint, 0.3, -0.09, 0, 22);
5647
+ sphere(group, 0.06, M.redPaint, 0.51, -0.09, 0, 2).scale.x = 0.5;
5648
+ sphere(group, 0.06, M.redPaint, 0.09, -0.09, 0, 2).scale.x = 0.5;
5649
+ cylinderX(group, 0.02, 0.04, M.dark, 0.04, -0.09, 0, 12);
5650
+ box2(group, 0.02, 0.06, 5e-3, M.white, 0.3, -0.09, 0.06);
5651
+ for (const x of [0.15, 0.3, 0.45]) cylinderX(group, 0.063, 0.03, M.tape, x, -0.09, 0, 22);
5652
+ cylinderX(group, 0.016, 0.03, M.tape, 0.3, 0, 0, 12);
5653
+ 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);
5654
+ const rearGrip = kit.pivot(group, 0.12, -0.01, 0);
5655
+ rearGrip.rotation.z = -0.3;
5656
+ box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5657
+ const lever = kit.pivot(group, 0.17, -0.02, 0);
5658
+ parts.trigger = box2(lever, 8e-3, 0.04, 6e-3, M.metal, 0, -0.02, 0, 0.2);
5659
+ parts.muzzle = kit.pivot(group, 0.78, 0, 0);
5660
+ parts.rear = kit.pivot(group, 0.05, 0, 0);
5619
5661
  }
5620
5662
 
5621
- // src/models/revolver.ts
5622
- var PI7 = Math.PI;
5623
- var DEFAULT_CHAMBERS = 6;
5624
- function buildRevolver(kit, options) {
5663
+ // src/models/launcher-rounds.ts
5664
+ var PI6 = Math.PI;
5665
+ function buildRocketGrenade(kit) {
5666
+ const { M, cylinderX } = kit;
5667
+ const round2 = kit.group();
5668
+ cylinderX(round2, 0.018, 0.15, M.olive, -0.05, 0, 0, 14);
5669
+ cylinderX(round2, 0.02, 0.07, M.olive, 0.035, 0, 0, 18, 0.0425);
5670
+ cylinderX(round2, 0.0425, 0.12, M.olive, 0.13, 0, 0, 20);
5671
+ cylinderX(round2, 0.0428, 0.012, M.dark, 0.09, 0, 0, 20);
5672
+ cylinderX(round2, 0.0425, 0.13, M.olive, 0.255, 0, 0, 20, 0.011);
5673
+ cylinderX(round2, 0.011, 0.03, M.metal, 0.335, 0, 0, 10, 4e-3);
5674
+ return round2;
5675
+ }
5676
+ function buildRecoillessRound(kit) {
5677
+ const { M, cylinderX } = kit;
5678
+ const round2 = kit.group();
5679
+ cylinderX(round2, 0.041, 0.32, M.green, -0.16, 0, 0, 20);
5680
+ cylinderX(round2, 0.0415, 0.02, M.yellow, -0.05, 0, 0, 20);
5681
+ cylinderX(round2, 0.041, 0.12, M.green, 0.06, 0, 0, 20, 0.012);
5682
+ cylinderX(round2, 0.043, 0.03, M.brass, -0.33, 0, 0, 20);
5683
+ return round2;
5684
+ }
5685
+ function buildFlyingRocket(kit, launcherType) {
5625
5686
  const { M, box: box2, cylinderX } = kit;
5626
- const group = kit.group();
5627
- const parts = { family: "revolver", kick: options.cal === ".44 Magnum" ? 1.5 : 0.9, glow: [] };
5628
- const barrel = (options.B ?? 0) / 1e3;
5629
- const chambers = options.chambers ?? DEFAULT_CHAMBERS;
5630
- const metal2 = options.steel ? M.steel : M.metal;
5631
- box2(group, 0.08, 0.044, 0.024, metal2, 0.07, 0, 0);
5632
- box2(group, 0.036, 0.012, 0.022, metal2, 0.096, 0.022, 0);
5633
- box2(group, 0.024, 0.012, 4e-3, M.dark, 0.055, 6e-3, -0.013);
5634
- const crane = kit.pivot(group, 0.077, -0.024, 0);
5635
- parts.crane = crane;
5636
- const cylinder = kit.pivot(crane, 0, 0.022, 0);
5637
- parts.cylinder = cylinder;
5638
- cylinderX(cylinder, 0.0215, 0.042, metal2, 0, 0, 0, 24);
5639
- for (let i = 0; i < chambers; i++) {
5640
- const angle = i * 2 * PI7 / chambers;
5641
- box2(cylinder, 0.026, 5e-3, 5e-3, M.dark, 0, Math.cos(angle) * 0.0205, Math.sin(angle) * 0.0205, 0, 0, angle);
5642
- }
5643
- parts.chambers = [];
5644
- for (let i = 0; i < chambers; i++) {
5645
- const angle = i * 2 * PI7 / chambers + PI7 / chambers;
5646
- parts.chambers.push(kit.mark(cylinder, -0.022, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013));
5647
- cylinderX(cylinder, 46e-4, 2e-3, M.brass, -0.0215, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013, 12);
5687
+ const rocket = kit.group();
5688
+ const diameter = launcherType === "at4" || launcherType === "carl" ? 0.041 : 0.032;
5689
+ cylinderX(rocket, diameter * 0.5, 0.2, M.dark, -0.1, 0, 0, 10);
5690
+ cylinderX(rocket, diameter, 0.3, M.olive, 0.15, 0, 0, 18);
5691
+ cylinderX(rocket, diameter, 0.12, M.olive, 0.36, 0, 0, 18, diameter * 0.25);
5692
+ for (let i = 0; i < 4; i++) {
5693
+ const fin = box2(rocket, 0.06, 2e-3, diameter * 1.4, M.metal, -0.17, 0, 0);
5694
+ fin.rotation.x = i * PI6 / 4;
5648
5695
  }
5649
- heatify(parts.glow, cylinderX(group, 88e-4, barrel, metal2, 0.112 + barrel / 2, 9e-3, 0, 18));
5650
- box2(group, barrel * 0.92, 0.015, 0.013, metal2, 0.112 + barrel * 0.46, -5e-3, 0);
5651
- cylinderX(group, 4e-3, 5e-3, M.hole, 0.112 + barrel + 15e-4, 9e-3, 0, 12);
5652
- box2(group, 0.012, 9e-3, 4e-3, M.dark, 0.112 + barrel - 8e-3, 0.021, 0, -0.3);
5653
- box2(group, 0.012, 6e-3, 0.012, M.dark, 0.105, 0.031, 0);
5654
- const hammer = kit.pivot(group, 0.036, 0.018, 0);
5655
- box2(hammer, 0.01, 0.02, 8e-3, M.dark, -4e-3, 8e-3, 0, 0.45);
5656
- parts.hammer = hammer;
5657
- const grip = kit.pivot(group, 0.024, -0.062, 0);
5658
- grip.rotation.z = -0.38;
5659
- parts.grip = grip;
5660
- box2(grip, 0.034, 0.1, 0.031, options.wood ? M.wood : M.rubber, 0, 0, 0);
5661
- 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);
5662
- box2(group, 0.038, 4e-3, 8e-3, metal2, 0.062, -0.036, 0);
5663
- box2(group, 4e-3, 0.022, 8e-3, metal2, 0.08, -0.026, 0, 0.2);
5664
- parts.trigger = box2(group, 4e-3, 0.015, 4e-3, M.dark, 0.06, -0.027, 0, 0.2);
5665
- parts.muzzle = kit.mark(group, 0.112 + barrel + 6e-3, 9e-3, 0);
5666
- parts.eject = kit.mark(group, 0.077, 0, 0);
5667
- return { group, parts };
5696
+ return rocket;
5668
5697
  }
5669
5698
 
5670
- // src/models/attachments/muzzle-brake.ts
5671
- function buildBrake(kit, parent, kind, x, y, barrelRadius, heat = () => void 0) {
5699
+ // src/models/launcher-rocket.ts
5700
+ function buildRpg(kit, group, parts) {
5672
5701
  const { M, box: box2, cylinderX } = kit;
5673
- const scale = kind === 2 ? 3.2 : 2;
5674
- const length = kind === 2 ? 0.12 : 0.06;
5675
- heat(cylinderX(parent, barrelRadius * scale, length, M.dark, x - length / 2, y, 0, kind === 2 ? 8 : 16));
5676
- 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);
5677
- }
5678
-
5679
- // src/models/capture.ts
5680
- function captured(parent, draw) {
5681
- const before = parent.children.length;
5682
- draw();
5683
- return parent.children.slice(before);
5702
+ parts.kick = 1.6;
5703
+ cylinderX(group, 0.036, 0.13, M.dark, 0.065, 0, 0, 20, 0.021);
5704
+ cylinderX(group, 0.038, 0.012, M.metal, 0, 0, 0, 20);
5705
+ heatify(parts.glow, cylinderX(group, 0.021, 0.83, M.steel, 0.545, 0, 0, 18));
5706
+ cylinderX(group, 0.03, 0.3, M.wood, 0.47, 0, 0, 16);
5707
+ cylinderX(group, 0.031, 0.012, M.metal, 0.32, 0, 0, 16);
5708
+ cylinderX(group, 0.031, 0.012, M.metal, 0.62, 0, 0, 16);
5709
+ cylinderX(group, 0.024, 0.02, M.metal, 0.94, 0, 0, 16);
5710
+ box2(group, 0.09, 0.03, 0.032, M.metal, 0.46, -0.028, 0);
5711
+ const frontGrip = kit.pivot(group, 0.4, -0.04, 0);
5712
+ frontGrip.rotation.z = -0.22;
5713
+ box2(frontGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5714
+ const rearGrip = kit.pivot(group, 0.5, -0.04, 0);
5715
+ rearGrip.rotation.z = -0.18;
5716
+ box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
5717
+ box2(group, 0.06, 4e-3, 0.012, M.metal, 0.53, -0.05, 0);
5718
+ box2(group, 4e-3, 0.025, 0.012, M.metal, 0.56, -0.04, 0);
5719
+ parts.trigger = box2(group, 4e-3, 0.018, 4e-3, M.dark, 0.535, -0.045, 0, 0.15);
5720
+ const hammer = kit.pivot(group, 0.44, -0.012, 0);
5721
+ parts.hammer = hammer;
5722
+ box2(hammer, 0.012, 0.02, 0.01, M.dark, 0, 0, 0);
5723
+ box2(group, 6e-3, 0.03, 6e-3, M.dark, 0.78, 0.032, 0);
5724
+ box2(group, 0.02, 0.022, 0.014, M.dark, 0.46, 0.032, 0);
5725
+ box2(group, 0.12, 0.042, 0.032, M.dark, 0.5, 0.04, -0.04);
5726
+ cylinderX(group, 0.013, 0.05, M.rubber, 0.42, 0.048, -0.04, 14);
5727
+ cylinderX(group, 0.016, 4e-3, M.lens, 0.565, 0.048, -0.04, 14);
5728
+ box2(group, 0.04, 0.012, 0.012, M.metal, 0.5, 0.016, -0.03);
5729
+ const round2 = buildRocketGrenade(kit);
5730
+ round2.position.x = 0.95;
5731
+ group.add(round2);
5732
+ parts.round = round2;
5733
+ parts.roundRest = 0.95;
5734
+ parts.makeRound = () => buildRocketGrenade(kit);
5735
+ parts.muzzle = kit.pivot(group, 0.95, 0, 0);
5736
+ parts.rear = kit.pivot(group, 0, 0, 0);
5684
5737
  }
5685
- function recordFitting(parts, id, parent, draw) {
5686
- parts.fittings = { ...parts.fittings, [id]: captured(parent, draw) };
5738
+ function buildM72(kit, group, parts) {
5739
+ const { M, box: box2, cylinderX, cylinderY } = kit;
5740
+ parts.kick = 1.1;
5741
+ parts.disposable = true;
5742
+ heatify(parts.glow, cylinderX(group, 0.0365, 0.6, M.olive, 0.58, 0, 0, 20));
5743
+ cylinderX(group, 0.032, 0.32, M.olive, 0.16, 0, 0, 20);
5744
+ cylinderX(group, 0.039, 0.03, M.rubber, 0.866, 0, 0, 20);
5745
+ cylinderX(group, 0.035, 0.025, M.rubber, 0.012, 0, 0, 20);
5746
+ cylinderX(group, 0.037, 0.015, M.yellow, 0.8, 0, 0, 20);
5747
+ cylinderX(group, 0.037, 0.012, M.dark, 0.3, 0, 0, 20);
5748
+ box2(group, 4e-3, 0.05, 0.05, M.dark, 0.79, 0.055, 0);
5749
+ box2(group, 4e-3, 6e-3, 0.04, M.dark, 0.79, 0.078, 0);
5750
+ box2(group, 4e-3, 0.035, 0.024, M.dark, 0.38, 0.05, 0);
5751
+ cylinderY(group, 4e-3, 6e-3, M.lens, 0.38, 0.064, 0, 8);
5752
+ parts.trigger = box2(group, 0.05, 0.016, 0.024, M.rubber, 0.5, 0.042, 0);
5753
+ box2(group, 0.1, 0.01, 0.03, M.dark, 0.47, 0.036, 0);
5754
+ box2(group, 0.02, 0.03, 0.01, M.dark, 0.66, -0.035, 0.03);
5755
+ box2(group, 8e-3, 0.03, 8e-3, M.metal, 0.25, -0.04, 0.03);
5756
+ parts.muzzle = kit.pivot(group, 0.881, 0, 0);
5757
+ parts.rear = kit.pivot(group, 0, 0, 0);
5687
5758
  }
5688
-
5689
- // src/models/attachments/bipod.ts
5690
- function buildBipod(kit, parent, x, y) {
5759
+ function buildAt4(kit, group, parts) {
5691
5760
  const { M, box: box2, cylinderX } = kit;
5692
- cylinderX(parent, 8e-3, 0.02, M.dark, x, y, 0, 12);
5693
- for (const side of [-1, 1]) {
5694
- const leg = kit.pivot(parent, x, y, side * 0.012);
5695
- leg.rotation.set(side * 0.15, 0, -0.35);
5696
- box2(leg, 6e-3, 0.2, 6e-3, M.dark, 0, -0.1, 0);
5697
- box2(leg, 0.01, 6e-3, 0.01, M.rubber, 0, -0.2, 0);
5698
- }
5761
+ parts.kick = 1.3;
5762
+ parts.disposable = true;
5763
+ heatify(parts.glow, cylinderX(group, 0.05, 0.86, M.olive, 0.51, 0, 0, 24));
5764
+ cylinderX(group, 0.056, 0.08, M.dark, 0.98, 0, 0, 24);
5765
+ cylinderX(group, 0.058, 0.07, M.dark, 0.04, 0, 0, 24, 0.05);
5766
+ cylinderX(group, 0.051, 0.015, M.yellow, 0.86, 0, 0, 24);
5767
+ box2(group, 0.16, 0.026, 0.05, M.dark, 0.42, 0.058, 0);
5768
+ box2(group, 0.03, 0.012, 0.016, M.red, 0.47, 0.075, 0);
5769
+ parts.trigger = box2(group, 0.022, 0.012, 0.018, M.rubber, 0.5, 0.074, 0);
5770
+ box2(group, 0.012, 0.07, 0.02, M.dark, 0.35, 0.09, -0.035);
5771
+ box2(group, 0.012, 0.05, 0.02, M.dark, 0.71, 0.08, -0.035);
5772
+ const grip = kit.pivot(group, 0.68, -0.05, 0);
5773
+ grip.rotation.z = -0.15;
5774
+ parts.grip = grip;
5775
+ box2(grip, 0.035, 0.11, 0.032, M.dark, 0, -0.05, 0);
5776
+ box2(group, 0.14, 0.035, 0.06, M.rubber, 0.32, -0.06, 0);
5777
+ box2(group, 0.3, 0.012, 0.055, M.dark, 0.5, -0.05, 0);
5778
+ parts.muzzle = kit.pivot(group, 1.02, 0, 0);
5779
+ parts.rear = kit.pivot(group, 0, 0, 0);
5699
5780
  }
5700
- var buildBipodModel = ({ kit }) => {
5701
- const group = kit.group();
5702
- buildBipod(kit, group, 0, 0);
5703
- return group;
5704
- };
5705
-
5706
- // src/models/rifle-fittings.ts
5707
- var SCOPE_IDS = { 1: "red_dot", 2: "acog_4x", 3: "scope_3_9x", 4: "scope_6_24x" };
5708
- function buildFittings(context, gripX) {
5709
- const { kit, group, parts, options: o, furniture: furn, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
5710
- const { M, box: box2, cylinderX, cylinderZ, sphere } = kit;
5711
- const middle = (x0 + x1) / 2;
5712
- 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));
5713
- if (o.carry) {
5714
- box2(group, 0.15, 0.026, 0.014, M.metal, middle, hR / 2 + 0.03, 0);
5715
- box2(group, 0.012, 0.03, 0.014, M.metal, middle + 0.07, hR / 2 + 0.014, 0);
5716
- box2(group, 0.012, 0.03, 0.014, M.metal, middle - 0.07, hR / 2 + 0.014, 0);
5717
- }
5718
- if (o.bipod) recordFitting(parts, "bipod", group, () => buildBipod(kit, group, x1 + (L - x1) * 0.45 + 0.04, by - 0.012));
5719
- if (o.bolt) {
5720
- const handle = kit.pivot(group, x1 - 0.045, by, 0);
5721
- parts.boltHandle = handle;
5722
- parts.boltHandleRest = handle.position.x;
5723
- cylinderZ(handle, 42e-4, 0.05, M.steelL, 0, 0, wR / 2 + 0.022, 12);
5724
- sphere(handle, 95e-4, M.dark, 0, -4e-3, wR / 2 + 0.048, 1);
5725
- cylinderX(group, hR * 0.3, 0.03, M.steel, x0 + 5e-3, by, 0, 16);
5726
- }
5727
- if (o.lever) {
5728
- const lever = kit.pivot(group, gripX - 0.02, -hR / 2 - 0.01, 0);
5729
- parts.lever = lever;
5730
- box2(lever, 0.11, 6e-3, 0.012, M.metal, 0.055, -0.052, 0);
5731
- box2(lever, 6e-3, 0.05, 0.012, M.metal, 3e-3, -0.028, 0);
5732
- box2(lever, 6e-3, 0.05, 0.012, M.metal, 0.108, -0.028, 0);
5733
- box2(lever, 0.04, 6e-3, 0.012, M.metal, 0.08, -2e-3, 0);
5734
- box2(group, 0.03, 0.012, 3e-3, M.dark, x1 - 0.04, by, wR / 2 + 1e-3);
5735
- }
5736
- if (o.tube) {
5737
- const tubeLength = (L - x1) * (o.tubeLen || 0.8);
5738
- cylinderX(group, 0.0105, tubeLength, M.metal, x1 + tubeLength / 2, by - 0.023, 0, 16);
5739
- cylinderX(group, 0.0125, 0.02, M.dark, x1 + tubeLength + 6e-3, by - 0.023, 0, 16);
5740
- }
5741
- if (o.pump) {
5742
- const pump = kit.pivot(group);
5743
- parts.pump = pump;
5744
- box2(pump, 0.17, 0.042, 0.046, furn, x1 + 0.14, by - 0.022, 0);
5745
- 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);
5746
- }
5747
- if (o.heat) {
5748
- cylinderX(group, r * 1.75, (L - x1) * 0.7, M.dark, x1 + (L - x1) * 0.4, by, 0, 12);
5749
- 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);
5750
- }
5751
- if (o.bayo) box2(group, 0.025, 8e-3, 0.01, M.metal, L - 0.07, by - 0.016, 0);
5752
- if (o.rod) cylinderX(group, 3e-3, (L - x1) * 0.85, M.steel, x1 + (L - x1) * 0.45, by - 0.014, 0, 8);
5781
+ function buildCarlGustaf(kit, group, parts) {
5782
+ const { M, box: box2, cylinderX } = kit;
5783
+ parts.kick = 1.4;
5784
+ heatify(parts.glow, cylinderX(group, 0.045, 0.72, M.dark, 0.59, 0, 0, 24));
5785
+ cylinderX(group, 0.05, 0.04, M.rubber, 0.93, 0, 0, 24);
5786
+ const breech = kit.pivot(group, 0.21, 0, 0.05);
5787
+ parts.breech = [breech];
5788
+ cylinderX(breech, 0.06, 0.16, M.dark, -0.1, 0, -0.05, 24, 0.046);
5789
+ cylinderX(breech, 0.062, 0.015, M.metal, -0.18, 0, -0.05, 24);
5790
+ box2(group, 0.025, 0.03, 0.02, M.metal, 0.22, 0, 0.052);
5791
+ const foreGrip = kit.pivot(group, 0.62, -0.05, 0);
5792
+ foreGrip.rotation.z = -0.2;
5793
+ box2(foreGrip, 0.035, 0.11, 0.034, M.dark, 0, -0.05, 0);
5794
+ parts.trigger = box2(group, 4e-3, 0.018, 4e-3, M.dark, 0.66, -0.06, 0, 0.15);
5795
+ box2(group, 0.06, 4e-3, 0.012, M.metal, 0.66, -0.07, 0);
5796
+ const rearGrip = kit.pivot(group, 0.4, -0.05, 0);
5797
+ rearGrip.rotation.z = -0.25;
5798
+ box2(rearGrip, 0.035, 0.1, 0.034, M.dark, 0, -0.05, 0);
5799
+ box2(group, 0.13, 0.04, 0.07, M.rubber, 0.28, -0.065, 0);
5800
+ box2(group, 0.16, 0.05, 0.045, M.dark, 0.56, 0.07, -0.035);
5801
+ cylinderX(group, 0.016, 0.05, M.rubber, 0.46, 0.075, -0.035, 14);
5802
+ cylinderX(group, 0.02, 4e-3, M.lens, 0.645, 0.075, -0.035, 14);
5803
+ box2(group, 0.06, 0.02, 0.02, M.metal, 0.56, 0.04, -0.03);
5804
+ box2(group, 0.22, 0.01, 0.02, M.dark, 0.6, 0.06, 0.03);
5805
+ box2(group, 0.01, 0.03, 0.02, M.dark, 0.5, 0.05, 0.03);
5806
+ box2(group, 0.01, 0.03, 0.02, M.dark, 0.7, 0.05, 0.03);
5807
+ parts.makeRound = () => buildRecoillessRound(kit);
5808
+ parts.muzzle = kit.pivot(group, 0.95, 0, 0);
5809
+ parts.rear = kit.pivot(group, 0.05, 0, 0);
5753
5810
  }
5811
+ var ROCKET_BUILDERS = {
5812
+ rpg: buildRpg,
5813
+ m72: buildM72,
5814
+ at4: buildAt4,
5815
+ carl: buildCarlGustaf
5816
+ };
5754
5817
 
5755
- // src/models/rifle-forend.ts
5756
- var PI8 = Math.PI;
5757
- function buildForend(context) {
5758
- const { kit, group, options: o, furniture: furn, length: L, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
5759
- const { M, box: box2 } = kit;
5760
- const hasLongScope = (o.scope ?? 0) >= 3;
5761
- if (!context.isTraditional && o.hg !== "none") {
5762
- const handguardLength = (L - x1) * (o.hgFrac || 0.5);
5763
- const hx = x1 + handguardLength / 2;
5764
- const hh = hR * 0.8;
5765
- if (o.hg === "rail") {
5766
- box2(group, handguardLength, hh, wR * 1.04, M.dark, hx, by - 2e-3, 0);
5767
- rail(kit, group, x1, handguardLength, by + hh / 2, 0, wR * 0.66);
5768
- rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, wR * 0.54, wR * 0.4, PI8 / 2);
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 - hh / 2, 0, wR * 0.6, PI8);
5771
- } else {
5772
- const material = o.hg === "wood" ? M.wood : furn;
5773
- if (o.ak) {
5774
- box2(group, handguardLength, hh * 0.55, wR * 1.08, material, hx, by - hh * 0.22, 0);
5775
- box2(group, handguardLength * 0.75, hh * 0.35, wR * 0.7, material, hx - handguardLength * 0.1, by + r * 2.2, 0);
5776
- } else {
5777
- box2(group, handguardLength, hh, wR * 1.08, material, hx, by - 2e-3, 0);
5778
- 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);
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
- }
5781
- }
5782
- if (!hasLongScope && !o.rail && o.ak) {
5783
- box2(group, 0.03, 0.016, wR * 0.6, M.metal, x1 + 0.02, by + r * 2.5 + 0.01, 0);
5784
- box2(group, 0.012, 4e-3, wR * 0.5, M.dark, x1 + 0.03, by + r * 2.5 + 0.019, 0);
5785
- }
5786
- }
5787
- if (hasLongScope) return;
5788
- const sightX = L - (o.ak ? 0.06 : 0.08);
5789
- if (o.ak) {
5790
- box2(group, 0.022, 0.022, wR * 0.6, M.metal, sightX, by + 8e-3, 0);
5791
- box2(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, 8e-3);
5792
- box2(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, -8e-3);
5793
- box2(group, 3e-3, 0.016, 3e-3, M.dark, sightX, by + 0.022, 0);
5794
- } else if (!o.rail || o.carry) {
5795
- box2(group, 0.018, 0.014, wR * 0.55, M.dark, sightX, by + 8e-3, 0);
5796
- box2(group, 0.012, 0.034, 8e-3, M.dark, sightX, by + 0.03, 0, -0.25);
5797
- } else box2(group, 0.012, 0.024, 0.012, M.dark, sightX - 0.04, by + hR * 0.4 + 0.012, 0);
5818
+ // src/models/launcher.ts
5819
+ function buildLauncher(kit, options) {
5820
+ const group = kit.group();
5821
+ const type = options.lt ?? "";
5822
+ const isFlame = type === "flame" || type === "flameDIY";
5823
+ const parts = { family: isFlame ? "flame" : "rocket", launcherType: type, glow: [] };
5824
+ if (type === "flame") buildFlamethrower(kit, group, parts);
5825
+ else if (type === "flameDIY") buildImprovisedFlamethrower(kit, group, parts);
5826
+ else ROCKET_BUILDERS[type]?.(kit, group, parts);
5827
+ parts.eject = parts.muzzle;
5828
+ return { group, parts };
5798
5829
  }
5799
5830
 
5800
- // src/models/attachments/belt-box.ts
5801
- function buildBeltBox(kit, belt) {
5802
- const { M, box: box2, cylinderX } = kit;
5803
- box2(belt, 0.11, 0.1, 0.08, M.olive, 0, 0, 0);
5804
- box2(belt, 0.112, 0.012, 0.082, M.dark, 0, 0.046, 0);
5805
- for (let i = 0; i < 7; i++) {
5806
- cylinderX(belt, 42e-4, 0.034, M.brass, -0.04 + i * 0.012, 0.07, 0.035, 10);
5807
- box2(belt, 6e-3, 6e-3, 0.012, M.metal, -0.04 + i * 0.012, 0.066, 0.035);
5831
+ // src/models/m134.ts
5832
+ var PI7 = Math.PI;
5833
+ var BARRELS = 6;
5834
+ var CLUSTER_RADIUS = 0.03;
5835
+ var BARREL_RADIUS = 78e-4;
5836
+ var BARREL_END = 0.8;
5837
+ function buildBarrelCluster(kit, parts, group) {
5838
+ const { M, cylinderX } = kit;
5839
+ const barrels = kit.pivot(group, 0, 0, 0);
5840
+ parts.barrels = barrels;
5841
+ const length = BARREL_END - 0.3;
5842
+ let heated;
5843
+ for (let i = 0; i < BARRELS; i++) {
5844
+ const angle = i * 2 * PI7 / BARRELS;
5845
+ const y = Math.cos(angle) * CLUSTER_RADIUS;
5846
+ const z = Math.sin(angle) * CLUSTER_RADIUS;
5847
+ const barrel = cylinderX(barrels, BARREL_RADIUS, length, M.metal, 0.3 + length / 2, y, z, 14);
5848
+ if (heated) barrel.material = heated;
5849
+ else heated = heatify(parts.glow, barrel).material;
5850
+ cylinderX(barrels, BARREL_RADIUS * 0.55, 4e-3, M.hole, BARREL_END + 5e-4, y, z, 10);
5808
5851
  }
5852
+ cylinderX(barrels, 0.012, length, M.steel, 0.3 + length / 2, 0, 0, 12);
5853
+ 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);
5854
+ cylinderX(barrels, CLUSTER_RADIUS + BARREL_RADIUS * 1.8, 0.03, M.dark, BARREL_END - 0.015, 0, 0, 24);
5809
5855
  }
5810
-
5811
- // src/models/attachments/drum.ts
5812
- function buildDrum(kit, magazine) {
5813
- const { M, box: box2, cylinderZ } = kit;
5814
- cylinderZ(magazine, 0.075, 0.07, M.dark, 0.02, -0.085, 0, 22);
5815
- cylinderZ(magazine, 0.05, 0.074, M.metal, 0.02, -0.085, 0, 18);
5816
- box2(magazine, 0.05, 0.04, 0.03, M.dark, 0, -0.02, 0);
5856
+ function buildHousing(kit, parts, group) {
5857
+ const { M, box: box2, cylinderX, cylinderZ } = kit;
5858
+ box2(group, 0.3, 0.14, 0.15, M.metal, 0.18, 0, 0);
5859
+ cylinderX(group, 0.052, 0.12, M.dark, 0.06, 0, 0, 20);
5860
+ cylinderX(group, 0.062, 0.2, M.dark, 0.38, 0, 0, 22);
5861
+ for (let i = 0; i < 4; i++) cylinderX(group, 0.064, 8e-3, M.steel, 0.3 + i * 0.05, 0, 0, 22);
5862
+ box2(group, 0.04, 0.05, 0.2, M.dark, 0.1, 0.095, 0);
5863
+ box2(group, 0.2, 0.012, 0.045, M.dark, 0.22, 0.158, 0);
5864
+ box2(group, 0.012, 0.05, 0.045, M.dark, 0.13, 0.13, 0);
5865
+ box2(group, 0.012, 0.05, 0.045, M.dark, 0.31, 0.13, 0);
5866
+ for (const side of [-1, 1]) {
5867
+ box2(group, 0.03, 0.03, 0.1, M.poly, 0.06, -0.01, side * 0.14);
5868
+ cylinderZ(group, 0.016, 0.07, M.poly, 0.06, -0.01, side * 0.2, 14);
5869
+ }
5870
+ parts.trigger = box2(group, 0.012, 0.02, 0.016, M.dark, 0.1, 0.085, 0.14, 0.3);
5871
+ parts.safety = box2(group, 0.014, 6e-3, 0.01, M.dark, 0.2, 0.073, -0.076, 0.4);
5817
5872
  }
5818
-
5819
- // src/models/helical-magazine.ts
5820
- var PI9 = Math.PI;
5821
- var LENGTH = 0.26;
5822
- var RADIUS = 0.026;
5823
- var TURNS = 11;
5824
- function buildHelicalMagazine(context) {
5825
- const { kit, group, parts, receiverEnd, barrelY, barrelRadius } = context;
5826
- const { M, cylinderX, box: box2 } = kit;
5827
- const x = receiverEnd + LENGTH / 2 + 0.02;
5828
- const y = barrelY - barrelRadius - RADIUS - 0.012;
5829
- const magazine = kit.pivot(group, x, y, 0);
5830
- parts.mag = magazine;
5831
- cylinderX(magazine, RADIUS, LENGTH, M.dark, 0, 0, 0, 20);
5832
- cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, -LENGTH / 2, 0, 0, 20);
5833
- cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, LENGTH / 2, 0, 0, 20);
5834
- 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);
5835
- box2(magazine, 0.05, 0.03, 0.03, M.dark, -LENGTH / 2 - 0.02, RADIUS * 0.4, 0);
5836
- parts.magDrop = 0.16;
5837
- return x;
5873
+ function buildFeed(kit, parts, group) {
5874
+ const { M, box: box2, cylinderX } = kit;
5875
+ const belt = kit.pivot(group, 0.2, -0.13, -0.16);
5876
+ parts.belt = belt;
5877
+ box2(belt, 0.16, 0.12, 0.1, M.olive, 0, 0, 0);
5878
+ box2(belt, 0.162, 0.012, 0.102, M.dark, 0, 0.056, 0);
5879
+ 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);
5880
+ box2(group, 0.1, 0.03, 0.1, M.metal, 0.22, -0.06, -0.1, 0, 0, 0.5);
5881
+ const cover = kit.pivot(group, 0.1, 0.075, 0);
5882
+ parts.cover = cover;
5883
+ box2(cover, 0.24, 0.012, 0.15, M.metal, 0.12, 6e-3, 0);
5884
+ const bolt = kit.pivot(group);
5885
+ parts.bolt = bolt;
5886
+ parts.stroke = 0.03;
5887
+ box2(bolt, 0.03, 0.012, 0.012, M.steelL, 0.28, 0.02, 0.078);
5888
+ }
5889
+ function buildM134(kit) {
5890
+ const group = kit.group();
5891
+ const parts = { family: "belt", kick: 0.9, glow: [] };
5892
+ buildHousing(kit, parts, group);
5893
+ buildBarrelCluster(kit, parts, group);
5894
+ buildFeed(kit, parts, group);
5895
+ parts.frames = {
5896
+ length: BARREL_END,
5897
+ barrelY: 0,
5898
+ barrelRadius: BARREL_RADIUS,
5899
+ receiverStart: 0.06,
5900
+ receiverEnd: 0.48,
5901
+ receiverHeight: 0.14,
5902
+ receiverWidth: 0.15,
5903
+ handguardEnd: 0.6,
5904
+ magazineX: 0.2,
5905
+ gripX: 0.06,
5906
+ hasRail: false,
5907
+ // the tripod head, where the gun balances (the tripod of the counters joins here too)
5908
+ underbarrel: [0.4, -0.1]
5909
+ };
5910
+ parts.muzzle = kit.mark(group, BARREL_END + 0.01, 0, 0);
5911
+ parts.eject = kit.mark(group, 0.3, -0.07, 0.08);
5912
+ return { group, parts };
5838
5913
  }
5839
5914
 
5840
- // src/models/rifle-controls.ts
5841
- function buildGrip(context) {
5842
- const { kit, group, parts, options: o, furniture: furn, receiverStart: x0, receiverHeight: hR, receiverWidth: wR } = context;
5843
- const { M, box: box2 } = kit;
5844
- const gx = x0 + (o.gripAt || 0.07);
5845
- if (!context.isTraditional && o.grip !== "none") {
5846
- const grip = kit.pivot(group, gx - 0.012, -hR / 2 - 0.046, 0);
5847
- grip.rotation.z = -0.3;
5848
- parts.grip = grip;
5849
- box2(grip, 0.032, 0.105, 0.028, furn, 0, 0, 0);
5850
- for (let i = 0; i < 3; i++) box2(grip, 6e-3, 0.012, 0.026, furn, 0.017, 0.022 - i * 0.02, 0);
5851
- box2(grip, 0.034, 8e-3, 0.03, furn, 0, -0.052, 0);
5852
- }
5853
- box2(group, 0.06, 4e-3, 0.01, M.metal, gx + 0.03, -hR / 2 - 0.024, 0);
5854
- box2(group, 4e-3, 0.022, 0.01, M.metal, gx + 0.06, -hR / 2 - 0.013, 0);
5855
- parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.024, -hR / 2 - 0.012, 0, 0.15);
5856
- 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);
5857
- return gx;
5915
+ // src/models/m2.ts
5916
+ function buildM2(kit) {
5917
+ const { M, box: box2, cylinderX, cylinderZ } = kit;
5918
+ const group = kit.group();
5919
+ const parts = { family: "belt", kick: 2.2, stroke: 0.08, glow: [] };
5920
+ box2(group, 0.52, 0.13, 0.12, M.metal, 0.36, 0, 0);
5921
+ for (let i = 0; i < 5; i++) cylinderZ(group, 6e-3, 0.124, M.dark, 0.15 + i * 0.1, -0.03, 0, 10);
5922
+ heatify(parts.glow, cylinderX(group, 0.016, 1.14, M.metal, 1.14, 0.01, 0, 16));
5923
+ cylinderX(group, 0.034, 0.24, M.dark, 0.74, 0.01, 0, 16);
5924
+ for (let i = 0; i < 6; i++) cylinderX(group, 0.036, 0.012, M.metal, 0.66 + i * 0.035, 0.01, 0, 16);
5925
+ cylinderX(group, 0.022, 0.05, M.dark, 1.69, 0.01, 0, 16);
5926
+ box2(group, 0.04, 0.09, 0.03, M.wood, 0.05, -0.02, 0.05);
5927
+ box2(group, 0.04, 0.09, 0.03, M.wood, 0.05, -0.02, -0.05);
5928
+ box2(group, 0.06, 0.02, 0.12, M.metal, 0.09, -0.02, 0);
5929
+ const belt = kit.pivot(group, 0.38, -0.05, -0.12);
5930
+ parts.belt = belt;
5931
+ box2(belt, 0.16, 0.12, 0.09, M.olive, 0, 0, 0);
5932
+ 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);
5933
+ const cover = kit.pivot(group, 0.12, 0.065, 0);
5934
+ parts.cover = cover;
5935
+ box2(cover, 0.4, 0.012, 0.12, M.metal, 0.2, 6e-3, 0);
5936
+ box2(group, 0.04, 0.05, 0.04, M.dark, 0.66, 0.1, 0);
5937
+ const bolt = kit.pivot(group);
5938
+ parts.bolt = bolt;
5939
+ box2(bolt, 0.03, 0.03, 0.02, M.metal, 0.5, 0, 0.07);
5940
+ const tripod = kit.group();
5941
+ tripod.position.set(0.42, -0.1, 0);
5942
+ group.add(tripod);
5943
+ box2(tripod, 0.03, 0.06, 0.03, M.metal, 0, 0, 0);
5944
+ box2(tripod, 0.012, 0.62, 0.012, M.metal, -0.2, -0.25, 0, 0.75);
5945
+ box2(tripod, 0.012, 0.62, 0.012, M.metal, 0.22, -0.25, 0.16, -0.75, 0, 0.3);
5946
+ box2(tripod, 0.012, 0.62, 0.012, M.metal, 0.22, -0.25, -0.16, -0.75, 0, -0.3);
5947
+ parts.muzzle = kit.mark(group, 1.72, 0.01, 0);
5948
+ parts.eject = kit.mark(group, 0.3, -0.07, 0);
5949
+ return { group, parts };
5858
5950
  }
5859
- function buildMagazine(context, gx) {
5860
- const { kit, group, parts, options: o, receiverStart: x0, receiverHeight: hR, receiverWidth: wR, receiverLength: RL } = context;
5861
- const { M, box: box2 } = kit;
5862
- if (o.mag === "helical") return buildHelicalMagazine(context);
5863
- const mx = o.magAt != null ? x0 + o.magAt : gx + 0.075;
5864
- const scale = o.magLen || 1;
5865
- const magazine = kit.pivot(group, mx, -hR / 2, 0);
5951
+
5952
+ // src/models/p90.ts
5953
+ function buildP90(kit) {
5954
+ const { M, box: box2, cylinderX } = kit;
5955
+ const group = kit.group();
5956
+ const parts = { family: "p90", stroke: 0.04, kick: 0.6, glow: [] };
5957
+ box2(group, 0.4, 0.07, 0.055, M.poly, 0.2, 0, 0);
5958
+ box2(group, 0.12, 0.075, 0.05, M.poly, 0.2, -0.07, 0);
5959
+ box2(group, 0.08, 0.04, 0.03, M.hole, 0.21, -0.06, 0);
5960
+ box2(group, 0.06, 0.13, 0.056, M.poly, 0.03, -0.03, 0);
5961
+ box2(group, 0.05, 0.03, 0.056, M.poly, 0.33, -0.04, 0);
5962
+ heatify(parts.glow, cylinderX(group, 8e-3, 0.09, M.metal, 0.43, 0, 0, 16));
5963
+ const magazine = kit.pivot(group, 0.22, 0.047, 0);
5866
5964
  parts.mag = magazine;
5867
- const finish = o.magMat === "bakelite" ? M.bakelite : o.magMat === "poly" ? M.polyMag : void 0;
5868
- if (o.mag === "curved") {
5869
- let px = 0;
5870
- let py = 0;
5871
- const material = finish ?? M.metal;
5872
- for (const angle of [0.06, 0.22, 0.38]) {
5873
- const length = 0.07 * scale;
5874
- const cx = px + Math.sin(angle) * length / 2;
5875
- const cy = py - Math.cos(angle) * length / 2;
5876
- box2(magazine, 0.052, length + 4e-3, 0.024, material, cx, cy, 0, angle);
5877
- box2(magazine, 0.04, length * 0.8, 0.026, M.dark, cx, cy, 0, angle);
5878
- px += Math.sin(angle) * length;
5879
- py -= Math.cos(angle) * length;
5880
- }
5881
- box2(magazine, 0.058, 8e-3, 0.028, material, px, py, 0, 0.38);
5882
- parts.magDrop = 0.07 * scale * 3 + 0.04;
5883
- } else if (o.mag === "box") {
5884
- const length = 0.13 * scale;
5885
- const curve = o.curve || 0;
5886
- const segment = length / 3;
5887
- const material = finish ?? M.dark;
5888
- let px = 0;
5889
- let py = 0;
5890
- let angle = 0.08;
5891
- for (let i = 0; i < 3; i++) {
5892
- angle = 0.08 + curve * i;
5893
- const cx = px + Math.sin(angle) * segment / 2;
5894
- const cy = py - Math.cos(angle) * segment / 2;
5895
- box2(magazine, 0.05, segment + 4e-3, 0.024, material, cx, cy, 0, angle);
5896
- box2(magazine, 0.04, 3e-3, 0.026, M.metal, cx, cy, 0, angle);
5897
- px += Math.sin(angle) * segment;
5898
- py -= Math.cos(angle) * segment;
5899
- }
5900
- box2(magazine, 0.056, 7e-3, 0.028, material, px, py - 2e-3, 0, angle);
5901
- parts.magDrop = length + 0.03;
5902
- } else if (o.mag === "drum") {
5903
- buildDrum(kit, magazine);
5904
- parts.magDrop = 0.2;
5905
- } else if (o.mag === "internal") {
5906
- box2(group, 0.08, 0.025, 0.03, context.isTraditional ? context.furniture : M.metal, mx, -hR / 2 - 0.012, 0);
5907
- parts.mag = null;
5908
- } else parts.mag = null;
5909
- if (o.mag === "belt") {
5910
- const belt = kit.pivot(group, mx + 0.02, -hR / 2 - 0.05, -0.045);
5911
- parts.belt = belt;
5912
- buildBeltBox(kit, belt);
5913
- const cover = kit.pivot(group, x0 + 0.01, hR / 2, 0);
5914
- parts.cover = cover;
5915
- box2(cover, RL - 0.03, 0.014, wR * 1.05, M.metal, (RL - 0.03) / 2, 7e-3, 0);
5916
- box2(cover, 0.02, 0.012, wR * 0.6, M.dark, RL - 0.04, 0.016, 0);
5965
+ parts.magDir = 1;
5966
+ parts.magDrop = 0.08;
5967
+ box2(magazine, 0.3, 0.022, 0.05, M.glass, 0, 0, 0);
5968
+ for (let i = 0; i < 12; i++) cylinderX(magazine, 28e-4, 0.02, M.brass, -0.12 + i * 0.022, 0, 0, 6);
5969
+ scope(kit, group, 1, 0.24, 0.06, 0);
5970
+ const bolt = kit.pivot(group);
5971
+ parts.bolt = bolt;
5972
+ box2(bolt, 0.02, 0.012, 0.012, M.dark, 0.3, 0.02, 0.03);
5973
+ parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, 0.24, -0.045, 0);
5974
+ parts.muzzle = kit.mark(group, 0.48, 0, 0);
5975
+ parts.eject = kit.mark(group, 0.2, -0.07, 0);
5976
+ return { group, parts };
5977
+ }
5978
+
5979
+ // src/models/part-registry.ts
5980
+ 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"];
5981
+ function partRegistry(parts) {
5982
+ const registry = /* @__PURE__ */ new Map();
5983
+ for (const key of SINGLE_PARTS) {
5984
+ const part = parts[key];
5985
+ if (part) registry.set(key, part);
5917
5986
  }
5918
- return mx;
5987
+ parts.breech?.forEach((part, index) => registry.set(`breech_${index}`, part));
5988
+ parts.chambers?.forEach((part, index) => registry.set(`chamber_${index}`, part));
5989
+ return registry;
5919
5990
  }
5920
5991
 
5921
- // src/models/rifle-stock.ts
5922
- function buildStock(context, magazineX) {
5923
- const { kit, group, options: o, furniture: furn, length: L, stockLength: S, receiverStart: x0, receiverEnd: x1, receiverHeight: hR } = context;
5992
+ // src/models/pistol-slide.ts
5993
+ var PI8 = Math.PI;
5994
+ function buildSlide(kit, slide2, parts, options, style, size, metal2) {
5924
5995
  const { M, box: box2, cylinderX } = kit;
5925
- if (context.isTraditional) {
5926
- buttpad(kit, group, 6e-3, -0.04, 0.13, 0.038);
5927
- box2(group, 0.03, 0.13, 0.038, furn, 0.024, -0.04, 0);
5928
- box2(group, S, 0.055, 0.034, furn, S / 2, -0.012, 0, 0.06);
5929
- box2(group, 0.09, 0.05, 0.032, furn, x0 - 0.02, -0.035, 0, 0.25);
5930
- const forendLength = x1 - x0 + (L - x1) * (o.fore ?? 0.62);
5931
- if (forendLength > 0) box2(group, forendLength, 0.045, 0.04, furn, x0 + forendLength / 2, -hR / 2 + 4e-3, 0);
5932
- if (o.stock === "straight") box2(group, 0.15, 0.04, 0.035, furn, x1 + 0.08, -hR / 2, 0);
5933
- box2(group, 0.04, 4e-3, 0.03, M.metal, magazineX, -hR / 2 - 0.022, 0);
5934
- swivel(kit, group, 0.2, -0.08);
5935
- swivel(kit, group, x1 + (L - x1) * 0.45, -hR / 2 - 0.03);
5936
- 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);
5937
- } else if (o.stock === "fixed" || o.stock === "wood") {
5938
- box2(group, S, 0.05, 0.03, furn, S / 2, -8e-3, 0, 0.05);
5939
- box2(group, 0.03, 0.13, 0.036, furn, 0.022, -0.035, 0);
5940
- buttpad(kit, group, 4e-3, -0.035, 0.13, 0.036);
5941
- if (o.stock === "wood") box2(group, S * 0.6, 0.03, 0.028, furn, S * 0.55, -0.045, 0, 0.2);
5942
- swivel(kit, group, 0.08, -0.09);
5943
- } else if (o.stock === "skeleton") {
5944
- box2(group, S, 0.014, 0.026, furn, S / 2, 0.012, 0, 0.03);
5945
- box2(group, S, 0.014, 0.026, furn, S / 2, -0.05, 0, -0.08);
5946
- box2(group, 0.03, 0.12, 0.034, furn, 0.02, -0.025, 0);
5947
- buttpad(kit, group, 4e-3, -0.025, 0.12, 0.034);
5948
- } else if (o.stock === "retract") {
5949
- cylinderX(group, 0.013, S, M.metal, S / 2, 0, 0, 16);
5950
- 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);
5951
- box2(group, 0.034, 0.11, 0.036, furn, 0.02, -0.025, 0);
5952
- box2(group, S * 0.55, 0.032, 0.03, furn, S * 0.35, 0, 0);
5953
- buttpad(kit, group, 3e-3, -0.025, 0.11, 0.036);
5954
- } else if (o.stock === "fold") {
5955
- box2(group, S, 6e-3, 6e-3, M.metal, S / 2, 0.01, 0.012);
5956
- box2(group, S, 6e-3, 6e-3, M.metal, S / 2, -0.04, 0.012, -0.12);
5957
- box2(group, 0.012, 0.1, 0.03, M.metal, 6e-3, -0.02, 0.012);
5996
+ const { length: L, width: w } = size;
5997
+ const slideHeight = style.inset ? size.height * 0.92 : size.height;
5998
+ const slideWidth = style.inset ? w * 0.86 : w;
5999
+ kit.roundness = style.rf;
6000
+ if (style.tri) box2(slide2, L * 0.42, slideHeight, slideWidth, metal2, L * 0.21, 0, 0);
6001
+ else if (style.open) {
6002
+ box2(slide2, L * 0.45, slideHeight, slideWidth, metal2, L * 0.225, 0, 0);
6003
+ box2(slide2, L * 0.55, slideHeight * 0.62, slideWidth, metal2, L * 0.72, -slideHeight * 0.19, 0);
6004
+ } else box2(slide2, L, slideHeight, slideWidth, metal2, L / 2, 0, 0);
6005
+ if (style.slope) box2(slide2, L * 0.25, slideHeight * 0.5, slideWidth * 0.98, metal2, L * 0.86, slideHeight * 0.35, 0, -0.22);
6006
+ kit.roundness = null;
6007
+ 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);
6008
+ 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);
6009
+ if (style.port) {
6010
+ box2(slide2, L * style.port, 4e-3, slideWidth * 0.55, M.hole, L * 0.6, slideHeight / 2 - 1e-3, slideWidth * 0.12);
6011
+ box2(slide2, L * style.port * 0.85, 3e-3, slideWidth * 0.4, M.steelL, L * 0.6, slideHeight / 2 - 4e-4, slideWidth * 0.1);
6012
+ box2(slide2, 0.012, 4e-3, 2e-3, M.dark, L * 0.55, slideHeight * 0.15, slideWidth / 2 + 8e-4);
6013
+ }
6014
+ if (style.open) {
6015
+ heatify(parts.glow, cylinderX(slide2, 62e-4, L * 0.56, M.steelL, L * 0.7, slideHeight * 0.12, 0, 18));
6016
+ box2(slide2, 0.03, 4e-3, slideWidth * 0.6, M.hole, L * 0.48, slideHeight / 2 - 1e-3, 0);
6017
+ }
6018
+ if (style.tri) {
6019
+ const tube = new kit.THREE.Mesh(new kit.THREE.CylinderGeometry(0.016, 0.016, L * 0.6, 3), metal2);
6020
+ tube.castShadow = tube.receiveShadow = true;
6021
+ tube.rotation.set(0, 0, -PI8 / 2);
6022
+ tube.rotateY(PI8 / 6);
6023
+ tube.position.set(L * 0.7, slideHeight * 0.08, 0);
6024
+ slide2.add(tube);
6025
+ heatify(parts.glow, tube);
6026
+ box2(slide2, L * 0.6, 6e-3, 0.012, metal2, L * 0.7, slideHeight * 0.38, 0);
6027
+ 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);
6028
+ }
6029
+ if (style.bushing) {
6030
+ heatify(parts.glow, cylinderX(slide2, 78e-4, 6e-3, M.steelL, L + 2e-3, 1e-3, 0, 18));
6031
+ cylinderX(slide2, 45e-4, 4e-3, M.steel, L + 2e-3, -0.012, 0, 12);
6032
+ }
6033
+ const muzzleY = style.tri ? slideHeight * 0.08 : 2e-3;
6034
+ heatify(parts.glow, cylinderX(slide2, style.tri ? 72e-4 : 62e-4, 4e-3, M.dark, L + 15e-4, muzzleY, 0, 18));
6035
+ cylinderX(slide2, 34e-4, 5e-3, M.hole, L + 25e-4, muzzleY, 0, 12);
6036
+ const top = slideHeight / 2;
6037
+ if (style.sight === "u" || style.sight === "3dot") {
6038
+ box2(slide2, 4e-3, 7e-3, 4e-3, M.dark, L - 8e-3, top + 35e-4, 0);
6039
+ box2(slide2, 22e-4, 22e-4, 46e-4, M.white, L - 8e-3, top + 58e-4, 0);
6040
+ box2(slide2, 8e-3, 7e-3, 0.016, M.dark, 9e-3, top + 35e-4, 0);
6041
+ if (style.sight === "u") box2(slide2, 4e-3, 2e-3, 0.0122, M.white, 9e-3, top + 6e-3, 0);
6042
+ else box2(slide2, 22e-4, 22e-4, 0.0166, M.white, 9e-3, top + 58e-4, 0);
6043
+ } else {
6044
+ box2(slide2, 6e-3, 4e-3, 3e-3, M.dark, L - 6e-3, top + 2e-3, 0);
6045
+ box2(slide2, 6e-3, 4e-3, 0.012, M.dark, 8e-3, top + 2e-3, 0);
6046
+ }
6047
+ if (style.decock === "slide") {
6048
+ box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.02, slideHeight * 0.15, slideWidth / 2 + 2e-3, 0.3);
6049
+ box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.02, slideHeight * 0.15, -slideWidth / 2 - 2e-3, 0.3);
6050
+ }
6051
+ 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);
6052
+ if (options.sel) {
6053
+ const selector = box2(slide2, 0.01, 8e-3, 3e-3, M.dark, 0.014, -slideHeight * 0.2, slideWidth / 2 + 14e-4);
6054
+ parts.safety ?? (parts.safety = selector);
5958
6055
  }
5959
6056
  }
5960
6057
 
5961
- // src/models/rifle.ts
5962
- var PI10 = Math.PI;
5963
- function buildReceiver(context) {
5964
- const { kit, group, parts, options: o, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, receiverLength: RL, barrelY: by } = context;
5965
- const { M, box: box2, cylinderX, cylinderY, cylinderZ, sphere } = kit;
5966
- const receiverMaterial = o.tan ? M.tan : M.metal;
5967
- const middle = (x0 + x1) / 2;
5968
- if (o.round) {
5969
- cylinderX(group, hR * 0.36, RL, M.steel, middle, by, 0, 20);
5970
- cylinderX(group, hR * 0.32, 0.05, M.steel, x0 - 0.01, by, 0, 18);
5971
- } else if (o.ar) {
5972
- box2(group, RL, hR * 0.5, wR, receiverMaterial, middle, hR * 0.25, 0);
5973
- box2(group, RL * 0.92, hR * 0.52, wR * 0.96, receiverMaterial, middle - RL * 0.02, -hR * 0.24, 0);
5974
- box2(group, 0.05, 0.03, wR * 1.15, receiverMaterial, x0 + RL * 0.62, -hR * 0.5 - 0.01, 0);
5975
- cylinderZ(group, 6e-3, 0.012, M.metal, x1 - 0.03, hR * 0.28, wR / 2 + 6e-3, 12);
5976
- box2(group, 0.012, 0.01, 0.012, M.metal, x1 - 0.05, hR * 0.12, wR / 2 + 6e-3);
5977
- box2(group, 0.03, 8e-3, 0.012, M.dark, x0 + 0.02, hR / 2 + 4e-3, 0);
5978
- box2(group, 0.012, 6e-3, 4e-3, M.dark, x0 + RL * 0.55, -hR * 0.15, -wR / 2 - 2e-3);
5979
- } else box2(group, RL, hR, wR, receiverMaterial, middle, 0, 0);
5980
- if (o.ak) {
5981
- box2(group, RL * 0.86, hR * 0.28, wR * 0.95, M.metal, middle - 8e-3, hR / 2 + 4e-3, 0);
5982
- 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);
5983
- box2(group, RL * 0.5, 0.012, 3e-3, M.metal, x0 + RL * 0.55, -hR * 0.02, wR / 2 + 15e-4, -0.05);
5984
- box2(group, 0.012, 0.014, 3e-3, M.dark, x0 + RL * 0.32, -hR * 0.05, wR / 2 + 2e-3);
6058
+ // src/models/pistol-style.ts
6059
+ var STYLES = {
6060
+ glock: { rf: 0.07, ga: -0.36, grip: "finger", guard: "square", rail: 1, sight: "u", port: 0.2, slideM: "metal", frame: "poly" },
6061
+ taurus: { rf: 0.16, ga: -0.3, grip: "panel", guard: "square", rail: 1, sight: "3dot", port: 0.18, frontSer: 1, frame: "poly" },
6062
+ g2c: { rf: 0.2, ga: -0.3, grip: "panel", guard: "round", rail: 0, sight: "3dot", port: 0.17, frame: "poly", safety: "thumb" },
6063
+ beretta: { rf: 0.28, ga: -0.27, grip: "wood", guard: "round", hammer: "spur", sight: "3dot", open: 1, decock: "slide", port: 0, frame: "metal" },
6064
+ pt100: { rf: 0.25, ga: -0.27, grip: "rubber", guard: "round", hammer: "spur", sight: "3dot", port: 0.17, decock: "frame", frame: "metal" },
6065
+ 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" },
6066
+ cz: { rf: 0.32, ga: -0.3, grip: "rubber", guard: "round", hammer: "spur", sight: "3dot", port: 0.15, inset: 1, safety: "thumb", frame: "metal" },
6067
+ 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" },
6068
+ 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" },
6069
+ fiveseven: { rf: 0.2, ga: -0.4, grip: "chunky", guard: "square", rail: 1, sight: "3dot", port: 0.18, slope: 1, frame: "poly" },
6070
+ deagle: { rf: 0.08, ga: -0.28, grip: "rubber", guard: "round", hammer: "big", sight: "small", port: 0.16, tri: 1, safety: "slide", frame: "metal" }
6071
+ };
6072
+
6073
+ // src/models/pistol.ts
6074
+ var PI9 = Math.PI;
6075
+ function buildFrame(kit, group, parts, options, style, size, materials) {
6076
+ const { M, box: box2, cylinderZ, sphere } = kit;
6077
+ const { length: L, height: h, width: w } = size;
6078
+ const fm = materials.frame;
6079
+ if (style.inset) {
6080
+ box2(group, L * 0.9, h * 0.35, w * 0.12, fm, L * 0.47, -h * 0.32, w * 0.47);
6081
+ box2(group, L * 0.9, h * 0.35, w * 0.12, fm, L * 0.47, -h * 0.32, -w * 0.47);
5985
6082
  }
5986
- if (o.g3) {
5987
- cylinderX(group, 9e-3, (L - x1) * 0.6, M.metal, x1 + (L - x1) * 0.28, hR * 0.48, 0, 14);
5988
- box2(group, 0.012, 0.024, 8e-3, M.dark, x1 + (L - x1) * 0.5, hR * 0.48, -0.012);
5989
- cylinderY(group, 0.012, 0.016, M.dark, x0 + 0.03, hR / 2 + 8e-3, 0, 12);
6083
+ const frameLength = style.tri ? L * 0.66 : L * 0.8;
6084
+ kit.roundness = style.rf;
6085
+ box2(group, frameLength, 0.016, w * 0.92, fm, frameLength / 2 + L * 0.06, -h / 2 - 8e-3, 0);
6086
+ kit.roundness = null;
6087
+ 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);
6088
+ const isGlock = options.st === "glock" || !options.st;
6089
+ if (isGlock) {
6090
+ box2(group, 8e-3, 4e-3, 3e-3, M.dark, L * 0.5, -h / 2 - 3e-3, -w / 2 - 12e-4);
6091
+ box2(group, 8e-3, 4e-3, 3e-3, M.dark, L * 0.5, -h / 2 - 3e-3, w / 2 + 12e-4);
6092
+ }
6093
+ if (style.slideStop) {
6094
+ box2(group, 0.03, 6e-3, 3e-3, M.dark, L * 0.42, -h / 2 - 2e-3, -w / 2 - 14e-4);
6095
+ sphere(group, 28e-4, M.dark, L * 0.55, -h / 2 - 3e-3, w / 2 + 1e-3, 1);
6096
+ } else box2(group, 0.012, 4e-3, 3e-3, M.dark, L * 0.45, -h / 2 - 3e-3, -w / 2 - 12e-4);
6097
+ 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);
6098
+ 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);
6099
+ const gripLength = options.gl ?? 0.105;
6100
+ const gx = 0.034 + (L - 0.2) * 0.15;
6101
+ const gy = -h / 2 - gripLength / 2 + 6e-3;
6102
+ const grip = kit.pivot(group, gx, gy, 0);
6103
+ grip.rotation.z = style.ga;
6104
+ parts.grip = grip;
6105
+ const gripWidth = style.grip === "chunky" ? w * 1.18 : w * 1.06;
6106
+ const gripDepth = style.grip === "chunky" ? 0.036 : 0.032;
6107
+ kit.roundness = 0.3;
6108
+ if (style.grip === "wood" || style.grip === "star") {
6109
+ box2(grip, gripDepth * 0.86, gripLength, w * 0.9, fm, 0, 0, 0);
6110
+ box2(grip, gripDepth * 0.8, gripLength * 0.86, 4e-3, materials.grip, 1e-3, -2e-3, w * 0.47);
6111
+ box2(grip, gripDepth * 0.8, gripLength * 0.86, 4e-3, materials.grip, 1e-3, -2e-3, -w * 0.47);
6112
+ if (style.grip === "wood") {
6113
+ 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);
6114
+ cylinderZ(grip, 35e-4, 15e-4, M.brass, 0, gripLength * 0.25, w * 0.5, 10);
6115
+ cylinderZ(grip, 35e-4, 15e-4, M.brass, 0, -gripLength * 0.3, w * 0.5, 10);
6116
+ } else cylinderZ(grip, 8e-3, 15e-4, M.dark, 0, gripLength * 0.1, w * 0.5, 5);
6117
+ } else box2(grip, gripDepth, gripLength, gripWidth, materials.grip, 0, 0, 0);
6118
+ kit.roundness = null;
6119
+ if (style.grip === "finger") {
6120
+ 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);
6121
+ 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);
5990
6122
  }
5991
- const ejectX = x1 - (o.ejectBack || 0.07);
5992
- const ejectY = o.ar ? hR * 0.2 : by;
5993
- box2(group, 0.05, hR * 0.36, 4e-3, M.hole, ejectX, ejectY, wR / 2 - 4e-4);
5994
- const bolt = kit.pivot(group);
5995
- parts.bolt = bolt;
5996
- parts.stroke = Math.min(0.1, RL * 0.32);
5997
- box2(bolt, 0.044, hR * 0.26, 6e-3, M.steelL, ejectX, ejectY, wR / 2 - 12e-4);
5998
- if (o.charge) {
5999
- box2(bolt, 0.012, 0.01, 0.03, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.012);
6000
- sphere(bolt, 65e-4, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.028, 1);
6123
+ 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);
6124
+ if (style.grip === "chunky") box2(grip, 8e-3, gripLength * 0.8, gripWidth * 0.9, materials.grip, gripDepth / 2 + 2e-3, -4e-3, 0);
6125
+ if (style.beaver) {
6126
+ box2(group, 0.026, 8e-3, w * 0.9, fm, -4e-3, -h / 2 - 4e-3, 0, 0.25);
6127
+ box2(grip, 6e-3, gripLength * 0.55, w * 0.9, fm, -gripDepth * 0.5 - 2e-3, gripLength * 0.18, 0);
6128
+ } else box2(group, 0.02, 9e-3, w * 0.9, fm, 4e-3, -h / 2 - 6e-3, 0);
6129
+ if (style.lanyard) {
6130
+ const ring = kit.torus(grip, new kit.THREE.TorusGeometry(5e-3, 13e-4, 6, 12), M.dark, -0.012, -gripLength / 2 + 4e-3, 0);
6131
+ ring.position.set(-0.012, -gripLength / 2 + 4e-3, 0);
6001
6132
  }
6002
- if (o.ar) box2(group, 0.012, 0.016, 6e-3, receiverMaterial, ejectX - 0.03, ejectY + 4e-3, wR / 2 + 4e-3);
6003
- if (o.rail) rail(kit, group, x0 - 5e-3, RL + 0.07, hR / 2 + (o.ar ? 0 : 3e-3), 0, wR * 0.68);
6133
+ const magazine = kit.pivot(grip);
6134
+ parts.mag = magazine;
6135
+ parts.magDrop = gripLength * 1.15;
6136
+ box2(magazine, 0.022, gripLength * 0.94, w * 0.72, M.dark, 3e-3, -4e-3, 0);
6137
+ if (style.grip === "chunky") box2(magazine, 0.04, 0.01, gripWidth * 1.02, M.poly, 3e-3, -gripLength / 2 - 4e-3, 0);
6138
+ 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);
6139
+ else box2(magazine, 0.031, 6e-3, gripWidth * 1.02, M.dark, 3e-3, -gripLength / 2 - 2e-3, 0);
6140
+ return { gx, ty: -h / 2 };
6004
6141
  }
6005
- function buildBarrel(context) {
6006
- const { kit, group, parts, options: o, length: L, barrelLength: Bl, receiverEnd: x1, barrelRadius: r, barrelY: by } = context;
6007
- const { M, box: box2, cylinderX } = kit;
6008
- heatify(parts.glow, cylinderX(group, r, Bl, M.metal, L - Bl / 2, by, 0, 16));
6009
- if (o.jacket) {
6010
- cylinderX(group, r * 2, (L - x1) * 0.6, M.dark, x1 + (L - x1) * 0.3, by, 0, 16);
6011
- 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);
6142
+ function buildPistol(kit, options) {
6143
+ const { M, box: box2 } = kit;
6144
+ const style = { ...STYLES[options.st ?? "glock"] };
6145
+ const group = kit.group();
6146
+ const parts = { family: "pistol", kick: options.deagle ? 1.8 : 0.6, glow: [] };
6147
+ 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 };
6148
+ const { length: L, height: h, width: w } = size;
6149
+ const slideMetal = options.steel ? M.steel : options.stain ? M.steelL : M.metal;
6150
+ const frameMetal = style.frame === "poly" ? M.poly : slideMetal;
6151
+ const gripMaterial = style.grip === "wood" ? M.wood : style.grip === "star" ? M.bake : style.grip === "rubber" ? M.rubber : M.poly;
6152
+ const slide2 = kit.pivot(group);
6153
+ parts.slide = slide2;
6154
+ parts.stroke = L * 0.15;
6155
+ buildSlide(kit, slide2, parts, options, style, size, slideMetal);
6156
+ const { gx, ty } = buildFrame(kit, group, parts, options, style, size, { frame: frameMetal, grip: gripMaterial });
6157
+ if (style.guard === "square") {
6158
+ box2(group, 0.046, 4e-3, 9e-3, frameMetal, gx + 0.05, ty - 0.034, 0);
6159
+ box2(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.074, ty - 0.02, 0);
6160
+ box2(group, 6e-3, 4e-3, 9e-3, frameMetal, gx + 0.075, ty - 0.034, 0);
6161
+ } else if (style.guard === "hook") {
6162
+ box2(group, 0.046, 4e-3, 9e-3, frameMetal, gx + 0.05, ty - 0.034, 0);
6163
+ box2(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.073, ty - 0.02, 0, 0.2);
6164
+ box2(group, 8e-3, 6e-3, 9e-3, frameMetal, gx + 0.074, ty - 0.037, 0, 0.4);
6165
+ } else {
6166
+ const guard = kit.torus(group, new kit.THREE.TorusGeometry(0.019, 22e-4, 6, 16, PI9), frameMetal, gx + 0.05, ty - 0.014, 0);
6167
+ guard.rotation.z = PI9;
6168
+ guard.scale.set(1.2, 1, 1);
6012
6169
  }
6013
- 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);
6014
- if (o.brake) {
6015
- const kind = o.brake === 2 ? 2 : 1;
6016
- recordFitting(parts, kind === 2 ? "muzzle_brake_heavy" : "muzzle_brake", group, () => buildBrake(kit, group, kind, L, by, r, (body) => void heatify(parts.glow, body)));
6017
- } else if (!o.noFH) {
6018
- recordFitting(parts, "muzzle_default", group, () => {
6019
- heatify(parts.glow, cylinderX(group, r * 1.45, 0.042, M.dark, L - 0.021, by, 0, 16));
6020
- for (let i = 0; i < 4; i++) {
6021
- const angle = i * PI10 / 2 + PI10 / 4;
6022
- 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);
6023
- }
6024
- });
6170
+ const isGlock = options.st === "glock" || !options.st;
6171
+ parts.trigger = box2(group, 4e-3, 0.014, 4e-3, isGlock ? M.dark : M.steel, gx + 0.042, ty - 0.022, 0, 0.2);
6172
+ if (isGlock) box2(parts.trigger, 15e-4, 8e-3, 45e-4, M.metal, 0, -1e-3, 0);
6173
+ if (style.hammer) {
6174
+ const hammer = kit.pivot(group, 3e-3, h / 2 - 8e-3, 0);
6175
+ parts.hammer = hammer;
6176
+ if (style.hammer === "ring") {
6177
+ box2(hammer, 6e-3, 0.012, 6e-3, M.dark, -2e-3, 4e-3, 0, 0.3);
6178
+ kit.torus(hammer, new kit.THREE.TorusGeometry(45e-4, 16e-4, 6, 12), M.dark, -4e-3, 0.012, 0);
6179
+ } else if (style.hammer === "big") box2(hammer, 0.014, 0.02, 9e-3, M.dark, -4e-3, 7e-3, 0, 0.35);
6180
+ else {
6181
+ box2(hammer, 8e-3, 0.016, 7e-3, M.dark, -2e-3, 6e-3, 0, 0.3);
6182
+ box2(hammer, 8e-3, 4e-3, 8e-3, M.dark, -6e-3, 0.013, 0, 0.3);
6183
+ }
6025
6184
  }
6026
- cylinderX(group, r * 0.55, 4e-3, M.hole, L + 5e-4, by, 0, 12);
6185
+ parts.muzzle = kit.mark(group, L + 6e-3, style.tri ? h * 0.08 : 2e-3, 0);
6186
+ parts.eject = kit.mark(group, L * 0.6, h / 2, w * 0.25);
6187
+ return { group, parts };
6027
6188
  }
6028
- var familyOf = (o) => {
6029
- if (o.mag === "belt") return "belt";
6030
- if (o.pump) return "pump";
6031
- if (o.lever) return "lever";
6032
- if (o.tube) return "semi12";
6033
- if (o.bolt) return o.mag === "box" ? "boltmag" : "bolt";
6034
- return o.mag === "internal" ? "semiint" : "rifle";
6035
- };
6036
- function buildRifle(kit, options) {
6037
- const { M } = kit;
6189
+
6190
+ // src/models/revolver.ts
6191
+ var PI10 = Math.PI;
6192
+ var DEFAULT_CHAMBERS = 6;
6193
+ function buildRevolver(kit, options) {
6194
+ const { M, box: box2, cylinderX } = kit;
6038
6195
  const group = kit.group();
6039
- const parts = { family: "rifle", glow: [] };
6040
- const length = (options.L ?? 0) / 1e3;
6041
- const barrelLength = (options.B ?? 0) / 1e3;
6042
- const isTraditional = options.stock === "trad" || options.stock === "straight";
6043
- const stockLength = options.stock === "none" ? 0 : options.stockLen || Math.min(0.36, Math.max(0.2, 0.3 * length));
6044
- const receiverStart = stockLength;
6045
- const receiverEnd = Math.max(receiverStart + 0.12, length - barrelLength * 0.92);
6046
- const receiverHeight = options.hR || 0.06;
6047
- const context = {
6048
- kit,
6049
- group,
6050
- parts,
6051
- options,
6052
- furniture: options.wood ? M.wood : options.olive ? M.olive : options.tan ? M.tan : M.poly,
6053
- isTraditional,
6054
- length,
6055
- barrelLength,
6056
- stockLength,
6057
- receiverStart,
6058
- receiverEnd,
6059
- receiverHeight,
6060
- receiverWidth: options.wR || 0.034,
6061
- barrelRadius: options.br || 85e-4,
6062
- barrelY: receiverHeight * 0.15,
6063
- receiverLength: receiverEnd - receiverStart
6064
- };
6065
- buildReceiver(context);
6066
- buildBarrel(context);
6067
- buildForend(context);
6068
- const gripX = buildGrip(context);
6069
- const magazineX = buildMagazine(context, gripX);
6070
- parts.stockMeshes = context.isTraditional ? [] : captured(group, () => buildStock(context, magazineX));
6071
- if (context.isTraditional) buildStock(context, magazineX);
6072
- buildFittings(context, gripX);
6073
- const hasHandguard = !context.isTraditional && options.hg !== "none";
6074
- parts.frames = {
6075
- length,
6076
- barrelY: context.barrelY,
6077
- barrelRadius: context.barrelRadius,
6078
- receiverStart,
6079
- receiverEnd,
6080
- receiverHeight,
6081
- receiverWidth: context.receiverWidth,
6082
- handguardEnd: receiverEnd + (length - receiverEnd) * (hasHandguard ? options.hgFrac || 0.5 : 0.4),
6083
- magazineX,
6084
- gripX,
6085
- hasRail: Boolean(options.rail)
6086
- };
6087
- const { barrelY: by, receiverWidth: wR } = context;
6088
- const ejectX = receiverEnd - (options.ejectBack || 0.07);
6089
- const ejectY = options.ar ? receiverHeight * 0.2 : by;
6090
- parts.muzzle = kit.mark(group, length + 0.01, by, 0);
6091
- parts.eject = kit.mark(group, ejectX, ejectY, wR / 2 + 6e-3);
6092
- parts.loadPort = kit.mark(group, receiverEnd - 0.05, -receiverHeight / 2 - 4e-3, 0);
6093
- parts.topLoad = kit.mark(group, ejectX, receiverHeight / 2 + 4e-3, 0);
6094
- parts.family = familyOf(options);
6196
+ const parts = { family: "revolver", kick: options.cal === ".44 Magnum" ? 1.5 : 0.9, glow: [] };
6197
+ const barrel = (options.B ?? 0) / 1e3;
6198
+ const chambers = options.chambers ?? DEFAULT_CHAMBERS;
6199
+ const metal2 = options.steel ? M.steel : M.metal;
6200
+ box2(group, 0.08, 0.044, 0.024, metal2, 0.07, 0, 0);
6201
+ box2(group, 0.036, 0.012, 0.022, metal2, 0.096, 0.022, 0);
6202
+ box2(group, 0.024, 0.012, 4e-3, M.dark, 0.055, 6e-3, -0.013);
6203
+ const crane = kit.pivot(group, 0.077, -0.024, 0);
6204
+ parts.crane = crane;
6205
+ const cylinder = kit.pivot(crane, 0, 0.022, 0);
6206
+ parts.cylinder = cylinder;
6207
+ cylinderX(cylinder, 0.0215, 0.042, metal2, 0, 0, 0, 24);
6208
+ for (let i = 0; i < chambers; i++) {
6209
+ const angle = i * 2 * PI10 / chambers;
6210
+ box2(cylinder, 0.026, 5e-3, 5e-3, M.dark, 0, Math.cos(angle) * 0.0205, Math.sin(angle) * 0.0205, 0, 0, angle);
6211
+ }
6212
+ parts.chambers = [];
6213
+ for (let i = 0; i < chambers; i++) {
6214
+ const angle = i * 2 * PI10 / chambers + PI10 / chambers;
6215
+ parts.chambers.push(kit.mark(cylinder, -0.022, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013));
6216
+ cylinderX(cylinder, 46e-4, 2e-3, M.brass, -0.0215, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013, 12);
6217
+ }
6218
+ heatify(parts.glow, cylinderX(group, 88e-4, barrel, metal2, 0.112 + barrel / 2, 9e-3, 0, 18));
6219
+ box2(group, barrel * 0.92, 0.015, 0.013, metal2, 0.112 + barrel * 0.46, -5e-3, 0);
6220
+ cylinderX(group, 4e-3, 5e-3, M.hole, 0.112 + barrel + 15e-4, 9e-3, 0, 12);
6221
+ box2(group, 0.012, 9e-3, 4e-3, M.dark, 0.112 + barrel - 8e-3, 0.021, 0, -0.3);
6222
+ box2(group, 0.012, 6e-3, 0.012, M.dark, 0.105, 0.031, 0);
6223
+ const hammer = kit.pivot(group, 0.036, 0.018, 0);
6224
+ box2(hammer, 0.01, 0.02, 8e-3, M.dark, -4e-3, 8e-3, 0, 0.45);
6225
+ parts.hammer = hammer;
6226
+ const grip = kit.pivot(group, 0.024, -0.062, 0);
6227
+ grip.rotation.z = -0.38;
6228
+ parts.grip = grip;
6229
+ box2(grip, 0.034, 0.1, 0.031, options.wood ? M.wood : M.rubber, 0, 0, 0);
6230
+ 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);
6231
+ box2(group, 0.038, 4e-3, 8e-3, metal2, 0.062, -0.036, 0);
6232
+ box2(group, 4e-3, 0.022, 8e-3, metal2, 0.08, -0.026, 0, 0.2);
6233
+ parts.trigger = box2(group, 4e-3, 0.015, 4e-3, M.dark, 0.06, -0.027, 0, 0.2);
6234
+ parts.muzzle = kit.mark(group, 0.112 + barrel + 6e-3, 9e-3, 0);
6235
+ parts.eject = kit.mark(group, 0.077, 0, 0);
6095
6236
  return { group, parts };
6096
6237
  }
6097
6238
 
@@ -6107,13 +6248,14 @@ var ARCHETYPE_MODELS = (() => {
6107
6248
  }
6108
6249
  return chosen;
6109
6250
  })();
6110
- var BUILDERS = {
6251
+ var BUILDERS2 = {
6111
6252
  pistol: buildPistol,
6112
6253
  revolver: buildRevolver,
6113
6254
  rifle: buildRifle,
6114
6255
  bullpup: buildBullpup,
6115
6256
  p90: (kit) => buildP90(kit),
6116
6257
  break: buildBreakAction,
6258
+ breakLauncher: buildBreakLauncher,
6117
6259
  m2: (kit) => buildM2(kit),
6118
6260
  m134: (kit) => buildM134(kit),
6119
6261
  launcher: buildLauncher
@@ -6137,7 +6279,7 @@ function buildWeaponModelById(THREE, id) {
6137
6279
  const kit = modelKit(THREE);
6138
6280
  const options = { L: entry.length, B: entry.barrel, cal: entry.caliber, ...entry.options };
6139
6281
  if (id in ARCHETYPE_MODELS) delete options.chambers;
6140
- const built = BUILDERS[entry.builder](kit, options);
6282
+ const built = BUILDERS2[entry.builder](kit, options);
6141
6283
  nameParts(built.parts);
6142
6284
  let root;
6143
6285
  if (entry.builder === "launcher") {
@@ -6146,7 +6288,7 @@ function buildWeaponModelById(THREE, id) {
6146
6288
  built.group.position.x = -new THREE.Box3().setFromObject(built.group).min.x;
6147
6289
  } else root = fitLength(kit, built.group, entry.length / 1e3);
6148
6290
  root.name = id;
6149
- markKitOrigin2(root, { owner: "game-weapons-three", kind: "weapon", id });
6291
+ markKitOrigin3(root, { owner: "game-weapons-three", kind: "weapon", id });
6150
6292
  const model = { id, root, inner: built.group, parts: built.parts };
6151
6293
  if (!built.parts.frames) built.parts.frames = deriveFrames(THREE, model);
6152
6294
  return model;
@@ -6379,9 +6521,17 @@ function revolverReload(kind) {
6379
6521
  if (kind !== "one_by_one") events.push(cue(0.7, "move_in", { from: "chambers" }), sound2(0.7, "speedloader"));
6380
6522
  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 };
6381
6523
  }
6382
- var breakReload = () => ({
6524
+ var breakReload = (chambers) => ({
6383
6525
  tweens: [turn("barrels", "z", 0, 0.2, 0, -0.5), turn("barrels", "z", 1, 0.15, -0.5, 0)],
6384
- 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")],
6526
+ events: [
6527
+ sound2(0, "barrels_open"),
6528
+ cue(0.25, "eject_casing", { from: "breech", down: false }),
6529
+ sound2(0.25, "casings_eject"),
6530
+ cue(0.65, "move_in", { from: "breech" }),
6531
+ sound2(0.65, "shell_insert"),
6532
+ ...chambers > 1 ? [sound2(0.8, "shell_insert")] : [],
6533
+ sound2(1.13, "barrels_close")
6534
+ ],
6385
6535
  end: 1.2
6386
6536
  });
6387
6537
  var beltReload = (context) => ({
@@ -6454,7 +6604,7 @@ function draftFor(context, kind) {
6454
6604
  if (family === "rocket" || family === "flame") return heavyReload(context);
6455
6605
  if (parts.mag) return magazineReload(context, kind);
6456
6606
  if (family === "revolver" && parts.crane) return revolverReload(kind);
6457
- if (family === "break" && parts.barrels) return breakReload();
6607
+ if (family === "break" && parts.barrels) return breakReload(parts.breech?.length ?? 2);
6458
6608
  if (family === "belt" && parts.cover && parts.belt) return beltReload(context);
6459
6609
  if (isTube(context)) return tubeReload(context, kind);
6460
6610
  if (family === "bolt") return boltReload(context);
@@ -7154,7 +7304,7 @@ function applyCondition(kit, model, condition) {
7154
7304
  }
7155
7305
 
7156
7306
  // src/models/cartridge.ts
7157
- import { markKitOrigin as markKitOrigin3 } from "@adatechnology/game-contracts";
7307
+ import { markKitOrigin as markKitOrigin4 } from "@adatechnology/game-contracts";
7158
7308
  var BOTTLENECK_RATIO = 0.8;
7159
7309
  function cartridgeShape(caliber, dims) {
7160
7310
  if (caliber.caseType === "shell" || caliber.rim === "shell") return "shell";
@@ -7166,7 +7316,7 @@ function cartridgeShape(caliber, dims) {
7166
7316
  }
7167
7317
  function buildCartridge(kit, dims, shape) {
7168
7318
  const { M, cylinderY } = kit;
7169
- const group = markKitOrigin3(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7319
+ const group = markKitOrigin4(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7170
7320
  const radius = dims.caseDiameter / 2;
7171
7321
  const bulletRadius = dims.projectileDiameter / 2;
7172
7322
  const caseLength = dims.caseLength;
@@ -7198,7 +7348,7 @@ function buildCartridge(kit, dims, shape) {
7198
7348
  }
7199
7349
  function buildCasing(kit, dims, shape) {
7200
7350
  const { M, cylinderX } = kit;
7201
- const group = markKitOrigin3(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7351
+ const group = markKitOrigin4(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7202
7352
  const radius = dims.caseDiameter / 2e3;
7203
7353
  const length = dims.caseLength / 1e3;
7204
7354
  if (shape === "shell") {
@@ -7214,7 +7364,7 @@ function buildCasing(kit, dims, shape) {
7214
7364
  return { group, radius, length };
7215
7365
  }
7216
7366
  function buildRound(kit, dims, shape) {
7217
- const group = markKitOrigin3(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7367
+ const group = markKitOrigin4(kit.group(), { owner: "game-weapons-three", kind: "ammunition", id: shape });
7218
7368
  const cartridge = buildCartridge(kit, dims, shape);
7219
7369
  cartridge.scale.setScalar(1e-3);
7220
7370
  cartridge.rotation.z = -Math.PI / 2;
@@ -7251,6 +7401,38 @@ function createEffects(THREE, sink, random) {
7251
7401
  };
7252
7402
  }
7253
7403
 
7404
+ // src/fx/view-hold.ts
7405
+ var VIEW_HOLD = {
7406
+ /** The right hand's grip, from the eye. */
7407
+ grip: [0.2, -0.2, -0.3],
7408
+ /** A handgun's grip: higher and nearer the middle, the arms out in front. */
7409
+ handgunGrip: [0.13, -0.15, -0.33],
7410
+ /** A rocket launcher's grip: its tube rides on the shoulder, so the hands are higher. */
7411
+ launcherGrip: [0.2, -0.14, -0.3],
7412
+ /** A gun with no grip anchor: the rear of its bore, from the eye. */
7413
+ rear: [0.2, -0.24, -0.12]
7414
+ };
7415
+ var scratch2 = { grip: null };
7416
+ function placeInView(placement) {
7417
+ const { THREE, hand, holder, camera, right } = placement;
7418
+ holder.scale.setScalar(1);
7419
+ hand.position.copy(camera.position);
7420
+ hand.quaternion.copy(camera.quaternion);
7421
+ hand.scale.setScalar(1);
7422
+ holder.position.set(0, 0, 0);
7423
+ hand.updateMatrixWorld(true);
7424
+ if (!right) {
7425
+ holder.position.set(VIEW_HOLD.rear[0], VIEW_HOLD.rear[1], VIEW_HOLD.rear[2] + placement.rear);
7426
+ hand.updateMatrixWorld(true);
7427
+ return;
7428
+ }
7429
+ scratch2.grip ?? (scratch2.grip = new THREE.Vector3());
7430
+ const grip = hand.worldToLocal(right.part.localToWorld(scratch2.grip.copy(right.offset)));
7431
+ const [x, y, z] = placement.kind === "handgun" ? VIEW_HOLD.handgunGrip : placement.kind === "launcher" ? VIEW_HOLD.launcherGrip : VIEW_HOLD.grip;
7432
+ holder.position.set(x - grip.x, y - grip.y, z - grip.z);
7433
+ hand.updateMatrixWorld(true);
7434
+ }
7435
+
7254
7436
  // src/fx/heavy.ts
7255
7437
  import { LIGHT_PRIORITY as LIGHT_PRIORITY2, geometryKey, materialKey } from "@adatechnology/game-render-three";
7256
7438
  import { discardObject3D } from "@adatechnology/game-shapes-three";
@@ -8243,6 +8425,8 @@ function createViewmodel(options) {
8243
8425
  let triggerRest = 0;
8244
8426
  let feedCued = false;
8245
8427
  const rootQuaternion = new THREE.Quaternion();
8428
+ const muzzleAt = new THREE.Vector3();
8429
+ const muzzleAlong = new THREE.Vector3();
8246
8430
  const world = {
8247
8431
  kit,
8248
8432
  scene,
@@ -8342,6 +8526,7 @@ function createViewmodel(options) {
8342
8526
  if (model.parts.stockMove && onTool?.stock === "collapsed") rig.setPartPose("stock", collapsedStockPose(model.parts.stockMove));
8343
8527
  applyCondition(kit, model, condition);
8344
8528
  lastAmmo = options.tool?.().ammo ?? 0;
8529
+ void audio?.loadSamples();
8345
8530
  hand.updateMatrixWorld(true);
8346
8531
  hold = holdOfModel(THREE, model, 1);
8347
8532
  hold = { ...hold, works: handWorkOf(THREE, model, weapon, hold.right) };
@@ -8394,12 +8579,22 @@ function createViewmodel(options) {
8394
8579
  if (!session) return;
8395
8580
  const family = session.model.parts.family;
8396
8581
  session.holderScale = 1;
8582
+ if (options.firstPerson?.()) {
8583
+ placeInView({ THREE, hand, holder, camera, right: hold?.right ?? null, rear: modelRear, kind: family === "pistol" || family === "revolver" ? "handgun" : family === "rocket" ? "launcher" : "long" });
8584
+ lift = 0;
8585
+ pullFromWall(dt);
8586
+ return;
8587
+ }
8588
+ hand.scale.set(1, 1, 1);
8397
8589
  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 });
8398
8590
  hand.updateMatrixWorld(true);
8399
8591
  lift += (liftToKeepInView() - lift) * Math.min(1, dt * HAND_IN_VIEW.rate);
8400
8592
  if (lift > 1e-4) hand.position.addScaledVector(up.set(0, 1, 0).applyQuaternion(camera.quaternion), lift);
8401
8593
  hand.updateMatrixWorld(true);
8402
- if (!options.contact) return;
8594
+ pullFromWall(dt);
8595
+ };
8596
+ const pullFromWall = (dt) => {
8597
+ if (!options.contact || !session) return;
8403
8598
  ahead.set(0, 0, -1).applyQuaternion(camera.quaternion);
8404
8599
  if (reach === 0) reach = Math.max(0.01, measureReach(session));
8405
8600
  const free = options.contact(camera.position, ahead, WALL_PULL.probe);
@@ -8430,7 +8625,7 @@ function createViewmodel(options) {
8430
8625
  }
8431
8626
  const action = current.def.action;
8432
8627
  if (action.kind === "launcher") {
8433
- launchRocket(heavy(current), options.flyRocket !== false);
8628
+ if (current.model.parts.family === "rocket") launchRocket(heavy(current), options.flyRocket !== false);
8434
8629
  audio?.play("explosion", LAUNCH_VOLUME, distance());
8435
8630
  }
8436
8631
  if (action.kind !== "firearm") return;
@@ -8507,6 +8702,15 @@ function createViewmodel(options) {
8507
8702
  condition = next;
8508
8703
  if (session) applyCondition(kit, session.model, condition);
8509
8704
  },
8705
+ muzzle() {
8706
+ const mark = session?.model.parts.muzzle;
8707
+ if (!session || !mark) return null;
8708
+ hand.updateMatrixWorld(true);
8709
+ const at = worldPositionOf(session, mark, muzzleAt);
8710
+ const along = muzzleAlong.set(1, 0, 0).applyQuaternion(session.model.root.getWorldQuaternion(rootQuaternion)).normalize();
8711
+ const kind = session.def.action.kind;
8712
+ return { position: [at.x, at.y, at.z], direction: [along.x, along.y, along.z], drawsFlash: kind === "firearm" || kind === "launcher" };
8713
+ },
8510
8714
  handTargets() {
8511
8715
  if (!session || !hold) return null;
8512
8716
  hand.updateMatrixWorld(true);
@@ -8561,6 +8765,7 @@ function createWeaponFx(options) {
8561
8765
  ...options.raycast ? { raycast: options.raycast } : {},
8562
8766
  ...options.contact ? { contact: options.contact } : {},
8563
8767
  ...options.bodyHold ? { bodyHold: options.bodyHold } : {},
8768
+ ...options.firstPerson ? { firstPerson: options.firstPerson } : {},
8564
8769
  ...options.effects ? { effects: options.effects } : {},
8565
8770
  ...options.flyRocket === void 0 ? {} : { flyRocket: options.flyRocket },
8566
8771
  ...options.particleLimit ? { particleLimit: options.particleLimit } : {},
@@ -8583,6 +8788,7 @@ function createWeaponFx(options) {
8583
8788
  },
8584
8789
  setCondition: (condition) => model.setCondition(condition),
8585
8790
  handTargets: () => model.handTargets() ?? box2.handTargets(),
8791
+ muzzle: () => model.inHand !== null ? model.muzzle() : box2.muzzle(),
8586
8792
  layers,
8587
8793
  get inHand() {
8588
8794
  return model.inHand ?? box2.inHand;
@@ -8604,17 +8810,17 @@ function createWeaponFx(options) {
8604
8810
  }
8605
8811
 
8606
8812
  // src/modeled-prop.ts
8607
- import { markKitOrigin as markKitOrigin4 } from "@adatechnology/game-contracts";
8813
+ import { markKitOrigin as markKitOrigin5 } from "@adatechnology/game-contracts";
8608
8814
  import { mergeByMaterial } from "@adatechnology/game-shapes-three";
8609
8815
  var MODEL_BODY = "body";
8610
8816
  function buildModeledProp(THREE, def, name, decorate, items, props = defaultWeaponProps()) {
8611
8817
  const model = buildWeaponModelById(THREE, def.id) ?? (() => {
8612
- const root = props.buildItemModel(THREE, def, items);
8818
+ const root = props.buildItemModel(THREE, def, items) ?? buildThrowableModel(THREE, def);
8613
8819
  return root ? { root } : void 0;
8614
8820
  })();
8615
8821
  if (!model) return void 0;
8616
8822
  decorate?.(model.root);
8617
- const group = markKitOrigin4(new THREE.Group(), { owner: "game-weapons-three", kind: "weapon", id: def.id });
8823
+ const group = markKitOrigin5(new THREE.Group(), { owner: "game-weapons-three", kind: "weapon", id: def.id });
8618
8824
  group.name = name;
8619
8825
  const box2 = new THREE.Box3().setFromObject(model.root);
8620
8826
  const center = box2.getCenter(new THREE.Vector3());
@@ -9752,7 +9958,7 @@ var targetProp = (id, name) => {
9752
9958
  };
9753
9959
  var weaponItem = (def, name, items, props = defaultWeaponProps()) => {
9754
9960
  const id = `${WEAPON_ITEM_PREFIX}${def.id}`;
9755
- if (!hasWeaponModel(def.id) && !props.itemModelOf(def.id, items)) return asItem(id, name, weaponProp(def, name), { weapon: def.id, movable: true });
9961
+ if (!hasWeaponModel(def.id) && !props.itemModelOf(def.id, items) && !hasThrowableModel(def)) return asItem(id, name, weaponProp(def, name), { weapon: def.id, movable: true });
9756
9962
  return {
9757
9963
  id,
9758
9964
  name,
@@ -9772,7 +9978,7 @@ var mountedM134Item = (def, name, items, props) => ({
9772
9978
  });
9773
9979
  var targetItem = (id, name) => {
9774
9980
  const prop = targetProp(id, name);
9775
- return { id, name, build: (THREE) => markKitOrigin5(buildProp(THREE, prop), { owner: "game-weapons-three", kind: "target", id }), spec: prop.spec, movable: false };
9981
+ return { id, name, build: (THREE) => markKitOrigin6(buildProp(THREE, prop), { owner: "game-weapons-three", kind: "target", id }), spec: prop.spec, movable: false };
9776
9982
  };
9777
9983
  var asItem = (id, name, prop, extra = {}) => ({
9778
9984
  id,
@@ -9812,7 +10018,8 @@ function createWeaponsTestModule(options = {}) {
9812
10018
  const seedOption = options.seed === void 0 ? {} : { seed: options.seed };
9813
10019
  let audio;
9814
10020
  const ownAudio = () => audio === void 0 ? audio = options.sound === false ? null : createWeaponAudio(void 0, { ...seedOption, ...options.samples ? { samples: options.samples } : {} }) : audio;
9815
- const weaponDefs = [...arsenal.weapons.values()].filter((def) => !BODY_ACTIONS.has(def.id));
10021
+ const generics = genericWeapons(arsenal);
10022
+ const weaponDefs = [...arsenal.weapons.values()].filter((def) => !BODY_ACTIONS.has(def.id) && !generics.has(def.id));
9816
10023
  const thumbnailBase = options.thumbnailBase ?? DEFAULT_THUMBNAIL_BASE;
9817
10024
  const withThumbnail = (item, key) => {
9818
10025
  const thumbnail = thumbnailBase === false ? void 0 : weaponThumbnailUrl(key, thumbnailBase);
@@ -9929,6 +10136,7 @@ function createWeaponsTestModule(options = {}) {
9929
10136
  // 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)
9930
10137
  contact: createLaserRaycast(lent.THREE, lent.scene, lent.camera, { now: () => simulatedMs }),
9931
10138
  ...lent.holdFrame ? { bodyHold: () => lent.holdFrame?.() ?? null } : {},
10139
+ firstPerson: () => lent.player().cameraMode === "first",
9932
10140
  ...lent.effects ? { effects: lent.effects } : {},
9933
10141
  ...lent.particleLimit ? { particleLimit: lent.particleLimit } : {},
9934
10142
  ...isLightBudget(lent.lights) ? { lights: lent.lights } : {},
@@ -9979,6 +10187,7 @@ function createWeaponsTestModule(options = {}) {
9979
10187
  });
9980
10188
  const offFrame = lent.onFrame((dt) => created.update(dt));
9981
10189
  const offHands = lent.setHandTargets?.(() => created.handTargets());
10190
+ const offMuzzle = lent.setMuzzleSource?.(() => created.muzzle());
9982
10191
  created.handle({ type: "tool", weapon: lent.tool().weapon }, lent.tool());
9983
10192
  return () => {
9984
10193
  accessories.dispose();
@@ -9987,6 +10196,7 @@ function createWeaponsTestModule(options = {}) {
9987
10196
  offFrame();
9988
10197
  offClock();
9989
10198
  offHands?.();
10199
+ offMuzzle?.();
9990
10200
  created.dispose();
9991
10201
  audio?.dispose();
9992
10202
  fx = null;
@@ -10002,7 +10212,7 @@ import { createArsenal as createArsenal2 } from "@adatechnology/game-weapons";
10002
10212
 
10003
10213
  // src/test-area/explosives-bench.ts
10004
10214
  import { discardObject3D as discardObject3D4 } from "@adatechnology/game-shapes-three";
10005
- import { markKitOrigin as markKitOrigin6 } from "@adatechnology/game-contracts";
10215
+ import { markKitOrigin as markKitOrigin7 } from "@adatechnology/game-contracts";
10006
10216
 
10007
10217
  // src/test-area/explosives-text.ts
10008
10218
  var EXPLOSIVES_TEXT = {
@@ -10265,7 +10475,7 @@ function createExplosivesBench(options) {
10265
10475
  return new (T()).LineLoop(geometry, new (T()).LineBasicMaterial({ color: colour }));
10266
10476
  };
10267
10477
  const drawRings = (vessel) => {
10268
- const group = markKitOrigin6(new (T()).Group(), { owner: "game-weapons-three", kind: "aid" });
10478
+ const group = markKitOrigin7(new (T()).Group(), { owner: "game-weapons-three", kind: "aid" });
10269
10479
  group.name = "blast_rings";
10270
10480
  const lethal = lethalOf(vessel);
10271
10481
  const injury = injuryOf(vessel);
@@ -10725,6 +10935,7 @@ var SHELTERED = 0.5;
10725
10935
  var SHELTERED_MOST = 1;
10726
10936
  var CHEST = 1.1;
10727
10937
  var MIDDLE = 0.3;
10938
+ var AIM_SEARCH = 1.5;
10728
10939
  var HEAVY_ITEMS = ["camp_table"];
10729
10940
  var PERSON_MASS = 70;
10730
10941
  var GROUND_FRICTION = 7;
@@ -10784,6 +10995,27 @@ function pushScenario(all, bench, mode) {
10784
10995
  }
10785
10996
  const partsBefore = new Map(placed.map((body) => [body.objectId, partsOf(body.objectId)]));
10786
10997
  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) });
10998
+ const aimOf = new Map(
10999
+ placed.map((body) => {
11000
+ const from = origin.get(body.objectId) ?? body.from;
11001
+ if (body.kind === "zombie" || body.kind === "survivor") return [body.objectId, [from[0], from[1] + CHEST, from[2]]];
11002
+ const boxes = room.renderer.solidsNear?.(from[0], from[2], AIM_SEARCH).filter((box2) => box2.objectId === body.objectId) ?? [];
11003
+ if (boxes.length === 0) return [body.objectId, [from[0], from[1] + MIDDLE, from[2]]];
11004
+ const low = [0, 1, 2].map((axis) => Math.min(...boxes.map((box2) => box2.min[axis])));
11005
+ const high = [0, 1, 2].map((axis) => Math.max(...boxes.map((box2) => box2.max[axis])));
11006
+ return [body.objectId, [0, 1, 2].map((axis) => (low[axis] + high[axis]) / 2)];
11007
+ })
11008
+ );
11009
+ const coverAt = (point) => new Map(
11010
+ placed.map((body) => {
11011
+ const ignore = source ? [source, body.objectId] : [body.objectId];
11012
+ return [body.objectId, room.world.blastReach?.(point, aimOf.get(body.objectId) ?? body.from, { ignore }) ?? 1];
11013
+ })
11014
+ );
11015
+ let coverOf;
11016
+ const stopListening = room.world.on("*", (event) => {
11017
+ if ((event.type === "explode" || event.type === "blast") && !coverOf) coverOf = coverAt(event.point);
11018
+ });
10787
11019
  if (mode === "p13") bench.actions.shoot();
10788
11020
  else {
10789
11021
  room.setTool("frag_grenade");
@@ -10796,17 +11028,11 @@ function pushScenario(all, bench, mode) {
10796
11028
  step(0.05);
10797
11029
  waited += 0.05;
10798
11030
  }
11031
+ stopListening();
10799
11032
  const blast = bench.lastBlast();
10800
11033
  expect(!!blast, text.push_told, { waited });
10801
11034
  if (!blast) return;
10802
- const coverOf = new Map(
10803
- placed.map((body) => {
10804
- const from = origin.get(body.objectId) ?? body.from;
10805
- const person = body.kind === "zombie" || body.kind === "survivor";
10806
- const ignore = source ? [source, body.objectId] : [body.objectId];
10807
- return [body.objectId, room.world.blastReach?.(blast.point, [from[0], from[1] + (person ? CHEST : MIDDLE), from[2]], { ignore }) ?? 1];
10808
- })
10809
- );
11035
+ const cover = coverOf ?? coverAt(blast.point);
10810
11036
  step(PUSH_READ_AT);
10811
11037
  const rows = placed.map((body) => {
10812
11038
  const from = origin.get(body.objectId) ?? body.from;
@@ -10814,16 +11040,18 @@ function pushScenario(all, bench, mode) {
10814
11040
  const distance = flat(from, blast.point);
10815
11041
  const away = Math.max(1e-6, distance);
10816
11042
  const outward = ((now[0] - from[0]) * (from[0] - blast.point[0]) + (now[2] - from[2]) * (from[2] - blast.point[2])) / away;
10817
- return { ...body, distance, outward, moved: flat(now, from), cover: coverOf.get(body.objectId) ?? 1 };
11043
+ return { ...body, distance, outward, moved: flat(now, from), cover: cover.get(body.objectId) ?? 1 };
10818
11044
  });
10819
11045
  const torn = (row) => (PUSH_KINDS.some((entry) => entry.kind === row.kind) || HEAVY_ITEMS.includes(row.kind)) && partsOf(row.objectId) < (partsBefore.get(row.objectId) ?? 0);
10820
11046
  const inReach = rows.filter((row) => row.distance < blast.radius * 0.85 && !torn(row));
11047
+ const isPerson = (row) => row.kind === "survivor" || row.kind === "zombie";
11048
+ const reached = inReach.filter((row) => row.cover >= SHELTERED && !isPerson(row));
10821
11049
  const sheltered = inReach.filter((row) => row.cover < SHELTERED);
10822
11050
  for (const row of sheltered) {
10823
- 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 } });
11051
+ const open = reached.filter((other) => other.kind === row.kind && other.distance <= row.distance).map((other) => other.outward);
11052
+ const most = Math.max(SHELTERED_MOST, row.cover * Math.max(0, ...open));
11053
+ 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 } });
10824
11054
  }
10825
- const isPerson = (row) => row.kind === "survivor" || row.kind === "zombie";
10826
- const reached = inReach.filter((row) => row.cover >= SHELTERED && !isPerson(row));
10827
11055
  const movedAway = reached.filter((row) => row.outward >= MOVED);
10828
11056
  const least = reached.length === 0 ? 0 : Math.min(...reached.map((row) => row.outward));
10829
11057
  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) });
@@ -11013,14 +11241,19 @@ export {
11013
11241
  BODY_ACTIONS,
11014
11242
  weaponProp,
11015
11243
  buildProp,
11244
+ modelKit,
11245
+ EXPLOSIVE_MODEL_IDS,
11246
+ hasExplosiveModel,
11247
+ buildExplosiveModel,
11016
11248
  SHIPPED_WEAPON_ITEM_MODELS,
11249
+ hasThrowableModel,
11250
+ buildThrowableModel,
11017
11251
  weaponPropsOver,
11018
11252
  defaultWeaponProps,
11019
11253
  registerWeaponItemModel,
11020
11254
  itemModelOfWeapon,
11021
11255
  buildItemWeaponModel,
11022
11256
  buildWeaponModel,
11023
- modelKit,
11024
11257
  deriveFrames,
11025
11258
  mountFrame,
11026
11259
  buildFlyingRocket,
@@ -11066,4 +11299,4 @@ export {
11066
11299
  DEFAULT_EXPLOSIVES_SCENE,
11067
11300
  createExplosivesTestModule
11068
11301
  };
11069
- //# sourceMappingURL=chunk-PRC75VKC.js.map
11302
+ //# sourceMappingURL=chunk-GBO6L7CQ.js.map