@adatechnology/game-weapons-three 0.1.1 → 0.2.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.
- package/dist/index.d.ts +447 -9
- package/dist/index.global.js +10633 -1011
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +4218 -80
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -2,90 +2,296 @@
|
|
|
2
2
|
import { createArsenal } from "@adatechnology/game-weapons";
|
|
3
3
|
|
|
4
4
|
// src/audio.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { createRng } from "@adatechnology/game-weapons";
|
|
6
|
+
|
|
7
|
+
// src/audio/mechanical-sounds.ts
|
|
8
|
+
var metal = (at, frequency, gain) => ({ layer: "metal", at, frequency, gain });
|
|
9
|
+
var sound = (at, soundClass, volume) => ({ layer: "class", at, soundClass, volume });
|
|
10
|
+
var thud = (at, gain, frequency) => ({ layer: "thud", at, gain, frequency });
|
|
11
|
+
var slide = [metal(0, 900, 0.35), metal(0.14, 1300, 0.35)];
|
|
12
|
+
var seat = [metal(0, 1100, 0.45), sound(0, "reload", 0.8)];
|
|
13
|
+
var MECHANICAL_SOUNDS = {
|
|
14
|
+
dry_fire: [sound(0, "empty", 1), metal(0, 2600, 0.25)],
|
|
15
|
+
misfire: [sound(0, "empty", 0.8), metal(0, 2200, 0.2)],
|
|
16
|
+
feed: [metal(0, 1900, 0.2)],
|
|
17
|
+
cycle: [metal(0.22, 900, 0.35), metal(0.36, 1300, 0.35)],
|
|
18
|
+
mode: [metal(0, 2e3, 0.15)],
|
|
19
|
+
safety_on: [metal(0, 2200, 0.22)],
|
|
20
|
+
safety_off: [metal(0, 1800, 0.22)],
|
|
21
|
+
jam: [metal(0, 700, 0.4), metal(0.08, 500, 0.35)],
|
|
22
|
+
clear_start: [metal(0, 1e3, 0.3)],
|
|
23
|
+
clear_end: [metal(0, 1300, 0.35)],
|
|
24
|
+
magazine_out: [metal(0, 1500, 0.35), sound(0, "reload", 0.6)],
|
|
25
|
+
magazine_in: seat,
|
|
26
|
+
charge: slide,
|
|
27
|
+
bolt_open: [metal(0, 900, 0.35)],
|
|
28
|
+
round_in: [metal(0, 2e3, 0.2), sound(0, "reload", 0.6)],
|
|
29
|
+
bolt_close: [metal(0, 1300, 0.35)],
|
|
30
|
+
pump_cycle: slide,
|
|
31
|
+
lever_cycle: [metal(0, 1e3, 0.35), metal(0.12, 1500, 0.35)],
|
|
32
|
+
cylinder_open: [metal(0, 1500, 0.3)],
|
|
33
|
+
cylinder_eject: [metal(0, 2400, 0.25), metal(0.05, 2800, 0.2)],
|
|
34
|
+
cylinder_load: [metal(0, 2e3, 0.2)],
|
|
35
|
+
cylinder_close: seat,
|
|
36
|
+
barrels_open: [metal(0, 1e3, 0.4)],
|
|
37
|
+
casings_eject: [metal(0, 2600, 0.25)],
|
|
38
|
+
rounds_in: [metal(0, 2e3, 0.25), sound(0, "reload", 0.6)],
|
|
39
|
+
barrels_close: seat,
|
|
40
|
+
cover_open: [metal(0, 800, 0.4)],
|
|
41
|
+
belt_swap: [metal(0, 1400, 0.3), metal(0.08, 1800, 0.25)],
|
|
42
|
+
cover_close: [metal(0, 1e3, 0.45)],
|
|
43
|
+
tank_swap: [thud(0, 0.4, 500), metal(0.05, 600, 0.3)],
|
|
44
|
+
tube_drop: [thud(0, 0.35, 380)],
|
|
45
|
+
tube_seat: seat,
|
|
46
|
+
rocket_seat: [metal(0, 900, 0.45), sound(0, "reload", 0.8)],
|
|
47
|
+
breech_open: [metal(0, 1e3, 0.35)],
|
|
48
|
+
breech_close: seat
|
|
49
|
+
};
|
|
50
|
+
var mechanicalSoundOf = (event) => {
|
|
51
|
+
switch (event.type) {
|
|
52
|
+
case "reload_stage":
|
|
53
|
+
return event.stage;
|
|
54
|
+
case "empty":
|
|
55
|
+
return "dry_fire";
|
|
56
|
+
case "misfire":
|
|
57
|
+
return "misfire";
|
|
58
|
+
case "feed":
|
|
59
|
+
return "feed";
|
|
60
|
+
case "cycle":
|
|
61
|
+
return event.phase === "start" ? "cycle" : void 0;
|
|
62
|
+
case "mode":
|
|
63
|
+
return "mode";
|
|
64
|
+
case "safety":
|
|
65
|
+
return event.on ? "safety_on" : "safety_off";
|
|
66
|
+
case "fault":
|
|
67
|
+
return "jam";
|
|
68
|
+
case "clear":
|
|
69
|
+
return event.phase === "start" ? "clear_start" : "clear_end";
|
|
70
|
+
default:
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/audio/shot-sound.ts
|
|
76
|
+
import { muzzleEnergy } from "@adatechnology/game-weapons";
|
|
77
|
+
var GAME_SOUND_CLASSES = {
|
|
78
|
+
pistol: [1800, 0.9, 0.18],
|
|
79
|
+
shotgun: [900, 1.2, 0.35],
|
|
80
|
+
rifle: [1400, 1.1, 0.28],
|
|
81
|
+
long_rifle: [1100, 1.3, 0.45],
|
|
82
|
+
impact: [2500, 0.25, 0.05],
|
|
83
|
+
flesh: [600, 0.35, 0.08],
|
|
84
|
+
step: [400, 0.08, 0.05],
|
|
85
|
+
glass: [5e3, 0.5, 0.3],
|
|
86
|
+
wood: [700, 0.5, 0.2],
|
|
87
|
+
reload: [3e3, 0.15, 0.06],
|
|
88
|
+
empty: [4e3, 0.12, 0.03],
|
|
89
|
+
explosion: [200, 1.6, 1.4]
|
|
90
|
+
};
|
|
91
|
+
var FALLBACK_SOUND_CLASS = [1e3, 0.5, 0.1];
|
|
92
|
+
var GAME_DISTANCE_CURVE = { gainFalloff: 4e-3, cutoffFalloff: 0.01, minGain: 0.01 };
|
|
93
|
+
var DEFAULT_ENERGY_CURVE = {
|
|
94
|
+
floorJ: 50,
|
|
95
|
+
spanDecades: 2.5,
|
|
96
|
+
gain: [0.7, 0.6],
|
|
97
|
+
duration: [0.75, 0.5],
|
|
98
|
+
cutoff: [1.15, -0.3],
|
|
99
|
+
crackGain: [0.3, 0.4],
|
|
100
|
+
bodyGain: [0.35, 0.4],
|
|
101
|
+
bodyDuration: [0.14, 0.2],
|
|
102
|
+
bodyFrequency: [140, -70]
|
|
103
|
+
};
|
|
104
|
+
var CLASS_REFERENCE_ENERGY = { pistol: 500, shotgun: 2700, rifle: 2100, long_rifle: 3500 };
|
|
105
|
+
var HANDGUN_CATEGORIES = /* @__PURE__ */ new Set(["pistol", "revolver", "smg"]);
|
|
106
|
+
var SHELL_KINDS = /* @__PURE__ */ new Set(["buck", "slug", "birdshot"]);
|
|
107
|
+
var DEFAULT_BANDS = { handgunBelowJ: 1e3, rifleBelowJ: 3e3 };
|
|
108
|
+
var DEFAULT_SUPPRESSOR = { gain: 0.3, cutoff: 0.6, crack: 0, body: 0.5 };
|
|
109
|
+
var DEFAULT_INDOORS = { duration: 1.5, cutoff: 0.85 };
|
|
110
|
+
var CRACK_CUTOFF = 3e3;
|
|
111
|
+
var CRACK_SECONDS = 0.03;
|
|
112
|
+
var BODY_TO_FREQUENCY = 40;
|
|
113
|
+
var linear = ([base, span], k) => base + span * k;
|
|
114
|
+
var energyPosition = (energyJ, curve) => Math.min(1, Math.max(0, Math.log10(Math.max(energyJ, curve.floorJ) / curve.floorJ) / curve.spanDecades));
|
|
115
|
+
var energyOfShot = (input) => {
|
|
116
|
+
if (input.energyJ !== void 0) return input.energyJ;
|
|
117
|
+
if (!input.caliber) return 0;
|
|
118
|
+
return muzzleEnergy(input.caliber) * (input.caliber.pellets ?? 1);
|
|
119
|
+
};
|
|
120
|
+
var defaultClassOf = (input, energyJ, bands) => {
|
|
121
|
+
if (input.category === "shotgun" || (input.caliber?.pellets ?? 1) > 1 || SHELL_KINDS.has(input.caliber?.kind ?? "")) return "shotgun";
|
|
122
|
+
if (input.category && HANDGUN_CATEGORIES.has(input.category)) return "pistol";
|
|
123
|
+
if (energyJ < bands.handgunBelowJ) return "pistol";
|
|
124
|
+
return energyJ < bands.rifleBelowJ ? "rifle" : "long_rifle";
|
|
125
|
+
};
|
|
126
|
+
var computeShotSound = (input, options = {}) => {
|
|
127
|
+
const curve = { ...DEFAULT_ENERGY_CURVE, ...options.energy };
|
|
128
|
+
const distanceCurve = { ...GAME_DISTANCE_CURVE, ...options.distance };
|
|
129
|
+
const energyJ = energyOfShot(input);
|
|
130
|
+
const soundClass = options.classOf ? options.classOf(input, energyJ) : defaultClassOf(input, energyJ, options.bands ?? DEFAULT_BANDS);
|
|
131
|
+
const table = { ...GAME_SOUND_CLASSES, ...options.classes };
|
|
132
|
+
const [classCutoff, classGain, classSeconds] = table[soundClass] ?? FALLBACK_SOUND_CLASS;
|
|
133
|
+
const referenceEnergy = options.referenceEnergy?.[soundClass] ?? CLASS_REFERENCE_ENERGY[soundClass] ?? energyJ;
|
|
134
|
+
const k = energyPosition(energyJ, curve);
|
|
135
|
+
const referenceK = energyPosition(referenceEnergy, curve);
|
|
136
|
+
const distance = Math.max(0, input.distance ?? 0);
|
|
137
|
+
const attenuation = (input.volume ?? 1) / (1 + distance * distance * distanceCurve.gainFalloff);
|
|
138
|
+
const muffling = 1 / (1 + distance * distanceCurve.cutoffFalloff);
|
|
139
|
+
const suppressor = input.suppressed ? { ...DEFAULT_SUPPRESSOR, ...options.suppressor } : { gain: 1, cutoff: 1, crack: 1, body: 1 };
|
|
140
|
+
const indoors = input.indoors ? { ...DEFAULT_INDOORS, ...options.indoors } : { duration: 1, cutoff: 1 };
|
|
141
|
+
const gain = classGain * attenuation * (linear(curve.gain, k) / linear(curve.gain, referenceK)) * suppressor.gain;
|
|
142
|
+
const duration = classSeconds * (linear(curve.duration, k) / linear(curve.duration, referenceK)) * indoors.duration;
|
|
143
|
+
const cutoff = classCutoff * muffling * (linear(curve.cutoff, k) / linear(curve.cutoff, referenceK)) * suppressor.cutoff * indoors.cutoff;
|
|
144
|
+
return {
|
|
145
|
+
soundClass,
|
|
146
|
+
energyJ,
|
|
147
|
+
audible: attenuation >= distanceCurve.minGain,
|
|
148
|
+
cutoff,
|
|
149
|
+
gain,
|
|
150
|
+
duration,
|
|
151
|
+
crack: { gain: linear(curve.crackGain, k) * attenuation * suppressor.crack, cutoff: CRACK_CUTOFF, duration: CRACK_SECONDS },
|
|
152
|
+
body: {
|
|
153
|
+
gain: linear(curve.bodyGain, k) * attenuation * suppressor.body,
|
|
154
|
+
frequency: linear(curve.bodyFrequency, k),
|
|
155
|
+
to: BODY_TO_FREQUENCY,
|
|
156
|
+
duration: linear(curve.bodyDuration, k) * indoors.duration
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// src/audio.ts
|
|
162
|
+
var NOISE_SECONDS = 1.5;
|
|
163
|
+
var globalContext = () => {
|
|
164
|
+
const scope2 = globalThis;
|
|
165
|
+
const Ctor = scope2.AudioContext ?? scope2.webkitAudioContext;
|
|
166
|
+
return Ctor ? new Ctor() : null;
|
|
167
|
+
};
|
|
168
|
+
function createWeaponAudio(volume = 0.6, options = {}) {
|
|
169
|
+
const makeContext = options.createContext ?? globalContext;
|
|
170
|
+
const rng = createRng(options.seed ?? 1);
|
|
171
|
+
const classes = { ...GAME_SOUND_CLASSES, ...options.classes };
|
|
172
|
+
const distanceCurve = { ...GAME_DISTANCE_CURVE, ...options.distance };
|
|
173
|
+
const mechanicalTable = { ...MECHANICAL_SOUNDS, ...options.mechanical };
|
|
8
174
|
let ctx = null;
|
|
9
175
|
let master = null;
|
|
10
176
|
let noise = null;
|
|
11
177
|
let level = volume;
|
|
12
178
|
const ready = () => {
|
|
13
|
-
if (!Ctor) return null;
|
|
14
179
|
if (!ctx) {
|
|
15
|
-
|
|
180
|
+
const created = makeContext();
|
|
181
|
+
if (!created) return null;
|
|
182
|
+
ctx = created;
|
|
16
183
|
master = ctx.createGain();
|
|
17
184
|
master.gain.value = level;
|
|
18
185
|
master.connect(ctx.destination);
|
|
19
186
|
noise = ctx.createBuffer(1, Math.floor(ctx.sampleRate * NOISE_SECONDS), ctx.sampleRate);
|
|
20
187
|
const data = noise.getChannelData(0);
|
|
21
|
-
for (let i = 0; i < data.length; i++) data[i] =
|
|
188
|
+
for (let i = 0; i < data.length; i++) data[i] = rng() * 2 - 1;
|
|
22
189
|
}
|
|
23
190
|
if (ctx.state === "suspended") void ctx.resume();
|
|
24
191
|
return ctx;
|
|
25
192
|
};
|
|
26
|
-
const burst = (
|
|
193
|
+
const burst = (config) => {
|
|
27
194
|
const audio = ready();
|
|
28
195
|
if (!audio || !noise || !master) return;
|
|
29
|
-
const start = audio.currentTime + (
|
|
196
|
+
const start = audio.currentTime + (config.at ?? 0);
|
|
30
197
|
const source = audio.createBufferSource();
|
|
31
198
|
source.buffer = noise;
|
|
32
199
|
const filter = audio.createBiquadFilter();
|
|
33
|
-
filter.type =
|
|
34
|
-
filter.frequency.value =
|
|
35
|
-
filter.Q.value =
|
|
200
|
+
filter.type = config.filter;
|
|
201
|
+
filter.frequency.value = config.frequency;
|
|
202
|
+
filter.Q.value = config.q ?? 0.8;
|
|
36
203
|
const gain = audio.createGain();
|
|
37
204
|
gain.gain.setValueAtTime(1e-4, start);
|
|
38
|
-
gain.gain.exponentialRampToValueAtTime(
|
|
39
|
-
gain.gain.exponentialRampToValueAtTime(1e-4, start +
|
|
205
|
+
gain.gain.exponentialRampToValueAtTime(Math.max(config.gain, 2e-4), start + 4e-3);
|
|
206
|
+
gain.gain.exponentialRampToValueAtTime(1e-4, start + config.seconds);
|
|
40
207
|
source.connect(filter).connect(gain).connect(master);
|
|
41
|
-
source.start(start);
|
|
42
|
-
source.stop(start +
|
|
208
|
+
source.start(start, rng() * Math.max(0, NOISE_SECONDS - config.seconds));
|
|
209
|
+
source.stop(start + config.seconds + 0.05);
|
|
43
210
|
};
|
|
44
|
-
const
|
|
211
|
+
const classBurst = (at, cutoff, gain, seconds) => {
|
|
212
|
+
const audio = ready();
|
|
213
|
+
if (!audio || !noise || !master) return;
|
|
214
|
+
const start = audio.currentTime + at;
|
|
215
|
+
const source = audio.createBufferSource();
|
|
216
|
+
source.buffer = noise;
|
|
217
|
+
const filter = audio.createBiquadFilter();
|
|
218
|
+
filter.type = "lowpass";
|
|
219
|
+
filter.frequency.value = cutoff;
|
|
220
|
+
const envelope = audio.createGain();
|
|
221
|
+
envelope.gain.setValueAtTime(gain, start);
|
|
222
|
+
envelope.gain.exponentialRampToValueAtTime(1e-3, start + seconds);
|
|
223
|
+
source.connect(filter).connect(envelope).connect(master);
|
|
224
|
+
source.start(start, rng() * Math.max(0, NOISE_SECONDS - seconds));
|
|
225
|
+
source.stop(start + seconds + 0.05);
|
|
226
|
+
};
|
|
227
|
+
const tone = (config) => {
|
|
45
228
|
const audio = ready();
|
|
46
229
|
if (!audio || !master) return;
|
|
47
|
-
const start = audio.currentTime + (
|
|
230
|
+
const start = audio.currentTime + (config.at ?? 0);
|
|
48
231
|
const osc = audio.createOscillator();
|
|
49
|
-
osc.type =
|
|
50
|
-
osc.frequency.setValueAtTime(
|
|
51
|
-
if (
|
|
232
|
+
osc.type = config.type ?? "sine";
|
|
233
|
+
osc.frequency.setValueAtTime(config.frequency, start);
|
|
234
|
+
if (config.to) osc.frequency.exponentialRampToValueAtTime(config.to, start + config.seconds);
|
|
52
235
|
const gain = audio.createGain();
|
|
53
236
|
gain.gain.setValueAtTime(1e-4, start);
|
|
54
|
-
gain.gain.exponentialRampToValueAtTime(
|
|
55
|
-
gain.gain.exponentialRampToValueAtTime(1e-4, start +
|
|
237
|
+
gain.gain.exponentialRampToValueAtTime(Math.max(config.gain, 2e-4), start + 3e-3);
|
|
238
|
+
gain.gain.exponentialRampToValueAtTime(1e-4, start + config.seconds);
|
|
56
239
|
osc.connect(gain).connect(master);
|
|
57
240
|
osc.start(start);
|
|
58
|
-
osc.stop(start +
|
|
241
|
+
osc.stop(start + config.seconds + 0.05);
|
|
59
242
|
};
|
|
60
|
-
const
|
|
243
|
+
const metal2 = (at, frequency, gain) => {
|
|
61
244
|
burst({ at, seconds: 0.05, gain, filter: "highpass", frequency, q: 2 });
|
|
62
245
|
tone({ at, seconds: 0.04, gain: gain * 0.6, frequency: frequency * 0.6, type: "square" });
|
|
63
246
|
};
|
|
247
|
+
const play = (soundClass, classVolume = 1, distance = 0, at = 0) => {
|
|
248
|
+
const attenuation = classVolume / (1 + distance * distance * distanceCurve.gainFalloff);
|
|
249
|
+
if (attenuation < distanceCurve.minGain) return;
|
|
250
|
+
const [cutoff, gain, seconds] = classes[soundClass] ?? FALLBACK_SOUND_CLASS;
|
|
251
|
+
classBurst(at, cutoff / (1 + distance * distanceCurve.cutoffFalloff), gain * attenuation, seconds);
|
|
252
|
+
};
|
|
253
|
+
const step = (entry) => {
|
|
254
|
+
if (entry.layer === "metal") metal2(entry.at, entry.frequency, entry.gain);
|
|
255
|
+
else if (entry.layer === "class") play(entry.soundClass, entry.volume, 0, entry.at);
|
|
256
|
+
else burst({ at: entry.at, seconds: 0.09, gain: entry.gain, filter: "lowpass", frequency: entry.frequency });
|
|
257
|
+
};
|
|
258
|
+
const mechanical = (id) => {
|
|
259
|
+
for (const entry of mechanicalTable[id] ?? []) step(entry);
|
|
260
|
+
};
|
|
261
|
+
const shotSound = (input) => computeShotSound(input, options);
|
|
64
262
|
return {
|
|
65
|
-
shot(energyJ, category) {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
burst({ seconds:
|
|
70
|
-
tone({ seconds:
|
|
263
|
+
shot(energyJ, category, extras = {}) {
|
|
264
|
+
const sound2 = shotSound({ ...extras, energyJ, category });
|
|
265
|
+
if (!sound2.audible) return;
|
|
266
|
+
classBurst(0, sound2.cutoff, sound2.gain, sound2.duration);
|
|
267
|
+
if (sound2.crack.gain > 0) burst({ seconds: sound2.crack.duration, gain: sound2.crack.gain, filter: "highpass", frequency: sound2.crack.cutoff });
|
|
268
|
+
tone({ seconds: sound2.body.duration, gain: sound2.body.gain, frequency: sound2.body.frequency, to: sound2.body.to });
|
|
71
269
|
},
|
|
270
|
+
shotSound,
|
|
72
271
|
click() {
|
|
73
|
-
|
|
272
|
+
mechanical("dry_fire");
|
|
273
|
+
},
|
|
274
|
+
mechanical,
|
|
275
|
+
event(event) {
|
|
276
|
+
const id = mechanicalSoundOf(event);
|
|
277
|
+
if (id) mechanical(id);
|
|
278
|
+
},
|
|
279
|
+
play(soundClass, classVolume, distance) {
|
|
280
|
+
play(soundClass, classVolume, distance);
|
|
74
281
|
},
|
|
75
282
|
reloadStart(category) {
|
|
76
|
-
|
|
77
|
-
if (category !== "shotgun" && category !== "revolver")
|
|
283
|
+
metal2(0, 1500, 0.35);
|
|
284
|
+
if (category !== "shotgun" && category !== "revolver") metal2(0.12, 900, 0.2);
|
|
78
285
|
},
|
|
79
286
|
reloadEnd(category) {
|
|
80
|
-
|
|
81
|
-
if (category !== "revolver")
|
|
287
|
+
metal2(0, 1100, 0.45);
|
|
288
|
+
if (category !== "revolver") metal2(0.18, 1900, 0.4);
|
|
82
289
|
},
|
|
83
290
|
cycle() {
|
|
84
|
-
|
|
85
|
-
metal(0.36, 1300, 0.35);
|
|
291
|
+
mechanical("cycle");
|
|
86
292
|
},
|
|
87
293
|
clink() {
|
|
88
|
-
const f = 3200 +
|
|
294
|
+
const f = 3200 + rng() * 2e3;
|
|
89
295
|
tone({ seconds: 0.09, gain: 0.12, frequency: f });
|
|
90
296
|
tone({ seconds: 0.06, gain: 0.06, frequency: f * 2.7 });
|
|
91
297
|
},
|
|
@@ -105,6 +311,2512 @@ function createWeaponAudio(volume = 0.6) {
|
|
|
105
311
|
};
|
|
106
312
|
}
|
|
107
313
|
|
|
314
|
+
// src/box-fx.ts
|
|
315
|
+
import { createRng as createRng2, muzzleEnergy as muzzleEnergy2 } from "@adatechnology/game-weapons";
|
|
316
|
+
|
|
317
|
+
// src/fx/layers.ts
|
|
318
|
+
var createLayers = () => ({ casings: true, magazines: true, muzzleFlash: true });
|
|
319
|
+
|
|
320
|
+
// src/merge-by-material.ts
|
|
321
|
+
var ATTRIBUTES = ["position", "normal", "uv"];
|
|
322
|
+
var isVisible = (object) => {
|
|
323
|
+
for (let node = object; node; node = node.parent) if (!node.visible) return false;
|
|
324
|
+
return true;
|
|
325
|
+
};
|
|
326
|
+
var bake = (THREE, mesh, matrix) => {
|
|
327
|
+
const source = mesh.geometry.index ? mesh.geometry.toNonIndexed() : mesh.geometry.clone();
|
|
328
|
+
const baked = new THREE.BufferGeometry();
|
|
329
|
+
const count = source.getAttribute("position").count;
|
|
330
|
+
for (const name of ATTRIBUTES) {
|
|
331
|
+
const attribute = source.getAttribute(name);
|
|
332
|
+
baked.setAttribute(name, attribute ?? new THREE.BufferAttribute(new Float32Array(count * (name === "uv" ? 2 : 3)), name === "uv" ? 2 : 3));
|
|
333
|
+
}
|
|
334
|
+
source.dispose();
|
|
335
|
+
return baked.applyMatrix4(matrix);
|
|
336
|
+
};
|
|
337
|
+
var join = (THREE, geometries) => {
|
|
338
|
+
const merged = new THREE.BufferGeometry();
|
|
339
|
+
for (const name of ATTRIBUTES) {
|
|
340
|
+
const size = (geometries[0]?.getAttribute(name)).itemSize;
|
|
341
|
+
const total = geometries.reduce((sum, geometry) => sum + geometry.getAttribute(name).count, 0);
|
|
342
|
+
const array = new Float32Array(total * size);
|
|
343
|
+
let offset = 0;
|
|
344
|
+
for (const geometry of geometries) {
|
|
345
|
+
array.set(geometry.getAttribute(name).array, offset);
|
|
346
|
+
offset += geometry.getAttribute(name).count * size;
|
|
347
|
+
}
|
|
348
|
+
merged.setAttribute(name, new THREE.BufferAttribute(array, size));
|
|
349
|
+
}
|
|
350
|
+
return merged;
|
|
351
|
+
};
|
|
352
|
+
function mergeByMaterial(THREE, root, space) {
|
|
353
|
+
const toSpace = space.matrixWorld.clone().invert();
|
|
354
|
+
const byMaterial = /* @__PURE__ */ new Map();
|
|
355
|
+
root.traverse((object) => {
|
|
356
|
+
const mesh = object;
|
|
357
|
+
if (!mesh.isMesh || Array.isArray(mesh.material) || !isVisible(mesh)) return;
|
|
358
|
+
const list = byMaterial.get(mesh.material) ?? [];
|
|
359
|
+
list.push(bake(THREE, mesh, new THREE.Matrix4().multiplyMatrices(toSpace, mesh.matrixWorld)));
|
|
360
|
+
byMaterial.set(mesh.material, list);
|
|
361
|
+
});
|
|
362
|
+
return [...byMaterial].map(([material, geometries]) => {
|
|
363
|
+
const merged = new THREE.Mesh(join(THREE, geometries), material);
|
|
364
|
+
for (const geometry of geometries) geometry.dispose();
|
|
365
|
+
merged.castShadow = false;
|
|
366
|
+
merged.receiveShadow = true;
|
|
367
|
+
return merged;
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/models/index.ts
|
|
372
|
+
import { WEAPONS } from "@adatechnology/game-weapons";
|
|
373
|
+
|
|
374
|
+
// src/data/models.json
|
|
375
|
+
var models_default = {
|
|
376
|
+
glock_17: {
|
|
377
|
+
builder: "pistol",
|
|
378
|
+
caliber: "9\xD719",
|
|
379
|
+
length: 202,
|
|
380
|
+
barrel: 114,
|
|
381
|
+
options: {
|
|
382
|
+
st: "glock",
|
|
383
|
+
rel: 0.7
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
glock_18: {
|
|
387
|
+
builder: "pistol",
|
|
388
|
+
caliber: "9\xD719",
|
|
389
|
+
length: 202,
|
|
390
|
+
barrel: 114,
|
|
391
|
+
options: {
|
|
392
|
+
st: "glock",
|
|
393
|
+
sel: 1,
|
|
394
|
+
rpm: 1200,
|
|
395
|
+
rel: 0.9
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
glock_19: {
|
|
399
|
+
builder: "pistol",
|
|
400
|
+
caliber: "9\xD719",
|
|
401
|
+
length: 185,
|
|
402
|
+
barrel: 102,
|
|
403
|
+
options: {
|
|
404
|
+
st: "glock",
|
|
405
|
+
gl: 0.095,
|
|
406
|
+
rel: 0.7
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
taurus_g2c: {
|
|
410
|
+
builder: "pistol",
|
|
411
|
+
caliber: "9\xD719",
|
|
412
|
+
length: 159,
|
|
413
|
+
barrel: 81,
|
|
414
|
+
options: {
|
|
415
|
+
st: "g2c",
|
|
416
|
+
gl: 0.085,
|
|
417
|
+
rel: 1.5
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
taurus_ts9: {
|
|
421
|
+
builder: "pistol",
|
|
422
|
+
caliber: "9\xD719",
|
|
423
|
+
length: 194,
|
|
424
|
+
barrel: 104,
|
|
425
|
+
options: {
|
|
426
|
+
st: "taurus",
|
|
427
|
+
rel: 1.2
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
beretta_92fs: {
|
|
431
|
+
builder: "pistol",
|
|
432
|
+
caliber: "9\xD719",
|
|
433
|
+
length: 217,
|
|
434
|
+
barrel: 125,
|
|
435
|
+
options: {
|
|
436
|
+
st: "beretta",
|
|
437
|
+
h: 0.032,
|
|
438
|
+
rel: 0.9
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
sig_sauer_p226: {
|
|
442
|
+
builder: "pistol",
|
|
443
|
+
caliber: "9\xD719",
|
|
444
|
+
length: 196,
|
|
445
|
+
barrel: 112,
|
|
446
|
+
options: {
|
|
447
|
+
st: "sig",
|
|
448
|
+
h: 0.034,
|
|
449
|
+
rel: 0.8
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
cz_75: {
|
|
453
|
+
builder: "pistol",
|
|
454
|
+
caliber: "9\xD719",
|
|
455
|
+
length: 206,
|
|
456
|
+
barrel: 120,
|
|
457
|
+
options: {
|
|
458
|
+
st: "cz",
|
|
459
|
+
h: 0.03,
|
|
460
|
+
rel: 0.9
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
taurus_pt100: {
|
|
464
|
+
builder: "pistol",
|
|
465
|
+
caliber: ".40 S&W",
|
|
466
|
+
length: 216,
|
|
467
|
+
barrel: 127,
|
|
468
|
+
options: {
|
|
469
|
+
st: "pt100",
|
|
470
|
+
stain: 1,
|
|
471
|
+
rel: 1.2
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
colt_m1911: {
|
|
475
|
+
builder: "pistol",
|
|
476
|
+
caliber: ".45 ACP",
|
|
477
|
+
length: 216,
|
|
478
|
+
barrel: 127,
|
|
479
|
+
options: {
|
|
480
|
+
st: "m1911",
|
|
481
|
+
h: 0.029,
|
|
482
|
+
gl: 0.1,
|
|
483
|
+
rel: 1.1
|
|
484
|
+
}
|
|
485
|
+
},
|
|
486
|
+
makarov_pm: {
|
|
487
|
+
builder: "pistol",
|
|
488
|
+
caliber: "9\xD718",
|
|
489
|
+
length: 161,
|
|
490
|
+
barrel: 94,
|
|
491
|
+
options: {
|
|
492
|
+
st: "makarov",
|
|
493
|
+
h: 0.028,
|
|
494
|
+
gl: 0.088,
|
|
495
|
+
rel: 0.8
|
|
496
|
+
}
|
|
497
|
+
},
|
|
498
|
+
fn_five_seven: {
|
|
499
|
+
builder: "pistol",
|
|
500
|
+
caliber: "5,7\xD728",
|
|
501
|
+
length: 208,
|
|
502
|
+
barrel: 122,
|
|
503
|
+
options: {
|
|
504
|
+
st: "fiveseven",
|
|
505
|
+
gl: 0.12,
|
|
506
|
+
rel: 0.9
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
desert_eagle_mark_xix: {
|
|
510
|
+
builder: "pistol",
|
|
511
|
+
caliber: ".50 AE",
|
|
512
|
+
length: 270,
|
|
513
|
+
barrel: 152,
|
|
514
|
+
options: {
|
|
515
|
+
st: "deagle",
|
|
516
|
+
h: 0.04,
|
|
517
|
+
gl: 0.125,
|
|
518
|
+
deagle: 1,
|
|
519
|
+
steel: 1,
|
|
520
|
+
rel: 1.8
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
taurus_82: {
|
|
524
|
+
builder: "revolver",
|
|
525
|
+
caliber: ".38 SPL",
|
|
526
|
+
length: 240,
|
|
527
|
+
barrel: 102,
|
|
528
|
+
options: {
|
|
529
|
+
wood: 1
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
sw_686: {
|
|
533
|
+
builder: "revolver",
|
|
534
|
+
caliber: ".357 Magnum",
|
|
535
|
+
length: 244,
|
|
536
|
+
barrel: 102,
|
|
537
|
+
options: {
|
|
538
|
+
steel: 1,
|
|
539
|
+
chambers: 7
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
sw_model_29: {
|
|
543
|
+
builder: "revolver",
|
|
544
|
+
caliber: ".44 Magnum",
|
|
545
|
+
length: 302,
|
|
546
|
+
barrel: 165,
|
|
547
|
+
options: {
|
|
548
|
+
wood: 1
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
hk_mp5a3: {
|
|
552
|
+
builder: "rifle",
|
|
553
|
+
caliber: "9\xD719",
|
|
554
|
+
length: 700,
|
|
555
|
+
barrel: 225,
|
|
556
|
+
options: {
|
|
557
|
+
mag: "curved",
|
|
558
|
+
magLen: 0.72,
|
|
559
|
+
stock: "retract",
|
|
560
|
+
stockLen: 0.22,
|
|
561
|
+
hR: 0.055,
|
|
562
|
+
hg: "poly",
|
|
563
|
+
hgFrac: 0.85,
|
|
564
|
+
g3: 1,
|
|
565
|
+
rpm: 800,
|
|
566
|
+
sel: 1
|
|
567
|
+
}
|
|
568
|
+
},
|
|
569
|
+
taurus_smt9: {
|
|
570
|
+
builder: "rifle",
|
|
571
|
+
caliber: "9\xD719",
|
|
572
|
+
length: 660,
|
|
573
|
+
barrel: 200,
|
|
574
|
+
options: {
|
|
575
|
+
mag: "box",
|
|
576
|
+
stock: "retract",
|
|
577
|
+
stockLen: 0.2,
|
|
578
|
+
hg: "rail",
|
|
579
|
+
hgFrac: 0.8,
|
|
580
|
+
rail: 1,
|
|
581
|
+
rpm: 750,
|
|
582
|
+
sel: 1
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
hk_ump_45: {
|
|
586
|
+
builder: "rifle",
|
|
587
|
+
caliber: ".45 ACP",
|
|
588
|
+
length: 690,
|
|
589
|
+
barrel: 200,
|
|
590
|
+
options: {
|
|
591
|
+
mag: "box",
|
|
592
|
+
magLen: 1.1,
|
|
593
|
+
stock: "skeleton",
|
|
594
|
+
stockLen: 0.22,
|
|
595
|
+
hR: 0.07,
|
|
596
|
+
hg: "poly",
|
|
597
|
+
hgFrac: 0.85,
|
|
598
|
+
rpm: 600,
|
|
599
|
+
sel: 1,
|
|
600
|
+
rail: 1
|
|
601
|
+
}
|
|
602
|
+
},
|
|
603
|
+
uzi: {
|
|
604
|
+
builder: "rifle",
|
|
605
|
+
caliber: "9\xD719",
|
|
606
|
+
length: 650,
|
|
607
|
+
barrel: 260,
|
|
608
|
+
options: {
|
|
609
|
+
mag: "box",
|
|
610
|
+
magAt: 0.06,
|
|
611
|
+
magLen: 1.3,
|
|
612
|
+
stock: "fold",
|
|
613
|
+
stockLen: 0.2,
|
|
614
|
+
hR: 0.065,
|
|
615
|
+
hg: "poly",
|
|
616
|
+
hgFrac: 0.4,
|
|
617
|
+
grip: "none",
|
|
618
|
+
rpm: 600
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
fn_p90: {
|
|
622
|
+
builder: "p90",
|
|
623
|
+
caliber: "5,7\xD728",
|
|
624
|
+
length: 500,
|
|
625
|
+
barrel: 264,
|
|
626
|
+
options: {
|
|
627
|
+
rpm: 900
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
kriss_vector: {
|
|
631
|
+
builder: "rifle",
|
|
632
|
+
caliber: ".45 ACP",
|
|
633
|
+
length: 622,
|
|
634
|
+
barrel: 140,
|
|
635
|
+
options: {
|
|
636
|
+
mag: "box",
|
|
637
|
+
magLen: 1.1,
|
|
638
|
+
stock: "retract",
|
|
639
|
+
stockLen: 0.2,
|
|
640
|
+
hR: 0.085,
|
|
641
|
+
hg: "rail",
|
|
642
|
+
hgFrac: 0.9,
|
|
643
|
+
rail: 1,
|
|
644
|
+
scope: 1,
|
|
645
|
+
rpm: 1100
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
thompson_m1a1: {
|
|
649
|
+
builder: "rifle",
|
|
650
|
+
caliber: ".45 ACP",
|
|
651
|
+
length: 813,
|
|
652
|
+
barrel: 267,
|
|
653
|
+
options: {
|
|
654
|
+
wood: 1,
|
|
655
|
+
mag: "box",
|
|
656
|
+
magLen: 1.2,
|
|
657
|
+
stock: "fixed",
|
|
658
|
+
stockLen: 0.3,
|
|
659
|
+
hg: "none",
|
|
660
|
+
hR: 0.055,
|
|
661
|
+
jacket: 1,
|
|
662
|
+
rpm: 700,
|
|
663
|
+
charge: 1
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
cz_scorpion_evo_3: {
|
|
667
|
+
builder: "rifle",
|
|
668
|
+
caliber: "9\xD719",
|
|
669
|
+
length: 670,
|
|
670
|
+
barrel: 196,
|
|
671
|
+
options: {
|
|
672
|
+
mag: "box",
|
|
673
|
+
curve: 0.05,
|
|
674
|
+
magLen: 1.2,
|
|
675
|
+
stock: "fold",
|
|
676
|
+
stockLen: 0.22,
|
|
677
|
+
hg: "rail",
|
|
678
|
+
hgFrac: 0.85,
|
|
679
|
+
rail: 1,
|
|
680
|
+
scope: 1,
|
|
681
|
+
rpm: 1150
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
mac_10: {
|
|
685
|
+
builder: "rifle",
|
|
686
|
+
caliber: ".45 ACP",
|
|
687
|
+
length: 548,
|
|
688
|
+
barrel: 146,
|
|
689
|
+
options: {
|
|
690
|
+
mag: "box",
|
|
691
|
+
magAt: 0.04,
|
|
692
|
+
magLen: 1.2,
|
|
693
|
+
stock: "fold",
|
|
694
|
+
stockLen: 0.22,
|
|
695
|
+
hR: 0.075,
|
|
696
|
+
hg: "none",
|
|
697
|
+
grip: "none",
|
|
698
|
+
brake: 2,
|
|
699
|
+
rpm: 1090,
|
|
700
|
+
rel: 1.4
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
remington_870: {
|
|
704
|
+
builder: "rifle",
|
|
705
|
+
caliber: "12 ga",
|
|
706
|
+
length: 990,
|
|
707
|
+
barrel: 470,
|
|
708
|
+
options: {
|
|
709
|
+
wood: 1,
|
|
710
|
+
stock: "wood",
|
|
711
|
+
mag: "none",
|
|
712
|
+
tube: 1,
|
|
713
|
+
pump: 1,
|
|
714
|
+
hg: "none",
|
|
715
|
+
br: 0.0105,
|
|
716
|
+
hR: 0.055,
|
|
717
|
+
noFH: 1
|
|
718
|
+
}
|
|
719
|
+
},
|
|
720
|
+
mossberg_500: {
|
|
721
|
+
builder: "rifle",
|
|
722
|
+
caliber: "12 ga",
|
|
723
|
+
length: 990,
|
|
724
|
+
barrel: 470,
|
|
725
|
+
options: {
|
|
726
|
+
stock: "fixed",
|
|
727
|
+
mag: "none",
|
|
728
|
+
tube: 1,
|
|
729
|
+
pump: 1,
|
|
730
|
+
hg: "none",
|
|
731
|
+
br: 0.0105,
|
|
732
|
+
hR: 0.055,
|
|
733
|
+
noFH: 1
|
|
734
|
+
}
|
|
735
|
+
},
|
|
736
|
+
cbc_pump_military_3_0: {
|
|
737
|
+
builder: "rifle",
|
|
738
|
+
caliber: "12 ga",
|
|
739
|
+
length: 1e3,
|
|
740
|
+
barrel: 480,
|
|
741
|
+
options: {
|
|
742
|
+
stock: "fixed",
|
|
743
|
+
mag: "none",
|
|
744
|
+
tube: 1,
|
|
745
|
+
tubeLen: 0.95,
|
|
746
|
+
pump: 1,
|
|
747
|
+
hg: "none",
|
|
748
|
+
br: 0.0105,
|
|
749
|
+
hR: 0.055,
|
|
750
|
+
rail: 1,
|
|
751
|
+
noFH: 1
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
benelli_m4: {
|
|
755
|
+
builder: "rifle",
|
|
756
|
+
caliber: "12 ga",
|
|
757
|
+
length: 1010,
|
|
758
|
+
barrel: 470,
|
|
759
|
+
options: {
|
|
760
|
+
stock: "retract",
|
|
761
|
+
mag: "none",
|
|
762
|
+
tube: 1,
|
|
763
|
+
hg: "poly",
|
|
764
|
+
hgFrac: 0.55,
|
|
765
|
+
br: 0.0105,
|
|
766
|
+
hR: 0.06,
|
|
767
|
+
rail: 1,
|
|
768
|
+
scope: 1,
|
|
769
|
+
noFH: 1,
|
|
770
|
+
charge: 1
|
|
771
|
+
}
|
|
772
|
+
},
|
|
773
|
+
franchi_spas_12: {
|
|
774
|
+
builder: "rifle",
|
|
775
|
+
caliber: "12 ga",
|
|
776
|
+
length: 1041,
|
|
777
|
+
barrel: 546,
|
|
778
|
+
options: {
|
|
779
|
+
stock: "fold",
|
|
780
|
+
mag: "none",
|
|
781
|
+
tube: 1,
|
|
782
|
+
pump: 1,
|
|
783
|
+
hg: "none",
|
|
784
|
+
br: 0.0105,
|
|
785
|
+
hR: 0.06,
|
|
786
|
+
heat: 1,
|
|
787
|
+
noFH: 1
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
kel_tec_ksg: {
|
|
791
|
+
builder: "bullpup",
|
|
792
|
+
caliber: "12 ga",
|
|
793
|
+
length: 660,
|
|
794
|
+
barrel: 470,
|
|
795
|
+
options: {
|
|
796
|
+
shot: 1
|
|
797
|
+
}
|
|
798
|
+
},
|
|
799
|
+
saiga_12: {
|
|
800
|
+
builder: "rifle",
|
|
801
|
+
caliber: "12 ga",
|
|
802
|
+
length: 1e3,
|
|
803
|
+
barrel: 430,
|
|
804
|
+
options: {
|
|
805
|
+
mag: "box",
|
|
806
|
+
magLen: 1.15,
|
|
807
|
+
stock: "fixed",
|
|
808
|
+
hg: "poly",
|
|
809
|
+
br: 0.0105,
|
|
810
|
+
hR: 0.07,
|
|
811
|
+
gas: 1,
|
|
812
|
+
charge: 1,
|
|
813
|
+
ak: 1,
|
|
814
|
+
rel: 1.3
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
aa_12: {
|
|
818
|
+
builder: "rifle",
|
|
819
|
+
caliber: "12 ga",
|
|
820
|
+
length: 966,
|
|
821
|
+
barrel: 457,
|
|
822
|
+
options: {
|
|
823
|
+
mag: "drum",
|
|
824
|
+
stock: "fixed",
|
|
825
|
+
hg: "poly",
|
|
826
|
+
hgFrac: 0.6,
|
|
827
|
+
br: 0.0105,
|
|
828
|
+
hR: 0.085,
|
|
829
|
+
carry: 1,
|
|
830
|
+
rpm: 300
|
|
831
|
+
}
|
|
832
|
+
},
|
|
833
|
+
boito_double_barrel: {
|
|
834
|
+
builder: "break",
|
|
835
|
+
caliber: "12 ga",
|
|
836
|
+
length: 1130,
|
|
837
|
+
barrel: 710,
|
|
838
|
+
options: {}
|
|
839
|
+
},
|
|
840
|
+
ak_47: {
|
|
841
|
+
builder: "rifle",
|
|
842
|
+
caliber: "7,62\xD739",
|
|
843
|
+
length: 880,
|
|
844
|
+
barrel: 415,
|
|
845
|
+
options: {
|
|
846
|
+
wood: 1,
|
|
847
|
+
stock: "wood",
|
|
848
|
+
mag: "curved",
|
|
849
|
+
hg: "wood",
|
|
850
|
+
hgFrac: 0.42,
|
|
851
|
+
gas: 1,
|
|
852
|
+
charge: 1,
|
|
853
|
+
ak: 1,
|
|
854
|
+
rod: 1,
|
|
855
|
+
bayo: 1,
|
|
856
|
+
rpm: 600,
|
|
857
|
+
rel: 0.4
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
akm: {
|
|
861
|
+
builder: "rifle",
|
|
862
|
+
caliber: "7,62\xD739",
|
|
863
|
+
length: 880,
|
|
864
|
+
barrel: 415,
|
|
865
|
+
options: {
|
|
866
|
+
wood: 1,
|
|
867
|
+
stock: "wood",
|
|
868
|
+
mag: "curved",
|
|
869
|
+
hg: "wood",
|
|
870
|
+
hgFrac: 0.42,
|
|
871
|
+
gas: 1,
|
|
872
|
+
charge: 1,
|
|
873
|
+
ak: 1,
|
|
874
|
+
rod: 1,
|
|
875
|
+
rpm: 600,
|
|
876
|
+
magMat: "bakelite",
|
|
877
|
+
rel: 0.45
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
ak_74: {
|
|
881
|
+
builder: "rifle",
|
|
882
|
+
caliber: "5,45\xD739",
|
|
883
|
+
length: 943,
|
|
884
|
+
barrel: 415,
|
|
885
|
+
options: {
|
|
886
|
+
stock: "fixed",
|
|
887
|
+
mag: "curved",
|
|
888
|
+
hg: "poly",
|
|
889
|
+
hgFrac: 0.42,
|
|
890
|
+
gas: 1,
|
|
891
|
+
charge: 1,
|
|
892
|
+
brake: 1,
|
|
893
|
+
ak: 1,
|
|
894
|
+
rod: 1,
|
|
895
|
+
rpm: 650,
|
|
896
|
+
magMat: "bakelite"
|
|
897
|
+
}
|
|
898
|
+
},
|
|
899
|
+
ak_12: {
|
|
900
|
+
builder: "rifle",
|
|
901
|
+
caliber: "5,45\xD739",
|
|
902
|
+
length: 945,
|
|
903
|
+
barrel: 415,
|
|
904
|
+
options: {
|
|
905
|
+
stock: "retract",
|
|
906
|
+
mag: "curved",
|
|
907
|
+
hg: "rail",
|
|
908
|
+
hgFrac: 0.5,
|
|
909
|
+
rail: 1,
|
|
910
|
+
brake: 1,
|
|
911
|
+
scope: 1,
|
|
912
|
+
charge: 1,
|
|
913
|
+
ak: 1,
|
|
914
|
+
rpm: 700,
|
|
915
|
+
magMat: "poly"
|
|
916
|
+
}
|
|
917
|
+
},
|
|
918
|
+
imbel_ia2: {
|
|
919
|
+
builder: "rifle",
|
|
920
|
+
caliber: "5,56\xD745",
|
|
921
|
+
length: 850,
|
|
922
|
+
barrel: 330,
|
|
923
|
+
options: {
|
|
924
|
+
stock: "fold",
|
|
925
|
+
mag: "box",
|
|
926
|
+
curve: 0.07,
|
|
927
|
+
magLen: 1.25,
|
|
928
|
+
hg: "poly",
|
|
929
|
+
hgFrac: 0.6,
|
|
930
|
+
rail: 1,
|
|
931
|
+
charge: 1,
|
|
932
|
+
rpm: 700
|
|
933
|
+
}
|
|
934
|
+
},
|
|
935
|
+
taurus_t4: {
|
|
936
|
+
builder: "rifle",
|
|
937
|
+
caliber: "5,56\xD745",
|
|
938
|
+
length: 840,
|
|
939
|
+
barrel: 368,
|
|
940
|
+
options: {
|
|
941
|
+
stock: "retract",
|
|
942
|
+
mag: "box",
|
|
943
|
+
curve: 0.07,
|
|
944
|
+
magLen: 1.3,
|
|
945
|
+
hg: "rail",
|
|
946
|
+
hgFrac: 0.7,
|
|
947
|
+
rail: 1,
|
|
948
|
+
scope: 1,
|
|
949
|
+
ar: 1,
|
|
950
|
+
rpm: 750
|
|
951
|
+
}
|
|
952
|
+
},
|
|
953
|
+
colt_m4a1: {
|
|
954
|
+
builder: "rifle",
|
|
955
|
+
caliber: "5,56\xD745",
|
|
956
|
+
length: 840,
|
|
957
|
+
barrel: 368,
|
|
958
|
+
options: {
|
|
959
|
+
stock: "retract",
|
|
960
|
+
mag: "box",
|
|
961
|
+
curve: 0.07,
|
|
962
|
+
magLen: 1.3,
|
|
963
|
+
hg: "rail",
|
|
964
|
+
hgFrac: 0.6,
|
|
965
|
+
rail: 1,
|
|
966
|
+
scope: 2,
|
|
967
|
+
ar: 1,
|
|
968
|
+
rpm: 800
|
|
969
|
+
}
|
|
970
|
+
},
|
|
971
|
+
colt_m16a2: {
|
|
972
|
+
builder: "rifle",
|
|
973
|
+
caliber: "5,56\xD745",
|
|
974
|
+
length: 1e3,
|
|
975
|
+
barrel: 508,
|
|
976
|
+
options: {
|
|
977
|
+
stock: "fixed",
|
|
978
|
+
mag: "box",
|
|
979
|
+
curve: 0.07,
|
|
980
|
+
magLen: 1.3,
|
|
981
|
+
hg: "poly",
|
|
982
|
+
hgFrac: 0.55,
|
|
983
|
+
carry: 1,
|
|
984
|
+
ar: 1,
|
|
985
|
+
rpm: 800,
|
|
986
|
+
burst: 3
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
hk416: {
|
|
990
|
+
builder: "rifle",
|
|
991
|
+
caliber: "5,56\xD745",
|
|
992
|
+
length: 900,
|
|
993
|
+
barrel: 368,
|
|
994
|
+
options: {
|
|
995
|
+
stock: "retract",
|
|
996
|
+
mag: "box",
|
|
997
|
+
curve: 0.07,
|
|
998
|
+
magLen: 1.3,
|
|
999
|
+
hg: "rail",
|
|
1000
|
+
hgFrac: 0.75,
|
|
1001
|
+
rail: 1,
|
|
1002
|
+
scope: 1,
|
|
1003
|
+
ar: 1,
|
|
1004
|
+
rpm: 850
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
steyr_aug_a1: {
|
|
1008
|
+
builder: "bullpup",
|
|
1009
|
+
caliber: "5,56\xD745",
|
|
1010
|
+
length: 790,
|
|
1011
|
+
barrel: 508,
|
|
1012
|
+
options: {
|
|
1013
|
+
rpm: 680
|
|
1014
|
+
}
|
|
1015
|
+
},
|
|
1016
|
+
fn_fal: {
|
|
1017
|
+
builder: "rifle",
|
|
1018
|
+
caliber: "7,62\xD751",
|
|
1019
|
+
length: 1090,
|
|
1020
|
+
barrel: 533,
|
|
1021
|
+
options: {
|
|
1022
|
+
stock: "fixed",
|
|
1023
|
+
mag: "box",
|
|
1024
|
+
curve: 0.05,
|
|
1025
|
+
magLen: 1.05,
|
|
1026
|
+
hg: "poly",
|
|
1027
|
+
hgFrac: 0.45,
|
|
1028
|
+
carry: 1,
|
|
1029
|
+
brake: 1,
|
|
1030
|
+
charge: 1,
|
|
1031
|
+
rpm: 650
|
|
1032
|
+
}
|
|
1033
|
+
},
|
|
1034
|
+
hk_g3a3: {
|
|
1035
|
+
builder: "rifle",
|
|
1036
|
+
caliber: "7,62\xD751",
|
|
1037
|
+
length: 1025,
|
|
1038
|
+
barrel: 450,
|
|
1039
|
+
options: {
|
|
1040
|
+
stock: "fixed",
|
|
1041
|
+
mag: "box",
|
|
1042
|
+
curve: 0.07,
|
|
1043
|
+
magLen: 1.05,
|
|
1044
|
+
hg: "poly",
|
|
1045
|
+
hgFrac: 0.55,
|
|
1046
|
+
hR: 0.058,
|
|
1047
|
+
g3: 1,
|
|
1048
|
+
rpm: 550
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
sks: {
|
|
1052
|
+
builder: "rifle",
|
|
1053
|
+
caliber: "7,62\xD739",
|
|
1054
|
+
length: 1020,
|
|
1055
|
+
barrel: 520,
|
|
1056
|
+
options: {
|
|
1057
|
+
wood: 1,
|
|
1058
|
+
stock: "trad",
|
|
1059
|
+
mag: "internal",
|
|
1060
|
+
fore: 0.55,
|
|
1061
|
+
gas: 1,
|
|
1062
|
+
charge: 1,
|
|
1063
|
+
bayo: 1
|
|
1064
|
+
}
|
|
1065
|
+
},
|
|
1066
|
+
m14_ebr: {
|
|
1067
|
+
builder: "rifle",
|
|
1068
|
+
caliber: "7,62\xD751",
|
|
1069
|
+
length: 1e3,
|
|
1070
|
+
barrel: 457,
|
|
1071
|
+
options: {
|
|
1072
|
+
stock: "retract",
|
|
1073
|
+
mag: "box",
|
|
1074
|
+
curve: 0.035,
|
|
1075
|
+
magLen: 1.05,
|
|
1076
|
+
hg: "rail",
|
|
1077
|
+
hgFrac: 0.7,
|
|
1078
|
+
rail: 1,
|
|
1079
|
+
scope: 3,
|
|
1080
|
+
olive: 1,
|
|
1081
|
+
charge: 1
|
|
1082
|
+
}
|
|
1083
|
+
},
|
|
1084
|
+
svd_dragunov: {
|
|
1085
|
+
builder: "rifle",
|
|
1086
|
+
caliber: "7,62\xD754R",
|
|
1087
|
+
length: 1225,
|
|
1088
|
+
barrel: 620,
|
|
1089
|
+
options: {
|
|
1090
|
+
wood: 1,
|
|
1091
|
+
stock: "skeleton",
|
|
1092
|
+
stockLen: 0.32,
|
|
1093
|
+
mag: "box",
|
|
1094
|
+
curve: 0.09,
|
|
1095
|
+
magLen: 0.9,
|
|
1096
|
+
hg: "wood",
|
|
1097
|
+
hgFrac: 0.4,
|
|
1098
|
+
scope: 4,
|
|
1099
|
+
brake: 1,
|
|
1100
|
+
ak: 1,
|
|
1101
|
+
charge: 1
|
|
1102
|
+
}
|
|
1103
|
+
},
|
|
1104
|
+
fn_scar_h: {
|
|
1105
|
+
builder: "rifle",
|
|
1106
|
+
caliber: "7,62\xD751",
|
|
1107
|
+
length: 965,
|
|
1108
|
+
barrel: 400,
|
|
1109
|
+
options: {
|
|
1110
|
+
tan: 1,
|
|
1111
|
+
stock: "fold",
|
|
1112
|
+
mag: "box",
|
|
1113
|
+
magLen: 1.05,
|
|
1114
|
+
hg: "rail",
|
|
1115
|
+
hgFrac: 0.7,
|
|
1116
|
+
hR: 0.07,
|
|
1117
|
+
rail: 1,
|
|
1118
|
+
scope: 2,
|
|
1119
|
+
charge: 1,
|
|
1120
|
+
rpm: 600
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
knights_sr_25_mk11: {
|
|
1124
|
+
builder: "rifle",
|
|
1125
|
+
caliber: "7,62\xD751",
|
|
1126
|
+
length: 1156,
|
|
1127
|
+
barrel: 508,
|
|
1128
|
+
options: {
|
|
1129
|
+
stock: "fixed",
|
|
1130
|
+
mag: "box",
|
|
1131
|
+
magLen: 1,
|
|
1132
|
+
hg: "rail",
|
|
1133
|
+
hgFrac: 0.75,
|
|
1134
|
+
rail: 1,
|
|
1135
|
+
scope: 3,
|
|
1136
|
+
bipod: 1,
|
|
1137
|
+
ar: 1
|
|
1138
|
+
}
|
|
1139
|
+
},
|
|
1140
|
+
hk_psg_1: {
|
|
1141
|
+
builder: "rifle",
|
|
1142
|
+
caliber: "7,62\xD751",
|
|
1143
|
+
length: 1208,
|
|
1144
|
+
barrel: 650,
|
|
1145
|
+
options: {
|
|
1146
|
+
stock: "fixed",
|
|
1147
|
+
stockLen: 0.3,
|
|
1148
|
+
mag: "box",
|
|
1149
|
+
curve: 0.06,
|
|
1150
|
+
magLen: 0.75,
|
|
1151
|
+
hg: "poly",
|
|
1152
|
+
hgFrac: 0.55,
|
|
1153
|
+
hR: 0.058,
|
|
1154
|
+
g3: 1,
|
|
1155
|
+
scope: 3,
|
|
1156
|
+
rel: 0.7
|
|
1157
|
+
}
|
|
1158
|
+
},
|
|
1159
|
+
steyr_ssg_69: {
|
|
1160
|
+
builder: "rifle",
|
|
1161
|
+
caliber: "7,62\xD751",
|
|
1162
|
+
length: 1140,
|
|
1163
|
+
barrel: 650,
|
|
1164
|
+
options: {
|
|
1165
|
+
olive: 1,
|
|
1166
|
+
stock: "trad",
|
|
1167
|
+
mag: "internal",
|
|
1168
|
+
scope: 3,
|
|
1169
|
+
bolt: 1,
|
|
1170
|
+
round: 1,
|
|
1171
|
+
rel: 0.6
|
|
1172
|
+
}
|
|
1173
|
+
},
|
|
1174
|
+
barrett_mrad: {
|
|
1175
|
+
builder: "rifle",
|
|
1176
|
+
caliber: ".338 Lapua",
|
|
1177
|
+
length: 1260,
|
|
1178
|
+
barrel: 686,
|
|
1179
|
+
options: {
|
|
1180
|
+
tan: 1,
|
|
1181
|
+
stock: "fold",
|
|
1182
|
+
stockLen: 0.33,
|
|
1183
|
+
mag: "box",
|
|
1184
|
+
magLen: 0.75,
|
|
1185
|
+
hg: "rail",
|
|
1186
|
+
hgFrac: 0.55,
|
|
1187
|
+
rail: 1,
|
|
1188
|
+
scope: 3,
|
|
1189
|
+
bolt: 1,
|
|
1190
|
+
bipod: 1,
|
|
1191
|
+
brake: 1,
|
|
1192
|
+
round: 1
|
|
1193
|
+
}
|
|
1194
|
+
},
|
|
1195
|
+
cheytac_m200: {
|
|
1196
|
+
builder: "rifle",
|
|
1197
|
+
caliber: ".408 CheyTac",
|
|
1198
|
+
length: 1397,
|
|
1199
|
+
barrel: 737,
|
|
1200
|
+
options: {
|
|
1201
|
+
stock: "retract",
|
|
1202
|
+
stockLen: 0.3,
|
|
1203
|
+
mag: "box",
|
|
1204
|
+
magLen: 0.85,
|
|
1205
|
+
hg: "poly",
|
|
1206
|
+
hgFrac: 0.55,
|
|
1207
|
+
hR: 0.07,
|
|
1208
|
+
scope: 4,
|
|
1209
|
+
bolt: 1,
|
|
1210
|
+
bipod: 1,
|
|
1211
|
+
brake: 1,
|
|
1212
|
+
round: 1
|
|
1213
|
+
}
|
|
1214
|
+
},
|
|
1215
|
+
remington_700: {
|
|
1216
|
+
builder: "rifle",
|
|
1217
|
+
caliber: ".308",
|
|
1218
|
+
length: 1100,
|
|
1219
|
+
barrel: 610,
|
|
1220
|
+
options: {
|
|
1221
|
+
wood: 1,
|
|
1222
|
+
stock: "trad",
|
|
1223
|
+
mag: "internal",
|
|
1224
|
+
scope: 3,
|
|
1225
|
+
bolt: 1,
|
|
1226
|
+
round: 1
|
|
1227
|
+
}
|
|
1228
|
+
},
|
|
1229
|
+
m24_sws: {
|
|
1230
|
+
builder: "rifle",
|
|
1231
|
+
caliber: "7,62\xD751",
|
|
1232
|
+
length: 1092,
|
|
1233
|
+
barrel: 610,
|
|
1234
|
+
options: {
|
|
1235
|
+
olive: 1,
|
|
1236
|
+
stock: "trad",
|
|
1237
|
+
mag: "internal",
|
|
1238
|
+
scope: 3,
|
|
1239
|
+
bolt: 1,
|
|
1240
|
+
bipod: 1,
|
|
1241
|
+
round: 1
|
|
1242
|
+
}
|
|
1243
|
+
},
|
|
1244
|
+
mosin_nagant_91_30: {
|
|
1245
|
+
builder: "rifle",
|
|
1246
|
+
caliber: "7,62\xD754R",
|
|
1247
|
+
length: 1232,
|
|
1248
|
+
barrel: 730,
|
|
1249
|
+
options: {
|
|
1250
|
+
wood: 1,
|
|
1251
|
+
stock: "trad",
|
|
1252
|
+
mag: "internal",
|
|
1253
|
+
fore: 0.85,
|
|
1254
|
+
bolt: 1,
|
|
1255
|
+
round: 1,
|
|
1256
|
+
bands: 1
|
|
1257
|
+
}
|
|
1258
|
+
},
|
|
1259
|
+
accuracy_awm: {
|
|
1260
|
+
builder: "rifle",
|
|
1261
|
+
caliber: ".338 Lapua",
|
|
1262
|
+
length: 1230,
|
|
1263
|
+
barrel: 686,
|
|
1264
|
+
options: {
|
|
1265
|
+
olive: 1,
|
|
1266
|
+
stock: "skeleton",
|
|
1267
|
+
stockLen: 0.34,
|
|
1268
|
+
mag: "box",
|
|
1269
|
+
magLen: 0.6,
|
|
1270
|
+
hg: "poly",
|
|
1271
|
+
hgFrac: 0.3,
|
|
1272
|
+
scope: 3,
|
|
1273
|
+
bolt: 1,
|
|
1274
|
+
bipod: 1,
|
|
1275
|
+
brake: 1,
|
|
1276
|
+
round: 1
|
|
1277
|
+
}
|
|
1278
|
+
},
|
|
1279
|
+
sako_trg_42: {
|
|
1280
|
+
builder: "rifle",
|
|
1281
|
+
caliber: ".338 Lapua",
|
|
1282
|
+
length: 1200,
|
|
1283
|
+
barrel: 690,
|
|
1284
|
+
options: {
|
|
1285
|
+
stock: "fixed",
|
|
1286
|
+
stockLen: 0.33,
|
|
1287
|
+
mag: "box",
|
|
1288
|
+
magLen: 0.6,
|
|
1289
|
+
hg: "poly",
|
|
1290
|
+
hgFrac: 0.3,
|
|
1291
|
+
scope: 3,
|
|
1292
|
+
bolt: 1,
|
|
1293
|
+
bipod: 1,
|
|
1294
|
+
brake: 1,
|
|
1295
|
+
round: 1
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
barrett_m82a1: {
|
|
1299
|
+
builder: "rifle",
|
|
1300
|
+
caliber: ".50 BMG",
|
|
1301
|
+
length: 1448,
|
|
1302
|
+
barrel: 737,
|
|
1303
|
+
options: {
|
|
1304
|
+
stock: "fixed",
|
|
1305
|
+
stockLen: 0.3,
|
|
1306
|
+
mag: "box",
|
|
1307
|
+
magLen: 0.8,
|
|
1308
|
+
hg: "poly",
|
|
1309
|
+
hgFrac: 0.55,
|
|
1310
|
+
hR: 0.1,
|
|
1311
|
+
wR: 0.05,
|
|
1312
|
+
br: 0.014,
|
|
1313
|
+
scope: 3,
|
|
1314
|
+
bipod: 1,
|
|
1315
|
+
brake: 2,
|
|
1316
|
+
carry: 1,
|
|
1317
|
+
charge: 1
|
|
1318
|
+
}
|
|
1319
|
+
},
|
|
1320
|
+
ruger_10_22: {
|
|
1321
|
+
builder: "rifle",
|
|
1322
|
+
caliber: ".22 LR",
|
|
1323
|
+
length: 940,
|
|
1324
|
+
barrel: 470,
|
|
1325
|
+
options: {
|
|
1326
|
+
wood: 1,
|
|
1327
|
+
stock: "trad",
|
|
1328
|
+
mag: "internal",
|
|
1329
|
+
hR: 0.05,
|
|
1330
|
+
br: 7e-3,
|
|
1331
|
+
charge: 1
|
|
1332
|
+
}
|
|
1333
|
+
},
|
|
1334
|
+
cbc_7022: {
|
|
1335
|
+
builder: "rifle",
|
|
1336
|
+
caliber: ".22 LR",
|
|
1337
|
+
length: 1e3,
|
|
1338
|
+
barrel: 530,
|
|
1339
|
+
options: {
|
|
1340
|
+
stock: "trad",
|
|
1341
|
+
mag: "internal",
|
|
1342
|
+
hR: 0.05,
|
|
1343
|
+
br: 7e-3,
|
|
1344
|
+
charge: 1
|
|
1345
|
+
}
|
|
1346
|
+
},
|
|
1347
|
+
cbc_122: {
|
|
1348
|
+
builder: "rifle",
|
|
1349
|
+
caliber: ".22 LR",
|
|
1350
|
+
length: 1030,
|
|
1351
|
+
barrel: 590,
|
|
1352
|
+
options: {
|
|
1353
|
+
wood: 1,
|
|
1354
|
+
stock: "trad",
|
|
1355
|
+
mag: "internal",
|
|
1356
|
+
hR: 0.045,
|
|
1357
|
+
br: 7e-3,
|
|
1358
|
+
bolt: 1,
|
|
1359
|
+
round: 1
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
winchester_1894: {
|
|
1363
|
+
builder: "rifle",
|
|
1364
|
+
caliber: ".30-30",
|
|
1365
|
+
length: 965,
|
|
1366
|
+
barrel: 508,
|
|
1367
|
+
options: {
|
|
1368
|
+
wood: 1,
|
|
1369
|
+
stock: "straight",
|
|
1370
|
+
mag: "none",
|
|
1371
|
+
tube: 1,
|
|
1372
|
+
lever: 1,
|
|
1373
|
+
hR: 0.05,
|
|
1374
|
+
fore: 0,
|
|
1375
|
+
bands: 1
|
|
1376
|
+
}
|
|
1377
|
+
},
|
|
1378
|
+
rossi_puma: {
|
|
1379
|
+
builder: "rifle",
|
|
1380
|
+
caliber: ".38 SPL / .357",
|
|
1381
|
+
length: 950,
|
|
1382
|
+
barrel: 508,
|
|
1383
|
+
options: {
|
|
1384
|
+
wood: 1,
|
|
1385
|
+
stock: "straight",
|
|
1386
|
+
mag: "none",
|
|
1387
|
+
tube: 1,
|
|
1388
|
+
lever: 1,
|
|
1389
|
+
hR: 0.048,
|
|
1390
|
+
fore: 0
|
|
1391
|
+
}
|
|
1392
|
+
},
|
|
1393
|
+
hunting_rifle_30_06: {
|
|
1394
|
+
builder: "rifle",
|
|
1395
|
+
caliber: ".30-06",
|
|
1396
|
+
length: 1100,
|
|
1397
|
+
barrel: 560,
|
|
1398
|
+
options: {
|
|
1399
|
+
wood: 1,
|
|
1400
|
+
stock: "trad",
|
|
1401
|
+
mag: "internal",
|
|
1402
|
+
scope: 3,
|
|
1403
|
+
bolt: 1,
|
|
1404
|
+
round: 1
|
|
1405
|
+
}
|
|
1406
|
+
},
|
|
1407
|
+
fn_minimi: {
|
|
1408
|
+
builder: "rifle",
|
|
1409
|
+
caliber: "5,56\xD745",
|
|
1410
|
+
length: 1040,
|
|
1411
|
+
barrel: 465,
|
|
1412
|
+
options: {
|
|
1413
|
+
stock: "fixed",
|
|
1414
|
+
mag: "belt",
|
|
1415
|
+
hg: "poly",
|
|
1416
|
+
hgFrac: 0.35,
|
|
1417
|
+
bipod: 1,
|
|
1418
|
+
hR: 0.08,
|
|
1419
|
+
carry: 1,
|
|
1420
|
+
charge: 1,
|
|
1421
|
+
rpm: 850
|
|
1422
|
+
}
|
|
1423
|
+
},
|
|
1424
|
+
pkm: {
|
|
1425
|
+
builder: "rifle",
|
|
1426
|
+
caliber: "7,62\xD754R",
|
|
1427
|
+
length: 1173,
|
|
1428
|
+
barrel: 658,
|
|
1429
|
+
options: {
|
|
1430
|
+
wood: 1,
|
|
1431
|
+
stock: "skeleton",
|
|
1432
|
+
mag: "belt",
|
|
1433
|
+
hg: "none",
|
|
1434
|
+
bipod: 1,
|
|
1435
|
+
hR: 0.08,
|
|
1436
|
+
br: 0.01,
|
|
1437
|
+
charge: 1,
|
|
1438
|
+
rpm: 650
|
|
1439
|
+
}
|
|
1440
|
+
},
|
|
1441
|
+
m60: {
|
|
1442
|
+
builder: "rifle",
|
|
1443
|
+
caliber: "7,62\xD751",
|
|
1444
|
+
length: 1105,
|
|
1445
|
+
barrel: 560,
|
|
1446
|
+
options: {
|
|
1447
|
+
stock: "fixed",
|
|
1448
|
+
mag: "belt",
|
|
1449
|
+
hg: "poly",
|
|
1450
|
+
hgFrac: 0.3,
|
|
1451
|
+
bipod: 1,
|
|
1452
|
+
hR: 0.085,
|
|
1453
|
+
br: 0.011,
|
|
1454
|
+
charge: 1,
|
|
1455
|
+
rpm: 550,
|
|
1456
|
+
rel: 1.3
|
|
1457
|
+
}
|
|
1458
|
+
},
|
|
1459
|
+
rpg_7: {
|
|
1460
|
+
builder: "launcher",
|
|
1461
|
+
caliber: "PG-7V 40/85 mm",
|
|
1462
|
+
length: 950,
|
|
1463
|
+
barrel: 950,
|
|
1464
|
+
options: {
|
|
1465
|
+
lt: "rpg"
|
|
1466
|
+
}
|
|
1467
|
+
},
|
|
1468
|
+
m72_law: {
|
|
1469
|
+
builder: "launcher",
|
|
1470
|
+
caliber: "66 mm HEAT",
|
|
1471
|
+
length: 881,
|
|
1472
|
+
barrel: 881,
|
|
1473
|
+
options: {
|
|
1474
|
+
lt: "m72"
|
|
1475
|
+
}
|
|
1476
|
+
},
|
|
1477
|
+
at4: {
|
|
1478
|
+
builder: "launcher",
|
|
1479
|
+
caliber: "84 mm HEAT",
|
|
1480
|
+
length: 1020,
|
|
1481
|
+
barrel: 1020,
|
|
1482
|
+
options: {
|
|
1483
|
+
lt: "at4"
|
|
1484
|
+
}
|
|
1485
|
+
},
|
|
1486
|
+
carl_gustaf_m4: {
|
|
1487
|
+
builder: "launcher",
|
|
1488
|
+
caliber: "84 mm (HE / HEAT)",
|
|
1489
|
+
length: 950,
|
|
1490
|
+
barrel: 950,
|
|
1491
|
+
options: {
|
|
1492
|
+
lt: "carl"
|
|
1493
|
+
}
|
|
1494
|
+
},
|
|
1495
|
+
m2_flamethrower: {
|
|
1496
|
+
builder: "launcher",
|
|
1497
|
+
caliber: "Napalm / gasolina gel",
|
|
1498
|
+
length: 1e3,
|
|
1499
|
+
barrel: 620,
|
|
1500
|
+
options: {
|
|
1501
|
+
lt: "flame"
|
|
1502
|
+
}
|
|
1503
|
+
},
|
|
1504
|
+
improvised_flamethrower: {
|
|
1505
|
+
builder: "launcher",
|
|
1506
|
+
caliber: "Gasolina + extintor pressurizado",
|
|
1507
|
+
length: 800,
|
|
1508
|
+
barrel: 660,
|
|
1509
|
+
options: {
|
|
1510
|
+
lt: "flameDIY"
|
|
1511
|
+
}
|
|
1512
|
+
},
|
|
1513
|
+
browning_m2: {
|
|
1514
|
+
builder: "m2",
|
|
1515
|
+
caliber: ".50 BMG",
|
|
1516
|
+
length: 1650,
|
|
1517
|
+
barrel: 1140,
|
|
1518
|
+
options: {
|
|
1519
|
+
rpm: 550
|
|
1520
|
+
}
|
|
1521
|
+
},
|
|
1522
|
+
remington_700_300wm: {
|
|
1523
|
+
builder: "rifle",
|
|
1524
|
+
caliber: ".308",
|
|
1525
|
+
length: 1100,
|
|
1526
|
+
barrel: 610,
|
|
1527
|
+
options: {
|
|
1528
|
+
wood: 1,
|
|
1529
|
+
stock: "trad",
|
|
1530
|
+
mag: "internal",
|
|
1531
|
+
scope: 3,
|
|
1532
|
+
bolt: 1,
|
|
1533
|
+
round: 1
|
|
1534
|
+
},
|
|
1535
|
+
variantOf: "remington_700"
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
|
|
1539
|
+
// src/models/rounded-box.ts
|
|
1540
|
+
var uvAlong = (THREE, faceDirection, normal, uvAxis, projectionAxis, radius, sideLength) => {
|
|
1541
|
+
const arcLength = 2 * Math.PI * radius / 4;
|
|
1542
|
+
const centerLength = Math.max(sideLength - 2 * radius, 0);
|
|
1543
|
+
const halfArc = Math.PI / 4;
|
|
1544
|
+
const projected = new THREE.Vector3().copy(normal);
|
|
1545
|
+
projected[projectionAxis] = 0;
|
|
1546
|
+
projected.normalize();
|
|
1547
|
+
const arcUvRatio = 0.5 * arcLength / (arcLength + centerLength);
|
|
1548
|
+
const arcAngleRatio = 1 - projected.angleTo(faceDirection) / halfArc;
|
|
1549
|
+
if (Math.sign(projected[uvAxis]) === 1) return arcAngleRatio * arcUvRatio;
|
|
1550
|
+
const centerUv = centerLength / (arcLength + centerLength);
|
|
1551
|
+
return centerUv + arcUvRatio + arcUvRatio * (1 - arcAngleRatio);
|
|
1552
|
+
};
|
|
1553
|
+
var FACES = [
|
|
1554
|
+
{ direction: [1, 0, 0], u: ["z", "y", false], v: ["y", "z", true] },
|
|
1555
|
+
{ direction: [-1, 0, 0], u: ["z", "y", true], v: ["y", "z", true] },
|
|
1556
|
+
{ direction: [0, 1, 0], u: ["x", "z", true], v: ["z", "x", false] },
|
|
1557
|
+
{ direction: [0, -1, 0], u: ["x", "z", true], v: ["z", "x", true] },
|
|
1558
|
+
{ direction: [0, 0, 1], u: ["x", "y", true], v: ["y", "x", true] },
|
|
1559
|
+
{ direction: [0, 0, -1], u: ["x", "y", false], v: ["y", "x", true] }
|
|
1560
|
+
];
|
|
1561
|
+
function createRoundedBoxGeometry(THREE, width, height, depth, segments, radius) {
|
|
1562
|
+
const count = segments * 2 + 1;
|
|
1563
|
+
const rounding = Math.min(width / 2, height / 2, depth / 2, radius);
|
|
1564
|
+
const geometry = new THREE.BoxGeometry(1, 1, 1, count, count, count);
|
|
1565
|
+
const flat = geometry.toNonIndexed();
|
|
1566
|
+
geometry.setIndex(null);
|
|
1567
|
+
geometry.setAttribute("position", flat.attributes.position);
|
|
1568
|
+
geometry.setAttribute("normal", flat.attributes.normal);
|
|
1569
|
+
geometry.setAttribute("uv", flat.attributes.uv);
|
|
1570
|
+
const position = new THREE.Vector3();
|
|
1571
|
+
const normal = new THREE.Vector3();
|
|
1572
|
+
const box2 = new THREE.Vector3(width, height, depth).divideScalar(2).subScalar(rounding);
|
|
1573
|
+
const positions = geometry.attributes.position.array;
|
|
1574
|
+
const normals = geometry.attributes.normal.array;
|
|
1575
|
+
const uvs = geometry.attributes.uv.array;
|
|
1576
|
+
const faceTriangles = positions.length / 6;
|
|
1577
|
+
const halfSegment = 0.5 / count;
|
|
1578
|
+
const sides = { x: width, y: height, z: depth };
|
|
1579
|
+
for (let i = 0, j = 0; i < positions.length; i += 3, j += 2) {
|
|
1580
|
+
position.fromArray(positions, i);
|
|
1581
|
+
normal.copy(position);
|
|
1582
|
+
normal.x -= Math.sign(normal.x) * halfSegment;
|
|
1583
|
+
normal.y -= Math.sign(normal.y) * halfSegment;
|
|
1584
|
+
normal.z -= Math.sign(normal.z) * halfSegment;
|
|
1585
|
+
normal.normalize();
|
|
1586
|
+
positions[i] = box2.x * Math.sign(position.x) + normal.x * rounding;
|
|
1587
|
+
positions[i + 1] = box2.y * Math.sign(position.y) + normal.y * rounding;
|
|
1588
|
+
positions[i + 2] = box2.z * Math.sign(position.z) + normal.z * rounding;
|
|
1589
|
+
normals[i] = normal.x;
|
|
1590
|
+
normals[i + 1] = normal.y;
|
|
1591
|
+
normals[i + 2] = normal.z;
|
|
1592
|
+
const face = FACES[Math.floor(i / faceTriangles)];
|
|
1593
|
+
if (!face) continue;
|
|
1594
|
+
const direction = new THREE.Vector3(...face.direction);
|
|
1595
|
+
for (const [offset, [uvAxis, projectionAxis, mirrored]] of [[0, face.u], [1, face.v]]) {
|
|
1596
|
+
const value = uvAlong(THREE, direction, normal, uvAxis, projectionAxis, rounding, sides[uvAxis]);
|
|
1597
|
+
uvs[j + offset] = mirrored ? 1 - value : value;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
return geometry;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// src/models/kit.ts
|
|
1604
|
+
var PI = Math.PI;
|
|
1605
|
+
var createMaterials = (THREE) => {
|
|
1606
|
+
const standard = (color, roughness = 0.7, metalness = 0.2, extra = {}) => new THREE.MeshStandardMaterial({ color, roughness, metalness, ...extra });
|
|
1607
|
+
const basic = (color, extra = {}) => new THREE.MeshBasicMaterial({ color, ...extra });
|
|
1608
|
+
const additive = { transparent: true, opacity: 0.95, blending: THREE.AdditiveBlending, depthWrite: false };
|
|
1609
|
+
return {
|
|
1610
|
+
metal: standard(3422010, 0.42, 0.75),
|
|
1611
|
+
steel: standard(7106418, 0.32, 0.85),
|
|
1612
|
+
steelL: standard(9080208, 0.28, 0.9),
|
|
1613
|
+
dark: standard(1711133, 0.55, 0.5),
|
|
1614
|
+
poly: standard(2369058, 0.8, 0.05),
|
|
1615
|
+
olive: standard(4936241, 0.8, 0.05),
|
|
1616
|
+
tan: standard(10259043, 0.8, 0.05),
|
|
1617
|
+
wood: standard(4860951, 0.55, 0),
|
|
1618
|
+
woodL: standard(6175263, 0.55, 0),
|
|
1619
|
+
brass: standard(12819013, 0.28, 0.9),
|
|
1620
|
+
copper: standard(11561531, 0.3, 0.85),
|
|
1621
|
+
lead: standard(7171955, 0.5, 0.5),
|
|
1622
|
+
shell: standard(10692380, 0.5, 0.05),
|
|
1623
|
+
glass: standard(8364968, 0.05, 0.1, { transparent: true, opacity: 0.42 }),
|
|
1624
|
+
lens: standard(2311770, 0.05, 0.3, { transparent: true, opacity: 0.9, emissive: 399392 }),
|
|
1625
|
+
rubber: standard(1381653, 0.85, 0),
|
|
1626
|
+
white: standard(13617332, 0.8, 0),
|
|
1627
|
+
hole: standard(526343, 1, 0),
|
|
1628
|
+
bake: standard(5911072, 0.55, 0),
|
|
1629
|
+
rust: standard(7027230, 0.95, 0.3),
|
|
1630
|
+
rustD: standard(4860440, 0.95, 0.25),
|
|
1631
|
+
worn: standard(5593178, 0.6, 0.6),
|
|
1632
|
+
fire: basic(16742946),
|
|
1633
|
+
fire2: basic(16764762),
|
|
1634
|
+
flashA: basic(16757575, additive),
|
|
1635
|
+
flashB: basic(16773568, additive),
|
|
1636
|
+
smokeP: standard(9079430, 1, 0, { transparent: true, opacity: 0.35, depthWrite: false }),
|
|
1637
|
+
/** Launcher paints. */
|
|
1638
|
+
yellow: standard(11570978, 0.6, 0),
|
|
1639
|
+
red: standard(9313300, 0.45, 0.1),
|
|
1640
|
+
redPaint: standard(10691354, 0.35, 0.2),
|
|
1641
|
+
tape: standard(9079944, 0.9, 0),
|
|
1642
|
+
pipe: standard(10115634, 0.35, 0.8),
|
|
1643
|
+
green: standard(3818536, 0.75, 0.05),
|
|
1644
|
+
/** Magazine finishes named by the options. */
|
|
1645
|
+
bakelite: standard(6958620, 0.6, 0),
|
|
1646
|
+
polyMag: standard(2763302, 0.85, 0)
|
|
1647
|
+
};
|
|
1648
|
+
};
|
|
1649
|
+
var kits = /* @__PURE__ */ new WeakMap();
|
|
1650
|
+
function modelKit(THREE) {
|
|
1651
|
+
const cached = kits.get(THREE);
|
|
1652
|
+
if (cached) return cached;
|
|
1653
|
+
const geometries = /* @__PURE__ */ new Map();
|
|
1654
|
+
const shadowed = (mesh) => {
|
|
1655
|
+
mesh.castShadow = true;
|
|
1656
|
+
mesh.receiveShadow = true;
|
|
1657
|
+
return mesh;
|
|
1658
|
+
};
|
|
1659
|
+
const place = (parent, mesh, x, y, z) => {
|
|
1660
|
+
mesh.position.set(x, y, z);
|
|
1661
|
+
parent.add(mesh);
|
|
1662
|
+
return mesh;
|
|
1663
|
+
};
|
|
1664
|
+
const kit = {
|
|
1665
|
+
THREE,
|
|
1666
|
+
M: createMaterials(THREE),
|
|
1667
|
+
roundness: null,
|
|
1668
|
+
group: () => new THREE.Group(),
|
|
1669
|
+
box(parent, w, h, d, material, x, y, z, rz = 0, ry = 0, rx = 0) {
|
|
1670
|
+
const radius = Math.min(w, h, d) * (kit.roundness ?? 0.2);
|
|
1671
|
+
const key = `${w.toFixed(4)},${h.toFixed(4)},${d.toFixed(4)},${radius.toFixed(5)}`;
|
|
1672
|
+
let geometry = geometries.get(key);
|
|
1673
|
+
if (!geometry) {
|
|
1674
|
+
geometry = radius > 25e-5 ? createRoundedBoxGeometry(THREE, w, h, d, 2, radius) : new THREE.BoxGeometry(w, h, d);
|
|
1675
|
+
geometries.set(key, geometry);
|
|
1676
|
+
}
|
|
1677
|
+
const mesh = place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z);
|
|
1678
|
+
mesh.rotation.set(rx, ry, rz);
|
|
1679
|
+
return mesh;
|
|
1680
|
+
},
|
|
1681
|
+
cylinderX(parent, radius, length, material, x, y, z, segments = 16, radiusEnd) {
|
|
1682
|
+
const mesh = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, length, segments), material));
|
|
1683
|
+
mesh.rotation.z = -PI / 2;
|
|
1684
|
+
return place(parent, mesh, x, y, z);
|
|
1685
|
+
},
|
|
1686
|
+
cylinderY(parent, radius, height, material, x, y, z, segments = 16, radiusEnd) {
|
|
1687
|
+
return place(parent, shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radiusEnd ?? radius, radius, height, segments), material)), x, y, z);
|
|
1688
|
+
},
|
|
1689
|
+
cylinderZ(parent, radius, length, material, x, y, z, segments = 16) {
|
|
1690
|
+
const mesh = shadowed(new THREE.Mesh(new THREE.CylinderGeometry(radius, radius, length, segments), material));
|
|
1691
|
+
mesh.rotation.x = PI / 2;
|
|
1692
|
+
return place(parent, mesh, x, y, z);
|
|
1693
|
+
},
|
|
1694
|
+
sphere(parent, radius, material, x, y, z, detail = 1) {
|
|
1695
|
+
return place(parent, shadowed(new THREE.Mesh(new THREE.IcosahedronGeometry(radius, detail), material)), x, y, z);
|
|
1696
|
+
},
|
|
1697
|
+
torus: (parent, geometry, material, x, y, z) => place(parent, shadowed(new THREE.Mesh(geometry, material)), x, y, z),
|
|
1698
|
+
mark(parent, x, y, z) {
|
|
1699
|
+
const object = new THREE.Object3D();
|
|
1700
|
+
object.position.set(x, y, z);
|
|
1701
|
+
parent.add(object);
|
|
1702
|
+
return object;
|
|
1703
|
+
},
|
|
1704
|
+
pivot(parent, x = 0, y = 0, z = 0) {
|
|
1705
|
+
const group = new THREE.Group();
|
|
1706
|
+
group.position.set(x, y, z);
|
|
1707
|
+
parent.add(group);
|
|
1708
|
+
return group;
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
kits.set(THREE, kit);
|
|
1712
|
+
return kit;
|
|
1713
|
+
}
|
|
1714
|
+
function heatify(glow, mesh) {
|
|
1715
|
+
mesh.material = mesh.material.clone();
|
|
1716
|
+
glow.push(mesh);
|
|
1717
|
+
return mesh;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// src/models/break-action.ts
|
|
1721
|
+
function buildBreakAction(kit, options) {
|
|
1722
|
+
const { M, box: box2, cylinderX, sphere } = kit;
|
|
1723
|
+
const group = kit.group();
|
|
1724
|
+
const L = (options.L ?? 0) / 1e3;
|
|
1725
|
+
const barrel = (options.B ?? 0) / 1e3;
|
|
1726
|
+
const parts = { family: "break", kick: 1.6, glow: [] };
|
|
1727
|
+
box2(group, 0.09, 0.05, 0.042, M.steel, L - barrel - 0.04, -5e-3, 0);
|
|
1728
|
+
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);
|
|
1729
|
+
const barrels = kit.pivot(group, L - barrel, -0.01, 0);
|
|
1730
|
+
parts.barrels = barrels;
|
|
1731
|
+
heatify(parts.glow, cylinderX(barrels, 0.0105, barrel, M.steel, barrel / 2, 0.01, 0.011, 18));
|
|
1732
|
+
heatify(parts.glow, cylinderX(barrels, 0.0105, barrel, M.steel, barrel / 2, 0.01, -0.011, 18));
|
|
1733
|
+
box2(barrels, barrel, 4e-3, 8e-3, M.metal, barrel / 2, 0.022, 0);
|
|
1734
|
+
sphere(barrels, 2e-3, M.white, barrel - 0.01, 0.026, 0, 1);
|
|
1735
|
+
cylinderX(barrels, 75e-4, 4e-3, M.hole, barrel + 1e-3, 0.01, 0.011, 12);
|
|
1736
|
+
cylinderX(barrels, 75e-4, 4e-3, M.hole, barrel + 1e-3, 0.01, -0.011, 12);
|
|
1737
|
+
box2(barrels, 0.22, 0.03, 0.038, M.wood, 0.12, -0.01, 0);
|
|
1738
|
+
parts.breech = [kit.mark(barrels, 4e-3, 0.01, 0.011), kit.mark(barrels, 4e-3, 0.01, -0.011)];
|
|
1739
|
+
box2(group, 0.12, 0.042, 0.032, M.wood, L - barrel - 0.14, -0.022, 0, 0.14);
|
|
1740
|
+
box2(group, 0.28, 0.07, 0.036, M.wood, 0.15, -0.05, 0, 0.08);
|
|
1741
|
+
box2(group, 0.012, 0.13, 0.04, M.rubber, 6e-3, -0.06, 0);
|
|
1742
|
+
box2(group, 4e-3, 0.014, 4e-3, M.dark, L - barrel - 0.05, -0.04, 0);
|
|
1743
|
+
parts.trigger = box2(group, 4e-3, 0.014, 4e-3, M.dark, L - barrel - 0.035, -0.04, 0);
|
|
1744
|
+
box2(group, 0.04, 4e-3, 0.01, M.steel, L - barrel - 0.045, -0.05, 0);
|
|
1745
|
+
box2(group, 0.012, 0.016, 6e-3, M.dark, L - barrel - 0.08, 0.022, 0.012, 0.5);
|
|
1746
|
+
box2(group, 0.012, 0.016, 6e-3, M.dark, L - barrel - 0.08, 0.022, -0.012, 0.5);
|
|
1747
|
+
parts.safety = box2(group, 0.02, 6e-3, 8e-3, M.steel, L - barrel - 0.07, 0.025, 0);
|
|
1748
|
+
parts.muzzle = kit.mark(barrels, barrel + 0.01, 0.01, 0);
|
|
1749
|
+
parts.eject = parts.breech[0];
|
|
1750
|
+
return { group, parts };
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// src/models/details.ts
|
|
1754
|
+
var PI2 = Math.PI;
|
|
1755
|
+
function rail(kit, parent, x, length, y, z, width, rotationX = 0) {
|
|
1756
|
+
const { M, box: box2 } = kit;
|
|
1757
|
+
const pivot = kit.pivot(parent, 0, y, z);
|
|
1758
|
+
pivot.rotation.x = rotationX;
|
|
1759
|
+
box2(pivot, length, 5e-3, width, M.dark, x + length / 2, 0, 0);
|
|
1760
|
+
const slots = Math.floor(length / 95e-4);
|
|
1761
|
+
for (let i = 0; i < slots; i++) box2(pivot, 52e-4, 35e-4, width * 1.12, M.metal, x + 6e-3 + i * 95e-4, 35e-4, 0);
|
|
1762
|
+
return pivot;
|
|
1763
|
+
}
|
|
1764
|
+
function vents(kit, parent, x, length, y, z, count, height) {
|
|
1765
|
+
for (let i = 0; i < count; i++) kit.box(parent, length / count * 0.45, height, 3e-3, kit.M.hole, x + (i + 0.5) * length / count, y, z);
|
|
1766
|
+
}
|
|
1767
|
+
function scope(kit, parent, kind, cx, cy, cz = 0) {
|
|
1768
|
+
const { M, box: box2, cylinderX, cylinderY, cylinderZ, THREE } = kit;
|
|
1769
|
+
if (kind === 1) {
|
|
1770
|
+
box2(parent, 0.044, 0.01, 0.028, M.dark, cx, cy + 5e-3, cz);
|
|
1771
|
+
cylinderX(parent, 0.0155, 0.034, M.dark, cx + 4e-3, cy + 0.026, cz, 18);
|
|
1772
|
+
cylinderX(parent, 0.0125, 2e-3, M.lens, cx + 0.022, cy + 0.026, cz, 18);
|
|
1773
|
+
cylinderY(parent, 4e-3, 6e-3, M.dark, cx + 4e-3, cy + 0.044, cz, 10);
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
if (kind === 2) {
|
|
1777
|
+
box2(parent, 0.07, 0.012, 0.026, M.dark, cx, cy + 6e-3, cz);
|
|
1778
|
+
cylinderX(parent, 0.016, 0.1, M.dark, cx - 0.01, cy + 0.032, cz, 18);
|
|
1779
|
+
cylinderX(parent, 0.016, 0.035, M.dark, cx + 0.055, cy + 0.032, cz, 18, 0.02);
|
|
1780
|
+
cylinderX(parent, 0.017, 2e-3, M.lens, cx + 0.073, cy + 0.032, cz, 18);
|
|
1781
|
+
cylinderY(parent, 7e-3, 0.012, M.dark, cx, cy + 0.052, cz, 12);
|
|
1782
|
+
box2(parent, 0.03, 0.01, 0.012, M.dark, cx - 0.01, cy + 0.054, cz);
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
const sleeve = kind === 4 ? 0.2 : 0.32;
|
|
1786
|
+
cylinderX(parent, 0.0135, sleeve * 0.55, M.dark, cx, cy + 0.045, cz, 18);
|
|
1787
|
+
cylinderX(parent, 0.0135, sleeve * 0.25, M.dark, cx + sleeve * 0.36, cy + 0.045, cz, 18, 0.026);
|
|
1788
|
+
cylinderX(parent, 0.026, 0.05, M.dark, cx + sleeve * 0.5, cy + 0.045, cz, 18);
|
|
1789
|
+
cylinderX(parent, 0.024, 2e-3, M.lens, cx + sleeve * 0.5 + 0.026, cy + 0.045, cz, 18);
|
|
1790
|
+
cylinderX(parent, 0.019, 0.06, M.dark, cx - sleeve * 0.38, cy + 0.045, cz, 18, 0.0135);
|
|
1791
|
+
cylinderX(parent, 0.019, 0.035, M.rubber, cx - sleeve * 0.38 - 0.045, cy + 0.045, cz, 18);
|
|
1792
|
+
cylinderX(parent, 0.017, 2e-3, M.lens, cx - sleeve * 0.38 - 0.063, cy + 0.045, cz, 18);
|
|
1793
|
+
cylinderY(parent, 0.01, 0.022, M.dark, cx, cy + 0.066, cz, 14);
|
|
1794
|
+
cylinderY(parent, 0.011, 4e-3, M.metal, cx, cy + 0.078, cz, 14);
|
|
1795
|
+
cylinderZ(parent, 0.01, 0.022, M.dark, cx, cy + 0.045, cz + 0.022, 14);
|
|
1796
|
+
for (const offset of [-0.07, 0.07]) {
|
|
1797
|
+
const ring = kit.torus(parent, new THREE.TorusGeometry(0.0155, 3e-3, 8, 20), M.dark, cx + offset, cy + 0.045, cz);
|
|
1798
|
+
ring.rotation.y = PI2 / 2;
|
|
1799
|
+
box2(parent, 0.016, 0.03, 0.022, M.dark, cx + offset, cy + 0.018, cz);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
var buttpad = (kit, parent, x, y, height, width) => void kit.box(parent, 0.012, height, width, kit.M.rubber, x, y, 0);
|
|
1803
|
+
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);
|
|
1804
|
+
|
|
1805
|
+
// src/models/bullpup.ts
|
|
1806
|
+
function buildBullpup(kit, options) {
|
|
1807
|
+
const { M, box: box2, cylinderX } = kit;
|
|
1808
|
+
const group = kit.group();
|
|
1809
|
+
const L = (options.L ?? 0) / 1e3;
|
|
1810
|
+
const barrel = (options.B ?? 0) / 1e3;
|
|
1811
|
+
const bodyLength = Math.max(0.32, L - barrel * 0.72);
|
|
1812
|
+
const body = options.shot ? M.poly : M.olive;
|
|
1813
|
+
const parts = { family: options.shot ? "pump" : "rifle", stroke: 0.06, glow: [] };
|
|
1814
|
+
box2(group, bodyLength, 0.075, 0.042, body, bodyLength / 2, 0, 0);
|
|
1815
|
+
box2(group, bodyLength * 0.9, 0.02, 0.044, M.dark, bodyLength * 0.5, -0.02, 0);
|
|
1816
|
+
box2(group, 0.032, 0.13, 0.044, M.rubber, 0.016, -0.025, 0);
|
|
1817
|
+
const gx = bodyLength * 0.8;
|
|
1818
|
+
const grip = kit.pivot(group, gx, -0.085, 0);
|
|
1819
|
+
grip.rotation.z = -0.25;
|
|
1820
|
+
box2(grip, 0.03, 0.1, 0.03, M.poly, 0, 0, 0);
|
|
1821
|
+
box2(group, 0.1, 6e-3, 0.03, body, gx + 0.01, -0.06, 0);
|
|
1822
|
+
parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.02, -0.05, 0, 0.15);
|
|
1823
|
+
heatify(parts.glow, cylinderX(group, options.shot ? 0.0105 : 85e-4, barrel, M.metal, L - barrel / 2, 0.012, 0, 16));
|
|
1824
|
+
cylinderX(group, 6e-3, 4e-3, M.hole, L + 1e-3, 0.012, 0, 12);
|
|
1825
|
+
box2(group, 0.05, 0.024, 4e-3, M.hole, bodyLength * 0.25, 5e-3, 0.0212);
|
|
1826
|
+
const bolt = kit.pivot(group);
|
|
1827
|
+
parts.bolt = bolt;
|
|
1828
|
+
box2(bolt, 0.044, 0.02, 6e-3, M.steelL, bodyLength * 0.25, 5e-3, 0.02);
|
|
1829
|
+
if (options.shot) {
|
|
1830
|
+
cylinderX(group, 0.012, bodyLength * 0.75, M.metal, bodyLength * 0.55, -0.025, 0.014, 14);
|
|
1831
|
+
cylinderX(group, 0.012, bodyLength * 0.75, M.metal, bodyLength * 0.55, -0.025, -0.014, 14);
|
|
1832
|
+
const pump = kit.pivot(group);
|
|
1833
|
+
parts.pump = pump;
|
|
1834
|
+
box2(pump, 0.15, 0.045, 0.052, M.poly, bodyLength + 0.06, -0.015, 0);
|
|
1835
|
+
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);
|
|
1836
|
+
rail(kit, group, bodyLength * 0.15, bodyLength * 0.8, 0.04, 0, 0.02);
|
|
1837
|
+
parts.loadPort = kit.mark(group, bodyLength * 0.2, -0.04, 0);
|
|
1838
|
+
} else {
|
|
1839
|
+
const magazine = kit.pivot(group, gx - 0.11, -0.03, 0);
|
|
1840
|
+
magazine.rotation.z = 0.14;
|
|
1841
|
+
parts.mag = magazine;
|
|
1842
|
+
box2(magazine, 0.05, 0.14, 0.026, M.glass, 0, -0.07, 0);
|
|
1843
|
+
for (let i = 0; i < 6; i++) cylinderX(magazine, 3e-3, 0.04, M.brass, 0, -0.01 - i * 0.02, 0, 6);
|
|
1844
|
+
parts.magDrop = 0.18;
|
|
1845
|
+
scope(kit, group, 2, bodyLength * 0.5, 0.04, 0);
|
|
1846
|
+
box2(group, 0.2, 0.03, 0.02, body, bodyLength * 0.62, 0.048, 0);
|
|
1847
|
+
box2(group, 0.026, 0.075, 0.026, body, bodyLength + 0.05, -0.05, 0, 0.1);
|
|
1848
|
+
}
|
|
1849
|
+
parts.muzzle = kit.mark(group, L + 0.01, 0.012, 0);
|
|
1850
|
+
parts.eject = kit.mark(group, bodyLength * 0.25, 5e-3, 0.03);
|
|
1851
|
+
return { group, parts };
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// src/models/launcher-flame.ts
|
|
1855
|
+
var hose = (kit, group, points, radius, material) => {
|
|
1856
|
+
const { THREE } = kit;
|
|
1857
|
+
const curve = new THREE.CatmullRomCurve3(points.map((point) => new THREE.Vector3(...point)));
|
|
1858
|
+
const tube = new THREE.Mesh(new THREE.TubeGeometry(curve, 40, radius, 8, false), material);
|
|
1859
|
+
tube.castShadow = tube.receiveShadow = true;
|
|
1860
|
+
group.add(tube);
|
|
1861
|
+
};
|
|
1862
|
+
var pilot = (kit, group, x, y) => {
|
|
1863
|
+
const { M, cylinderX } = kit;
|
|
1864
|
+
cylinderX(group, 6e-3, 0.03, M.fire2, x + 0.015, y, 0, 6, 0).userData.flame = 1;
|
|
1865
|
+
cylinderX(group, 4e-3, 0.022, M.fire, x + 0.012, y, 0, 6, 0).userData.flame = 1;
|
|
1866
|
+
};
|
|
1867
|
+
function buildFlamethrower(kit, group, parts) {
|
|
1868
|
+
const { M, box: box2, cylinderX, cylinderY, sphere } = kit;
|
|
1869
|
+
parts.kick = 0.15;
|
|
1870
|
+
heatify(parts.glow, cylinderX(group, 0.016, 0.62, M.steel, 0.42, 0, 0, 14));
|
|
1871
|
+
cylinderX(group, 0.022, 0.1, M.dark, 0.78, 0, 0, 14);
|
|
1872
|
+
heatify(parts.glow, cylinderX(group, 0.013, 0.1, M.steel, 0.88, 0, 0, 12, 9e-3));
|
|
1873
|
+
cylinderX(group, 0.018, 0.02, M.metal, 0.73, 0, 0, 14);
|
|
1874
|
+
cylinderX(group, 0.012, 0.06, M.metal, 0.84, -0.025, 0, 10);
|
|
1875
|
+
pilot(kit, group, 0.92, -0.012);
|
|
1876
|
+
const foreGrip = kit.pivot(group, 0.6, -0.02, 0);
|
|
1877
|
+
foreGrip.rotation.z = -0.1;
|
|
1878
|
+
box2(foreGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
|
|
1879
|
+
const lever = kit.pivot(group, 0.62, -0.02, 0);
|
|
1880
|
+
parts.trigger = box2(lever, 8e-3, 0.05, 6e-3, M.metal, 0.02, -0.03, 0, 0.3);
|
|
1881
|
+
const rearGrip = kit.pivot(group, 0.17, -0.02, 0);
|
|
1882
|
+
rearGrip.rotation.z = -0.28;
|
|
1883
|
+
box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
|
|
1884
|
+
box2(group, 0.05, 4e-3, 0.012, M.metal, 0.2, -0.035, 0);
|
|
1885
|
+
cylinderX(group, 0.02, 0.04, M.metal, 0.1, 0, 0, 12);
|
|
1886
|
+
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);
|
|
1887
|
+
for (const z of [-0.1, 0.1]) {
|
|
1888
|
+
cylinderY(group, 0.085, 0.5, M.olive, -0.3, -0.05, z, 20);
|
|
1889
|
+
sphere(group, 0.085, M.olive, -0.3, 0.2, z, 2).scale.y = 0.45;
|
|
1890
|
+
sphere(group, 0.085, M.olive, -0.3, -0.3, z, 2).scale.y = 0.45;
|
|
1891
|
+
cylinderY(group, 0.03, 0.03, M.metal, -0.3, 0.24, z, 12);
|
|
1892
|
+
}
|
|
1893
|
+
sphere(group, 0.075, M.olive, -0.3, 0.1, 0, 2).position.x = -0.39;
|
|
1894
|
+
box2(group, 0.03, 0.6, 0.24, M.dark, -0.4, -0.05, 0);
|
|
1895
|
+
cylinderY(group, 0.03, 0.012, M.metal, -0.39, 0.18, 0, 14);
|
|
1896
|
+
const gauge = kit.pivot(group, -0.39, 0.18, 3e-3);
|
|
1897
|
+
parts.gauge = gauge;
|
|
1898
|
+
box2(gauge, 2e-3, 0.022, 2e-3, M.red, 0, 8e-3, 0.012);
|
|
1899
|
+
parts.muzzle = kit.pivot(group, 0.94, 0, 0);
|
|
1900
|
+
parts.rear = kit.pivot(group, 0.1, 0, 0);
|
|
1901
|
+
}
|
|
1902
|
+
function buildImprovisedFlamethrower(kit, group, parts) {
|
|
1903
|
+
const { M, box: box2, cylinderX, cylinderY, sphere } = kit;
|
|
1904
|
+
parts.kick = 0.12;
|
|
1905
|
+
heatify(parts.glow, cylinderX(group, 0.011, 0.66, M.pipe, 0.38, 0, 0, 12));
|
|
1906
|
+
heatify(parts.glow, cylinderX(group, 0.015, 0.06, M.steel, 0.74, 0, 0, 12, 0.01));
|
|
1907
|
+
cylinderX(group, 6e-3, 0.05, M.metal, 0.735, -0.02, 0, 8);
|
|
1908
|
+
pilot(kit, group, 0.77, -0.02);
|
|
1909
|
+
cylinderY(group, 0.012, 0.04, M.redPaint, 0.72, -0.055, 0, 10);
|
|
1910
|
+
box2(group, 0.02, 0.03, 0.012, M.dark, 0.72, -0.085, 0);
|
|
1911
|
+
cylinderX(group, 0.06, 0.42, M.redPaint, 0.3, -0.09, 0, 22);
|
|
1912
|
+
sphere(group, 0.06, M.redPaint, 0.51, -0.09, 0, 2).scale.x = 0.5;
|
|
1913
|
+
sphere(group, 0.06, M.redPaint, 0.09, -0.09, 0, 2).scale.x = 0.5;
|
|
1914
|
+
cylinderX(group, 0.02, 0.04, M.dark, 0.04, -0.09, 0, 12);
|
|
1915
|
+
box2(group, 0.02, 0.06, 5e-3, M.white, 0.3, -0.09, 0.06);
|
|
1916
|
+
for (const x of [0.15, 0.3, 0.45]) cylinderX(group, 0.063, 0.03, M.tape, x, -0.09, 0, 22);
|
|
1917
|
+
cylinderX(group, 0.016, 0.03, M.tape, 0.3, 0, 0, 12);
|
|
1918
|
+
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);
|
|
1919
|
+
const rearGrip = kit.pivot(group, 0.12, -0.01, 0);
|
|
1920
|
+
rearGrip.rotation.z = -0.3;
|
|
1921
|
+
box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
|
|
1922
|
+
const lever = kit.pivot(group, 0.17, -0.02, 0);
|
|
1923
|
+
parts.trigger = box2(lever, 8e-3, 0.04, 6e-3, M.metal, 0, -0.02, 0, 0.2);
|
|
1924
|
+
parts.muzzle = kit.pivot(group, 0.78, 0, 0);
|
|
1925
|
+
parts.rear = kit.pivot(group, 0.05, 0, 0);
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
// src/models/launcher-rounds.ts
|
|
1929
|
+
var PI3 = Math.PI;
|
|
1930
|
+
function buildRocketGrenade(kit) {
|
|
1931
|
+
const { M, cylinderX } = kit;
|
|
1932
|
+
const round2 = kit.group();
|
|
1933
|
+
cylinderX(round2, 0.018, 0.15, M.olive, -0.05, 0, 0, 14);
|
|
1934
|
+
cylinderX(round2, 0.02, 0.07, M.olive, 0.035, 0, 0, 18, 0.0425);
|
|
1935
|
+
cylinderX(round2, 0.0425, 0.12, M.olive, 0.13, 0, 0, 20);
|
|
1936
|
+
cylinderX(round2, 0.0428, 0.012, M.dark, 0.09, 0, 0, 20);
|
|
1937
|
+
cylinderX(round2, 0.0425, 0.13, M.olive, 0.255, 0, 0, 20, 0.011);
|
|
1938
|
+
cylinderX(round2, 0.011, 0.03, M.metal, 0.335, 0, 0, 10, 4e-3);
|
|
1939
|
+
return round2;
|
|
1940
|
+
}
|
|
1941
|
+
function buildRecoillessRound(kit) {
|
|
1942
|
+
const { M, cylinderX } = kit;
|
|
1943
|
+
const round2 = kit.group();
|
|
1944
|
+
cylinderX(round2, 0.041, 0.32, M.green, -0.16, 0, 0, 20);
|
|
1945
|
+
cylinderX(round2, 0.0415, 0.02, M.yellow, -0.05, 0, 0, 20);
|
|
1946
|
+
cylinderX(round2, 0.041, 0.12, M.green, 0.06, 0, 0, 20, 0.012);
|
|
1947
|
+
cylinderX(round2, 0.043, 0.03, M.brass, -0.33, 0, 0, 20);
|
|
1948
|
+
return round2;
|
|
1949
|
+
}
|
|
1950
|
+
function buildFlyingRocket(kit, launcherType) {
|
|
1951
|
+
const { M, box: box2, cylinderX } = kit;
|
|
1952
|
+
const rocket = kit.group();
|
|
1953
|
+
const diameter = launcherType === "at4" || launcherType === "carl" ? 0.041 : 0.032;
|
|
1954
|
+
cylinderX(rocket, diameter * 0.5, 0.2, M.dark, -0.1, 0, 0, 10);
|
|
1955
|
+
cylinderX(rocket, diameter, 0.3, M.olive, 0.15, 0, 0, 18);
|
|
1956
|
+
cylinderX(rocket, diameter, 0.12, M.olive, 0.36, 0, 0, 18, diameter * 0.25);
|
|
1957
|
+
for (let i = 0; i < 4; i++) {
|
|
1958
|
+
const fin = box2(rocket, 0.06, 2e-3, diameter * 1.4, M.metal, -0.17, 0, 0);
|
|
1959
|
+
fin.rotation.x = i * PI3 / 4;
|
|
1960
|
+
}
|
|
1961
|
+
return rocket;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
// src/models/launcher-rocket.ts
|
|
1965
|
+
function buildRpg(kit, group, parts) {
|
|
1966
|
+
const { M, box: box2, cylinderX } = kit;
|
|
1967
|
+
parts.kick = 1.6;
|
|
1968
|
+
cylinderX(group, 0.036, 0.13, M.dark, 0.065, 0, 0, 20, 0.021);
|
|
1969
|
+
cylinderX(group, 0.038, 0.012, M.metal, 0, 0, 0, 20);
|
|
1970
|
+
heatify(parts.glow, cylinderX(group, 0.021, 0.83, M.steel, 0.545, 0, 0, 18));
|
|
1971
|
+
cylinderX(group, 0.03, 0.3, M.wood, 0.47, 0, 0, 16);
|
|
1972
|
+
cylinderX(group, 0.031, 0.012, M.metal, 0.32, 0, 0, 16);
|
|
1973
|
+
cylinderX(group, 0.031, 0.012, M.metal, 0.62, 0, 0, 16);
|
|
1974
|
+
cylinderX(group, 0.024, 0.02, M.metal, 0.94, 0, 0, 16);
|
|
1975
|
+
box2(group, 0.09, 0.03, 0.032, M.metal, 0.46, -0.028, 0);
|
|
1976
|
+
const frontGrip = kit.pivot(group, 0.4, -0.04, 0);
|
|
1977
|
+
frontGrip.rotation.z = -0.22;
|
|
1978
|
+
box2(frontGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
|
|
1979
|
+
const rearGrip = kit.pivot(group, 0.5, -0.04, 0);
|
|
1980
|
+
rearGrip.rotation.z = -0.18;
|
|
1981
|
+
box2(rearGrip, 0.03, 0.1, 0.03, M.wood, 0, -0.05, 0);
|
|
1982
|
+
box2(group, 0.06, 4e-3, 0.012, M.metal, 0.53, -0.05, 0);
|
|
1983
|
+
box2(group, 4e-3, 0.025, 0.012, M.metal, 0.56, -0.04, 0);
|
|
1984
|
+
parts.trigger = box2(group, 4e-3, 0.018, 4e-3, M.dark, 0.535, -0.045, 0, 0.15);
|
|
1985
|
+
const hammer = kit.pivot(group, 0.44, -0.012, 0);
|
|
1986
|
+
parts.hammer = hammer;
|
|
1987
|
+
box2(hammer, 0.012, 0.02, 0.01, M.dark, 0, 0, 0);
|
|
1988
|
+
box2(group, 6e-3, 0.03, 6e-3, M.dark, 0.78, 0.032, 0);
|
|
1989
|
+
box2(group, 0.02, 0.022, 0.014, M.dark, 0.46, 0.032, 0);
|
|
1990
|
+
box2(group, 0.12, 0.042, 0.032, M.dark, 0.5, 0.04, -0.04);
|
|
1991
|
+
cylinderX(group, 0.013, 0.05, M.rubber, 0.42, 0.048, -0.04, 14);
|
|
1992
|
+
cylinderX(group, 0.016, 4e-3, M.lens, 0.565, 0.048, -0.04, 14);
|
|
1993
|
+
box2(group, 0.04, 0.012, 0.012, M.metal, 0.5, 0.016, -0.03);
|
|
1994
|
+
const round2 = buildRocketGrenade(kit);
|
|
1995
|
+
round2.position.x = 0.95;
|
|
1996
|
+
group.add(round2);
|
|
1997
|
+
parts.round = round2;
|
|
1998
|
+
parts.roundRest = 0.95;
|
|
1999
|
+
parts.makeRound = () => buildRocketGrenade(kit);
|
|
2000
|
+
parts.muzzle = kit.pivot(group, 0.95, 0, 0);
|
|
2001
|
+
parts.rear = kit.pivot(group, 0, 0, 0);
|
|
2002
|
+
}
|
|
2003
|
+
function buildM72(kit, group, parts) {
|
|
2004
|
+
const { M, box: box2, cylinderX, cylinderY } = kit;
|
|
2005
|
+
parts.kick = 1.1;
|
|
2006
|
+
parts.disposable = true;
|
|
2007
|
+
heatify(parts.glow, cylinderX(group, 0.0365, 0.6, M.olive, 0.58, 0, 0, 20));
|
|
2008
|
+
cylinderX(group, 0.032, 0.32, M.olive, 0.16, 0, 0, 20);
|
|
2009
|
+
cylinderX(group, 0.039, 0.03, M.rubber, 0.866, 0, 0, 20);
|
|
2010
|
+
cylinderX(group, 0.035, 0.025, M.rubber, 0.012, 0, 0, 20);
|
|
2011
|
+
cylinderX(group, 0.037, 0.015, M.yellow, 0.8, 0, 0, 20);
|
|
2012
|
+
cylinderX(group, 0.037, 0.012, M.dark, 0.3, 0, 0, 20);
|
|
2013
|
+
box2(group, 4e-3, 0.05, 0.05, M.dark, 0.79, 0.055, 0);
|
|
2014
|
+
box2(group, 4e-3, 6e-3, 0.04, M.dark, 0.79, 0.078, 0);
|
|
2015
|
+
box2(group, 4e-3, 0.035, 0.024, M.dark, 0.38, 0.05, 0);
|
|
2016
|
+
cylinderY(group, 4e-3, 6e-3, M.lens, 0.38, 0.064, 0, 8);
|
|
2017
|
+
parts.trigger = box2(group, 0.05, 0.016, 0.024, M.rubber, 0.5, 0.042, 0);
|
|
2018
|
+
box2(group, 0.1, 0.01, 0.03, M.dark, 0.47, 0.036, 0);
|
|
2019
|
+
box2(group, 0.02, 0.03, 0.01, M.dark, 0.66, -0.035, 0.03);
|
|
2020
|
+
box2(group, 8e-3, 0.03, 8e-3, M.metal, 0.25, -0.04, 0.03);
|
|
2021
|
+
parts.muzzle = kit.pivot(group, 0.881, 0, 0);
|
|
2022
|
+
parts.rear = kit.pivot(group, 0, 0, 0);
|
|
2023
|
+
}
|
|
2024
|
+
function buildAt4(kit, group, parts) {
|
|
2025
|
+
const { M, box: box2, cylinderX } = kit;
|
|
2026
|
+
parts.kick = 1.3;
|
|
2027
|
+
parts.disposable = true;
|
|
2028
|
+
heatify(parts.glow, cylinderX(group, 0.05, 0.86, M.olive, 0.51, 0, 0, 24));
|
|
2029
|
+
cylinderX(group, 0.056, 0.08, M.dark, 0.98, 0, 0, 24);
|
|
2030
|
+
cylinderX(group, 0.058, 0.07, M.dark, 0.04, 0, 0, 24, 0.05);
|
|
2031
|
+
cylinderX(group, 0.051, 0.015, M.yellow, 0.86, 0, 0, 24);
|
|
2032
|
+
box2(group, 0.16, 0.026, 0.05, M.dark, 0.42, 0.058, 0);
|
|
2033
|
+
box2(group, 0.03, 0.012, 0.016, M.red, 0.47, 0.075, 0);
|
|
2034
|
+
parts.trigger = box2(group, 0.022, 0.012, 0.018, M.rubber, 0.5, 0.074, 0);
|
|
2035
|
+
box2(group, 0.012, 0.07, 0.02, M.dark, 0.35, 0.09, -0.035);
|
|
2036
|
+
box2(group, 0.012, 0.05, 0.02, M.dark, 0.71, 0.08, -0.035);
|
|
2037
|
+
const grip = kit.pivot(group, 0.68, -0.05, 0);
|
|
2038
|
+
grip.rotation.z = -0.15;
|
|
2039
|
+
box2(grip, 0.035, 0.11, 0.032, M.dark, 0, -0.05, 0);
|
|
2040
|
+
box2(group, 0.14, 0.035, 0.06, M.rubber, 0.32, -0.06, 0);
|
|
2041
|
+
box2(group, 0.3, 0.012, 0.055, M.dark, 0.5, -0.05, 0);
|
|
2042
|
+
parts.muzzle = kit.pivot(group, 1.02, 0, 0);
|
|
2043
|
+
parts.rear = kit.pivot(group, 0, 0, 0);
|
|
2044
|
+
}
|
|
2045
|
+
function buildCarlGustaf(kit, group, parts) {
|
|
2046
|
+
const { M, box: box2, cylinderX } = kit;
|
|
2047
|
+
parts.kick = 1.4;
|
|
2048
|
+
heatify(parts.glow, cylinderX(group, 0.045, 0.72, M.dark, 0.59, 0, 0, 24));
|
|
2049
|
+
cylinderX(group, 0.05, 0.04, M.rubber, 0.93, 0, 0, 24);
|
|
2050
|
+
const breech = kit.pivot(group, 0.21, 0, 0.05);
|
|
2051
|
+
parts.breech = [breech];
|
|
2052
|
+
cylinderX(breech, 0.06, 0.16, M.dark, -0.1, 0, -0.05, 24, 0.046);
|
|
2053
|
+
cylinderX(breech, 0.062, 0.015, M.metal, -0.18, 0, -0.05, 24);
|
|
2054
|
+
box2(group, 0.025, 0.03, 0.02, M.metal, 0.22, 0, 0.052);
|
|
2055
|
+
const foreGrip = kit.pivot(group, 0.62, -0.05, 0);
|
|
2056
|
+
foreGrip.rotation.z = -0.2;
|
|
2057
|
+
box2(foreGrip, 0.035, 0.11, 0.034, M.dark, 0, -0.05, 0);
|
|
2058
|
+
parts.trigger = box2(group, 4e-3, 0.018, 4e-3, M.dark, 0.66, -0.06, 0, 0.15);
|
|
2059
|
+
box2(group, 0.06, 4e-3, 0.012, M.metal, 0.66, -0.07, 0);
|
|
2060
|
+
const rearGrip = kit.pivot(group, 0.4, -0.05, 0);
|
|
2061
|
+
rearGrip.rotation.z = -0.25;
|
|
2062
|
+
box2(rearGrip, 0.035, 0.1, 0.034, M.dark, 0, -0.05, 0);
|
|
2063
|
+
box2(group, 0.13, 0.04, 0.07, M.rubber, 0.28, -0.065, 0);
|
|
2064
|
+
box2(group, 0.16, 0.05, 0.045, M.dark, 0.56, 0.07, -0.035);
|
|
2065
|
+
cylinderX(group, 0.016, 0.05, M.rubber, 0.46, 0.075, -0.035, 14);
|
|
2066
|
+
cylinderX(group, 0.02, 4e-3, M.lens, 0.645, 0.075, -0.035, 14);
|
|
2067
|
+
box2(group, 0.06, 0.02, 0.02, M.metal, 0.56, 0.04, -0.03);
|
|
2068
|
+
box2(group, 0.22, 0.01, 0.02, M.dark, 0.6, 0.06, 0.03);
|
|
2069
|
+
box2(group, 0.01, 0.03, 0.02, M.dark, 0.5, 0.05, 0.03);
|
|
2070
|
+
box2(group, 0.01, 0.03, 0.02, M.dark, 0.7, 0.05, 0.03);
|
|
2071
|
+
parts.makeRound = () => buildRecoillessRound(kit);
|
|
2072
|
+
parts.muzzle = kit.pivot(group, 0.95, 0, 0);
|
|
2073
|
+
parts.rear = kit.pivot(group, 0.05, 0, 0);
|
|
2074
|
+
}
|
|
2075
|
+
var ROCKET_BUILDERS = {
|
|
2076
|
+
rpg: buildRpg,
|
|
2077
|
+
m72: buildM72,
|
|
2078
|
+
at4: buildAt4,
|
|
2079
|
+
carl: buildCarlGustaf
|
|
2080
|
+
};
|
|
2081
|
+
|
|
2082
|
+
// src/models/launcher.ts
|
|
2083
|
+
function buildLauncher(kit, options) {
|
|
2084
|
+
const group = kit.group();
|
|
2085
|
+
const type = options.lt ?? "";
|
|
2086
|
+
const isFlame = type === "flame" || type === "flameDIY";
|
|
2087
|
+
const parts = { family: isFlame ? "flame" : "rocket", launcherType: type, glow: [] };
|
|
2088
|
+
if (type === "flame") buildFlamethrower(kit, group, parts);
|
|
2089
|
+
else if (type === "flameDIY") buildImprovisedFlamethrower(kit, group, parts);
|
|
2090
|
+
else ROCKET_BUILDERS[type]?.(kit, group, parts);
|
|
2091
|
+
parts.eject = parts.muzzle;
|
|
2092
|
+
return { group, parts };
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// src/models/m2.ts
|
|
2096
|
+
function buildM2(kit) {
|
|
2097
|
+
const { M, box: box2, cylinderX, cylinderZ } = kit;
|
|
2098
|
+
const group = kit.group();
|
|
2099
|
+
const parts = { family: "belt", kick: 2.2, stroke: 0.08, glow: [] };
|
|
2100
|
+
box2(group, 0.52, 0.13, 0.12, M.metal, 0.36, 0, 0);
|
|
2101
|
+
for (let i = 0; i < 5; i++) cylinderZ(group, 6e-3, 0.124, M.dark, 0.15 + i * 0.1, -0.03, 0, 10);
|
|
2102
|
+
heatify(parts.glow, cylinderX(group, 0.016, 1.14, M.metal, 1.14, 0.01, 0, 16));
|
|
2103
|
+
cylinderX(group, 0.034, 0.24, M.dark, 0.74, 0.01, 0, 16);
|
|
2104
|
+
for (let i = 0; i < 6; i++) cylinderX(group, 0.036, 0.012, M.metal, 0.66 + i * 0.035, 0.01, 0, 16);
|
|
2105
|
+
cylinderX(group, 0.022, 0.05, M.dark, 1.69, 0.01, 0, 16);
|
|
2106
|
+
box2(group, 0.04, 0.09, 0.03, M.wood, 0.05, -0.02, 0.05);
|
|
2107
|
+
box2(group, 0.04, 0.09, 0.03, M.wood, 0.05, -0.02, -0.05);
|
|
2108
|
+
box2(group, 0.06, 0.02, 0.12, M.metal, 0.09, -0.02, 0);
|
|
2109
|
+
const belt = kit.pivot(group, 0.38, -0.05, -0.12);
|
|
2110
|
+
parts.belt = belt;
|
|
2111
|
+
box2(belt, 0.16, 0.12, 0.09, M.olive, 0, 0, 0);
|
|
2112
|
+
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);
|
|
2113
|
+
const cover = kit.pivot(group, 0.12, 0.065, 0);
|
|
2114
|
+
parts.cover = cover;
|
|
2115
|
+
box2(cover, 0.4, 0.012, 0.12, M.metal, 0.2, 6e-3, 0);
|
|
2116
|
+
box2(group, 0.04, 0.05, 0.04, M.dark, 0.66, 0.1, 0);
|
|
2117
|
+
const bolt = kit.pivot(group);
|
|
2118
|
+
parts.bolt = bolt;
|
|
2119
|
+
box2(bolt, 0.03, 0.03, 0.02, M.metal, 0.5, 0, 0.07);
|
|
2120
|
+
const tripod = kit.group();
|
|
2121
|
+
tripod.position.set(0.42, -0.1, 0);
|
|
2122
|
+
group.add(tripod);
|
|
2123
|
+
box2(tripod, 0.03, 0.06, 0.03, M.metal, 0, 0, 0);
|
|
2124
|
+
box2(tripod, 0.012, 0.62, 0.012, M.metal, -0.2, -0.25, 0, 0.75);
|
|
2125
|
+
box2(tripod, 0.012, 0.62, 0.012, M.metal, 0.22, -0.25, 0.16, -0.75, 0, 0.3);
|
|
2126
|
+
box2(tripod, 0.012, 0.62, 0.012, M.metal, 0.22, -0.25, -0.16, -0.75, 0, -0.3);
|
|
2127
|
+
parts.muzzle = kit.mark(group, 1.72, 0.01, 0);
|
|
2128
|
+
parts.eject = kit.mark(group, 0.3, -0.07, 0);
|
|
2129
|
+
return { group, parts };
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
// src/models/p90.ts
|
|
2133
|
+
function buildP90(kit) {
|
|
2134
|
+
const { M, box: box2, cylinderX } = kit;
|
|
2135
|
+
const group = kit.group();
|
|
2136
|
+
const parts = { family: "p90", stroke: 0.04, kick: 0.6, glow: [] };
|
|
2137
|
+
box2(group, 0.4, 0.07, 0.055, M.poly, 0.2, 0, 0);
|
|
2138
|
+
box2(group, 0.12, 0.075, 0.05, M.poly, 0.2, -0.07, 0);
|
|
2139
|
+
box2(group, 0.08, 0.04, 0.03, M.hole, 0.21, -0.06, 0);
|
|
2140
|
+
box2(group, 0.06, 0.13, 0.056, M.poly, 0.03, -0.03, 0);
|
|
2141
|
+
box2(group, 0.05, 0.03, 0.056, M.poly, 0.33, -0.04, 0);
|
|
2142
|
+
heatify(parts.glow, cylinderX(group, 8e-3, 0.09, M.metal, 0.43, 0, 0, 16));
|
|
2143
|
+
const magazine = kit.pivot(group, 0.22, 0.047, 0);
|
|
2144
|
+
parts.mag = magazine;
|
|
2145
|
+
parts.magDir = 1;
|
|
2146
|
+
parts.magDrop = 0.08;
|
|
2147
|
+
box2(magazine, 0.3, 0.022, 0.05, M.glass, 0, 0, 0);
|
|
2148
|
+
for (let i = 0; i < 12; i++) cylinderX(magazine, 28e-4, 0.02, M.brass, -0.12 + i * 0.022, 0, 0, 6);
|
|
2149
|
+
scope(kit, group, 1, 0.24, 0.06, 0);
|
|
2150
|
+
const bolt = kit.pivot(group);
|
|
2151
|
+
parts.bolt = bolt;
|
|
2152
|
+
box2(bolt, 0.02, 0.012, 0.012, M.dark, 0.3, 0.02, 0.03);
|
|
2153
|
+
parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, 0.24, -0.045, 0);
|
|
2154
|
+
parts.muzzle = kit.mark(group, 0.48, 0, 0);
|
|
2155
|
+
parts.eject = kit.mark(group, 0.2, -0.07, 0);
|
|
2156
|
+
return { group, parts };
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
// src/models/part-registry.ts
|
|
2160
|
+
var SINGLE_PARTS = ["slide", "bolt", "boltHandle", "mag", "trigger", "hammer", "lever", "pump", "belt", "cover", "crane", "cylinder", "barrels", "safety", "muzzle", "eject", "loadPort", "topLoad", "rear", "round", "gauge"];
|
|
2161
|
+
function partRegistry(parts) {
|
|
2162
|
+
const registry = /* @__PURE__ */ new Map();
|
|
2163
|
+
for (const key of SINGLE_PARTS) {
|
|
2164
|
+
const part = parts[key];
|
|
2165
|
+
if (part) registry.set(key, part);
|
|
2166
|
+
}
|
|
2167
|
+
parts.breech?.forEach((part, index) => registry.set(`breech_${index}`, part));
|
|
2168
|
+
parts.chambers?.forEach((part, index) => registry.set(`chamber_${index}`, part));
|
|
2169
|
+
return registry;
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// src/models/pistol-slide.ts
|
|
2173
|
+
var PI4 = Math.PI;
|
|
2174
|
+
function buildSlide(kit, slide2, parts, options, style, size, metal2) {
|
|
2175
|
+
const { M, box: box2, cylinderX } = kit;
|
|
2176
|
+
const { length: L, width: w } = size;
|
|
2177
|
+
const slideHeight = style.inset ? size.height * 0.92 : size.height;
|
|
2178
|
+
const slideWidth = style.inset ? w * 0.86 : w;
|
|
2179
|
+
kit.roundness = style.rf;
|
|
2180
|
+
if (style.tri) box2(slide2, L * 0.42, slideHeight, slideWidth, metal2, L * 0.21, 0, 0);
|
|
2181
|
+
else if (style.open) {
|
|
2182
|
+
box2(slide2, L * 0.45, slideHeight, slideWidth, metal2, L * 0.225, 0, 0);
|
|
2183
|
+
box2(slide2, L * 0.55, slideHeight * 0.62, slideWidth, metal2, L * 0.72, -slideHeight * 0.19, 0);
|
|
2184
|
+
} else box2(slide2, L, slideHeight, slideWidth, metal2, L / 2, 0, 0);
|
|
2185
|
+
if (style.slope) box2(slide2, L * 0.25, slideHeight * 0.5, slideWidth * 0.98, metal2, L * 0.86, slideHeight * 0.35, 0, -0.22);
|
|
2186
|
+
kit.roundness = null;
|
|
2187
|
+
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);
|
|
2188
|
+
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);
|
|
2189
|
+
if (style.port) {
|
|
2190
|
+
box2(slide2, L * style.port, 4e-3, slideWidth * 0.55, M.hole, L * 0.6, slideHeight / 2 - 1e-3, slideWidth * 0.12);
|
|
2191
|
+
box2(slide2, L * style.port * 0.85, 3e-3, slideWidth * 0.4, M.steelL, L * 0.6, slideHeight / 2 - 4e-4, slideWidth * 0.1);
|
|
2192
|
+
box2(slide2, 0.012, 4e-3, 2e-3, M.dark, L * 0.55, slideHeight * 0.15, slideWidth / 2 + 8e-4);
|
|
2193
|
+
}
|
|
2194
|
+
if (style.open) {
|
|
2195
|
+
heatify(parts.glow, cylinderX(slide2, 62e-4, L * 0.56, M.steelL, L * 0.7, slideHeight * 0.12, 0, 18));
|
|
2196
|
+
box2(slide2, 0.03, 4e-3, slideWidth * 0.6, M.hole, L * 0.48, slideHeight / 2 - 1e-3, 0);
|
|
2197
|
+
}
|
|
2198
|
+
if (style.tri) {
|
|
2199
|
+
const tube = new kit.THREE.Mesh(new kit.THREE.CylinderGeometry(0.016, 0.016, L * 0.6, 3), metal2);
|
|
2200
|
+
tube.castShadow = tube.receiveShadow = true;
|
|
2201
|
+
tube.rotation.set(0, 0, -PI4 / 2);
|
|
2202
|
+
tube.rotateY(PI4 / 6);
|
|
2203
|
+
tube.position.set(L * 0.7, slideHeight * 0.08, 0);
|
|
2204
|
+
slide2.add(tube);
|
|
2205
|
+
heatify(parts.glow, tube);
|
|
2206
|
+
box2(slide2, L * 0.6, 6e-3, 0.012, metal2, L * 0.7, slideHeight * 0.38, 0);
|
|
2207
|
+
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);
|
|
2208
|
+
}
|
|
2209
|
+
if (style.bushing) {
|
|
2210
|
+
heatify(parts.glow, cylinderX(slide2, 78e-4, 6e-3, M.steelL, L + 2e-3, 1e-3, 0, 18));
|
|
2211
|
+
cylinderX(slide2, 45e-4, 4e-3, M.steel, L + 2e-3, -0.012, 0, 12);
|
|
2212
|
+
}
|
|
2213
|
+
const muzzleY = style.tri ? slideHeight * 0.08 : 2e-3;
|
|
2214
|
+
heatify(parts.glow, cylinderX(slide2, style.tri ? 72e-4 : 62e-4, 4e-3, M.dark, L + 15e-4, muzzleY, 0, 18));
|
|
2215
|
+
cylinderX(slide2, 34e-4, 5e-3, M.hole, L + 25e-4, muzzleY, 0, 12);
|
|
2216
|
+
const top = slideHeight / 2;
|
|
2217
|
+
if (style.sight === "u" || style.sight === "3dot") {
|
|
2218
|
+
box2(slide2, 4e-3, 7e-3, 4e-3, M.dark, L - 8e-3, top + 35e-4, 0);
|
|
2219
|
+
box2(slide2, 22e-4, 22e-4, 46e-4, M.white, L - 8e-3, top + 58e-4, 0);
|
|
2220
|
+
box2(slide2, 8e-3, 7e-3, 0.016, M.dark, 9e-3, top + 35e-4, 0);
|
|
2221
|
+
if (style.sight === "u") box2(slide2, 4e-3, 2e-3, 0.0122, M.white, 9e-3, top + 6e-3, 0);
|
|
2222
|
+
else box2(slide2, 22e-4, 22e-4, 0.0166, M.white, 9e-3, top + 58e-4, 0);
|
|
2223
|
+
} else {
|
|
2224
|
+
box2(slide2, 6e-3, 4e-3, 3e-3, M.dark, L - 6e-3, top + 2e-3, 0);
|
|
2225
|
+
box2(slide2, 6e-3, 4e-3, 0.012, M.dark, 8e-3, top + 2e-3, 0);
|
|
2226
|
+
}
|
|
2227
|
+
if (style.decock === "slide") {
|
|
2228
|
+
box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.02, slideHeight * 0.15, slideWidth / 2 + 2e-3, 0.3);
|
|
2229
|
+
box2(slide2, 0.012, 8e-3, 4e-3, M.dark, 0.02, slideHeight * 0.15, -slideWidth / 2 - 2e-3, 0.3);
|
|
2230
|
+
}
|
|
2231
|
+
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);
|
|
2232
|
+
if (options.sel) {
|
|
2233
|
+
const selector = box2(slide2, 0.01, 8e-3, 3e-3, M.dark, 0.014, -slideHeight * 0.2, slideWidth / 2 + 14e-4);
|
|
2234
|
+
parts.safety ?? (parts.safety = selector);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
// src/models/pistol-style.ts
|
|
2239
|
+
var STYLES = {
|
|
2240
|
+
glock: { rf: 0.07, ga: -0.36, grip: "finger", guard: "square", rail: 1, sight: "u", port: 0.2, slideM: "metal", frame: "poly" },
|
|
2241
|
+
taurus: { rf: 0.16, ga: -0.3, grip: "panel", guard: "square", rail: 1, sight: "3dot", port: 0.18, frontSer: 1, frame: "poly" },
|
|
2242
|
+
g2c: { rf: 0.2, ga: -0.3, grip: "panel", guard: "round", rail: 0, sight: "3dot", port: 0.17, frame: "poly", safety: "thumb" },
|
|
2243
|
+
beretta: { rf: 0.28, ga: -0.27, grip: "wood", guard: "round", hammer: "spur", sight: "3dot", open: 1, decock: "slide", port: 0, frame: "metal" },
|
|
2244
|
+
pt100: { rf: 0.25, ga: -0.27, grip: "rubber", guard: "round", hammer: "spur", sight: "3dot", port: 0.17, decock: "frame", frame: "metal" },
|
|
2245
|
+
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" },
|
|
2246
|
+
cz: { rf: 0.32, ga: -0.3, grip: "rubber", guard: "round", hammer: "spur", sight: "3dot", port: 0.15, inset: 1, safety: "thumb", frame: "metal" },
|
|
2247
|
+
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" },
|
|
2248
|
+
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" },
|
|
2249
|
+
fiveseven: { rf: 0.2, ga: -0.4, grip: "chunky", guard: "square", rail: 1, sight: "3dot", port: 0.18, slope: 1, frame: "poly" },
|
|
2250
|
+
deagle: { rf: 0.08, ga: -0.28, grip: "rubber", guard: "round", hammer: "big", sight: "small", port: 0.16, tri: 1, safety: "slide", frame: "metal" }
|
|
2251
|
+
};
|
|
2252
|
+
|
|
2253
|
+
// src/models/pistol.ts
|
|
2254
|
+
var PI5 = Math.PI;
|
|
2255
|
+
function buildFrame(kit, group, parts, options, style, size, materials) {
|
|
2256
|
+
const { M, box: box2, cylinderZ, sphere } = kit;
|
|
2257
|
+
const { length: L, height: h, width: w } = size;
|
|
2258
|
+
const fm = materials.frame;
|
|
2259
|
+
if (style.inset) {
|
|
2260
|
+
box2(group, L * 0.9, h * 0.35, w * 0.12, fm, L * 0.47, -h * 0.32, w * 0.47);
|
|
2261
|
+
box2(group, L * 0.9, h * 0.35, w * 0.12, fm, L * 0.47, -h * 0.32, -w * 0.47);
|
|
2262
|
+
}
|
|
2263
|
+
const frameLength = style.tri ? L * 0.66 : L * 0.8;
|
|
2264
|
+
kit.roundness = style.rf;
|
|
2265
|
+
box2(group, frameLength, 0.016, w * 0.92, fm, frameLength / 2 + L * 0.06, -h / 2 - 8e-3, 0);
|
|
2266
|
+
kit.roundness = null;
|
|
2267
|
+
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);
|
|
2268
|
+
const isGlock = options.st === "glock" || !options.st;
|
|
2269
|
+
if (isGlock) {
|
|
2270
|
+
box2(group, 8e-3, 4e-3, 3e-3, M.dark, L * 0.5, -h / 2 - 3e-3, -w / 2 - 12e-4);
|
|
2271
|
+
box2(group, 8e-3, 4e-3, 3e-3, M.dark, L * 0.5, -h / 2 - 3e-3, w / 2 + 12e-4);
|
|
2272
|
+
}
|
|
2273
|
+
if (style.slideStop) {
|
|
2274
|
+
box2(group, 0.03, 6e-3, 3e-3, M.dark, L * 0.42, -h / 2 - 2e-3, -w / 2 - 14e-4);
|
|
2275
|
+
sphere(group, 28e-4, M.dark, L * 0.55, -h / 2 - 3e-3, w / 2 + 1e-3, 1);
|
|
2276
|
+
} else box2(group, 0.012, 4e-3, 3e-3, M.dark, L * 0.45, -h / 2 - 3e-3, -w / 2 - 12e-4);
|
|
2277
|
+
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);
|
|
2278
|
+
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);
|
|
2279
|
+
const gripLength = options.gl ?? 0.105;
|
|
2280
|
+
const gx = 0.034 + (L - 0.2) * 0.15;
|
|
2281
|
+
const gy = -h / 2 - gripLength / 2 + 6e-3;
|
|
2282
|
+
const grip = kit.pivot(group, gx, gy, 0);
|
|
2283
|
+
grip.rotation.z = style.ga;
|
|
2284
|
+
const gripWidth = style.grip === "chunky" ? w * 1.18 : w * 1.06;
|
|
2285
|
+
const gripDepth = style.grip === "chunky" ? 0.036 : 0.032;
|
|
2286
|
+
kit.roundness = 0.3;
|
|
2287
|
+
if (style.grip === "wood" || style.grip === "star") {
|
|
2288
|
+
box2(grip, gripDepth * 0.86, gripLength, w * 0.9, fm, 0, 0, 0);
|
|
2289
|
+
box2(grip, gripDepth * 0.8, gripLength * 0.86, 4e-3, materials.grip, 1e-3, -2e-3, w * 0.47);
|
|
2290
|
+
box2(grip, gripDepth * 0.8, gripLength * 0.86, 4e-3, materials.grip, 1e-3, -2e-3, -w * 0.47);
|
|
2291
|
+
if (style.grip === "wood") {
|
|
2292
|
+
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);
|
|
2293
|
+
cylinderZ(grip, 35e-4, 15e-4, M.brass, 0, gripLength * 0.25, w * 0.5, 10);
|
|
2294
|
+
cylinderZ(grip, 35e-4, 15e-4, M.brass, 0, -gripLength * 0.3, w * 0.5, 10);
|
|
2295
|
+
} else cylinderZ(grip, 8e-3, 15e-4, M.dark, 0, gripLength * 0.1, w * 0.5, 5);
|
|
2296
|
+
} else box2(grip, gripDepth, gripLength, gripWidth, materials.grip, 0, 0, 0);
|
|
2297
|
+
kit.roundness = null;
|
|
2298
|
+
if (style.grip === "finger") {
|
|
2299
|
+
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);
|
|
2300
|
+
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);
|
|
2301
|
+
}
|
|
2302
|
+
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);
|
|
2303
|
+
if (style.grip === "chunky") box2(grip, 8e-3, gripLength * 0.8, gripWidth * 0.9, materials.grip, gripDepth / 2 + 2e-3, -4e-3, 0);
|
|
2304
|
+
if (style.beaver) {
|
|
2305
|
+
box2(group, 0.026, 8e-3, w * 0.9, fm, -4e-3, -h / 2 - 4e-3, 0, 0.25);
|
|
2306
|
+
box2(grip, 6e-3, gripLength * 0.55, w * 0.9, fm, -gripDepth * 0.5 - 2e-3, gripLength * 0.18, 0);
|
|
2307
|
+
} else box2(group, 0.02, 9e-3, w * 0.9, fm, 4e-3, -h / 2 - 6e-3, 0);
|
|
2308
|
+
if (style.lanyard) {
|
|
2309
|
+
const ring = kit.torus(grip, new kit.THREE.TorusGeometry(5e-3, 13e-4, 6, 12), M.dark, -0.012, -gripLength / 2 + 4e-3, 0);
|
|
2310
|
+
ring.position.set(-0.012, -gripLength / 2 + 4e-3, 0);
|
|
2311
|
+
}
|
|
2312
|
+
const magazine = kit.pivot(grip);
|
|
2313
|
+
parts.mag = magazine;
|
|
2314
|
+
parts.magDrop = gripLength * 1.15;
|
|
2315
|
+
box2(magazine, 0.022, gripLength * 0.94, w * 0.72, M.dark, 3e-3, -4e-3, 0);
|
|
2316
|
+
if (style.grip === "chunky") box2(magazine, 0.04, 0.01, gripWidth * 1.02, M.poly, 3e-3, -gripLength / 2 - 4e-3, 0);
|
|
2317
|
+
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);
|
|
2318
|
+
else box2(magazine, 0.031, 6e-3, gripWidth * 1.02, M.dark, 3e-3, -gripLength / 2 - 2e-3, 0);
|
|
2319
|
+
return { gx, ty: -h / 2 };
|
|
2320
|
+
}
|
|
2321
|
+
function buildPistol(kit, options) {
|
|
2322
|
+
const { M, box: box2 } = kit;
|
|
2323
|
+
const style = { ...STYLES[options.st ?? "glock"] };
|
|
2324
|
+
const group = kit.group();
|
|
2325
|
+
const parts = { family: "pistol", kick: options.deagle ? 1.8 : 0.6, glow: [] };
|
|
2326
|
+
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 };
|
|
2327
|
+
const { length: L, height: h, width: w } = size;
|
|
2328
|
+
const slideMetal = options.steel ? M.steel : options.stain ? M.steelL : M.metal;
|
|
2329
|
+
const frameMetal = style.frame === "poly" ? M.poly : slideMetal;
|
|
2330
|
+
const gripMaterial = style.grip === "wood" ? M.wood : style.grip === "star" ? M.bake : style.grip === "rubber" ? M.rubber : M.poly;
|
|
2331
|
+
const slide2 = kit.pivot(group);
|
|
2332
|
+
parts.slide = slide2;
|
|
2333
|
+
parts.stroke = L * 0.15;
|
|
2334
|
+
buildSlide(kit, slide2, parts, options, style, size, slideMetal);
|
|
2335
|
+
const { gx, ty } = buildFrame(kit, group, parts, options, style, size, { frame: frameMetal, grip: gripMaterial });
|
|
2336
|
+
if (style.guard === "square") {
|
|
2337
|
+
box2(group, 0.046, 4e-3, 9e-3, frameMetal, gx + 0.05, ty - 0.034, 0);
|
|
2338
|
+
box2(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.074, ty - 0.02, 0);
|
|
2339
|
+
box2(group, 6e-3, 4e-3, 9e-3, frameMetal, gx + 0.075, ty - 0.034, 0);
|
|
2340
|
+
} else if (style.guard === "hook") {
|
|
2341
|
+
box2(group, 0.046, 4e-3, 9e-3, frameMetal, gx + 0.05, ty - 0.034, 0);
|
|
2342
|
+
box2(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.073, ty - 0.02, 0, 0.2);
|
|
2343
|
+
box2(group, 8e-3, 6e-3, 9e-3, frameMetal, gx + 0.074, ty - 0.037, 0, 0.4);
|
|
2344
|
+
} else {
|
|
2345
|
+
const guard = kit.torus(group, new kit.THREE.TorusGeometry(0.019, 22e-4, 6, 16, PI5), frameMetal, gx + 0.05, ty - 0.014, 0);
|
|
2346
|
+
guard.rotation.z = PI5;
|
|
2347
|
+
guard.scale.set(1.2, 1, 1);
|
|
2348
|
+
}
|
|
2349
|
+
const isGlock = options.st === "glock" || !options.st;
|
|
2350
|
+
parts.trigger = box2(group, 4e-3, 0.014, 4e-3, isGlock ? M.dark : M.steel, gx + 0.042, ty - 0.022, 0, 0.2);
|
|
2351
|
+
if (isGlock) box2(parts.trigger, 15e-4, 8e-3, 45e-4, M.metal, 0, -1e-3, 0);
|
|
2352
|
+
if (style.hammer) {
|
|
2353
|
+
const hammer = kit.pivot(group, 3e-3, h / 2 - 8e-3, 0);
|
|
2354
|
+
parts.hammer = hammer;
|
|
2355
|
+
if (style.hammer === "ring") {
|
|
2356
|
+
box2(hammer, 6e-3, 0.012, 6e-3, M.dark, -2e-3, 4e-3, 0, 0.3);
|
|
2357
|
+
kit.torus(hammer, new kit.THREE.TorusGeometry(45e-4, 16e-4, 6, 12), M.dark, -4e-3, 0.012, 0);
|
|
2358
|
+
} else if (style.hammer === "big") box2(hammer, 0.014, 0.02, 9e-3, M.dark, -4e-3, 7e-3, 0, 0.35);
|
|
2359
|
+
else {
|
|
2360
|
+
box2(hammer, 8e-3, 0.016, 7e-3, M.dark, -2e-3, 6e-3, 0, 0.3);
|
|
2361
|
+
box2(hammer, 8e-3, 4e-3, 8e-3, M.dark, -6e-3, 0.013, 0, 0.3);
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
parts.muzzle = kit.mark(group, L + 6e-3, style.tri ? h * 0.08 : 2e-3, 0);
|
|
2365
|
+
parts.eject = kit.mark(group, L * 0.6, h / 2, w * 0.25);
|
|
2366
|
+
return { group, parts };
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// src/models/revolver.ts
|
|
2370
|
+
var PI6 = Math.PI;
|
|
2371
|
+
var DEFAULT_CHAMBERS = 6;
|
|
2372
|
+
function buildRevolver(kit, options) {
|
|
2373
|
+
const { M, box: box2, cylinderX } = kit;
|
|
2374
|
+
const group = kit.group();
|
|
2375
|
+
const parts = { family: "revolver", kick: options.cal === ".44 Magnum" ? 1.5 : 0.9, glow: [] };
|
|
2376
|
+
const barrel = (options.B ?? 0) / 1e3;
|
|
2377
|
+
const chambers = options.chambers ?? DEFAULT_CHAMBERS;
|
|
2378
|
+
const metal2 = options.steel ? M.steel : M.metal;
|
|
2379
|
+
box2(group, 0.08, 0.044, 0.024, metal2, 0.07, 0, 0);
|
|
2380
|
+
box2(group, 0.036, 0.012, 0.022, metal2, 0.096, 0.022, 0);
|
|
2381
|
+
box2(group, 0.024, 0.012, 4e-3, M.dark, 0.055, 6e-3, -0.013);
|
|
2382
|
+
const crane = kit.pivot(group, 0.077, -0.024, 0);
|
|
2383
|
+
parts.crane = crane;
|
|
2384
|
+
const cylinder = kit.pivot(crane, 0, 0.022, 0);
|
|
2385
|
+
parts.cylinder = cylinder;
|
|
2386
|
+
cylinderX(cylinder, 0.0215, 0.042, metal2, 0, 0, 0, 24);
|
|
2387
|
+
for (let i = 0; i < chambers; i++) {
|
|
2388
|
+
const angle = i * 2 * PI6 / chambers;
|
|
2389
|
+
box2(cylinder, 0.026, 5e-3, 5e-3, M.dark, 0, Math.cos(angle) * 0.0205, Math.sin(angle) * 0.0205, 0, 0, angle);
|
|
2390
|
+
}
|
|
2391
|
+
parts.chambers = [];
|
|
2392
|
+
for (let i = 0; i < chambers; i++) {
|
|
2393
|
+
const angle = i * 2 * PI6 / chambers + PI6 / chambers;
|
|
2394
|
+
parts.chambers.push(kit.mark(cylinder, -0.022, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013));
|
|
2395
|
+
cylinderX(cylinder, 46e-4, 2e-3, M.brass, -0.0215, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013, 12);
|
|
2396
|
+
}
|
|
2397
|
+
heatify(parts.glow, cylinderX(group, 88e-4, barrel, metal2, 0.112 + barrel / 2, 9e-3, 0, 18));
|
|
2398
|
+
box2(group, barrel * 0.92, 0.015, 0.013, metal2, 0.112 + barrel * 0.46, -5e-3, 0);
|
|
2399
|
+
cylinderX(group, 4e-3, 5e-3, M.hole, 0.112 + barrel + 15e-4, 9e-3, 0, 12);
|
|
2400
|
+
box2(group, 0.012, 9e-3, 4e-3, M.dark, 0.112 + barrel - 8e-3, 0.021, 0, -0.3);
|
|
2401
|
+
box2(group, 0.012, 6e-3, 0.012, M.dark, 0.105, 0.031, 0);
|
|
2402
|
+
const hammer = kit.pivot(group, 0.036, 0.018, 0);
|
|
2403
|
+
box2(hammer, 0.01, 0.02, 8e-3, M.dark, -4e-3, 8e-3, 0, 0.45);
|
|
2404
|
+
parts.hammer = hammer;
|
|
2405
|
+
const grip = kit.pivot(group, 0.024, -0.062, 0);
|
|
2406
|
+
grip.rotation.z = -0.38;
|
|
2407
|
+
box2(grip, 0.034, 0.1, 0.031, options.wood ? M.wood : M.rubber, 0, 0, 0);
|
|
2408
|
+
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);
|
|
2409
|
+
box2(group, 0.038, 4e-3, 8e-3, metal2, 0.062, -0.036, 0);
|
|
2410
|
+
box2(group, 4e-3, 0.022, 8e-3, metal2, 0.08, -0.026, 0, 0.2);
|
|
2411
|
+
parts.trigger = box2(group, 4e-3, 0.015, 4e-3, M.dark, 0.06, -0.027, 0, 0.2);
|
|
2412
|
+
parts.muzzle = kit.mark(group, 0.112 + barrel + 6e-3, 9e-3, 0);
|
|
2413
|
+
parts.eject = kit.mark(group, 0.077, 0, 0);
|
|
2414
|
+
return { group, parts };
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// src/models/rifle-fittings.ts
|
|
2418
|
+
function buildFittings(context, gripX) {
|
|
2419
|
+
const { kit, group, parts, options: o, furniture: furn, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
|
|
2420
|
+
const { M, box: box2, cylinderX, cylinderZ, sphere } = kit;
|
|
2421
|
+
const middle = (x0 + x1) / 2;
|
|
2422
|
+
if (o.scope) scope(kit, group, o.scope, middle + 0.03, hR / 2 + (o.rail ? 6e-3 : 0), o.scope === 4 ? -0.02 : 0);
|
|
2423
|
+
if (o.carry) {
|
|
2424
|
+
box2(group, 0.15, 0.026, 0.014, M.metal, middle, hR / 2 + 0.03, 0);
|
|
2425
|
+
box2(group, 0.012, 0.03, 0.014, M.metal, middle + 0.07, hR / 2 + 0.014, 0);
|
|
2426
|
+
box2(group, 0.012, 0.03, 0.014, M.metal, middle - 0.07, hR / 2 + 0.014, 0);
|
|
2427
|
+
}
|
|
2428
|
+
if (o.bipod) {
|
|
2429
|
+
const bx = x1 + (L - x1) * 0.45;
|
|
2430
|
+
cylinderX(group, 8e-3, 0.02, M.dark, bx + 0.04, by - 0.012, 0, 12);
|
|
2431
|
+
for (const side of [-1, 1]) {
|
|
2432
|
+
const leg = kit.pivot(group, bx + 0.04, by - 0.012, side * 0.012);
|
|
2433
|
+
leg.rotation.set(side * 0.15, 0, -0.35);
|
|
2434
|
+
box2(leg, 6e-3, 0.2, 6e-3, M.dark, 0, -0.1, 0);
|
|
2435
|
+
box2(leg, 0.01, 6e-3, 0.01, M.rubber, 0, -0.2, 0);
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
if (o.bolt) {
|
|
2439
|
+
const handle = kit.pivot(group, x1 - 0.045, by, 0);
|
|
2440
|
+
parts.boltHandle = handle;
|
|
2441
|
+
parts.boltHandleRest = handle.position.x;
|
|
2442
|
+
cylinderZ(handle, 42e-4, 0.05, M.steelL, 0, 0, wR / 2 + 0.022, 12);
|
|
2443
|
+
sphere(handle, 95e-4, M.dark, 0, -4e-3, wR / 2 + 0.048, 1);
|
|
2444
|
+
cylinderX(group, hR * 0.3, 0.03, M.steel, x0 + 5e-3, by, 0, 16);
|
|
2445
|
+
}
|
|
2446
|
+
if (o.lever) {
|
|
2447
|
+
const lever = kit.pivot(group, gripX - 0.02, -hR / 2 - 0.01, 0);
|
|
2448
|
+
parts.lever = lever;
|
|
2449
|
+
box2(lever, 0.11, 6e-3, 0.012, M.metal, 0.055, -0.052, 0);
|
|
2450
|
+
box2(lever, 6e-3, 0.05, 0.012, M.metal, 3e-3, -0.028, 0);
|
|
2451
|
+
box2(lever, 6e-3, 0.05, 0.012, M.metal, 0.108, -0.028, 0);
|
|
2452
|
+
box2(lever, 0.04, 6e-3, 0.012, M.metal, 0.08, -2e-3, 0);
|
|
2453
|
+
box2(group, 0.03, 0.012, 3e-3, M.dark, x1 - 0.04, by, wR / 2 + 1e-3);
|
|
2454
|
+
}
|
|
2455
|
+
if (o.tube) {
|
|
2456
|
+
const tubeLength = (L - x1) * (o.tubeLen || 0.8);
|
|
2457
|
+
cylinderX(group, 0.0105, tubeLength, M.metal, x1 + tubeLength / 2, by - 0.023, 0, 16);
|
|
2458
|
+
cylinderX(group, 0.0125, 0.02, M.dark, x1 + tubeLength + 6e-3, by - 0.023, 0, 16);
|
|
2459
|
+
}
|
|
2460
|
+
if (o.pump) {
|
|
2461
|
+
const pump = kit.pivot(group);
|
|
2462
|
+
parts.pump = pump;
|
|
2463
|
+
box2(pump, 0.17, 0.042, 0.046, furn, x1 + 0.14, by - 0.022, 0);
|
|
2464
|
+
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);
|
|
2465
|
+
}
|
|
2466
|
+
if (o.heat) {
|
|
2467
|
+
cylinderX(group, r * 1.75, (L - x1) * 0.7, M.dark, x1 + (L - x1) * 0.4, by, 0, 12);
|
|
2468
|
+
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);
|
|
2469
|
+
}
|
|
2470
|
+
if (o.bayo) box2(group, 0.025, 8e-3, 0.01, M.metal, L - 0.07, by - 0.016, 0);
|
|
2471
|
+
if (o.rod) cylinderX(group, 3e-3, (L - x1) * 0.85, M.steel, x1 + (L - x1) * 0.45, by - 0.014, 0, 8);
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// src/models/rifle-forend.ts
|
|
2475
|
+
var PI7 = Math.PI;
|
|
2476
|
+
function buildForend(context) {
|
|
2477
|
+
const { kit, group, options: o, furniture: furn, length: L, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
|
|
2478
|
+
const { M, box: box2 } = kit;
|
|
2479
|
+
const hasLongScope = (o.scope ?? 0) >= 3;
|
|
2480
|
+
if (!context.isTraditional && o.hg !== "none") {
|
|
2481
|
+
const handguardLength = (L - x1) * (o.hgFrac || 0.5);
|
|
2482
|
+
const hx = x1 + handguardLength / 2;
|
|
2483
|
+
const hh = hR * 0.8;
|
|
2484
|
+
if (o.hg === "rail") {
|
|
2485
|
+
box2(group, handguardLength, hh, wR * 1.04, M.dark, hx, by - 2e-3, 0);
|
|
2486
|
+
rail(kit, group, x1, handguardLength, by + hh / 2, 0, wR * 0.66);
|
|
2487
|
+
rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, wR * 0.54, wR * 0.4, PI7 / 2);
|
|
2488
|
+
rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, -wR * 0.54, wR * 0.4, -PI7 / 2);
|
|
2489
|
+
rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - hh / 2, 0, wR * 0.6, PI7);
|
|
2490
|
+
} else {
|
|
2491
|
+
const material = o.hg === "wood" ? M.wood : furn;
|
|
2492
|
+
if (o.ak) {
|
|
2493
|
+
box2(group, handguardLength, hh * 0.55, wR * 1.08, material, hx, by - hh * 0.22, 0);
|
|
2494
|
+
box2(group, handguardLength * 0.75, hh * 0.35, wR * 0.7, material, hx - handguardLength * 0.1, by + r * 2.2, 0);
|
|
2495
|
+
} else {
|
|
2496
|
+
box2(group, handguardLength, hh, wR * 1.08, material, hx, by - 2e-3, 0);
|
|
2497
|
+
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);
|
|
2498
|
+
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);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
if (!hasLongScope && !o.rail && o.ak) {
|
|
2502
|
+
box2(group, 0.03, 0.016, wR * 0.6, M.metal, x1 + 0.02, by + r * 2.5 + 0.01, 0);
|
|
2503
|
+
box2(group, 0.012, 4e-3, wR * 0.5, M.dark, x1 + 0.03, by + r * 2.5 + 0.019, 0);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
if (hasLongScope) return;
|
|
2507
|
+
const sightX = L - (o.ak ? 0.06 : 0.08);
|
|
2508
|
+
if (o.ak) {
|
|
2509
|
+
box2(group, 0.022, 0.022, wR * 0.6, M.metal, sightX, by + 8e-3, 0);
|
|
2510
|
+
box2(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, 8e-3);
|
|
2511
|
+
box2(group, 6e-3, 0.024, 4e-3, M.metal, sightX, by + 0.026, -8e-3);
|
|
2512
|
+
box2(group, 3e-3, 0.016, 3e-3, M.dark, sightX, by + 0.022, 0);
|
|
2513
|
+
} else if (!o.rail || o.carry) {
|
|
2514
|
+
box2(group, 0.018, 0.014, wR * 0.55, M.dark, sightX, by + 8e-3, 0);
|
|
2515
|
+
box2(group, 0.012, 0.034, 8e-3, M.dark, sightX, by + 0.03, 0, -0.25);
|
|
2516
|
+
} else box2(group, 0.012, 0.024, 0.012, M.dark, sightX - 0.04, by + hR * 0.4 + 0.012, 0);
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
// src/models/rifle-controls.ts
|
|
2520
|
+
function buildGrip(context) {
|
|
2521
|
+
const { kit, group, parts, options: o, furniture: furn, receiverStart: x0, receiverHeight: hR, receiverWidth: wR } = context;
|
|
2522
|
+
const { M, box: box2 } = kit;
|
|
2523
|
+
const gx = x0 + (o.gripAt || 0.07);
|
|
2524
|
+
if (!context.isTraditional && o.grip !== "none") {
|
|
2525
|
+
const grip = kit.pivot(group, gx - 0.012, -hR / 2 - 0.046, 0);
|
|
2526
|
+
grip.rotation.z = -0.3;
|
|
2527
|
+
box2(grip, 0.032, 0.105, 0.028, furn, 0, 0, 0);
|
|
2528
|
+
for (let i = 0; i < 3; i++) box2(grip, 6e-3, 0.012, 0.026, furn, 0.017, 0.022 - i * 0.02, 0);
|
|
2529
|
+
box2(grip, 0.034, 8e-3, 0.03, furn, 0, -0.052, 0);
|
|
2530
|
+
}
|
|
2531
|
+
box2(group, 0.06, 4e-3, 0.01, M.metal, gx + 0.03, -hR / 2 - 0.024, 0);
|
|
2532
|
+
box2(group, 4e-3, 0.022, 0.01, M.metal, gx + 0.06, -hR / 2 - 0.013, 0);
|
|
2533
|
+
parts.trigger = box2(group, 4e-3, 0.016, 4e-3, M.dark, gx + 0.024, -hR / 2 - 0.012, 0, 0.15);
|
|
2534
|
+
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);
|
|
2535
|
+
return gx;
|
|
2536
|
+
}
|
|
2537
|
+
function buildMagazine(context, gx) {
|
|
2538
|
+
const { kit, group, parts, options: o, receiverStart: x0, receiverHeight: hR, receiverWidth: wR, receiverLength: RL } = context;
|
|
2539
|
+
const { M, box: box2, cylinderX, cylinderZ } = kit;
|
|
2540
|
+
const mx = o.magAt != null ? x0 + o.magAt : gx + 0.075;
|
|
2541
|
+
const scale = o.magLen || 1;
|
|
2542
|
+
const magazine = kit.pivot(group, mx, -hR / 2, 0);
|
|
2543
|
+
parts.mag = magazine;
|
|
2544
|
+
const finish = o.magMat === "bakelite" ? M.bakelite : o.magMat === "poly" ? M.polyMag : void 0;
|
|
2545
|
+
if (o.mag === "curved") {
|
|
2546
|
+
let px = 0;
|
|
2547
|
+
let py = 0;
|
|
2548
|
+
const material = finish ?? M.metal;
|
|
2549
|
+
for (const angle of [0.06, 0.22, 0.38]) {
|
|
2550
|
+
const length = 0.07 * scale;
|
|
2551
|
+
const cx = px + Math.sin(angle) * length / 2;
|
|
2552
|
+
const cy = py - Math.cos(angle) * length / 2;
|
|
2553
|
+
box2(magazine, 0.052, length + 4e-3, 0.024, material, cx, cy, 0, angle);
|
|
2554
|
+
box2(magazine, 0.04, length * 0.8, 0.026, M.dark, cx, cy, 0, angle);
|
|
2555
|
+
px += Math.sin(angle) * length;
|
|
2556
|
+
py -= Math.cos(angle) * length;
|
|
2557
|
+
}
|
|
2558
|
+
box2(magazine, 0.058, 8e-3, 0.028, material, px, py, 0, 0.38);
|
|
2559
|
+
parts.magDrop = 0.07 * scale * 3 + 0.04;
|
|
2560
|
+
} else if (o.mag === "box") {
|
|
2561
|
+
const length = 0.13 * scale;
|
|
2562
|
+
const curve = o.curve || 0;
|
|
2563
|
+
const segment = length / 3;
|
|
2564
|
+
const material = finish ?? M.dark;
|
|
2565
|
+
let px = 0;
|
|
2566
|
+
let py = 0;
|
|
2567
|
+
let angle = 0.08;
|
|
2568
|
+
for (let i = 0; i < 3; i++) {
|
|
2569
|
+
angle = 0.08 + curve * i;
|
|
2570
|
+
const cx = px + Math.sin(angle) * segment / 2;
|
|
2571
|
+
const cy = py - Math.cos(angle) * segment / 2;
|
|
2572
|
+
box2(magazine, 0.05, segment + 4e-3, 0.024, material, cx, cy, 0, angle);
|
|
2573
|
+
box2(magazine, 0.04, 3e-3, 0.026, M.metal, cx, cy, 0, angle);
|
|
2574
|
+
px += Math.sin(angle) * segment;
|
|
2575
|
+
py -= Math.cos(angle) * segment;
|
|
2576
|
+
}
|
|
2577
|
+
box2(magazine, 0.056, 7e-3, 0.028, material, px, py - 2e-3, 0, angle);
|
|
2578
|
+
parts.magDrop = length + 0.03;
|
|
2579
|
+
} else if (o.mag === "drum") {
|
|
2580
|
+
cylinderZ(magazine, 0.075, 0.07, M.dark, 0.02, -0.085, 0, 22);
|
|
2581
|
+
cylinderZ(magazine, 0.05, 0.074, M.metal, 0.02, -0.085, 0, 18);
|
|
2582
|
+
box2(magazine, 0.05, 0.04, 0.03, M.dark, 0, -0.02, 0);
|
|
2583
|
+
parts.magDrop = 0.2;
|
|
2584
|
+
} else if (o.mag === "internal") {
|
|
2585
|
+
box2(group, 0.08, 0.025, 0.03, context.isTraditional ? context.furniture : M.metal, mx, -hR / 2 - 0.012, 0);
|
|
2586
|
+
parts.mag = null;
|
|
2587
|
+
} else parts.mag = null;
|
|
2588
|
+
if (o.mag === "belt") {
|
|
2589
|
+
const belt = kit.pivot(group, mx + 0.02, -hR / 2 - 0.05, -0.045);
|
|
2590
|
+
parts.belt = belt;
|
|
2591
|
+
box2(belt, 0.11, 0.1, 0.08, M.olive, 0, 0, 0);
|
|
2592
|
+
box2(belt, 0.112, 0.012, 0.082, M.dark, 0, 0.046, 0);
|
|
2593
|
+
for (let i = 0; i < 7; i++) {
|
|
2594
|
+
cylinderX(belt, 42e-4, 0.034, M.brass, -0.04 + i * 0.012, 0.07, 0.035, 10);
|
|
2595
|
+
box2(belt, 6e-3, 6e-3, 0.012, M.metal, -0.04 + i * 0.012, 0.066, 0.035);
|
|
2596
|
+
}
|
|
2597
|
+
const cover = kit.pivot(group, x0 + 0.01, hR / 2, 0);
|
|
2598
|
+
parts.cover = cover;
|
|
2599
|
+
box2(cover, RL - 0.03, 0.014, wR * 1.05, M.metal, (RL - 0.03) / 2, 7e-3, 0);
|
|
2600
|
+
box2(cover, 0.02, 0.012, wR * 0.6, M.dark, RL - 0.04, 0.016, 0);
|
|
2601
|
+
}
|
|
2602
|
+
return mx;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
// src/models/rifle-stock.ts
|
|
2606
|
+
function buildStock(context, magazineX) {
|
|
2607
|
+
const { kit, group, options: o, furniture: furn, length: L, stockLength: S, receiverStart: x0, receiverEnd: x1, receiverHeight: hR } = context;
|
|
2608
|
+
const { M, box: box2, cylinderX } = kit;
|
|
2609
|
+
if (context.isTraditional) {
|
|
2610
|
+
buttpad(kit, group, 6e-3, -0.04, 0.13, 0.038);
|
|
2611
|
+
box2(group, 0.03, 0.13, 0.038, furn, 0.024, -0.04, 0);
|
|
2612
|
+
box2(group, S, 0.055, 0.034, furn, S / 2, -0.012, 0, 0.06);
|
|
2613
|
+
box2(group, 0.09, 0.05, 0.032, furn, x0 - 0.02, -0.035, 0, 0.25);
|
|
2614
|
+
const forendLength = x1 - x0 + (L - x1) * (o.fore ?? 0.62);
|
|
2615
|
+
if (forendLength > 0) box2(group, forendLength, 0.045, 0.04, furn, x0 + forendLength / 2, -hR / 2 + 4e-3, 0);
|
|
2616
|
+
if (o.stock === "straight") box2(group, 0.15, 0.04, 0.035, furn, x1 + 0.08, -hR / 2, 0);
|
|
2617
|
+
box2(group, 0.04, 4e-3, 0.03, M.metal, magazineX, -hR / 2 - 0.022, 0);
|
|
2618
|
+
swivel(kit, group, 0.2, -0.08);
|
|
2619
|
+
swivel(kit, group, x1 + (L - x1) * 0.45, -hR / 2 - 0.03);
|
|
2620
|
+
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);
|
|
2621
|
+
} else if (o.stock === "fixed" || o.stock === "wood") {
|
|
2622
|
+
box2(group, S, 0.05, 0.03, furn, S / 2, -8e-3, 0, 0.05);
|
|
2623
|
+
box2(group, 0.03, 0.13, 0.036, furn, 0.022, -0.035, 0);
|
|
2624
|
+
buttpad(kit, group, 4e-3, -0.035, 0.13, 0.036);
|
|
2625
|
+
if (o.stock === "wood") box2(group, S * 0.6, 0.03, 0.028, furn, S * 0.55, -0.045, 0, 0.2);
|
|
2626
|
+
swivel(kit, group, 0.08, -0.09);
|
|
2627
|
+
} else if (o.stock === "skeleton") {
|
|
2628
|
+
box2(group, S, 0.014, 0.026, furn, S / 2, 0.012, 0, 0.03);
|
|
2629
|
+
box2(group, S, 0.014, 0.026, furn, S / 2, -0.05, 0, -0.08);
|
|
2630
|
+
box2(group, 0.03, 0.12, 0.034, furn, 0.02, -0.025, 0);
|
|
2631
|
+
buttpad(kit, group, 4e-3, -0.025, 0.12, 0.034);
|
|
2632
|
+
} else if (o.stock === "retract") {
|
|
2633
|
+
cylinderX(group, 0.013, S, M.metal, S / 2, 0, 0, 16);
|
|
2634
|
+
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);
|
|
2635
|
+
box2(group, 0.034, 0.11, 0.036, furn, 0.02, -0.025, 0);
|
|
2636
|
+
box2(group, S * 0.55, 0.032, 0.03, furn, S * 0.35, 0, 0);
|
|
2637
|
+
buttpad(kit, group, 3e-3, -0.025, 0.11, 0.036);
|
|
2638
|
+
} else if (o.stock === "fold") {
|
|
2639
|
+
box2(group, S, 6e-3, 6e-3, M.metal, S / 2, 0.01, 0.012);
|
|
2640
|
+
box2(group, S, 6e-3, 6e-3, M.metal, S / 2, -0.04, 0.012, -0.12);
|
|
2641
|
+
box2(group, 0.012, 0.1, 0.03, M.metal, 6e-3, -0.02, 0.012);
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
// src/models/rifle.ts
|
|
2646
|
+
var PI8 = Math.PI;
|
|
2647
|
+
function buildReceiver(context) {
|
|
2648
|
+
const { kit, group, parts, options: o, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, receiverLength: RL, barrelY: by } = context;
|
|
2649
|
+
const { M, box: box2, cylinderX, cylinderY, cylinderZ, sphere } = kit;
|
|
2650
|
+
const receiverMaterial = o.tan ? M.tan : M.metal;
|
|
2651
|
+
const middle = (x0 + x1) / 2;
|
|
2652
|
+
if (o.round) {
|
|
2653
|
+
cylinderX(group, hR * 0.36, RL, M.steel, middle, by, 0, 20);
|
|
2654
|
+
cylinderX(group, hR * 0.32, 0.05, M.steel, x0 - 0.01, by, 0, 18);
|
|
2655
|
+
} else if (o.ar) {
|
|
2656
|
+
box2(group, RL, hR * 0.5, wR, receiverMaterial, middle, hR * 0.25, 0);
|
|
2657
|
+
box2(group, RL * 0.92, hR * 0.52, wR * 0.96, receiverMaterial, middle - RL * 0.02, -hR * 0.24, 0);
|
|
2658
|
+
box2(group, 0.05, 0.03, wR * 1.15, receiverMaterial, x0 + RL * 0.62, -hR * 0.5 - 0.01, 0);
|
|
2659
|
+
cylinderZ(group, 6e-3, 0.012, M.metal, x1 - 0.03, hR * 0.28, wR / 2 + 6e-3, 12);
|
|
2660
|
+
box2(group, 0.012, 0.01, 0.012, M.metal, x1 - 0.05, hR * 0.12, wR / 2 + 6e-3);
|
|
2661
|
+
box2(group, 0.03, 8e-3, 0.012, M.dark, x0 + 0.02, hR / 2 + 4e-3, 0);
|
|
2662
|
+
box2(group, 0.012, 6e-3, 4e-3, M.dark, x0 + RL * 0.55, -hR * 0.15, -wR / 2 - 2e-3);
|
|
2663
|
+
} else box2(group, RL, hR, wR, receiverMaterial, middle, 0, 0);
|
|
2664
|
+
if (o.ak) {
|
|
2665
|
+
box2(group, RL * 0.86, hR * 0.28, wR * 0.95, M.metal, middle - 8e-3, hR / 2 + 4e-3, 0);
|
|
2666
|
+
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);
|
|
2667
|
+
box2(group, RL * 0.5, 0.012, 3e-3, M.metal, x0 + RL * 0.55, -hR * 0.02, wR / 2 + 15e-4, -0.05);
|
|
2668
|
+
box2(group, 0.012, 0.014, 3e-3, M.dark, x0 + RL * 0.32, -hR * 0.05, wR / 2 + 2e-3);
|
|
2669
|
+
}
|
|
2670
|
+
if (o.g3) {
|
|
2671
|
+
cylinderX(group, 9e-3, (L - x1) * 0.6, M.metal, x1 + (L - x1) * 0.28, hR * 0.48, 0, 14);
|
|
2672
|
+
box2(group, 0.012, 0.024, 8e-3, M.dark, x1 + (L - x1) * 0.5, hR * 0.48, -0.012);
|
|
2673
|
+
cylinderY(group, 0.012, 0.016, M.dark, x0 + 0.03, hR / 2 + 8e-3, 0, 12);
|
|
2674
|
+
}
|
|
2675
|
+
const ejectX = x1 - (o.ejectBack || 0.07);
|
|
2676
|
+
const ejectY = o.ar ? hR * 0.2 : by;
|
|
2677
|
+
box2(group, 0.05, hR * 0.36, 4e-3, M.hole, ejectX, ejectY, wR / 2 - 4e-4);
|
|
2678
|
+
const bolt = kit.pivot(group);
|
|
2679
|
+
parts.bolt = bolt;
|
|
2680
|
+
parts.stroke = Math.min(0.1, RL * 0.32);
|
|
2681
|
+
box2(bolt, 0.044, hR * 0.26, 6e-3, M.steelL, ejectX, ejectY, wR / 2 - 12e-4);
|
|
2682
|
+
if (o.charge) {
|
|
2683
|
+
box2(bolt, 0.012, 0.01, 0.03, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.012);
|
|
2684
|
+
sphere(bolt, 65e-4, M.metal, ejectX + 0.04, ejectY + 4e-3, wR / 2 + 0.028, 1);
|
|
2685
|
+
}
|
|
2686
|
+
if (o.ar) box2(group, 0.012, 0.016, 6e-3, receiverMaterial, ejectX - 0.03, ejectY + 4e-3, wR / 2 + 4e-3);
|
|
2687
|
+
if (o.rail) rail(kit, group, x0 - 5e-3, RL + 0.07, hR / 2 + (o.ar ? 0 : 3e-3), 0, wR * 0.68);
|
|
2688
|
+
}
|
|
2689
|
+
function buildBarrel(context) {
|
|
2690
|
+
const { kit, group, parts, options: o, length: L, barrelLength: Bl, receiverEnd: x1, barrelRadius: r, barrelY: by } = context;
|
|
2691
|
+
const { M, box: box2, cylinderX } = kit;
|
|
2692
|
+
heatify(parts.glow, cylinderX(group, r, Bl, M.metal, L - Bl / 2, by, 0, 16));
|
|
2693
|
+
if (o.jacket) {
|
|
2694
|
+
cylinderX(group, r * 2, (L - x1) * 0.6, M.dark, x1 + (L - x1) * 0.3, by, 0, 16);
|
|
2695
|
+
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);
|
|
2696
|
+
}
|
|
2697
|
+
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);
|
|
2698
|
+
if (o.brake) {
|
|
2699
|
+
const scale = o.brake === 2 ? 3.2 : 2;
|
|
2700
|
+
const brakeLength = o.brake === 2 ? 0.12 : 0.06;
|
|
2701
|
+
heatify(parts.glow, cylinderX(group, r * scale, brakeLength, M.dark, L - brakeLength / 2, by, 0, o.brake === 2 ? 8 : 16));
|
|
2702
|
+
for (let i = 0; i < 3; i++) box2(group, brakeLength * 0.16, r * scale * 1.3, r * scale * 2.2, M.hole, L - brakeLength * 0.25 - i * brakeLength * 0.25, by, 0);
|
|
2703
|
+
} else if (!o.noFH) {
|
|
2704
|
+
heatify(parts.glow, cylinderX(group, r * 1.45, 0.042, M.dark, L - 0.021, by, 0, 16));
|
|
2705
|
+
for (let i = 0; i < 4; i++) {
|
|
2706
|
+
const angle = i * PI8 / 2 + PI8 / 4;
|
|
2707
|
+
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);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
cylinderX(group, r * 0.55, 4e-3, M.hole, L + 5e-4, by, 0, 12);
|
|
2711
|
+
}
|
|
2712
|
+
var familyOf = (o) => {
|
|
2713
|
+
if (o.mag === "belt") return "belt";
|
|
2714
|
+
if (o.pump) return "pump";
|
|
2715
|
+
if (o.lever) return "lever";
|
|
2716
|
+
if (o.tube) return "semi12";
|
|
2717
|
+
if (o.bolt) return o.mag === "box" ? "boltmag" : "bolt";
|
|
2718
|
+
return o.mag === "internal" ? "semiint" : "rifle";
|
|
2719
|
+
};
|
|
2720
|
+
function buildRifle(kit, options) {
|
|
2721
|
+
const { M } = kit;
|
|
2722
|
+
const group = kit.group();
|
|
2723
|
+
const parts = { family: "rifle", glow: [] };
|
|
2724
|
+
const length = (options.L ?? 0) / 1e3;
|
|
2725
|
+
const barrelLength = (options.B ?? 0) / 1e3;
|
|
2726
|
+
const isTraditional = options.stock === "trad" || options.stock === "straight";
|
|
2727
|
+
const stockLength = options.stock === "none" ? 0 : options.stockLen || Math.min(0.36, Math.max(0.2, 0.3 * length));
|
|
2728
|
+
const receiverStart = stockLength;
|
|
2729
|
+
const receiverEnd = Math.max(receiverStart + 0.12, length - barrelLength * 0.92);
|
|
2730
|
+
const receiverHeight = options.hR || 0.06;
|
|
2731
|
+
const context = {
|
|
2732
|
+
kit,
|
|
2733
|
+
group,
|
|
2734
|
+
parts,
|
|
2735
|
+
options,
|
|
2736
|
+
furniture: options.wood ? M.wood : options.olive ? M.olive : options.tan ? M.tan : M.poly,
|
|
2737
|
+
isTraditional,
|
|
2738
|
+
length,
|
|
2739
|
+
barrelLength,
|
|
2740
|
+
stockLength,
|
|
2741
|
+
receiverStart,
|
|
2742
|
+
receiverEnd,
|
|
2743
|
+
receiverHeight,
|
|
2744
|
+
receiverWidth: options.wR || 0.034,
|
|
2745
|
+
barrelRadius: options.br || 85e-4,
|
|
2746
|
+
barrelY: receiverHeight * 0.15,
|
|
2747
|
+
receiverLength: receiverEnd - receiverStart
|
|
2748
|
+
};
|
|
2749
|
+
buildReceiver(context);
|
|
2750
|
+
buildBarrel(context);
|
|
2751
|
+
buildForend(context);
|
|
2752
|
+
const gripX = buildGrip(context);
|
|
2753
|
+
const magazineX = buildMagazine(context, gripX);
|
|
2754
|
+
buildStock(context, magazineX);
|
|
2755
|
+
buildFittings(context, gripX);
|
|
2756
|
+
const { barrelY: by, receiverWidth: wR } = context;
|
|
2757
|
+
const ejectX = receiverEnd - (options.ejectBack || 0.07);
|
|
2758
|
+
const ejectY = options.ar ? receiverHeight * 0.2 : by;
|
|
2759
|
+
parts.muzzle = kit.mark(group, length + 0.01, by, 0);
|
|
2760
|
+
parts.eject = kit.mark(group, ejectX, ejectY, wR / 2 + 6e-3);
|
|
2761
|
+
parts.loadPort = kit.mark(group, receiverEnd - 0.05, -receiverHeight / 2 - 4e-3, 0);
|
|
2762
|
+
parts.topLoad = kit.mark(group, ejectX, receiverHeight / 2 + 4e-3, 0);
|
|
2763
|
+
parts.family = familyOf(options);
|
|
2764
|
+
return { group, parts };
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
// src/models/index.ts
|
|
2768
|
+
var MODEL_ENTRIES = models_default;
|
|
2769
|
+
var ARCHETYPE_MODELS = (() => {
|
|
2770
|
+
const actionOf = new Map(WEAPONS.map((def) => [def.id, def.action.kind]));
|
|
2771
|
+
const chosen = {};
|
|
2772
|
+
for (const def of [...WEAPONS].sort((a, b) => a.id < b.id ? -1 : 1)) {
|
|
2773
|
+
const { archetype } = def;
|
|
2774
|
+
if (!archetype || !(def.id in MODEL_ENTRIES) || archetype in MODEL_ENTRIES || archetype in chosen) continue;
|
|
2775
|
+
if (actionOf.get(archetype) === def.action.kind) chosen[archetype] = def.id;
|
|
2776
|
+
}
|
|
2777
|
+
return chosen;
|
|
2778
|
+
})();
|
|
2779
|
+
var BUILDERS = {
|
|
2780
|
+
pistol: buildPistol,
|
|
2781
|
+
revolver: buildRevolver,
|
|
2782
|
+
rifle: buildRifle,
|
|
2783
|
+
bullpup: buildBullpup,
|
|
2784
|
+
p90: (kit) => buildP90(kit),
|
|
2785
|
+
break: buildBreakAction,
|
|
2786
|
+
m2: (kit) => buildM2(kit),
|
|
2787
|
+
launcher: buildLauncher
|
|
2788
|
+
};
|
|
2789
|
+
var nameParts = (parts) => {
|
|
2790
|
+
for (const [name, part] of partRegistry(parts)) part.name = name;
|
|
2791
|
+
};
|
|
2792
|
+
var fitLength = (kit, inner, lengthMetres) => {
|
|
2793
|
+
const box2 = new kit.THREE.Box3().setFromObject(inner);
|
|
2794
|
+
const factor = lengthMetres / (box2.max.x - box2.min.x);
|
|
2795
|
+
inner.scale.x = factor;
|
|
2796
|
+
inner.position.x = -box2.min.x * factor;
|
|
2797
|
+
const root = kit.group();
|
|
2798
|
+
root.add(inner);
|
|
2799
|
+
return root;
|
|
2800
|
+
};
|
|
2801
|
+
var hasWeaponModel = (id) => id in MODEL_ENTRIES || id in ARCHETYPE_MODELS;
|
|
2802
|
+
function buildWeaponModelById(THREE, id) {
|
|
2803
|
+
const entry = MODEL_ENTRIES[ARCHETYPE_MODELS[id] ?? id];
|
|
2804
|
+
if (!entry) return void 0;
|
|
2805
|
+
const kit = modelKit(THREE);
|
|
2806
|
+
const options = { L: entry.length, B: entry.barrel, cal: entry.caliber, ...entry.options };
|
|
2807
|
+
if (id in ARCHETYPE_MODELS) delete options.chambers;
|
|
2808
|
+
const built = BUILDERS[entry.builder](kit, options);
|
|
2809
|
+
nameParts(built.parts);
|
|
2810
|
+
let root;
|
|
2811
|
+
if (entry.builder === "launcher") {
|
|
2812
|
+
root = kit.group();
|
|
2813
|
+
root.add(built.group);
|
|
2814
|
+
built.group.position.x = -new THREE.Box3().setFromObject(built.group).min.x;
|
|
2815
|
+
} else root = fitLength(kit, built.group, entry.length / 1e3);
|
|
2816
|
+
root.name = id;
|
|
2817
|
+
return { id, root, inner: built.group, parts: built.parts };
|
|
2818
|
+
}
|
|
2819
|
+
|
|
108
2820
|
// src/props.ts
|
|
109
2821
|
var box = (id, material, size, position, extra = {}) => ({ id, material, shape: { kind: "box", size }, position, ...extra });
|
|
110
2822
|
var joint = (a, b, kind) => ({ a, b, kind });
|
|
@@ -176,19 +2888,47 @@ function buildProp(THREE, prop) {
|
|
|
176
2888
|
function buildWeaponModel(THREE, def) {
|
|
177
2889
|
return buildProp(THREE, weaponProp(def));
|
|
178
2890
|
}
|
|
2891
|
+
var MODEL_BODY = "body";
|
|
2892
|
+
function buildModeledProp(THREE, def, name) {
|
|
2893
|
+
const model = buildWeaponModelById(THREE, def.id);
|
|
2894
|
+
if (!model) return void 0;
|
|
2895
|
+
const group = new THREE.Group();
|
|
2896
|
+
group.name = name;
|
|
2897
|
+
const box2 = new THREE.Box3().setFromObject(model.root);
|
|
2898
|
+
const center = box2.getCenter(new THREE.Vector3());
|
|
2899
|
+
model.root.position.set(-center.x, -box2.min.y, -center.z);
|
|
2900
|
+
group.add(model.root);
|
|
2901
|
+
group.updateMatrixWorld(true);
|
|
2902
|
+
for (const mesh of mergeByMaterial(THREE, model.root, group)) {
|
|
2903
|
+
mesh.userData.destructionPart = MODEL_BODY;
|
|
2904
|
+
group.add(mesh);
|
|
2905
|
+
}
|
|
2906
|
+
group.remove(model.root);
|
|
2907
|
+
const size = box2.getSize(new THREE.Vector3());
|
|
2908
|
+
group.userData.footprint = [size.x, size.y, size.z];
|
|
2909
|
+
return group;
|
|
2910
|
+
}
|
|
2911
|
+
function modeledPropSpec(root, def, name) {
|
|
2912
|
+
const footprint = root.userData.footprint ?? [0.3, 0.1, 0.1];
|
|
2913
|
+
const size = [Math.max(0.02, footprint[0]), Math.max(0.02, footprint[1]), Math.max(0.02, footprint[2])];
|
|
2914
|
+
return { name, parts: [{ id: MODEL_BODY, material: "steel", shape: { kind: "box", size }, position: [0, size[1] / 2, 0], mass: def.mass }], joints: [], tags: ["weapon"] };
|
|
2915
|
+
}
|
|
179
2916
|
|
|
180
|
-
// src/fx.ts
|
|
2917
|
+
// src/box-fx.ts
|
|
181
2918
|
var EJECTS_ON_SHOT = /* @__PURE__ */ new Set(["pistol", "smg", "carbine", "rifle", "sniper", "shotgun"]);
|
|
182
2919
|
var DROPS_MAGAZINE = /* @__PURE__ */ new Set(["pistol", "smg", "carbine", "rifle", "sniper"]);
|
|
183
2920
|
var CYCLES = /* @__PURE__ */ new Set(["shotgun_pump_12", "rifle_bolt_308"]);
|
|
2921
|
+
var FALLBACK_ENERGY = 300;
|
|
184
2922
|
var MAX_CASINGS = 120;
|
|
185
2923
|
var MAX_MAGAZINES = 12;
|
|
186
2924
|
var LIFETIME_S = 25;
|
|
187
2925
|
var HAND_OFFSET = [0.2, -0.2, -0.42];
|
|
188
2926
|
var GRAVITY = 9.81;
|
|
189
|
-
function
|
|
2927
|
+
function createBoxFx(options) {
|
|
190
2928
|
const { THREE, scene, camera, arsenal, groundAt } = options;
|
|
191
2929
|
const audio = options.audio ?? null;
|
|
2930
|
+
const random = createRng2(options.seed ?? 1);
|
|
2931
|
+
const layers = options.layers ?? createLayers();
|
|
192
2932
|
const bodies = [];
|
|
193
2933
|
const brass = new THREE.MeshStandardMaterial({ color: 13214282, metalness: 0.8, roughness: 0.35 });
|
|
194
2934
|
const shell = new THREE.MeshStandardMaterial({ color: 11022891, roughness: 0.6 });
|
|
@@ -220,13 +2960,17 @@ function createWeaponFx(options) {
|
|
|
220
2960
|
};
|
|
221
2961
|
const handPoint = (x, y, z, out) => out.set(HAND_OFFSET[0] + x, HAND_OFFSET[1] + y, HAND_OFFSET[2] + z).applyQuaternion(camera.quaternion).add(camera.position);
|
|
222
2962
|
const throwBody = (kind, mesh, from, velocity, radius) => {
|
|
2963
|
+
if (!(kind === "casing" ? layers.casings : layers.magazines)) {
|
|
2964
|
+
mesh.geometry.dispose();
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
223
2967
|
const limit = kind === "casing" ? MAX_CASINGS : MAX_MAGAZINES;
|
|
224
2968
|
const same = bodies.filter((b) => b.kind === kind);
|
|
225
2969
|
if (same.length >= limit) removeBody(same[0]);
|
|
226
2970
|
mesh.position.copy(from);
|
|
227
2971
|
mesh.castShadow = true;
|
|
228
2972
|
scene.add(mesh);
|
|
229
|
-
bodies.push({ mesh, velocity, spin: new THREE.Vector3(
|
|
2973
|
+
bodies.push({ mesh, velocity, spin: new THREE.Vector3(random() * 20 - 10, random() * 30 - 15, random() * 20 - 10), radius, age: 0, resting: false, bounces: 0, kind });
|
|
230
2974
|
};
|
|
231
2975
|
const ejectCasing = (caliberMm, isShell, downward) => {
|
|
232
2976
|
const radius = isShell ? 0.0105 : Math.max(3e-3, caliberMm / 1e3 * 0.6);
|
|
@@ -236,7 +2980,7 @@ function createWeaponFx(options) {
|
|
|
236
2980
|
const from = handPoint(0.03, 0.03, 0.05, new THREE.Vector3());
|
|
237
2981
|
const right = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion);
|
|
238
2982
|
const up = new THREE.Vector3(0, 1, 0);
|
|
239
|
-
const velocity = downward ? up.clone().multiplyScalar(-0.5).addScaledVector(right, (
|
|
2983
|
+
const velocity = downward ? up.clone().multiplyScalar(-0.5).addScaledVector(right, (random() - 0.5) * 0.4) : right.multiplyScalar(1.6 + random() * 0.8).addScaledVector(up, 1.4 + random() * 0.8);
|
|
240
2984
|
throwBody("casing", mesh, from, velocity, radius);
|
|
241
2985
|
};
|
|
242
2986
|
const dropMagazine = (category) => {
|
|
@@ -248,7 +2992,7 @@ function createWeaponFx(options) {
|
|
|
248
2992
|
throwBody("magazine", mesh, from, new THREE.Vector3(0, -0.4, 0), size[1] / 2);
|
|
249
2993
|
audio?.reloadStart(category);
|
|
250
2994
|
};
|
|
251
|
-
const
|
|
2995
|
+
const caliberOf2 = (weapon) => {
|
|
252
2996
|
if (weapon.action.kind !== "firearm") return 9;
|
|
253
2997
|
return arsenal.calibers.get(weapon.action.caliber)?.diameter ?? 9;
|
|
254
2998
|
};
|
|
@@ -260,17 +3004,17 @@ function createWeaponFx(options) {
|
|
|
260
3004
|
const onShot = (weapon) => {
|
|
261
3005
|
kick = 1;
|
|
262
3006
|
const caliber = weapon.action.kind === "firearm" ? arsenal.calibers.get(weapon.action.caliber) : void 0;
|
|
263
|
-
const energy = caliber ?
|
|
264
|
-
audio?.shot(energy, weapon.category);
|
|
3007
|
+
const energy = caliber ? muzzleEnergy2(caliber) * (caliber.pellets ?? 1) : FALLBACK_ENERGY;
|
|
3008
|
+
audio?.shot(energy, weapon.category, { ...caliber ? { caliber } : {}, distance: options.soundDistance?.() ?? 0 });
|
|
265
3009
|
if (CYCLES.has(weapon.id)) audio?.cycle();
|
|
266
|
-
if (EJECTS_ON_SHOT.has(weapon.category) && weapon.id !== "shotgun_double_12") ejectCasing(
|
|
3010
|
+
if (EJECTS_ON_SHOT.has(weapon.category) && weapon.id !== "shotgun_double_12") ejectCasing(caliberOf2(weapon), weapon.category === "shotgun", false);
|
|
267
3011
|
};
|
|
268
3012
|
const onReloadStart = (weapon, tool) => {
|
|
269
3013
|
if (DROPS_MAGAZINE.has(weapon.category)) dropMagazine(weapon.category);
|
|
270
3014
|
else audio?.reloadStart(weapon.category);
|
|
271
3015
|
if (weapon.category === "revolver" || weapon.id === "shotgun_double_12") {
|
|
272
3016
|
const spent = Math.max(1, tool.capacity - tool.ammo);
|
|
273
|
-
for (let i = 0; i < spent; i++) ejectCasing(
|
|
3017
|
+
for (let i = 0; i < spent; i++) ejectCasing(caliberOf2(weapon), weapon.category === "shotgun", true);
|
|
274
3018
|
}
|
|
275
3019
|
};
|
|
276
3020
|
const stepBody = (body, dt) => {
|
|
@@ -300,7 +3044,13 @@ function createWeaponFx(options) {
|
|
|
300
3044
|
body.mesh.rotation.z = 0;
|
|
301
3045
|
};
|
|
302
3046
|
return {
|
|
3047
|
+
unequip() {
|
|
3048
|
+
if (model) hand.remove(model);
|
|
3049
|
+
model = null;
|
|
3050
|
+
inHand = null;
|
|
3051
|
+
},
|
|
303
3052
|
handle(event, tool) {
|
|
3053
|
+
if (event.type === "explosion") return;
|
|
304
3054
|
const weapon = arsenal.weapons.get(event.weapon);
|
|
305
3055
|
if (event.type === "tool") equip(event.weapon);
|
|
306
3056
|
if (!weapon) return;
|
|
@@ -342,14 +3092,1265 @@ function createWeaponFx(options) {
|
|
|
342
3092
|
};
|
|
343
3093
|
}
|
|
344
3094
|
|
|
3095
|
+
// src/fx/viewmodel.ts
|
|
3096
|
+
import { createToolClipPlayer, toolAnimationStep } from "@adatechnology/game-contracts";
|
|
3097
|
+
import { createRng as createRng3, muzzleEnergy as muzzleEnergy3 } from "@adatechnology/game-weapons";
|
|
3098
|
+
|
|
3099
|
+
// src/animation/clip-builder.ts
|
|
3100
|
+
import { STAGE_EVENT } from "@adatechnology/game-contracts";
|
|
3101
|
+
var ROOT = "root";
|
|
3102
|
+
var AXIS_INDEX = { x: 0, y: 1, z: 2 };
|
|
3103
|
+
var EASE_STEPS = 10;
|
|
3104
|
+
var ease = (progress) => progress < 0.5 ? 2 * progress * progress : 1 - (-2 * progress + 2) ** 2 / 2;
|
|
3105
|
+
var move = (part, axis, start, duration, from, to) => ({ part, channel: "position", axis, start, duration, from, to });
|
|
3106
|
+
var turn = (part, axis, start, duration, from, to) => ({ part, channel: "rotation", axis, start, duration, from, to });
|
|
3107
|
+
var cue = (t, type, data) => ({ t, type, ...data ? { data } : {} });
|
|
3108
|
+
var stage = (id, t) => ({ t, type: STAGE_EVENT, data: { stage: id } });
|
|
3109
|
+
var valueAt = (tweens, t, includeInstants) => {
|
|
3110
|
+
let value = 0;
|
|
3111
|
+
for (const tween of tweens) {
|
|
3112
|
+
if (tween.start > t || tween.duration === 0 && (tween.start === t ? !includeInstants : false)) continue;
|
|
3113
|
+
if (tween.duration === 0 || t >= tween.start + tween.duration) value = tween.to;
|
|
3114
|
+
else value = tween.from + (tween.to - tween.from) * ease((t - tween.start) / tween.duration);
|
|
3115
|
+
}
|
|
3116
|
+
return value;
|
|
3117
|
+
};
|
|
3118
|
+
var round = (value) => Math.round(value * 1e6) / 1e6;
|
|
3119
|
+
function composeClip(id, tweens, events = [], minDuration = 0) {
|
|
3120
|
+
const ordered = [...tweens].sort((a, b) => a.start - b.start);
|
|
3121
|
+
const duration = Math.max(minDuration, 1e-3, ...ordered.map((tween) => tween.start + tween.duration), ...events.map((event) => event.t));
|
|
3122
|
+
const times = /* @__PURE__ */ new Set([0, duration]);
|
|
3123
|
+
for (const tween of ordered) {
|
|
3124
|
+
times.add(round(tween.start));
|
|
3125
|
+
if (tween.duration > 0) for (let step = 1; step <= EASE_STEPS; step++) times.add(round(tween.start + tween.duration * step / EASE_STEPS));
|
|
3126
|
+
}
|
|
3127
|
+
const instants = new Set(ordered.filter((tween) => tween.duration === 0).map((tween) => round(tween.start)));
|
|
3128
|
+
const channels = /* @__PURE__ */ new Map();
|
|
3129
|
+
for (const tween of ordered) {
|
|
3130
|
+
const key = `${tween.part}|${tween.channel}|${tween.axis}`;
|
|
3131
|
+
channels.set(key, [...channels.get(key) ?? [], tween]);
|
|
3132
|
+
}
|
|
3133
|
+
const sampleKey = (t, includeInstants) => {
|
|
3134
|
+
const poses = /* @__PURE__ */ new Map();
|
|
3135
|
+
for (const [key, list] of channels) {
|
|
3136
|
+
const [part, channel, axis] = key.split("|");
|
|
3137
|
+
const pose = poses.get(part) ?? { position: [0, 0, 0], rotation: [0, 0, 0] };
|
|
3138
|
+
pose[channel][AXIS_INDEX[axis]] = round(valueAt(list, t, includeInstants));
|
|
3139
|
+
poses.set(part, pose);
|
|
3140
|
+
}
|
|
3141
|
+
const root = poses.get(ROOT);
|
|
3142
|
+
poses.delete(ROOT);
|
|
3143
|
+
const parts = {};
|
|
3144
|
+
for (const [part, pose] of poses) parts[part] = pose;
|
|
3145
|
+
return { t, ...root ? { position: root.position, rotation: root.rotation } : {}, ...poses.size ? { parts } : {} };
|
|
3146
|
+
};
|
|
3147
|
+
const keys = [];
|
|
3148
|
+
for (const t of [...times].sort((a, b) => a - b)) {
|
|
3149
|
+
if (instants.has(t)) keys.push(sampleKey(t, false));
|
|
3150
|
+
keys.push(sampleKey(t, true));
|
|
3151
|
+
}
|
|
3152
|
+
const sorted = [...events].sort((a, b) => a.t - b.t);
|
|
3153
|
+
return { id, duration, keys, ...sorted.length ? { events: sorted } : {} };
|
|
3154
|
+
}
|
|
3155
|
+
var renamed = (clip, id) => ({ ...clip, id });
|
|
3156
|
+
|
|
3157
|
+
// src/animation/magazine-clips.ts
|
|
3158
|
+
var MAGAZINE_SWAP_SECONDS = 0.82;
|
|
3159
|
+
var MAGAZINE_IN_AT = 0.45;
|
|
3160
|
+
function magazineSwap(parts, start) {
|
|
3161
|
+
const direction = parts.magDir || -1;
|
|
3162
|
+
const drop = parts.magDrop || 0.15;
|
|
3163
|
+
return {
|
|
3164
|
+
tweens: [
|
|
3165
|
+
move("mag", "y", start, 0.16, 0, direction * drop * 0.6),
|
|
3166
|
+
move("mag", "y", start + 0.16, 0, 0, direction * drop * 2.2),
|
|
3167
|
+
move("mag", "y", start + MAGAZINE_IN_AT, 0.3, direction * drop * 2.2, 0)
|
|
3168
|
+
],
|
|
3169
|
+
events: [cue(start + 0.16, "drop_magazine", { up: direction > 0 })],
|
|
3170
|
+
end: start + MAGAZINE_SWAP_SECONDS
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
|
|
3174
|
+
// src/animation/context.ts
|
|
3175
|
+
var KICK_BY_CASE = { shell: 1.6, big: 2.4, tiny: 0.3, pistol: 0.7, rifle: 1.1 };
|
|
3176
|
+
var kickOf = (context) => context.parts.kick ?? KICK_BY_CASE[context.caseType];
|
|
3177
|
+
var BLOWBACK = /* @__PURE__ */ new Set(["pistol", "rifle", "semiint", "semi12", "p90", "belt"]);
|
|
3178
|
+
var PUMP_TRAVEL = -0.075;
|
|
3179
|
+
var BOLT_TRAVEL = -0.07;
|
|
3180
|
+
var BOLT_TURN = -1.15;
|
|
3181
|
+
var LEVER_TURN = -0.85;
|
|
3182
|
+
var strokeOf = (parts) => -(parts.stroke || 0.06);
|
|
3183
|
+
var blowbackPart = (context) => context.family === "pistol" ? "slide" : "bolt";
|
|
3184
|
+
|
|
3185
|
+
// src/animation/cycle-clips.ts
|
|
3186
|
+
function boltStroke(start, hold = 0) {
|
|
3187
|
+
return [
|
|
3188
|
+
turn("boltHandle", "x", start, 0.09, 0, BOLT_TURN),
|
|
3189
|
+
move("boltHandle", "x", start + 0.1, 0.12, 0, BOLT_TRAVEL),
|
|
3190
|
+
move("bolt", "x", start + 0.1, 0.12, 0, BOLT_TRAVEL),
|
|
3191
|
+
move("boltHandle", "x", start + 0.26 + hold, 0.12, BOLT_TRAVEL, 0),
|
|
3192
|
+
move("bolt", "x", start + 0.26 + hold, 0.12, BOLT_TRAVEL, 0),
|
|
3193
|
+
turn("boltHandle", "x", start + 0.4 + hold, 0.09, BOLT_TURN, 0)
|
|
3194
|
+
];
|
|
3195
|
+
}
|
|
3196
|
+
function cycleClip(context) {
|
|
3197
|
+
const { parts, family } = context;
|
|
3198
|
+
if (family === "pump" && parts.pump) return composeClip("cycle", [move("pump", "x", 0.22, 0.11, 0, PUMP_TRAVEL), move("pump", "x", 0.36, 0.11, PUMP_TRAVEL, 0)], [cue(0.3, "eject_casing", { down: false })], 0.5);
|
|
3199
|
+
if ((family === "bolt" || family === "boltmag") && parts.bolt && parts.boltHandle) return composeClip("cycle", boltStroke(0.3), [cue(0.5, "eject_casing", { down: false })], 0.82);
|
|
3200
|
+
if (family === "lever" && parts.lever) return composeClip("cycle", [turn("lever", "z", 0.22, 0.13, 0, LEVER_TURN), turn("lever", "z", 0.38, 0.13, LEVER_TURN, 0)], [cue(0.3, "eject_casing", { down: false })], 0.55);
|
|
3201
|
+
return void 0;
|
|
3202
|
+
}
|
|
3203
|
+
var STUCK_AT = { stovepipe: 0.35, double_feed: 0.6, misfire: 0, failure_to_extract: 0 };
|
|
3204
|
+
var CLEAR_HOLD = 0.04;
|
|
3205
|
+
function clearBlowback(context, fault) {
|
|
3206
|
+
const { parts, family } = context;
|
|
3207
|
+
const part = blowbackPart(context);
|
|
3208
|
+
const travel = strokeOf(parts);
|
|
3209
|
+
const stuck = travel * (STUCK_AT[fault] ?? 0);
|
|
3210
|
+
const swap = fault === "double_feed" && parts.mag ? magazineSwap(parts, 0) : void 0;
|
|
3211
|
+
const start = swap?.end ?? 0;
|
|
3212
|
+
const tweens = [...swap?.tweens ?? []];
|
|
3213
|
+
const events = [...swap?.events ?? []];
|
|
3214
|
+
if (family === "belt" && parts.cover) {
|
|
3215
|
+
tweens.push(turn("cover", "z", start, 0.18, 0, 0.9), turn("cover", "z", start + 0.3, 0.15, 0.9, 0));
|
|
3216
|
+
return composeClip(`clear_${fault}`, [...tweens, ...slideOut(part, stuck, travel, start + 0.5)], [...events, cue(start + 0.5 + 0.1, "fly_jam", { jam: fault })]);
|
|
3217
|
+
}
|
|
3218
|
+
return composeClip(`clear_${fault}`, [...tweens, ...slideOut(part, stuck, travel, start)], [...events, cue(start + 0.1, "fly_jam", { jam: fault })]);
|
|
3219
|
+
}
|
|
3220
|
+
var slideOut = (part, from, travel, start) => [move(part, "x", start, 0.12, from, travel), move(part, "x", start + 0.2, 0.07, travel, 0)];
|
|
3221
|
+
function clearManual(context, fault) {
|
|
3222
|
+
const { parts, family } = context;
|
|
3223
|
+
const fly = (t) => [cue(t, "fly_jam", { jam: fault })];
|
|
3224
|
+
if (family === "pump" && parts.pump) return composeClip(`clear_${fault}`, [move("pump", "x", 0, 0.12, 0, PUMP_TRAVEL), move("pump", "x", 0.2, 0.12, PUMP_TRAVEL, 0)], fly(0.12), 0.4);
|
|
3225
|
+
if ((family === "bolt" || family === "boltmag") && parts.bolt && parts.boltHandle) return composeClip(`clear_${fault}`, boltStroke(0, CLEAR_HOLD), fly(0.22), 0.6);
|
|
3226
|
+
if (family === "lever" && parts.lever) return composeClip(`clear_${fault}`, [turn("lever", "z", 0, 0.13, 0, LEVER_TURN), turn("lever", "z", 0.16, 0.13, LEVER_TURN, 0)], fly(0.1), 0.35);
|
|
3227
|
+
return void 0;
|
|
3228
|
+
}
|
|
3229
|
+
function clearClips(context) {
|
|
3230
|
+
return ["misfire", "stovepipe", "double_feed", "failure_to_extract"].map((fault) => {
|
|
3231
|
+
const manual = clearManual(context, fault);
|
|
3232
|
+
if (manual) return manual;
|
|
3233
|
+
const part = blowbackPart(context);
|
|
3234
|
+
if (context.parts[part] && ["pistol", "rifle", "semiint", "semi12", "p90", "belt"].includes(context.family)) return clearBlowback(context, fault);
|
|
3235
|
+
return composeClip(`clear_${fault}`, [], [cue(0, "fly_jam", { jam: fault })], 0.1);
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
// src/animation/reload-clips.ts
|
|
3240
|
+
import { RELOAD_STAGES } from "@adatechnology/game-weapons";
|
|
3241
|
+
var KINDS = ["tactical", "empty", "one_by_one"];
|
|
3242
|
+
var charge = (context, start) => {
|
|
3243
|
+
const travel = strokeOf(context.parts);
|
|
3244
|
+
return [move("bolt", "x", start, 0.12, 0, travel), move("bolt", "x", start + 0.18, 0.06, travel, 0)];
|
|
3245
|
+
};
|
|
3246
|
+
var cycleWithoutEject = (start) => ({ tweens: boltStroke(start), events: [], end: start + 0.55 });
|
|
3247
|
+
function magazineReload(context, kind) {
|
|
3248
|
+
const { parts, family } = context;
|
|
3249
|
+
const swap = magazineSwap(parts, 0);
|
|
3250
|
+
const draft = { tweens: [...swap.tweens], events: [...swap.events], end: swap.end };
|
|
3251
|
+
if (family === "pistol") {
|
|
3252
|
+
if (kind === "empty" && parts.slide) {
|
|
3253
|
+
draft.tweens.push(move("slide", "x", 0, 0, 0, strokeOf(parts)), move("slide", "x", swap.end, 0.05, strokeOf(parts), 0));
|
|
3254
|
+
draft.end += 0.12;
|
|
3255
|
+
}
|
|
3256
|
+
} else if (family === "boltmag") {
|
|
3257
|
+
const stroke = cycleWithoutEject(swap.end);
|
|
3258
|
+
draft.tweens.push(...stroke.tweens);
|
|
3259
|
+
draft.end = stroke.end;
|
|
3260
|
+
} else if (kind === "empty" && parts.bolt) {
|
|
3261
|
+
draft.tweens.push(...charge(context, swap.end));
|
|
3262
|
+
draft.end += 0.35;
|
|
3263
|
+
}
|
|
3264
|
+
return draft;
|
|
3265
|
+
}
|
|
3266
|
+
function revolverReload(kind) {
|
|
3267
|
+
const events = [cue(0.32, "eject_casing", { from: "chambers", down: false })];
|
|
3268
|
+
if (kind !== "one_by_one") events.push(cue(0.7, "move_in", { from: "chambers" }));
|
|
3269
|
+
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 };
|
|
3270
|
+
}
|
|
3271
|
+
var breakReload = () => ({
|
|
3272
|
+
tweens: [turn("barrels", "z", 0, 0.2, 0, -0.5), turn("barrels", "z", 1, 0.15, -0.5, 0)],
|
|
3273
|
+
events: [cue(0.25, "eject_casing", { from: "breech", down: false }), cue(0.65, "move_in", { from: "breech" })],
|
|
3274
|
+
end: 1.2
|
|
3275
|
+
});
|
|
3276
|
+
var beltReload = (context) => ({
|
|
3277
|
+
tweens: [turn("cover", "z", 0, 0.2, 0, 0.9), move("belt", "z", 0.2, 0.25, 0, -0.3), move("belt", "z", 0.55, 0.3, -0.3, 0), turn("cover", "z", 0.9, 0.18, 0.9, 0), ...charge(context, 1.15)],
|
|
3278
|
+
events: [],
|
|
3279
|
+
end: 1.55
|
|
3280
|
+
});
|
|
3281
|
+
var UNIT = 1;
|
|
3282
|
+
function tubeReload(context, kind) {
|
|
3283
|
+
const { parts, family } = context;
|
|
3284
|
+
const draft = { tweens: [], events: [], end: UNIT };
|
|
3285
|
+
if (kind !== "empty") return draft;
|
|
3286
|
+
if (family === "pump" && parts.pump) draft.tweens.push(move("pump", "x", 0.88, 0.05, 0, PUMP_TRAVEL), move("pump", "x", 0.94, 0.05, PUMP_TRAVEL, 0));
|
|
3287
|
+
else if (family === "lever" && parts.lever) draft.tweens.push(turn("lever", "z", 0.87, 0.06, 0, LEVER_TURN), turn("lever", "z", 0.94, 0.06, LEVER_TURN, 0));
|
|
3288
|
+
else if (parts.bolt) draft.tweens.push(move("bolt", "x", 0.88, 0.07, 0, strokeOf(parts)), move("bolt", "x", 0.95, 0.04, strokeOf(parts), 0));
|
|
3289
|
+
return draft;
|
|
3290
|
+
}
|
|
3291
|
+
function boltReload(context) {
|
|
3292
|
+
if (!context.parts.boltHandle || !context.parts.bolt) return { tweens: [], events: [], end: UNIT };
|
|
3293
|
+
return {
|
|
3294
|
+
tweens: [
|
|
3295
|
+
turn("boltHandle", "x", 0, 0.05, 0, BOLT_TURN),
|
|
3296
|
+
move("boltHandle", "x", 0.05, 0.07, 0, BOLT_TRAVEL),
|
|
3297
|
+
move("bolt", "x", 0.05, 0.07, 0, BOLT_TRAVEL),
|
|
3298
|
+
move("boltHandle", "x", 0.86, 0.07, BOLT_TRAVEL, 0),
|
|
3299
|
+
move("bolt", "x", 0.86, 0.07, BOLT_TRAVEL, 0),
|
|
3300
|
+
turn("boltHandle", "x", 0.93, 0.05, BOLT_TURN, 0)
|
|
3301
|
+
],
|
|
3302
|
+
events: [],
|
|
3303
|
+
end: UNIT
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
function internalMagazineReload(context) {
|
|
3307
|
+
const travel = strokeOf(context.parts);
|
|
3308
|
+
return { tweens: context.parts.bolt ? [move("bolt", "x", 0, 0.04, 0, travel), move("bolt", "x", 0.89, 0.04, travel, 0)] : [], events: [], end: UNIT };
|
|
3309
|
+
}
|
|
3310
|
+
function heavyReload(context) {
|
|
3311
|
+
const { parts, family } = context;
|
|
3312
|
+
if (family === "flame") return { tweens: parts.gauge ? [turn("gauge", "z", 0, 1, -2.4, 0)] : [], events: [], end: 1.1 };
|
|
3313
|
+
if (parts.disposable) {
|
|
3314
|
+
return {
|
|
3315
|
+
tweens: [move(ROOT, "y", 0.6, 0, 0, -0.6), move(ROOT, "y", 0.6, 0.5, -0.6, 0)],
|
|
3316
|
+
events: [cue(0, "drop_tube"), cue(0.6, "seat_tube")],
|
|
3317
|
+
end: 1.15
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
if (parts.round) {
|
|
3321
|
+
return {
|
|
3322
|
+
tweens: [move("round", "x", 0, 0.55, 0.45, 0), move("round", "y", 0, 0.55, 0.12, 0)],
|
|
3323
|
+
events: [cue(0, "show_round")],
|
|
3324
|
+
end: 0.6
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
return {
|
|
3328
|
+
tweens: [turn("breech_0", "y", 0, 0.25, 0, -1.6), turn("breech_0", "y", 0.76, 0.22, -1.6, 0)],
|
|
3329
|
+
events: [cue(0.3, "breech_round")],
|
|
3330
|
+
end: 1.05
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
var isTube = (context) => ["pump", "semi12", "lever"].includes(context.family);
|
|
3334
|
+
function draftFor(context, kind) {
|
|
3335
|
+
const { parts, family } = context;
|
|
3336
|
+
if (family === "rocket" || family === "flame") return heavyReload(context);
|
|
3337
|
+
if (parts.mag) return magazineReload(context, kind);
|
|
3338
|
+
if (family === "revolver" && parts.crane) return revolverReload(kind);
|
|
3339
|
+
if (family === "break" && parts.barrels) return breakReload();
|
|
3340
|
+
if (family === "belt" && parts.cover && parts.belt) return beltReload(context);
|
|
3341
|
+
if (isTube(context)) return tubeReload(context, kind);
|
|
3342
|
+
if (family === "bolt") return boltReload(context);
|
|
3343
|
+
if (family === "semiint") return internalMagazineReload(context);
|
|
3344
|
+
return void 0;
|
|
3345
|
+
}
|
|
3346
|
+
var markers = (context, kind, duration) => context.reloadFamily ? RELOAD_STAGES[context.reloadFamily][kind].map((entry) => stage(entry.stage, entry.at * duration)) : [];
|
|
3347
|
+
function reloadClips(context) {
|
|
3348
|
+
const clips = [];
|
|
3349
|
+
let first;
|
|
3350
|
+
for (const kind of KINDS) {
|
|
3351
|
+
const draft = draftFor(context, kind);
|
|
3352
|
+
if (!draft) return [];
|
|
3353
|
+
const clip = composeClip(kind === "tactical" ? "reload" : `reload_${kind}`, draft.tweens, [...draft.events, ...markers(context, kind, draft.end)], draft.end);
|
|
3354
|
+
if (!first) first = clip;
|
|
3355
|
+
else if (JSON.stringify({ ...clip, id: first.id }) === JSON.stringify(first)) continue;
|
|
3356
|
+
clips.push(clip);
|
|
3357
|
+
}
|
|
3358
|
+
return clips.length > 0 ? clips : first ? [renamed(first, "reload")] : [];
|
|
3359
|
+
}
|
|
3360
|
+
function feedClip(context) {
|
|
3361
|
+
const { family } = context;
|
|
3362
|
+
if (family === "revolver") return composeClip("feed", [], [cue(0, "move_in", { from: "chambers", next: true })], 0.24);
|
|
3363
|
+
if (family === "bolt" || family === "semiint") return composeClip("feed", [], [cue(0, "move_in", { from: "topLoad" })], 0.24);
|
|
3364
|
+
if (isTube(context)) return composeClip("feed", [], [cue(0, "move_in", { from: "loadPort" })], 0.24);
|
|
3365
|
+
return void 0;
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
// src/animation/shot-clips.ts
|
|
3369
|
+
var FAULT_SLIDE_BACK = { stovepipe: 0.35, double_feed: 0.6 };
|
|
3370
|
+
var pullTrigger = (context) => context.parts.trigger ? [turn("trigger", "z", 0, 0.04, 0, 0.35), turn("trigger", "z", 0.06, 0.08, 0.35, 0)] : [];
|
|
3371
|
+
var recoil = (context) => {
|
|
3372
|
+
const kick = kickOf(context);
|
|
3373
|
+
return [
|
|
3374
|
+
move(ROOT, "x", 0, 0.03, 0, -0.014 * kick),
|
|
3375
|
+
turn(ROOT, "z", 0, 0.03, 0, 0.045 * kick),
|
|
3376
|
+
move(ROOT, "x", 0.03, 0.18, -0.014 * kick, 0),
|
|
3377
|
+
turn(ROOT, "z", 0.03, 0.18, 0.045 * kick, 0)
|
|
3378
|
+
];
|
|
3379
|
+
};
|
|
3380
|
+
var hammerFall = (context) => context.parts.hammer && context.family !== "revolver" ? [turn("hammer", "z", 0, 0.02, 0, -0.5), turn("hammer", "z", 0.05, 0.06, -0.5, 0)] : [];
|
|
3381
|
+
function shotClip(id, context, locked) {
|
|
3382
|
+
const { parts, family } = context;
|
|
3383
|
+
const tweens = [...pullTrigger(context), ...recoil(context), ...hammerFall(context)];
|
|
3384
|
+
const events = [cue(0, "muzzle_flash")];
|
|
3385
|
+
if (BLOWBACK.has(family)) {
|
|
3386
|
+
const part = blowbackPart(context);
|
|
3387
|
+
const travel = strokeOf(parts);
|
|
3388
|
+
if (parts[part]) {
|
|
3389
|
+
tweens.push(move(part, "x", 0, 0.025, 0, travel));
|
|
3390
|
+
if (!locked) tweens.push(move(part, "x", 0.025, 0.045, travel, 0));
|
|
3391
|
+
}
|
|
3392
|
+
events.push(cue(0.012, "eject_casing", { down: family === "p90" || family === "belt" }));
|
|
3393
|
+
} else if (family === "revolver" && parts.cylinder && parts.hammer) {
|
|
3394
|
+
tweens.push(turn("cylinder", "x", 0, 0.05, 0, Math.PI / 3), turn("hammer", "z", 0, 0.05, 0, 0.5), turn("hammer", "z", 0.05, 0.03, 0.5, -0.5), turn("hammer", "z", 0.12, 0.08, -0.5, 0));
|
|
3395
|
+
}
|
|
3396
|
+
return composeClip(id, tweens, events);
|
|
3397
|
+
}
|
|
3398
|
+
function shotClips(context) {
|
|
3399
|
+
const clips = [shotClip("shot", context, false)];
|
|
3400
|
+
if (context.family === "pistol") clips.push(shotClip("shot_last", context, true));
|
|
3401
|
+
return clips;
|
|
3402
|
+
}
|
|
3403
|
+
var dudTweens = (context) => [...pullTrigger(context), ...context.parts.hammer ? [turn("hammer", "z", 0, 0.03, 0, -0.5)] : []];
|
|
3404
|
+
function misfireClip(context) {
|
|
3405
|
+
const tweens = dudTweens(context);
|
|
3406
|
+
if (context.family === "revolver" && context.parts.cylinder) tweens.push(turn("cylinder", "x", 0, 0.07, 0, Math.PI / 3));
|
|
3407
|
+
return composeClip("misfire", tweens);
|
|
3408
|
+
}
|
|
3409
|
+
var emptyClip = (context) => composeClip("empty", pullTrigger(context));
|
|
3410
|
+
function faultClips(context) {
|
|
3411
|
+
const { parts, family } = context;
|
|
3412
|
+
const misfire = composeClip("fault_misfire", dudTweens(context));
|
|
3413
|
+
const part = blowbackPart(context);
|
|
3414
|
+
if (!BLOWBACK.has(family) || !parts[part]) return [misfire];
|
|
3415
|
+
const travel = strokeOf(parts);
|
|
3416
|
+
const stopped = Object.entries(FAULT_SLIDE_BACK).map(
|
|
3417
|
+
([fault, back]) => composeClip(`fault_${fault}`, [move(part, "x", 0, 0.025, 0, travel), move(part, "x", 0.025, 0.04, travel, travel * back)], fault === "stovepipe" ? [cue(0.03, "stuck_case")] : [], 0.25)
|
|
3418
|
+
);
|
|
3419
|
+
return [misfire, ...stopped];
|
|
3420
|
+
}
|
|
3421
|
+
function safetyClips(context) {
|
|
3422
|
+
if (!context.parts.safety) return [];
|
|
3423
|
+
return [composeClip("safety_on", [turn("safety", "z", 0, 0.12, 0, -0.6)]), composeClip("safety_off", [turn("safety", "z", 0, 0.12, -0.6, 0)]), composeClip("mode", [turn("safety", "z", 0, 0.07, 0, 0.4), turn("safety", "z", 0.07, 0.07, 0.4, 0)])];
|
|
3424
|
+
}
|
|
3425
|
+
var drawClip = () => composeClip("draw", [move(ROOT, "y", 0, 0.35, -0.1, 0), turn(ROOT, "z", 0, 0.35, -0.6, 0)]);
|
|
3426
|
+
|
|
3427
|
+
// src/animation/clip-set.ts
|
|
3428
|
+
function buildToolClips(context) {
|
|
3429
|
+
const clips = [
|
|
3430
|
+
...shotClips(context),
|
|
3431
|
+
misfireClip(context),
|
|
3432
|
+
emptyClip(context),
|
|
3433
|
+
cycleClip(context),
|
|
3434
|
+
...reloadClips(context),
|
|
3435
|
+
feedClip(context),
|
|
3436
|
+
...faultClips(context),
|
|
3437
|
+
...clearClips(context),
|
|
3438
|
+
...safetyClips(context),
|
|
3439
|
+
drawClip()
|
|
3440
|
+
];
|
|
3441
|
+
const set = {};
|
|
3442
|
+
for (const clip of clips) if (clip) set[clip.id] = clip;
|
|
3443
|
+
return set;
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
// src/audio/host-events.ts
|
|
3447
|
+
var weaponEventOf = (event) => {
|
|
3448
|
+
switch (event.type) {
|
|
3449
|
+
case "misfire":
|
|
3450
|
+
return { type: "misfire", at: 0 };
|
|
3451
|
+
case "stage":
|
|
3452
|
+
return { type: "reload_stage", stage: event.stage, at: 0 };
|
|
3453
|
+
case "empty":
|
|
3454
|
+
case "feed":
|
|
3455
|
+
return { type: event.type, at: 0 };
|
|
3456
|
+
case "cycle":
|
|
3457
|
+
return { type: "cycle", phase: event.phase, at: 0 };
|
|
3458
|
+
case "mode":
|
|
3459
|
+
return { type: "mode", mode: event.mode, at: 0 };
|
|
3460
|
+
case "fault":
|
|
3461
|
+
return { type: "fault", fault: event.fault, at: 0 };
|
|
3462
|
+
case "clear":
|
|
3463
|
+
return { type: "clear", fault: event.fault, phase: event.phase, at: 0 };
|
|
3464
|
+
case "safety":
|
|
3465
|
+
return { type: "safety", on: event.on, at: 0 };
|
|
3466
|
+
default:
|
|
3467
|
+
return void 0;
|
|
3468
|
+
}
|
|
3469
|
+
};
|
|
3470
|
+
|
|
3471
|
+
// src/models/condition.ts
|
|
3472
|
+
var substitutions = (kit, condition) => {
|
|
3473
|
+
const { M } = kit;
|
|
3474
|
+
if (condition === "rusty")
|
|
3475
|
+
return /* @__PURE__ */ new Map([
|
|
3476
|
+
[M.metal, M.rust],
|
|
3477
|
+
[M.dark, M.rustD],
|
|
3478
|
+
[M.steel, M.rust],
|
|
3479
|
+
[M.steelL, M.rust]
|
|
3480
|
+
]);
|
|
3481
|
+
if (condition === "worn")
|
|
3482
|
+
return /* @__PURE__ */ new Map([
|
|
3483
|
+
[M.metal, M.worn],
|
|
3484
|
+
[M.dark, M.worn]
|
|
3485
|
+
]);
|
|
3486
|
+
return null;
|
|
3487
|
+
};
|
|
3488
|
+
function applyCondition(kit, model, condition) {
|
|
3489
|
+
const replacement = substitutions(kit, condition);
|
|
3490
|
+
const glowing = new Set(model.parts.glow);
|
|
3491
|
+
const paint = condition === "rusty" ? kit.M.rust.color : condition === "worn" ? kit.M.worn.color : void 0;
|
|
3492
|
+
model.root.traverse((object) => {
|
|
3493
|
+
var _a, _b;
|
|
3494
|
+
const mesh = object;
|
|
3495
|
+
if (!mesh.isMesh) return;
|
|
3496
|
+
(_a = mesh.userData).baseMaterial ?? (_a.baseMaterial = mesh.material);
|
|
3497
|
+
const base = mesh.userData.baseMaterial;
|
|
3498
|
+
if (glowing.has(mesh)) {
|
|
3499
|
+
const material = mesh.material;
|
|
3500
|
+
(_b = mesh.userData).baseColor ?? (_b.baseColor = material.color.clone());
|
|
3501
|
+
material.color.copy(paint ?? mesh.userData.baseColor);
|
|
3502
|
+
return;
|
|
3503
|
+
}
|
|
3504
|
+
mesh.material = replacement?.get(base) ?? base;
|
|
3505
|
+
});
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
// src/models/cartridge.ts
|
|
3509
|
+
var BOTTLENECK_RATIO = 0.8;
|
|
3510
|
+
function cartridgeShape(caliber, dims) {
|
|
3511
|
+
if (caliber.caseType === "shell" || caliber.rim === "shell") return "shell";
|
|
3512
|
+
if (caliber.rim === "rimfire") return "rim";
|
|
3513
|
+
const isRimmed = caliber.rim === "rimmed";
|
|
3514
|
+
const isBottleneck = dims.projectileDiameter / dims.caseDiameter < BOTTLENECK_RATIO;
|
|
3515
|
+
if (isBottleneck) return isRimmed ? "rr" : "r";
|
|
3516
|
+
return isRimmed ? "rimp" : "p";
|
|
3517
|
+
}
|
|
3518
|
+
function buildCartridge(kit, dims, shape) {
|
|
3519
|
+
const { M, cylinderY } = kit;
|
|
3520
|
+
const group = kit.group();
|
|
3521
|
+
const radius = dims.caseDiameter / 2;
|
|
3522
|
+
const bulletRadius = dims.projectileDiameter / 2;
|
|
3523
|
+
const caseLength = dims.caseLength;
|
|
3524
|
+
if (shape === "shell") {
|
|
3525
|
+
cylinderY(group, radius, caseLength * 0.8, M.shell, 0, caseLength * 0.6, 0, 14);
|
|
3526
|
+
cylinderY(group, radius * 1.03, caseLength * 0.22, M.brass, 0, caseLength * 0.11, 0, 14);
|
|
3527
|
+
cylinderY(group, radius * 1.08, 1.2, M.brass, 0, 0.6, 0, 14);
|
|
3528
|
+
return group;
|
|
3529
|
+
}
|
|
3530
|
+
if (shape === "rim" || shape === "rimp" || shape === "rr") cylinderY(group, radius * 1.15, 1.2, M.brass, 0, 0.6, 0, 14);
|
|
3531
|
+
const extra = dims.overallLength - caseLength;
|
|
3532
|
+
if (shape === "r" || shape === "rr") {
|
|
3533
|
+
const bodyHeight = caseLength * 0.78;
|
|
3534
|
+
const shoulder = caseLength * 0.08;
|
|
3535
|
+
const neck = caseLength - bodyHeight - shoulder;
|
|
3536
|
+
const neckRadius = bulletRadius + 0.35;
|
|
3537
|
+
cylinderY(group, radius, bodyHeight, M.brass, 0, bodyHeight / 2, 0, 14);
|
|
3538
|
+
cylinderY(group, neckRadius, shoulder, M.brass, 0, bodyHeight + shoulder / 2, 0, 14, radius);
|
|
3539
|
+
cylinderY(group, neckRadius, neck, M.brass, 0, bodyHeight + shoulder + neck / 2, 0, 14);
|
|
3540
|
+
cylinderY(group, bulletRadius, extra * 0.3, M.copper, 0, caseLength + extra * 0.15, 0, 14);
|
|
3541
|
+
cylinderY(group, bulletRadius, extra * 0.7, M.copper, 0, caseLength + extra * 0.3 + extra * 0.35, 0, 14, bulletRadius * 0.12);
|
|
3542
|
+
return group;
|
|
3543
|
+
}
|
|
3544
|
+
const bullet = shape === "rim" || shape === "rimp" ? M.lead : M.copper;
|
|
3545
|
+
cylinderY(group, radius, caseLength, M.brass, 0, caseLength / 2, 0, 14);
|
|
3546
|
+
cylinderY(group, bulletRadius, extra * 0.45, bullet, 0, caseLength + extra * 0.225, 0, 14);
|
|
3547
|
+
cylinderY(group, bulletRadius, extra * 0.55, bullet, 0, caseLength + extra * 0.45 + extra * 0.275, 0, 14, bulletRadius * 0.45);
|
|
3548
|
+
return group;
|
|
3549
|
+
}
|
|
3550
|
+
function buildCasing(kit, dims, shape) {
|
|
3551
|
+
const { M, cylinderX } = kit;
|
|
3552
|
+
const group = kit.group();
|
|
3553
|
+
const radius = dims.caseDiameter / 2e3;
|
|
3554
|
+
const length = dims.caseLength / 1e3;
|
|
3555
|
+
if (shape === "shell") {
|
|
3556
|
+
const scale = length / 0.07;
|
|
3557
|
+
cylinderX(group, 0.0105 * scale, 0.05 * scale, M.shell, 8e-3 * scale, 0, 0, 14);
|
|
3558
|
+
cylinderX(group, 0.011 * scale, 0.016 * scale, M.brass, -0.025 * scale, 0, 0, 14);
|
|
3559
|
+
return { group, radius: 0.0105 * scale, length };
|
|
3560
|
+
}
|
|
3561
|
+
if (shape === "r" || shape === "rr") {
|
|
3562
|
+
cylinderX(group, radius, length * 0.75, M.brass, -length * 0.12, 0, 0, 12);
|
|
3563
|
+
cylinderX(group, radius * 0.62, length * 0.25, M.brass, length * 0.37, 0, 0, 12, radius * 0.55);
|
|
3564
|
+
} else cylinderX(group, radius, length, M.brass, 0, 0, 0, 12);
|
|
3565
|
+
return { group, radius, length };
|
|
3566
|
+
}
|
|
3567
|
+
function buildRound(kit, dims, shape) {
|
|
3568
|
+
const group = kit.group();
|
|
3569
|
+
const cartridge = buildCartridge(kit, dims, shape);
|
|
3570
|
+
cartridge.scale.setScalar(1e-3);
|
|
3571
|
+
cartridge.rotation.z = -Math.PI / 2;
|
|
3572
|
+
group.add(cartridge);
|
|
3573
|
+
return { group, radius: dims.caseDiameter / 2e3 };
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
// src/fx/effects.ts
|
|
3577
|
+
var PI9 = Math.PI;
|
|
3578
|
+
var FLASH_BY_CASE = { shell: 1.6, big: 2.6, tiny: 0.5, pistol: 0.8, rifle: 1.2 };
|
|
3579
|
+
var FLASH_SECONDS = 0.055;
|
|
3580
|
+
function createEffects(kit, scene, particles, random) {
|
|
3581
|
+
const { THREE, M } = kit;
|
|
3582
|
+
const light = new THREE.PointLight(16756832, 0, 3, 2);
|
|
3583
|
+
scene.add(light);
|
|
3584
|
+
let flash = null;
|
|
3585
|
+
const buildFlash = (size) => {
|
|
3586
|
+
const group = new THREE.Group();
|
|
3587
|
+
const cone = new THREE.Mesh(new THREE.ConeGeometry(0.013 * size, 0.085 * size, 10, 1, true), M.flashA);
|
|
3588
|
+
cone.rotation.z = -PI9 / 2;
|
|
3589
|
+
cone.position.x = 0.042 * size;
|
|
3590
|
+
group.add(cone);
|
|
3591
|
+
for (let i = 0; i < 4; i++) {
|
|
3592
|
+
const angle = i * PI9 / 2 + random() * 0.6;
|
|
3593
|
+
const spike = new THREE.Mesh(new THREE.ConeGeometry(5e-3 * size, 0.04 * size, 6), M.flashA);
|
|
3594
|
+
spike.position.set(0.012 * size, Math.cos(angle) * 0.016 * size, Math.sin(angle) * 0.016 * size);
|
|
3595
|
+
spike.rotation.set(angle, 0, 0);
|
|
3596
|
+
spike.rotateZ(-PI9 / 2);
|
|
3597
|
+
group.add(spike);
|
|
3598
|
+
}
|
|
3599
|
+
const core = new THREE.Mesh(new THREE.SphereGeometry(0.016 * size, 12, 8), M.flashB);
|
|
3600
|
+
core.position.x = 8e-3 * size;
|
|
3601
|
+
group.add(core);
|
|
3602
|
+
return group;
|
|
3603
|
+
};
|
|
3604
|
+
const endFlash = () => {
|
|
3605
|
+
if (!flash) return;
|
|
3606
|
+
flash.group.removeFromParent();
|
|
3607
|
+
flash.group.traverse((child) => {
|
|
3608
|
+
const mesh = child;
|
|
3609
|
+
if (mesh.isMesh) mesh.geometry.dispose();
|
|
3610
|
+
});
|
|
3611
|
+
flash = null;
|
|
3612
|
+
light.intensity = 0;
|
|
3613
|
+
};
|
|
3614
|
+
const effects = {
|
|
3615
|
+
muzzleFlash(position, orientation, size) {
|
|
3616
|
+
endFlash();
|
|
3617
|
+
const group = buildFlash(size);
|
|
3618
|
+
group.position.copy(position);
|
|
3619
|
+
group.quaternion.copy(orientation);
|
|
3620
|
+
group.rotateX(random() * PI9);
|
|
3621
|
+
scene.add(group);
|
|
3622
|
+
flash = { group, age: 0 };
|
|
3623
|
+
light.position.copy(position);
|
|
3624
|
+
light.intensity = 4 * size;
|
|
3625
|
+
light.distance = 2 * size;
|
|
3626
|
+
for (let i = 0; i < 6; i++) {
|
|
3627
|
+
const velocity = new THREE.Vector3(0.15 + random() * 0.35, 0.03 + random() * 0.12, (random() - 0.5) * 0.14).applyQuaternion(orientation);
|
|
3628
|
+
effects.smoke(position, velocity, 0.013 * size, 1.3 + random() * 0.6, 0.45);
|
|
3629
|
+
}
|
|
3630
|
+
},
|
|
3631
|
+
smoke(position, velocity, size, life, opacity) {
|
|
3632
|
+
const puff = new THREE.Mesh(new THREE.SphereGeometry(size, 10, 8), M.smokeP.clone());
|
|
3633
|
+
puff.position.copy(position);
|
|
3634
|
+
particles.spawn(puff, velocity, { gravity: -0.08, life, fadeFrom: opacity, grow: 2.1, drag: 2.2, radius: -1, ownsGeometry: true });
|
|
3635
|
+
},
|
|
3636
|
+
update(dt) {
|
|
3637
|
+
if (!flash) return;
|
|
3638
|
+
flash.age += dt;
|
|
3639
|
+
flash.group.scale.setScalar(0.6 + Math.min(1, flash.age / FLASH_SECONDS) * 0.9);
|
|
3640
|
+
if (flash.age >= FLASH_SECONDS) endFlash();
|
|
3641
|
+
},
|
|
3642
|
+
dispose() {
|
|
3643
|
+
endFlash();
|
|
3644
|
+
light.removeFromParent();
|
|
3645
|
+
light.dispose();
|
|
3646
|
+
}
|
|
3647
|
+
};
|
|
3648
|
+
return effects;
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
// src/fx/heavy.ts
|
|
3652
|
+
var PI10 = Math.PI;
|
|
3653
|
+
var flameMaterials = (kit) => {
|
|
3654
|
+
const { THREE } = kit;
|
|
3655
|
+
return [
|
|
3656
|
+
new THREE.MeshBasicMaterial({ color: 16742946, transparent: true, opacity: 0.9, depthWrite: false }),
|
|
3657
|
+
new THREE.MeshBasicMaterial({ color: 16760896, transparent: true, opacity: 0.9, depthWrite: false }),
|
|
3658
|
+
new THREE.MeshBasicMaterial({ color: 13121050, transparent: true, opacity: 0.8, depthWrite: false }),
|
|
3659
|
+
new THREE.MeshBasicMaterial({ color: 16773296, transparent: true, opacity: 0.9, blending: THREE.AdditiveBlending, depthWrite: false })
|
|
3660
|
+
];
|
|
3661
|
+
};
|
|
3662
|
+
var spread = (context, x, y, z) => new context.kit.THREE.Vector3((context.random() - 0.5) * x, (context.random() - 0.5) * y, (context.random() - 0.5) * z);
|
|
3663
|
+
function createBits(context) {
|
|
3664
|
+
const { kit, particles, random } = context;
|
|
3665
|
+
const { THREE } = kit;
|
|
3666
|
+
const palette = flameMaterials(kit);
|
|
3667
|
+
return {
|
|
3668
|
+
flame(position, velocity, size, life) {
|
|
3669
|
+
const bit = new THREE.Mesh(new THREE.IcosahedronGeometry(size, 1), palette[Math.floor(random() * 4)].clone());
|
|
3670
|
+
bit.position.copy(position);
|
|
3671
|
+
bit.rotation.set(random() * 3, random() * 3, 0);
|
|
3672
|
+
particles.spawn(bit, velocity, { gravity: -1.4, life, fadeFrom: 0.9, grow: 2.2, drag: 1.4, radius: -1, spin: new THREE.Vector3(3, 4, 2), ownsGeometry: true });
|
|
3673
|
+
},
|
|
3674
|
+
smoke(position, velocity, size, life, opacity) {
|
|
3675
|
+
context.effects.smoke(position, velocity, size, life, opacity);
|
|
3676
|
+
},
|
|
3677
|
+
dispose: () => palette.forEach((material) => material.dispose())
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3680
|
+
function launchRocket(context) {
|
|
3681
|
+
const { kit, scene, parts, random } = context;
|
|
3682
|
+
const { THREE } = kit;
|
|
3683
|
+
const bits = createBits(context);
|
|
3684
|
+
const forward = context.forward();
|
|
3685
|
+
const muzzle = context.positionOf(parts.muzzle);
|
|
3686
|
+
const rear = context.positionOf(parts.rear);
|
|
3687
|
+
for (let i = 0; i < 26; i++) bits.smoke(rear.clone(), forward.clone().multiplyScalar(-(1.5 + random() * 3)).add(spread(context, 1.4, 0.8, 1.4)), 0.03 + random() * 0.03, 1.8 + random() * 1.4, 0.5);
|
|
3688
|
+
for (let i = 0; i < 14; i++) bits.flame(rear.clone(), forward.clone().multiplyScalar(-(2 + random() * 2)).add(spread(context, 1, 0.6, 1)), 0.02, 0.25);
|
|
3689
|
+
const rocket = parts.makeRound ? parts.makeRound() : buildFlyingRocket(kit, parts.launcherType);
|
|
3690
|
+
if (parts.round) parts.round.visible = false;
|
|
3691
|
+
rocket.quaternion.copy(context.orientation());
|
|
3692
|
+
rocket.position.copy(muzzle);
|
|
3693
|
+
scene.add(rocket);
|
|
3694
|
+
const exhaust = new THREE.Mesh(new THREE.ConeGeometry(0.018, 0.12, 8, 1, true), new THREE.MeshBasicMaterial({ color: 16760896, transparent: true, opacity: 0.9, depthWrite: false }));
|
|
3695
|
+
exhaust.rotation.z = PI10 / 2;
|
|
3696
|
+
exhaust.position.x = -0.08;
|
|
3697
|
+
rocket.add(exhaust);
|
|
3698
|
+
const light = new THREE.PointLight(16751168, 3, 2.5);
|
|
3699
|
+
rocket.add(light);
|
|
3700
|
+
context.every(
|
|
3701
|
+
1.6,
|
|
3702
|
+
(progress, dt) => {
|
|
3703
|
+
const flight = progress * 1.6;
|
|
3704
|
+
const distance = 1.2 * flight + (flight > 0.25 ? 9 * (flight - 0.25) ** 2 : 0);
|
|
3705
|
+
rocket.position.copy(muzzle).addScaledVector(forward, distance);
|
|
3706
|
+
rocket.rotation.x += 0.25 * dt * 60;
|
|
3707
|
+
exhaust.scale.set(1, 0.6 + random() * 0.8, 1);
|
|
3708
|
+
if (random() < 0.9) bits.smoke(rocket.position.clone().addScaledVector(forward, -0.12), spread(context, 0.1, 0, 0.1).setY(0.05), 0.02 + random() * 0.015, 2.2, 0.5);
|
|
3709
|
+
},
|
|
3710
|
+
() => {
|
|
3711
|
+
rocket.removeFromParent();
|
|
3712
|
+
exhaust.geometry.dispose();
|
|
3713
|
+
exhaust.material.dispose();
|
|
3714
|
+
light.dispose();
|
|
3715
|
+
bits.dispose();
|
|
3716
|
+
}
|
|
3717
|
+
);
|
|
3718
|
+
}
|
|
3719
|
+
function flameJet(context, dt, scale) {
|
|
3720
|
+
const { kit, particles, random, parts } = context;
|
|
3721
|
+
const { THREE } = kit;
|
|
3722
|
+
const bits = createBits(context);
|
|
3723
|
+
const forward = context.forward();
|
|
3724
|
+
const muzzle = context.positionOf(parts.muzzle);
|
|
3725
|
+
const count = Math.floor(dt * 420 * scale);
|
|
3726
|
+
for (let i = 0; i < count; i++) {
|
|
3727
|
+
const speed = (2.2 + random() * 1.1) * scale;
|
|
3728
|
+
bits.flame(muzzle.clone().addScaledVector(forward, random() * speed * Math.min(dt, 0.3)), forward.clone().multiplyScalar(speed).add(new THREE.Vector3((random() - 0.5) * 0.35, (random() - 0.3) * 0.3, (random() - 0.5) * 0.35)), 0.02 + random() * 0.022, 0.4 + random() * 0.35);
|
|
3729
|
+
}
|
|
3730
|
+
if (random() < 0.5) bits.smoke(muzzle.clone().addScaledVector(forward, 1.1 * scale + random() * 0.4), new THREE.Vector3(forward.x * 0.6, 0.35, forward.z * 0.6), 0.03, 1.6, 0.4);
|
|
3731
|
+
if (random() < 0.18) {
|
|
3732
|
+
const droplet = new THREE.Mesh(new THREE.SphereGeometry(6e-3, 6, 4), new THREE.MeshBasicMaterial({ color: 16742946 }));
|
|
3733
|
+
droplet.position.copy(muzzle).addScaledVector(forward, 0.3 + random() * 0.5);
|
|
3734
|
+
particles.spawn(droplet, forward.clone().multiplyScalar(1.2 * scale).add(new THREE.Vector3(0, 0.2, 0)), { life: 3, radius: 3e-3, ownsGeometry: true, onGround: (position) => groundFire(context, bits, position) });
|
|
3735
|
+
}
|
|
3736
|
+
bits.dispose();
|
|
3737
|
+
}
|
|
3738
|
+
function groundFire(context, bits, position) {
|
|
3739
|
+
const { kit, scene, random } = context;
|
|
3740
|
+
const { THREE } = kit;
|
|
3741
|
+
const fire = new THREE.Group();
|
|
3742
|
+
fire.position.set(position.x, context.groundAt(position.x, position.z) + 5e-3, position.z);
|
|
3743
|
+
scene.add(fire);
|
|
3744
|
+
const materials = flameMaterials(kit);
|
|
3745
|
+
const flames = [0, 1, 2].map((index) => {
|
|
3746
|
+
const flame = new THREE.Mesh(new THREE.ConeGeometry(0.02, 0.07, 6), materials[index % 2].clone());
|
|
3747
|
+
flame.position.set((random() - 0.5) * 0.04, 0.035, (random() - 0.5) * 0.04);
|
|
3748
|
+
fire.add(flame);
|
|
3749
|
+
return flame;
|
|
3750
|
+
});
|
|
3751
|
+
let clock = 0;
|
|
3752
|
+
context.every(
|
|
3753
|
+
4,
|
|
3754
|
+
(progress, dt) => {
|
|
3755
|
+
clock += dt;
|
|
3756
|
+
flames.forEach((flame, index) => flame.scale.set(1 - progress * 0.7, (0.6 + Math.sin(clock * 20 + index * 2) * 0.35) * (1 - progress), 1 - progress * 0.7));
|
|
3757
|
+
if (random() < 0.08) bits.smoke(fire.position.clone().add(new THREE.Vector3(0, 0.08, 0)), new THREE.Vector3(0, 0.15, 0), 0.015, 1.2, 0.25);
|
|
3758
|
+
},
|
|
3759
|
+
() => {
|
|
3760
|
+
fire.removeFromParent();
|
|
3761
|
+
flames.forEach((flame) => {
|
|
3762
|
+
flame.geometry.dispose();
|
|
3763
|
+
flame.material.dispose();
|
|
3764
|
+
});
|
|
3765
|
+
materials.forEach((material) => material.dispose());
|
|
3766
|
+
}
|
|
3767
|
+
);
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
// src/fx/particles.ts
|
|
3771
|
+
var MAX_PARTICLES = 900;
|
|
3772
|
+
var FLOOR_FRICTION = 0.45;
|
|
3773
|
+
var disposeOwned = (object) => {
|
|
3774
|
+
object.traverse((child) => {
|
|
3775
|
+
const mesh = child;
|
|
3776
|
+
if (mesh.isMesh) mesh.geometry.dispose();
|
|
3777
|
+
});
|
|
3778
|
+
};
|
|
3779
|
+
function createParticles(THREE, scene, groundAt) {
|
|
3780
|
+
let list = [];
|
|
3781
|
+
const remove = (particle) => {
|
|
3782
|
+
particle.object.removeFromParent();
|
|
3783
|
+
if (particle.ownsGeometry) disposeOwned(particle.object);
|
|
3784
|
+
if (particle.fadeFrom !== void 0) particle.object.material.dispose();
|
|
3785
|
+
list = list.filter((other) => other !== particle);
|
|
3786
|
+
};
|
|
3787
|
+
const api = {
|
|
3788
|
+
spawn(object, velocity, options = {}) {
|
|
3789
|
+
scene.add(object);
|
|
3790
|
+
list.push({
|
|
3791
|
+
object,
|
|
3792
|
+
velocity,
|
|
3793
|
+
spin: options.spin ?? new THREE.Vector3(),
|
|
3794
|
+
age: 0,
|
|
3795
|
+
resting: false,
|
|
3796
|
+
life: options.life ?? 2,
|
|
3797
|
+
gravity: options.gravity ?? 9.8,
|
|
3798
|
+
bounce: options.bounce ?? 0.3,
|
|
3799
|
+
drag: options.drag ?? 0,
|
|
3800
|
+
radius: options.radius ?? 3e-3,
|
|
3801
|
+
grow: options.grow ?? 0,
|
|
3802
|
+
kind: options.kind ?? "other",
|
|
3803
|
+
fadeFrom: options.fadeFrom,
|
|
3804
|
+
onRest: options.onRest,
|
|
3805
|
+
onGround: options.onGround,
|
|
3806
|
+
ownsGeometry: options.ownsGeometry ?? false,
|
|
3807
|
+
bounces: 0
|
|
3808
|
+
});
|
|
3809
|
+
const oldest = list[0];
|
|
3810
|
+
if (list.length > MAX_PARTICLES && oldest) remove(oldest);
|
|
3811
|
+
},
|
|
3812
|
+
update(dt) {
|
|
3813
|
+
for (const particle of [...list]) {
|
|
3814
|
+
particle.age += dt;
|
|
3815
|
+
if (particle.age > particle.life) {
|
|
3816
|
+
remove(particle);
|
|
3817
|
+
continue;
|
|
3818
|
+
}
|
|
3819
|
+
const { object } = particle;
|
|
3820
|
+
if (particle.fadeFrom !== void 0) object.material.opacity = Math.max(0, particle.fadeFrom * (1 - particle.age / particle.life));
|
|
3821
|
+
if (particle.resting) continue;
|
|
3822
|
+
particle.velocity.y -= particle.gravity * dt;
|
|
3823
|
+
if (particle.drag) particle.velocity.multiplyScalar(Math.max(0, 1 - particle.drag * dt));
|
|
3824
|
+
object.position.addScaledVector(particle.velocity, dt);
|
|
3825
|
+
object.rotation.x += particle.spin.x * dt;
|
|
3826
|
+
object.rotation.y += particle.spin.y * dt;
|
|
3827
|
+
object.rotation.z += particle.spin.z * dt;
|
|
3828
|
+
if (particle.grow) object.scale.multiplyScalar(1 + particle.grow * dt);
|
|
3829
|
+
if (particle.radius < 0) continue;
|
|
3830
|
+
const floor = groundAt(object.position.x, object.position.z) + particle.radius;
|
|
3831
|
+
if (object.position.y >= floor || particle.velocity.y >= 0) continue;
|
|
3832
|
+
if (particle.onGround) {
|
|
3833
|
+
particle.onGround(object.position.clone());
|
|
3834
|
+
remove(particle);
|
|
3835
|
+
continue;
|
|
3836
|
+
}
|
|
3837
|
+
object.position.y = floor;
|
|
3838
|
+
particle.velocity.y *= -particle.bounce;
|
|
3839
|
+
particle.velocity.x *= FLOOR_FRICTION;
|
|
3840
|
+
particle.velocity.z *= FLOOR_FRICTION;
|
|
3841
|
+
particle.spin.multiplyScalar(FLOOR_FRICTION);
|
|
3842
|
+
if (particle.bounces++ < 4 && Math.abs(particle.velocity.y) >= 0.18) api.onBounce?.(particle.kind);
|
|
3843
|
+
if (Math.abs(particle.velocity.y) < 0.18) {
|
|
3844
|
+
particle.resting = true;
|
|
3845
|
+
particle.onRest?.(object);
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
},
|
|
3849
|
+
count: (kind) => list.filter((particle) => particle.kind === kind).length,
|
|
3850
|
+
dispose() {
|
|
3851
|
+
for (const particle of [...list]) remove(particle);
|
|
3852
|
+
}
|
|
3853
|
+
};
|
|
3854
|
+
return api;
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3857
|
+
// src/fx/rig.ts
|
|
3858
|
+
function createModelRig(model) {
|
|
3859
|
+
const registry = partRegistry(model.parts);
|
|
3860
|
+
const rest = /* @__PURE__ */ new Map();
|
|
3861
|
+
for (const part of [...registry.values(), model.root]) rest.set(part, { position: part.position.clone(), rotation: part.rotation.clone() });
|
|
3862
|
+
const apply = (object, pose) => {
|
|
3863
|
+
const base = rest.get(object);
|
|
3864
|
+
if (!base) return;
|
|
3865
|
+
const [dx, dy, dz] = pose.position ?? [0, 0, 0];
|
|
3866
|
+
const [rx, ry, rz] = pose.rotation ?? [0, 0, 0];
|
|
3867
|
+
object.position.set(base.position.x + dx, base.position.y + dy, base.position.z + dz);
|
|
3868
|
+
object.rotation.set(base.rotation.x + rx, base.rotation.y + ry, base.rotation.z + rz);
|
|
3869
|
+
};
|
|
3870
|
+
return {
|
|
3871
|
+
setPartPose(partId, pose) {
|
|
3872
|
+
const part = registry.get(partId);
|
|
3873
|
+
if (part) apply(part, pose);
|
|
3874
|
+
},
|
|
3875
|
+
setRootPose: (pose) => apply(model.root, pose)
|
|
3876
|
+
};
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
// src/fx/session.ts
|
|
3880
|
+
var worldPositionOf = (session, part, out) => {
|
|
3881
|
+
session.model.root.updateWorldMatrix(true, true);
|
|
3882
|
+
return part.getWorldPosition(out);
|
|
3883
|
+
};
|
|
3884
|
+
var toWorld = (session, vector, scaled = false) => {
|
|
3885
|
+
const quaternion = session.model.root.getWorldQuaternion(new session.kit.THREE.Quaternion());
|
|
3886
|
+
const world = vector.clone().applyQuaternion(quaternion);
|
|
3887
|
+
return scaled ? world.multiplyScalar(session.holderScale) : world;
|
|
3888
|
+
};
|
|
3889
|
+
|
|
3890
|
+
// src/fx/viewmodel-events.ts
|
|
3891
|
+
var PI11 = Math.PI;
|
|
3892
|
+
var restFlat = (object) => {
|
|
3893
|
+
object.rotation.x = 0;
|
|
3894
|
+
object.rotation.z = 0;
|
|
3895
|
+
};
|
|
3896
|
+
function createClipEventHandler(world, session) {
|
|
3897
|
+
const { kit, particles, effects, random } = world;
|
|
3898
|
+
const { THREE } = kit;
|
|
3899
|
+
const { parts, root } = session.model;
|
|
3900
|
+
const vector = (x, y, z) => new THREE.Vector3(x, y, z);
|
|
3901
|
+
const positionOf = (part) => worldPositionOf(session, part, new THREE.Vector3());
|
|
3902
|
+
const reach = Math.max(0.55, Math.min(1.2, session.lengthMetres));
|
|
3903
|
+
const throwCase = (mark, velocity, spin, tilt = 0) => {
|
|
3904
|
+
if (!session.cartridge || !world.layers.casings) return;
|
|
3905
|
+
const casing = buildCasing(kit, session.cartridge.dims, session.cartridge.shape);
|
|
3906
|
+
casing.group.position.copy(positionOf(mark));
|
|
3907
|
+
casing.group.rotation.set(0, random() * 0.6 - 0.3, tilt);
|
|
3908
|
+
particles.spawn(casing.group, velocity, { kind: "casing", life: 25, radius: casing.radius, bounce: 0.35, spin, onRest: restFlat, ownsGeometry: true });
|
|
3909
|
+
};
|
|
3910
|
+
const ejectFromPort = (down) => {
|
|
3911
|
+
if (!parts.eject) return;
|
|
3912
|
+
const local = down ? vector((random() - 0.5) * 0.3, -0.4, (random() - 0.5) * 0.3) : vector(-0.2 - random() * 0.4, (0.9 + random() * 0.5) * reach, (1 + random() * 0.6) * reach);
|
|
3913
|
+
throwCase(parts.eject, toWorld(session, local), vector(random() * 30, random() * 30, random() * 30));
|
|
3914
|
+
};
|
|
3915
|
+
const ejectAll = (marks, spent) => {
|
|
3916
|
+
marks?.forEach((mark, index) => {
|
|
3917
|
+
const { velocity, spin, tilt } = spent(index);
|
|
3918
|
+
throwCase(mark, toWorld(session, velocity), spin, tilt);
|
|
3919
|
+
});
|
|
3920
|
+
};
|
|
3921
|
+
const moveIn = (from, to, tilt) => {
|
|
3922
|
+
if (!session.cartridge) return;
|
|
3923
|
+
const { group } = buildRound(kit, session.cartridge.dims, session.cartridge.shape);
|
|
3924
|
+
const start = root.worldToLocal(from.clone());
|
|
3925
|
+
const end = root.worldToLocal(to.clone());
|
|
3926
|
+
group.rotation.z = tilt;
|
|
3927
|
+
group.position.copy(start);
|
|
3928
|
+
root.add(group);
|
|
3929
|
+
world.schedule(0, 0.24, (eased) => group.position.lerpVectors(start, end, eased), () => {
|
|
3930
|
+
group.removeFromParent();
|
|
3931
|
+
group.traverse((child) => {
|
|
3932
|
+
const mesh = child;
|
|
3933
|
+
if (mesh.isMesh) mesh.geometry.dispose();
|
|
3934
|
+
});
|
|
3935
|
+
});
|
|
3936
|
+
};
|
|
3937
|
+
const offsetFrom = (point, x, y, z) => point.clone().add(toWorld(session, vector(x, y, z), true));
|
|
3938
|
+
const feedRound = (from, next) => {
|
|
3939
|
+
if (from === "loadPort" && parts.loadPort) {
|
|
3940
|
+
const port = positionOf(parts.loadPort);
|
|
3941
|
+
moveIn(offsetFrom(port, -0.03, -0.08, 0.07), port, 0.25);
|
|
3942
|
+
} else if (from === "topLoad" && parts.topLoad) {
|
|
3943
|
+
const point = positionOf(parts.topLoad);
|
|
3944
|
+
moveIn(offsetFrom(point, 0, 0.07, 0), point, 0);
|
|
3945
|
+
} else if (from === "chambers" && parts.chambers) {
|
|
3946
|
+
const marks = next ? [parts.chambers[session.nextChamber++ % parts.chambers.length]] : parts.chambers;
|
|
3947
|
+
for (const mark of marks) {
|
|
3948
|
+
if (!mark) continue;
|
|
3949
|
+
const point = positionOf(mark);
|
|
3950
|
+
moveIn(offsetFrom(point, -0.07, 0, 0), offsetFrom(point, 6e-3, 0, 0), 0);
|
|
3951
|
+
}
|
|
3952
|
+
} else if (from === "breech" && parts.breech) {
|
|
3953
|
+
for (const mark of parts.breech) {
|
|
3954
|
+
const point = positionOf(mark);
|
|
3955
|
+
moveIn(offsetFrom(point, -0.09, 0.05, 0), offsetFrom(point, 0.02, -0.01, 0), -0.5);
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
};
|
|
3959
|
+
const dropMagazine = (up) => {
|
|
3960
|
+
if (!parts.mag || !world.layers.magazines) return;
|
|
3961
|
+
root.updateWorldMatrix(true, true);
|
|
3962
|
+
const copy = parts.mag.clone(true);
|
|
3963
|
+
parts.mag.matrixWorld.decompose(copy.position, copy.quaternion, copy.scale);
|
|
3964
|
+
particles.spawn(copy, toWorld(session, vector(up ? -0.25 : 0.05, up ? 0.9 : -0.3, 0.18)), { kind: "magazine", life: 25, radius: 0.014, bounce: 0.15, spin: vector(1.5, 0.5, 2.5), onRest: (object) => object.rotation.set(0, object.rotation.y, PI11 / 2) });
|
|
3965
|
+
};
|
|
3966
|
+
const stuckCase = () => {
|
|
3967
|
+
if (!session.cartridge || !parts.eject) return;
|
|
3968
|
+
const casing = buildCasing(kit, session.cartridge.dims, session.cartridge.shape);
|
|
3969
|
+
const at = root.worldToLocal(offsetFrom(positionOf(parts.eject), 0, casing.length * 0.35, 0));
|
|
3970
|
+
casing.group.position.copy(at);
|
|
3971
|
+
casing.group.rotation.set(0, 0, PI11 / 2 - 0.25);
|
|
3972
|
+
root.add(casing.group);
|
|
3973
|
+
session.stuckCase = casing.group;
|
|
3974
|
+
};
|
|
3975
|
+
const flyJam = (jam) => {
|
|
3976
|
+
const stuck = session.stuckCase;
|
|
3977
|
+
session.stuckCase = null;
|
|
3978
|
+
if (stuck) {
|
|
3979
|
+
root.updateWorldMatrix(true, true);
|
|
3980
|
+
stuck.updateWorldMatrix(true, false);
|
|
3981
|
+
stuck.matrixWorld.decompose(stuck.position, stuck.quaternion, stuck.scale);
|
|
3982
|
+
particles.spawn(stuck, toWorld(session, vector(-0.2, 1.2, 1.3)), { kind: "casing", life: 25, radius: 6e-3, bounce: 0.3, spin: vector(9, 14, 7), onRest: restFlat, ownsGeometry: true });
|
|
3983
|
+
} else if (jam === "misfire" && session.cartridge && parts.eject) {
|
|
3984
|
+
const round2 = buildRound(kit, session.cartridge.dims, session.cartridge.shape);
|
|
3985
|
+
round2.group.position.copy(positionOf(parts.eject));
|
|
3986
|
+
particles.spawn(round2.group, toWorld(session, vector(-0.2, 1.1, 1.2)), { kind: "round", life: 25, radius: round2.radius, bounce: 0.3, spin: vector(8, 12, 6), onRest: restFlat, ownsGeometry: true });
|
|
3987
|
+
} else ejectFromPort(false);
|
|
3988
|
+
};
|
|
3989
|
+
return (event) => {
|
|
3990
|
+
const data = event.data ?? {};
|
|
3991
|
+
switch (event.type) {
|
|
3992
|
+
case "muzzle_flash":
|
|
3993
|
+
if (parts.muzzle && world.layers.muzzleFlash) effects.muzzleFlash(positionOf(parts.muzzle), root.getWorldQuaternion(new THREE.Quaternion()), session.def.action.kind === "launcher" ? 2.2 : FLASH_BY_CASE[session.caseType]);
|
|
3994
|
+
break;
|
|
3995
|
+
case "eject_casing":
|
|
3996
|
+
if (data.from === "chambers") ejectAll(parts.chambers, () => ({ velocity: vector(-0.5 - random() * 0.3, -0.1 + random() * 0.3, (random() - 0.5) * 0.25), spin: vector(10, 20, 5), tilt: 0 }));
|
|
3997
|
+
else if (data.from === "breech") ejectAll(parts.breech, () => ({ velocity: vector(-1, 0.9, (random() - 0.5) * 0.2), spin: vector(3, 8, 12), tilt: -0.5 }));
|
|
3998
|
+
else ejectFromPort(data.down === true);
|
|
3999
|
+
break;
|
|
4000
|
+
case "drop_magazine":
|
|
4001
|
+
dropMagazine(data.up === true);
|
|
4002
|
+
break;
|
|
4003
|
+
case "move_in":
|
|
4004
|
+
feedRound(String(data.from), data.next === true);
|
|
4005
|
+
break;
|
|
4006
|
+
case "stuck_case":
|
|
4007
|
+
stuckCase();
|
|
4008
|
+
break;
|
|
4009
|
+
case "fly_jam":
|
|
4010
|
+
flyJam(String(data.jam));
|
|
4011
|
+
break;
|
|
4012
|
+
case "show_round":
|
|
4013
|
+
if (parts.round) parts.round.visible = true;
|
|
4014
|
+
break;
|
|
4015
|
+
case "drop_tube":
|
|
4016
|
+
dropTube();
|
|
4017
|
+
break;
|
|
4018
|
+
case "seat_tube":
|
|
4019
|
+
root.visible = true;
|
|
4020
|
+
break;
|
|
4021
|
+
case "breech_round":
|
|
4022
|
+
breechRound();
|
|
4023
|
+
break;
|
|
4024
|
+
default:
|
|
4025
|
+
break;
|
|
4026
|
+
}
|
|
4027
|
+
};
|
|
4028
|
+
function dropTube() {
|
|
4029
|
+
root.updateWorldMatrix(true, true);
|
|
4030
|
+
const old = root.clone(true);
|
|
4031
|
+
root.matrixWorld.decompose(old.position, old.quaternion, old.scale);
|
|
4032
|
+
root.visible = false;
|
|
4033
|
+
particles.spawn(old, toWorld(session, vector(0.2, 0.4, 0.6)), { life: 8, radius: 0.05, bounce: 0.2, spin: vector(0, 0.8, 2.5) });
|
|
4034
|
+
}
|
|
4035
|
+
function breechRound() {
|
|
4036
|
+
const make = parts.makeRound;
|
|
4037
|
+
if (!make) return;
|
|
4038
|
+
const shell = make();
|
|
4039
|
+
session.model.inner.add(shell);
|
|
4040
|
+
shell.position.set(-0.25, 0, 0);
|
|
4041
|
+
world.schedule(0.02, 0.4, (eased) => shell.position.x = -0.25 + 0.6 * eased, () => {
|
|
4042
|
+
shell.removeFromParent();
|
|
4043
|
+
});
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
// src/fx/viewmodel.ts
|
|
4048
|
+
var PI12 = Math.PI;
|
|
4049
|
+
var HAND_OFFSET2 = [0.2, -0.2, -0.42];
|
|
4050
|
+
var HEAT_DISPLAY_MAX_FALLBACK = 120;
|
|
4051
|
+
var LAUNCH_VOLUME = 0.6;
|
|
4052
|
+
var caliberOf = (arsenal, def) => def.action.kind === "firearm" ? arsenal.calibers.get(def.action.caliber) : void 0;
|
|
4053
|
+
function createViewmodel(options) {
|
|
4054
|
+
const { THREE, scene, camera, arsenal, groundAt, audio } = options;
|
|
4055
|
+
const kit = modelKit(THREE);
|
|
4056
|
+
const random = createRng3(options.seed ?? 1);
|
|
4057
|
+
const layers = options.layers ?? createLayers();
|
|
4058
|
+
const distance = () => options.soundDistance?.() ?? 0;
|
|
4059
|
+
const particles = createParticles(THREE, scene, groundAt);
|
|
4060
|
+
particles.onBounce = (kind) => kind === "casing" ? audio?.clink() : kind === "magazine" ? audio?.thud() : void 0;
|
|
4061
|
+
const effects = createEffects(kit, scene, particles, random);
|
|
4062
|
+
const hand = new THREE.Group();
|
|
4063
|
+
hand.name = "weapon-viewmodel";
|
|
4064
|
+
scene.add(hand);
|
|
4065
|
+
const holder = new THREE.Group();
|
|
4066
|
+
holder.quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), PI12 / 2);
|
|
4067
|
+
hand.add(holder);
|
|
4068
|
+
let tasks = [];
|
|
4069
|
+
let session = null;
|
|
4070
|
+
let player = null;
|
|
4071
|
+
let condition = options.condition ?? "new";
|
|
4072
|
+
let lastAmmo = 0;
|
|
4073
|
+
let smokeDebt = 0;
|
|
4074
|
+
const world = {
|
|
4075
|
+
kit,
|
|
4076
|
+
scene,
|
|
4077
|
+
particles,
|
|
4078
|
+
effects,
|
|
4079
|
+
random,
|
|
4080
|
+
audio,
|
|
4081
|
+
layers,
|
|
4082
|
+
groundAt,
|
|
4083
|
+
schedule: (delay, seconds, step, done) => void tasks.push({ delay, seconds, elapsed: 0, step: (progress) => step(ease(progress)), done })
|
|
4084
|
+
};
|
|
4085
|
+
const heatMax = arsenal.rules.heat.displayMax || HEAT_DISPLAY_MAX_FALLBACK;
|
|
4086
|
+
const release = () => {
|
|
4087
|
+
if (!session) return;
|
|
4088
|
+
session.model.root.removeFromParent();
|
|
4089
|
+
player = null;
|
|
4090
|
+
session = null;
|
|
4091
|
+
tasks = [];
|
|
4092
|
+
};
|
|
4093
|
+
const heavy = (current) => ({
|
|
4094
|
+
kit,
|
|
4095
|
+
scene,
|
|
4096
|
+
particles,
|
|
4097
|
+
effects,
|
|
4098
|
+
random,
|
|
4099
|
+
parts: current.model.parts,
|
|
4100
|
+
forward: () => toWorld(current, new THREE.Vector3(1, 0, 0)).normalize(),
|
|
4101
|
+
positionOf: (part) => worldPositionOf(current, part, new THREE.Vector3()),
|
|
4102
|
+
orientation: () => current.model.root.getWorldQuaternion(new THREE.Quaternion()),
|
|
4103
|
+
every: (seconds, step, done) => void tasks.push({ delay: 0, seconds, elapsed: 0, step, done }),
|
|
4104
|
+
groundAt
|
|
4105
|
+
});
|
|
4106
|
+
const equip = (weapon) => {
|
|
4107
|
+
const def = arsenal.weapons.get(weapon);
|
|
4108
|
+
const model = def ? buildWeaponModelById(THREE, weapon) : void 0;
|
|
4109
|
+
if (!def || !model) return false;
|
|
4110
|
+
release();
|
|
4111
|
+
const length = new THREE.Box3().setFromObject(model.root).getSize(new THREE.Vector3()).x;
|
|
4112
|
+
const scale = length > 0.8 ? 0.55 : length > 0.4 ? 0.75 : 1;
|
|
4113
|
+
holder.scale.setScalar(scale);
|
|
4114
|
+
holder.position.set(0, 0, Math.min(0.25, 0.25 * length * scale));
|
|
4115
|
+
holder.add(model.root);
|
|
4116
|
+
const caliber = caliberOf(arsenal, def);
|
|
4117
|
+
const dims = caliber?.cartridge;
|
|
4118
|
+
session = {
|
|
4119
|
+
id: weapon,
|
|
4120
|
+
def,
|
|
4121
|
+
model,
|
|
4122
|
+
holderScale: scale,
|
|
4123
|
+
lengthMetres: length,
|
|
4124
|
+
caseType: caliber?.caseType ?? "rifle",
|
|
4125
|
+
kit,
|
|
4126
|
+
cartridge: caliber && dims ? { dims, shape: cartridgeShape(caliber, dims) } : null,
|
|
4127
|
+
stuckCase: null,
|
|
4128
|
+
nextChamber: 0
|
|
4129
|
+
};
|
|
4130
|
+
const action = def.action;
|
|
4131
|
+
const reloadFamily = action.kind === "firearm" || action.kind === "flame" || action.kind === "launcher" ? action.family : void 0;
|
|
4132
|
+
const rpm = action.kind === "firearm" ? action.rpm : 0;
|
|
4133
|
+
player = createToolClipPlayer({
|
|
4134
|
+
clips: buildToolClips({ parts: model.parts, family: model.parts.family, caseType: session.caseType, reloadFamily }),
|
|
4135
|
+
rig: createModelRig(model),
|
|
4136
|
+
onEvent: createClipEventHandler(world, session),
|
|
4137
|
+
repeatInterval: () => rpm > 0 ? 60 / rpm : 0.1
|
|
4138
|
+
});
|
|
4139
|
+
applyCondition(kit, model, condition);
|
|
4140
|
+
lastAmmo = options.tool?.().ammo ?? 0;
|
|
4141
|
+
return true;
|
|
4142
|
+
};
|
|
4143
|
+
const place = () => {
|
|
4144
|
+
const [x, y, z] = HAND_OFFSET2;
|
|
4145
|
+
hand.position.set(x, y, z).applyQuaternion(camera.quaternion).add(camera.position);
|
|
4146
|
+
hand.quaternion.copy(camera.quaternion);
|
|
4147
|
+
hand.updateMatrixWorld(true);
|
|
4148
|
+
};
|
|
4149
|
+
const glow = (tool) => {
|
|
4150
|
+
if (!session) return;
|
|
4151
|
+
const heat = (tool?.heat ?? 0) * heatMax;
|
|
4152
|
+
const level = Math.max(0, Math.min(1, (heat - 50) / 50));
|
|
4153
|
+
for (const mesh of session.model.parts.glow) {
|
|
4154
|
+
const material = mesh.material;
|
|
4155
|
+
material.emissive.setRGB(level, 0.2 * level * level, 0.02 * level * level);
|
|
4156
|
+
material.emissiveIntensity = 2.2 * level;
|
|
4157
|
+
}
|
|
4158
|
+
};
|
|
4159
|
+
const handleShot = (current) => {
|
|
4160
|
+
const action = current.def.action;
|
|
4161
|
+
if (action.kind === "launcher") {
|
|
4162
|
+
launchRocket(heavy(current));
|
|
4163
|
+
audio?.play("explosion", LAUNCH_VOLUME, distance());
|
|
4164
|
+
}
|
|
4165
|
+
if (action.kind !== "firearm") return;
|
|
4166
|
+
const caliber = caliberOf(arsenal, current.def);
|
|
4167
|
+
if (!caliber) return;
|
|
4168
|
+
audio?.shot(muzzleEnergy3(caliber) * (caliber.pellets ?? 1), current.def.category, { caliber, distance: distance() });
|
|
4169
|
+
};
|
|
4170
|
+
return {
|
|
4171
|
+
equip,
|
|
4172
|
+
unequip: release,
|
|
4173
|
+
handle(event, tool) {
|
|
4174
|
+
if (event.type === "explosion") return;
|
|
4175
|
+
if (event.type === "tool") equip(event.weapon);
|
|
4176
|
+
if (!session || !player || event.weapon !== session.id) return;
|
|
4177
|
+
const current = session;
|
|
4178
|
+
if (event.type === "shot") handleShot(current);
|
|
4179
|
+
else if (event.type === "reload" && event.phase === "start") current.nextChamber = 0;
|
|
4180
|
+
else if (event.type === "clear" && event.phase === "end") current.stuckCase = null;
|
|
4181
|
+
const mechanical = weaponEventOf(event);
|
|
4182
|
+
if (mechanical) audio?.event(mechanical);
|
|
4183
|
+
const step = toolAnimationStep(event);
|
|
4184
|
+
if (!step) return;
|
|
4185
|
+
const last = step.type === "play" && step.clip === "shot" && current.model.parts.family === "pistol" && tool.ammo === 0;
|
|
4186
|
+
player.apply(last ? { ...step, variant: "last" } : step);
|
|
4187
|
+
},
|
|
4188
|
+
update(dt) {
|
|
4189
|
+
if (session) {
|
|
4190
|
+
place();
|
|
4191
|
+
player?.update(dt);
|
|
4192
|
+
const tool = options.tool?.();
|
|
4193
|
+
glow(tool);
|
|
4194
|
+
if (tool && session.def.action.kind === "flame") {
|
|
4195
|
+
if (tool.ammo < lastAmmo) flameJet(heavy(session), dt, session.model.parts.launcherType === "flame" ? 1 : 0.6);
|
|
4196
|
+
lastAmmo = tool.ammo;
|
|
4197
|
+
}
|
|
4198
|
+
if (tool && tool.heat !== void 0 && tool.heat * heatMax > 10 && session.model.parts.muzzle) {
|
|
4199
|
+
smokeDebt += dt * tool.heat * heatMax * 0.1;
|
|
4200
|
+
while (smokeDebt > 1) {
|
|
4201
|
+
smokeDebt -= 1;
|
|
4202
|
+
const k = 0.5 + tool.heat * heatMax / 60;
|
|
4203
|
+
effects.smoke(worldPositionOf(session, session.model.parts.muzzle, new THREE.Vector3()), new THREE.Vector3((random() - 0.5) * 0.03, 0.07 + random() * 0.08, (random() - 0.5) * 0.03), 7e-3 * k, 1.6 + random(), Math.min(0.42, 0.14 + k * 0.1));
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
for (const task of [...tasks]) {
|
|
4208
|
+
task.elapsed += dt;
|
|
4209
|
+
if (task.elapsed < task.delay) continue;
|
|
4210
|
+
const progress = task.seconds > 0 ? Math.min(1, (task.elapsed - task.delay) / task.seconds) : 1;
|
|
4211
|
+
task.step(progress, dt);
|
|
4212
|
+
if (progress >= 1) {
|
|
4213
|
+
task.done?.();
|
|
4214
|
+
tasks = tasks.filter((other) => other !== task);
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
effects.update(dt);
|
|
4218
|
+
particles.update(dt);
|
|
4219
|
+
},
|
|
4220
|
+
setCondition(next) {
|
|
4221
|
+
condition = next;
|
|
4222
|
+
if (session) applyCondition(kit, session.model, condition);
|
|
4223
|
+
},
|
|
4224
|
+
get inHand() {
|
|
4225
|
+
return session?.id ?? null;
|
|
4226
|
+
},
|
|
4227
|
+
get casings() {
|
|
4228
|
+
return particles.count("casing");
|
|
4229
|
+
},
|
|
4230
|
+
get magazines() {
|
|
4231
|
+
return particles.count("magazine");
|
|
4232
|
+
},
|
|
4233
|
+
dispose() {
|
|
4234
|
+
release();
|
|
4235
|
+
particles.dispose();
|
|
4236
|
+
effects.dispose();
|
|
4237
|
+
hand.removeFromParent();
|
|
4238
|
+
}
|
|
4239
|
+
};
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
// src/fx.ts
|
|
4243
|
+
function createWeaponFx(options) {
|
|
4244
|
+
const layers = options.layers ?? createLayers();
|
|
4245
|
+
const box2 = createBoxFx({ ...options, layers });
|
|
4246
|
+
const model = createViewmodel({
|
|
4247
|
+
layers,
|
|
4248
|
+
...options.soundDistance ? { soundDistance: options.soundDistance } : {},
|
|
4249
|
+
THREE: options.THREE,
|
|
4250
|
+
scene: options.scene,
|
|
4251
|
+
camera: options.camera,
|
|
4252
|
+
arsenal: options.arsenal,
|
|
4253
|
+
groundAt: options.groundAt,
|
|
4254
|
+
audio: options.audio ?? null,
|
|
4255
|
+
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
4256
|
+
...options.tool ? { tool: options.tool } : {},
|
|
4257
|
+
...options.condition ? { condition: options.condition } : {}
|
|
4258
|
+
});
|
|
4259
|
+
return {
|
|
4260
|
+
handle(event, tool) {
|
|
4261
|
+
if (event.type === "explosion") return;
|
|
4262
|
+
if (event.type === "tool") {
|
|
4263
|
+
if (hasWeaponModel(event.weapon)) box2.unequip();
|
|
4264
|
+
else model.unequip();
|
|
4265
|
+
}
|
|
4266
|
+
if (model.inHand !== null || event.type === "tool" && hasWeaponModel(event.weapon)) model.handle(event, tool);
|
|
4267
|
+
else box2.handle(event, tool);
|
|
4268
|
+
},
|
|
4269
|
+
update(dt) {
|
|
4270
|
+
box2.update(dt);
|
|
4271
|
+
model.update(dt);
|
|
4272
|
+
},
|
|
4273
|
+
setCondition: (condition) => model.setCondition(condition),
|
|
4274
|
+
layers,
|
|
4275
|
+
get inHand() {
|
|
4276
|
+
return model.inHand ?? box2.inHand;
|
|
4277
|
+
},
|
|
4278
|
+
get casings() {
|
|
4279
|
+
return box2.casings + model.casings;
|
|
4280
|
+
},
|
|
4281
|
+
get magazines() {
|
|
4282
|
+
return box2.magazines + model.magazines;
|
|
4283
|
+
},
|
|
4284
|
+
dispose() {
|
|
4285
|
+
box2.dispose();
|
|
4286
|
+
model.dispose();
|
|
4287
|
+
}
|
|
4288
|
+
};
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4291
|
+
// src/world-explosives.ts
|
|
4292
|
+
var WORLD_EXPLOSIVE_PREFIX = "world_";
|
|
4293
|
+
var SHAPES2 = {
|
|
4294
|
+
fuel_barrel: { shape: { kind: "cylinder", radius: 0.3, height: 0.9 }, position: [0, 0.45, 0], color: 3104666 },
|
|
4295
|
+
propane_p13: { shape: { kind: "cylinder", radius: 0.15, height: 0.48 }, position: [0, 0.24, 0], color: 13620184 },
|
|
4296
|
+
car_fuel_tank: { shape: { kind: "box", size: [0.9, 0.25, 0.5] }, position: [0, 0.4, 0], color: 4935509 }
|
|
4297
|
+
};
|
|
4298
|
+
var WORLD_EXPLOSIVE_IDS = Object.keys(SHAPES2);
|
|
4299
|
+
var SHELL = { thickness: 1e-3, fill: 0.03 };
|
|
4300
|
+
var explosivePart = (def, shape) => ({
|
|
4301
|
+
id: "tank",
|
|
4302
|
+
material: "steel",
|
|
4303
|
+
shape: shape.shape,
|
|
4304
|
+
position: shape.position,
|
|
4305
|
+
...SHELL,
|
|
4306
|
+
explodes: { energy: def.energy, radius: def.injuryRadius, ...def.chain ? { chain: { ...def.chain } } : {} }
|
|
4307
|
+
});
|
|
4308
|
+
var meshOf = (THREE, shape) => {
|
|
4309
|
+
const geometry = shape.shape.kind === "cylinder" ? new THREE.CylinderGeometry(shape.shape.radius, shape.shape.radius, shape.shape.height, 16) : shape.shape.kind === "box" ? new THREE.BoxGeometry(...shape.shape.size) : new THREE.SphereGeometry(0.2);
|
|
4310
|
+
const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color: shape.color, roughness: 0.55, metalness: 0.5 }));
|
|
4311
|
+
mesh.name = "tank";
|
|
4312
|
+
mesh.userData.destructionPart = "tank";
|
|
4313
|
+
mesh.position.set(...shape.position);
|
|
4314
|
+
mesh.castShadow = true;
|
|
4315
|
+
mesh.receiveShadow = true;
|
|
4316
|
+
return mesh;
|
|
4317
|
+
};
|
|
4318
|
+
var worldExplosiveItem = (def, name) => {
|
|
4319
|
+
const shape = SHAPES2[def.id];
|
|
4320
|
+
if (!shape) return void 0;
|
|
4321
|
+
const spec = { name, parts: [explosivePart(def, shape)], joints: [], tags: ["explosive"] };
|
|
4322
|
+
return {
|
|
4323
|
+
id: `${WORLD_EXPLOSIVE_PREFIX}${def.id}`,
|
|
4324
|
+
name,
|
|
4325
|
+
build: (THREE) => {
|
|
4326
|
+
const group = new THREE.Group();
|
|
4327
|
+
group.add(meshOf(THREE, shape));
|
|
4328
|
+
return group;
|
|
4329
|
+
},
|
|
4330
|
+
spec
|
|
4331
|
+
};
|
|
4332
|
+
};
|
|
4333
|
+
|
|
345
4334
|
// src/area.ts
|
|
346
4335
|
var WEAPON_ITEM_PREFIX = "weapon_";
|
|
347
4336
|
var COUNTER_ITEM_ID = "counter_weapons";
|
|
4337
|
+
var EXPLOSION_VOLUME_BASE = 0.4;
|
|
4338
|
+
var EXPLOSION_VOLUME_PER_METRE = 0.04;
|
|
4339
|
+
var SOUND_DISTANCE_MAX = 200;
|
|
4340
|
+
var FUSE_LENGTH_MAX = 1;
|
|
4341
|
+
var DEFAULT_FUSE_LENGTH = 0.03;
|
|
348
4342
|
var COUNTER = { length: 4.8, depth: 1.6, height: 0.9 };
|
|
349
4343
|
var COUNTER_X = [-6.5, -9, -11.5];
|
|
350
|
-
var COUNTER_Z = -1;
|
|
351
|
-
var END_MARGIN = 0.
|
|
352
|
-
var
|
|
4344
|
+
var COUNTER_Z = [-1, 5.5];
|
|
4345
|
+
var END_MARGIN = 0.5;
|
|
4346
|
+
var DEFAULT_PITCH = 0.16;
|
|
4347
|
+
var PITCH_BY_ID = { browning_m2: 0.5, m2_flamethrower: 0.45, bow: 0.3, crossbow: 0.3 };
|
|
4348
|
+
var WORLD_EXPLOSIVES = [
|
|
4349
|
+
{ id: "fuel_barrel", at: [10, 0, -8] },
|
|
4350
|
+
{ id: "fuel_barrel", at: [11.2, 0, -8] },
|
|
4351
|
+
{ id: "propane_p13", at: [10.6, 0, -9.3] },
|
|
4352
|
+
{ id: "car_fuel_tank", at: [8.6, 0, -9.3] }
|
|
4353
|
+
];
|
|
353
4354
|
var TARGETS = [
|
|
354
4355
|
{ id: "target_steel_plate", at: [-1.5, 0, -7] },
|
|
355
4356
|
{ id: "target_plywood", at: [0, 0, -7] },
|
|
@@ -362,7 +4363,20 @@ var TEXT = {
|
|
|
362
4363
|
counter: "Weapon counter",
|
|
363
4364
|
targets: { target_steel_plate: "Steel plate target", target_plywood: "Plywood target", target_concrete: "Concrete block target" },
|
|
364
4365
|
reloadStart: "Reload started: {weapon} ({ammo}/{capacity})",
|
|
365
|
-
reloadEnd: "Reload done: {weapon} ({ammo}/{capacity})"
|
|
4366
|
+
reloadEnd: "Reload done: {weapon} ({ammo}/{capacity})",
|
|
4367
|
+
worldExplosives: { fuel_barrel: "Fuel barrel", propane_p13: "Propane cylinder (P13)", car_fuel_tank: "Car fuel tank" },
|
|
4368
|
+
condition: "Weapon condition",
|
|
4369
|
+
conditionHint: "Wear of the weapon: it jams more and the metal shows it",
|
|
4370
|
+
conditions: { new: "New", used: "Used", worn: "Worn", rusty: "Rusty" },
|
|
4371
|
+
ammoQuality: "Ammunition",
|
|
4372
|
+
ammoQualityHint: "Reloaded rounds misfire more often",
|
|
4373
|
+
ammoQualities: { factory: "Factory", reloaded: "Reloaded" },
|
|
4374
|
+
soundDistance: "Sound distance",
|
|
4375
|
+
soundDistanceHint: "How far the listener is from the shot: farther is quieter and duller",
|
|
4376
|
+
fuseLength: "Dynamite fuse length",
|
|
4377
|
+
fuseLengthHint: "Metres of safety fuse (131 s per metre): a long one is set down lit, for a delayed trap",
|
|
4378
|
+
layers: { muzzle_flash: "Muzzle flash", casings: "Ejected casings", magazines: "Dropped magazines" },
|
|
4379
|
+
metrics: { casings_live: "Casings in the scene", magazines_live: "Magazines in the scene", heat: "Barrel heat" }
|
|
366
4380
|
},
|
|
367
4381
|
"pt-BR": {
|
|
368
4382
|
group: "Armas",
|
|
@@ -370,7 +4384,20 @@ var TEXT = {
|
|
|
370
4384
|
counter: "Balc\xE3o de armas",
|
|
371
4385
|
targets: { target_steel_plate: "Alvo: chapa de a\xE7o", target_plywood: "Alvo: compensado", target_concrete: "Alvo: bloco de concreto" },
|
|
372
4386
|
reloadStart: "Recarga iniciada: {weapon} ({ammo}/{capacity})",
|
|
373
|
-
reloadEnd: "Recarga completa: {weapon} ({ammo}/{capacity})"
|
|
4387
|
+
reloadEnd: "Recarga completa: {weapon} ({ammo}/{capacity})",
|
|
4388
|
+
worldExplosives: { fuel_barrel: "Barril de combust\xEDvel", propane_p13: "Botij\xE3o de g\xE1s (P13)", car_fuel_tank: "Tanque de combust\xEDvel do carro" },
|
|
4389
|
+
condition: "Estado da arma",
|
|
4390
|
+
conditionHint: "Desgaste da arma: trava mais e o metal mostra",
|
|
4391
|
+
conditions: { new: "Nova", used: "Usada", worn: "Gasta", rusty: "Enferrujada" },
|
|
4392
|
+
ammoQuality: "Muni\xE7\xE3o",
|
|
4393
|
+
ammoQualityHint: "Muni\xE7\xE3o recarregada falha mais",
|
|
4394
|
+
ammoQualities: { factory: "De f\xE1brica", reloaded: "Recarregada" },
|
|
4395
|
+
soundDistance: "Dist\xE2ncia do som",
|
|
4396
|
+
soundDistanceHint: "Qu\xE3o longe o ouvinte est\xE1 do tiro: mais longe soa mais baixo e abafado",
|
|
4397
|
+
fuseLength: "Pavio da dinamite",
|
|
4398
|
+
fuseLengthHint: "Metros de pavio de seguran\xE7a (131 s por metro): um longo \xE9 posto no ch\xE3o aceso, para armadilha com atraso",
|
|
4399
|
+
layers: { muzzle_flash: "Clar\xE3o do cano", casings: "C\xE1psulas ejetadas", magazines: "Pentes que caem" },
|
|
4400
|
+
metrics: { casings_live: "C\xE1psulas na cena", magazines_live: "Pentes na cena", heat: "Calor do cano" }
|
|
374
4401
|
}
|
|
375
4402
|
};
|
|
376
4403
|
var textOf = (locale) => TEXT[locale] ?? TEXT.en;
|
|
@@ -412,6 +4439,18 @@ var targetProp = (id, name) => {
|
|
|
412
4439
|
};
|
|
413
4440
|
return { spec, colors: { post: 3816770, face: color } };
|
|
414
4441
|
};
|
|
4442
|
+
var weaponItem = (def, name) => {
|
|
4443
|
+
const id = `${WEAPON_ITEM_PREFIX}${def.id}`;
|
|
4444
|
+
if (!hasWeaponModel(def.id)) return asItem(id, name, weaponProp(def, name), { weapon: def.id, movable: true });
|
|
4445
|
+
return {
|
|
4446
|
+
id,
|
|
4447
|
+
name,
|
|
4448
|
+
build: (THREE) => buildModeledProp(THREE, def, name),
|
|
4449
|
+
spec: (root) => modeledPropSpec(root, def, name),
|
|
4450
|
+
weapon: def.id,
|
|
4451
|
+
movable: true
|
|
4452
|
+
};
|
|
4453
|
+
};
|
|
415
4454
|
var asItem = (id, name, prop, extra = {}) => ({
|
|
416
4455
|
id,
|
|
417
4456
|
name,
|
|
@@ -419,26 +4458,47 @@ var asItem = (id, name, prop, extra = {}) => ({
|
|
|
419
4458
|
spec: prop.spec,
|
|
420
4459
|
...extra
|
|
421
4460
|
});
|
|
422
|
-
function weaponsLayout(weaponItemIds) {
|
|
423
|
-
const
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
4461
|
+
function weaponsLayout(weaponItemIds, pitchOf = () => DEFAULT_PITCH) {
|
|
4462
|
+
const usable = COUNTER.length - 2 * END_MARGIN;
|
|
4463
|
+
const slots = COUNTER_Z.flatMap((z) => COUNTER_X.map((x) => ({ x, z })));
|
|
4464
|
+
const counterOf = [];
|
|
4465
|
+
const placements = [];
|
|
4466
|
+
for (const item of weaponItemIds) {
|
|
4467
|
+
const pitch = pitchOf(item);
|
|
4468
|
+
let counter = counterOf[counterOf.length - 1];
|
|
4469
|
+
if (!counter || counter.used + pitch > usable + 1e-9) {
|
|
4470
|
+
const slot = slots[counterOf.length];
|
|
4471
|
+
if (!slot) throw new Error(`weapons area: ${weaponItemIds.length} weapons do not fit on ${slots.length} counters of ${usable.toFixed(2)} m`);
|
|
4472
|
+
if (pitch > usable + 1e-9) throw new Error(`weapons area: "${item}" takes ${pitch} m and a counter holds ${usable.toFixed(2)} m`);
|
|
4473
|
+
counter = { ...slot, used: 0 };
|
|
4474
|
+
counterOf.push(counter);
|
|
4475
|
+
}
|
|
4476
|
+
placements.push({ item, position: [counter.x, COUNTER.height + 0.01, counter.z - COUNTER.length / 2 + END_MARGIN + counter.used + pitch / 2], asleep: true });
|
|
4477
|
+
counter.used += pitch;
|
|
4478
|
+
}
|
|
4479
|
+
const counters = counterOf.map(({ x, z }) => ({ item: COUNTER_ITEM_ID, position: [x, 0, z], surface: { halfSize: [COUNTER.depth / 2, COUNTER.length / 2], height: COUNTER.height } }));
|
|
4480
|
+
const targets = TARGETS.map((target) => ({ item: target.id, position: target.at }));
|
|
4481
|
+
const explosives = WORLD_EXPLOSIVES.map((explosive) => ({ item: `${WORLD_EXPLOSIVE_PREFIX}${explosive.id}`, position: explosive.at }));
|
|
4482
|
+
return [...counters, ...placements, ...targets, ...explosives];
|
|
433
4483
|
}
|
|
434
4484
|
function createWeaponsTestModule(options = {}) {
|
|
435
4485
|
const locale = options.locale ?? "pt-BR";
|
|
436
4486
|
const arsenal = options.arsenal ?? createArsenal({ locale });
|
|
437
4487
|
const text = textOf(locale);
|
|
438
|
-
const audio = options.sound === false ? null : createWeaponAudio();
|
|
4488
|
+
const audio = options.sound === false ? null : createWeaponAudio(void 0, options.seed === void 0 ? {} : { seed: options.seed });
|
|
439
4489
|
const weaponDefs = [...arsenal.weapons.values()].filter((def) => !BODY_ACTIONS.has(def.id));
|
|
440
|
-
const weaponIds = weaponDefs.map((def) => `${WEAPON_ITEM_PREFIX}${def.id}`);
|
|
4490
|
+
const weaponIds = weaponDefs.filter((def) => !(def.id in ARCHETYPE_MODELS)).map((def) => `${WEAPON_ITEM_PREFIX}${def.id}`);
|
|
4491
|
+
const pitchOf = (itemId) => PITCH_BY_ID[itemId.slice(WEAPON_ITEM_PREFIX.length)] ?? DEFAULT_PITCH;
|
|
4492
|
+
const worldExplosives = WORLD_EXPLOSIVE_IDS.flatMap((id) => {
|
|
4493
|
+
const def = arsenal.explosives.get(id);
|
|
4494
|
+
const item = def ? worldExplosiveItem(def, text.worldExplosives[id] ?? id) : void 0;
|
|
4495
|
+
return item ? [item] : [];
|
|
4496
|
+
});
|
|
4497
|
+
const worldExplosiveIds = new Set(worldExplosives.map((item) => item.id));
|
|
441
4498
|
let fx = null;
|
|
4499
|
+
let host = null;
|
|
4500
|
+
let soundDistance = 0;
|
|
4501
|
+
const heatPercent = () => Math.min(100, (host?.tool().heat ?? 0) * 100);
|
|
442
4502
|
return {
|
|
443
4503
|
id: "weapons_area",
|
|
444
4504
|
name: text.name,
|
|
@@ -448,48 +4508,126 @@ function createWeaponsTestModule(options = {}) {
|
|
|
448
4508
|
return fx;
|
|
449
4509
|
},
|
|
450
4510
|
items: () => [
|
|
451
|
-
...weaponDefs.map((def) =>
|
|
4511
|
+
...weaponDefs.map((def) => weaponItem(def, arsenal.name(def.id))),
|
|
452
4512
|
asItem(COUNTER_ITEM_ID, text.counter, counterProp(text.counter), { movable: false }),
|
|
453
|
-
...TARGETS.map((target) => asItem(target.id, text.targets[target.id], targetProp(target.id, text.targets[target.id]), { movable: false }))
|
|
4513
|
+
...TARGETS.map((target) => asItem(target.id, text.targets[target.id] ?? target.id, targetProp(target.id, text.targets[target.id] ?? target.id), { movable: false })),
|
|
4514
|
+
...worldExplosives
|
|
4515
|
+
],
|
|
4516
|
+
area: () => ({
|
|
4517
|
+
placements: weaponsLayout(weaponIds, pitchOf).filter((placement) => !placement.item.startsWith(WORLD_EXPLOSIVE_PREFIX) || worldExplosiveIds.has(placement.item)),
|
|
4518
|
+
view: { eye: [-3.4, 1.9, 9.2], target: [-9, 0.9, 1.6] }
|
|
4519
|
+
}),
|
|
4520
|
+
controls: () => [
|
|
4521
|
+
{
|
|
4522
|
+
kind: "choice",
|
|
4523
|
+
id: "condition",
|
|
4524
|
+
name: text.condition,
|
|
4525
|
+
hint: text.conditionHint,
|
|
4526
|
+
options: arsenal.rules.conditions.map((entry) => ({ id: entry.id, name: text.conditions[entry.id] ?? entry.id })),
|
|
4527
|
+
default: options.condition ?? arsenal.rules.defaultCondition
|
|
4528
|
+
},
|
|
4529
|
+
{
|
|
4530
|
+
kind: "choice",
|
|
4531
|
+
id: "ammo_quality",
|
|
4532
|
+
name: text.ammoQuality,
|
|
4533
|
+
hint: text.ammoQualityHint,
|
|
4534
|
+
options: arsenal.rules.ammoQualities.map((entry) => ({ id: entry.id, name: text.ammoQualities[entry.id] ?? entry.id })),
|
|
4535
|
+
default: arsenal.rules.defaultAmmoQuality
|
|
4536
|
+
},
|
|
4537
|
+
{ kind: "number", id: "sound_distance", name: text.soundDistance, hint: text.soundDistanceHint, unit: "m", min: 0, max: SOUND_DISTANCE_MAX, step: 5, default: 0 },
|
|
4538
|
+
{ kind: "number", id: "fuse_length", name: text.fuseLength, hint: text.fuseLengthHint, unit: "m", min: 0.01, max: FUSE_LENGTH_MAX, step: 0.01, default: arsenal.explosives.get("dynamite")?.fuseLength ?? DEFAULT_FUSE_LENGTH }
|
|
454
4539
|
],
|
|
455
|
-
|
|
456
|
-
|
|
4540
|
+
layers: () => ["muzzle_flash", "casings", "magazines"].map((id) => ({ id, name: text.layers[id] })),
|
|
4541
|
+
metrics: () => [
|
|
4542
|
+
{ id: "casings_live", name: text.metrics.casings_live, read: () => fx?.casings ?? 0 },
|
|
4543
|
+
{ id: "magazines_live", name: text.metrics.magazines_live, read: () => fx?.magazines ?? 0 },
|
|
4544
|
+
{ id: "heat", name: text.metrics.heat, unit: "%", max: 100, digits: 0, read: heatPercent }
|
|
4545
|
+
],
|
|
4546
|
+
attach(lent) {
|
|
4547
|
+
host = lent;
|
|
457
4548
|
const created = createWeaponFx({
|
|
458
|
-
THREE:
|
|
459
|
-
scene:
|
|
460
|
-
camera:
|
|
4549
|
+
THREE: lent.THREE,
|
|
4550
|
+
scene: lent.scene,
|
|
4551
|
+
camera: lent.camera,
|
|
461
4552
|
arsenal,
|
|
462
|
-
groundAt:
|
|
463
|
-
audio
|
|
4553
|
+
groundAt: lent.groundAt,
|
|
4554
|
+
audio,
|
|
4555
|
+
tool: lent.tool,
|
|
4556
|
+
soundDistance: () => soundDistance,
|
|
4557
|
+
...options.condition ? { condition: options.condition } : {},
|
|
4558
|
+
...options.seed === void 0 ? {} : { seed: options.seed }
|
|
464
4559
|
});
|
|
465
4560
|
fx = created;
|
|
466
|
-
const
|
|
467
|
-
|
|
4561
|
+
const camera = lent.camera;
|
|
4562
|
+
const offBench = lent.bench?.onChange((change) => {
|
|
4563
|
+
if (change.kind === "layer") {
|
|
4564
|
+
if (change.id === "muzzle_flash") created.layers.muzzleFlash = change.on;
|
|
4565
|
+
else if (change.id === "casings") created.layers.casings = change.on;
|
|
4566
|
+
else if (change.id === "magazines") created.layers.magazines = change.on;
|
|
4567
|
+
} else if (change.kind === "control" && typeof change.value === "string") {
|
|
4568
|
+
if (change.id === "condition") {
|
|
4569
|
+
created.setCondition(change.value);
|
|
4570
|
+
lent.configureTool?.({ condition: change.value });
|
|
4571
|
+
} else if (change.id === "ammo_quality") lent.configureTool?.({ ammo_quality: change.value });
|
|
4572
|
+
} else if (change.kind === "control" && change.id === "sound_distance" && typeof change.value === "number") soundDistance = change.value;
|
|
4573
|
+
else if (change.kind === "control" && change.id === "fuse_length" && typeof change.value === "number") lent.configureTool?.({ fuse_length: String(change.value) });
|
|
4574
|
+
});
|
|
4575
|
+
const offEvents = lent.on((event) => {
|
|
4576
|
+
if (event.type === "explosion") {
|
|
4577
|
+
const away = Math.hypot(event.point[0] - camera.position.x, event.point[1] - camera.position.y, event.point[2] - camera.position.z);
|
|
4578
|
+
audio?.play("explosion", Math.min(1, EXPLOSION_VOLUME_BASE + event.radius * EXPLOSION_VOLUME_PER_METRE), away);
|
|
4579
|
+
return;
|
|
4580
|
+
}
|
|
4581
|
+
const tool = lent.tool();
|
|
468
4582
|
created.handle(event, tool);
|
|
469
|
-
if (event.type === "reload")
|
|
4583
|
+
if (event.type === "reload") lent.log(fill(event.phase === "start" ? text.reloadStart : text.reloadEnd, { weapon: arsenal.name(event.weapon), ammo: tool.ammo, capacity: tool.capacity }), { weapon: event.weapon, phase: event.phase });
|
|
470
4584
|
});
|
|
471
|
-
const offFrame =
|
|
472
|
-
created.handle({ type: "tool", weapon:
|
|
4585
|
+
const offFrame = lent.onFrame((dt) => created.update(dt));
|
|
4586
|
+
created.handle({ type: "tool", weapon: lent.tool().weapon }, lent.tool());
|
|
473
4587
|
return () => {
|
|
4588
|
+
offBench?.();
|
|
474
4589
|
offEvents();
|
|
475
4590
|
offFrame();
|
|
476
4591
|
created.dispose();
|
|
477
4592
|
audio?.dispose();
|
|
478
4593
|
fx = null;
|
|
4594
|
+
host = null;
|
|
479
4595
|
};
|
|
480
4596
|
}
|
|
481
4597
|
};
|
|
482
4598
|
}
|
|
483
4599
|
export {
|
|
4600
|
+
ARCHETYPE_MODELS,
|
|
484
4601
|
BODY_ACTIONS,
|
|
4602
|
+
CLASS_REFERENCE_ENERGY,
|
|
485
4603
|
COUNTER_ITEM_ID,
|
|
4604
|
+
DEFAULT_ENERGY_CURVE,
|
|
4605
|
+
GAME_DISTANCE_CURVE,
|
|
4606
|
+
GAME_SOUND_CLASSES,
|
|
4607
|
+
MECHANICAL_SOUNDS,
|
|
4608
|
+
MODEL_ENTRIES,
|
|
486
4609
|
WEAPON_ITEM_PREFIX,
|
|
4610
|
+
WORLD_EXPLOSIVE_IDS,
|
|
4611
|
+
WORLD_EXPLOSIVE_PREFIX,
|
|
4612
|
+
applyCondition,
|
|
4613
|
+
buildCartridge,
|
|
4614
|
+
buildCasing,
|
|
487
4615
|
buildProp,
|
|
4616
|
+
buildRound,
|
|
4617
|
+
buildToolClips,
|
|
488
4618
|
buildWeaponModel,
|
|
4619
|
+
buildWeaponModelById,
|
|
4620
|
+
cartridgeShape,
|
|
4621
|
+
computeShotSound,
|
|
489
4622
|
createWeaponAudio,
|
|
490
4623
|
createWeaponFx,
|
|
491
4624
|
createWeaponsTestModule,
|
|
4625
|
+
hasWeaponModel,
|
|
4626
|
+
mechanicalSoundOf,
|
|
4627
|
+
modelKit,
|
|
4628
|
+
partRegistry,
|
|
492
4629
|
weaponProp,
|
|
493
|
-
weaponsLayout
|
|
4630
|
+
weaponsLayout,
|
|
4631
|
+
worldExplosiveItem
|
|
494
4632
|
};
|
|
495
4633
|
//# sourceMappingURL=index.js.map
|