@adatechnology/game-weapons-three 0.2.0 → 0.3.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 +254 -96
- package/dist/index.global.js +12063 -4466
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1677 -99
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
package/dist/index.js
CHANGED
|
@@ -1,6 +1,294 @@
|
|
|
1
1
|
// src/area.ts
|
|
2
2
|
import { createArsenal } from "@adatechnology/game-weapons";
|
|
3
3
|
|
|
4
|
+
// src/attachments-key.ts
|
|
5
|
+
var attachmentsKey = (parts) => {
|
|
6
|
+
if (!parts) return "";
|
|
7
|
+
let key = String(parts.length);
|
|
8
|
+
for (const part of parts) key += `|${part.id}@${part.mount}${part.on ? "+" : ""}`;
|
|
9
|
+
return key;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/accessory-bench.ts
|
|
13
|
+
var ACCESSORY_MOUNTS = ["muzzle", "rail_top", "rail_left", "rail_right", "rail_bottom", "underbarrel", "magazine", "feed", "sling", "barrel"];
|
|
14
|
+
var ACCESSORY_KEYS = { light: "KeyL", laser: "KeyK", stock: "KeyV", underbarrel: "KeyU" };
|
|
15
|
+
var FIT_PREFIX = "fit_";
|
|
16
|
+
var NONE = "none";
|
|
17
|
+
var SUPPORTED_CONTROL_ID = "supported_stance";
|
|
18
|
+
var ACTION_IDS = { light: "toggle_light", laser: "toggle_laser", stock: "toggle_stock", underbarrel: "switch_underbarrel" };
|
|
19
|
+
var SECONDARY_BUTTON = 2;
|
|
20
|
+
var TEXT_FIELDS = /* @__PURE__ */ new Set(["INPUT", "TEXTAREA", "SELECT"]);
|
|
21
|
+
var accessoryControls = (text) => [
|
|
22
|
+
...ACCESSORY_MOUNTS.map((mount) => ({ kind: "choice", id: `${FIT_PREFIX}${mount}`, name: text.mounts[mount] ?? mount, options: [{ id: NONE, name: text.none }], default: NONE })),
|
|
23
|
+
{ kind: "boolean", id: SUPPORTED_CONTROL_ID, name: text.supported.name, hint: text.supported.hint, default: false },
|
|
24
|
+
...Object.keys(ACTION_IDS).map((key) => ({ kind: "action", id: ACTION_IDS[key], name: text.actions[key].name, hint: text.actions[key].hint }))
|
|
25
|
+
];
|
|
26
|
+
var accessoryLayers = (text) => [
|
|
27
|
+
{ id: "attachments", name: text.layers.attachments },
|
|
28
|
+
{ id: "beams", name: text.layers.beams }
|
|
29
|
+
];
|
|
30
|
+
var accessoryMetrics = (text, tool) => [
|
|
31
|
+
{ id: "weapon_mass", name: text.metrics.mass, unit: "kg", digits: 2, read: () => tool().mass ?? 0 },
|
|
32
|
+
{ id: "aim_time", name: text.metrics.aim, unit: "s", digits: 2, read: () => tool().aimSeconds ?? 0 },
|
|
33
|
+
{ id: "accessories_fitted", name: text.metrics.fitted, digits: 0, read: () => (tool().attachments ?? []).length },
|
|
34
|
+
{ id: "suppressed", name: text.metrics.suppressed, read: () => tool().suppressed ? text.yes : text.no }
|
|
35
|
+
];
|
|
36
|
+
var fittedOf = (parts) => (parts ?? []).map((part) => ({ id: part.id, mount: part.mount, ...part.integral ? { integral: true } : {}, ...part.on === void 0 ? {} : { on: part.on } }));
|
|
37
|
+
var fillText = (template, vars) => template.replace(/\{(\w+)\}/g, (_, key) => vars[key] ?? "");
|
|
38
|
+
function createAccessoryBench(options) {
|
|
39
|
+
const { arsenal, host, text } = options;
|
|
40
|
+
const camera = host.camera;
|
|
41
|
+
const bench = host.bench;
|
|
42
|
+
const pending = /* @__PURE__ */ new Map();
|
|
43
|
+
let signature = "";
|
|
44
|
+
let aiming = false;
|
|
45
|
+
let aimProgress = 0;
|
|
46
|
+
let baseFov = camera.fov;
|
|
47
|
+
let appliedZoom = 1;
|
|
48
|
+
const nameOf = (id) => text.attachments[id] ?? arsenal.attachments.get(id)?.name ?? id;
|
|
49
|
+
const offers = (tool, mount) => {
|
|
50
|
+
const fitted = fittedOf(tool.attachments);
|
|
51
|
+
const here = fitted.find((part) => part.mount === mount);
|
|
52
|
+
const choices = [{ id: NONE, name: text.none }];
|
|
53
|
+
if (here) choices.push({ id: here.id, name: nameOf(here.id) });
|
|
54
|
+
for (const def of arsenal.attachments.values()) {
|
|
55
|
+
if (def.id === here?.id || !arsenal.canFit(tool.weapon, def.id, { fitted, mount }).ok) continue;
|
|
56
|
+
choices.push({ id: def.id, name: nameOf(def.id) });
|
|
57
|
+
}
|
|
58
|
+
return choices;
|
|
59
|
+
};
|
|
60
|
+
const refresh = () => {
|
|
61
|
+
const tool = host.tool();
|
|
62
|
+
const next = `${tool.weapon}|${attachmentsKey(tool.attachments)}`;
|
|
63
|
+
if (next === signature) return;
|
|
64
|
+
signature = next;
|
|
65
|
+
const def = arsenal.weapons.get(tool.weapon);
|
|
66
|
+
for (const mount of ACCESSORY_MOUNTS) {
|
|
67
|
+
const has = (def?.mounts ?? []).some((place) => place.kind === mount);
|
|
68
|
+
const here = (tool.attachments ?? []).find((part) => part.mount === mount);
|
|
69
|
+
const choices = offers(tool, mount);
|
|
70
|
+
const isFixed = here?.integral === true;
|
|
71
|
+
bench?.patch(`${FIT_PREFIX}${mount}`, { hidden: !has, disabled: isFixed, options: choices, ...pending.has(mount) ? {} : { value: here?.id ?? NONE } });
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const refuse = (mount, reason) => {
|
|
75
|
+
host.log(fillText(text.fitRefused, { mount: text.mounts[mount] ?? mount, weapon: arsenal.name(host.tool().weapon), reason: reason ?? "" }), { mount, reason });
|
|
76
|
+
pending.delete(mount);
|
|
77
|
+
signature = "";
|
|
78
|
+
};
|
|
79
|
+
const settle = () => {
|
|
80
|
+
for (const [mount, wanted] of pending) {
|
|
81
|
+
const here = (host.tool().attachments ?? []).find((part) => part.mount === mount && !part.integral);
|
|
82
|
+
if (here?.id === wanted || !here && wanted === NONE) {
|
|
83
|
+
pending.delete(mount);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const request = here ? { type: "remove", attachment: here.id } : { type: "fit", attachment: wanted, mount };
|
|
87
|
+
const result = host.command?.(request);
|
|
88
|
+
if (!result || !result.ok && result.reason !== "busy") refuse(mount, result?.reason);
|
|
89
|
+
if (result?.reason === "busy") return;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const toggleKind = (kind) => {
|
|
93
|
+
const part = (host.tool().attachments ?? []).find((candidate) => arsenal.attachments.get(candidate.id)?.kind === kind);
|
|
94
|
+
if (!part) host.log(kind === "light" ? text.noLight : text.noLaser);
|
|
95
|
+
else host.command?.({ type: "toggle", attachment: part.id });
|
|
96
|
+
};
|
|
97
|
+
const toggleStock = () => {
|
|
98
|
+
const tool = host.tool();
|
|
99
|
+
const kind = arsenal.weapons.get(tool.weapon)?.stock?.kind;
|
|
100
|
+
if (kind === void 0 || kind === "fixed" || kind === "none") return false;
|
|
101
|
+
host.command?.({ type: "stock", state: tool.stock === "collapsed" ? "extended" : "collapsed" });
|
|
102
|
+
return true;
|
|
103
|
+
};
|
|
104
|
+
const switchUnderbarrel = () => {
|
|
105
|
+
const tool = host.tool();
|
|
106
|
+
const hasLauncher = (tool.attachments ?? []).some((part) => arsenal.attachments.get(part.id)?.launcher !== void 0);
|
|
107
|
+
if (!hasLauncher) host.log(text.noLauncher);
|
|
108
|
+
else host.command?.({ type: "underbarrel", on: tool.underbarrelActive !== true });
|
|
109
|
+
};
|
|
110
|
+
const act = (id) => {
|
|
111
|
+
if (id === ACTION_IDS.light) toggleKind("light");
|
|
112
|
+
else if (id === ACTION_IDS.laser) toggleKind("laser");
|
|
113
|
+
else if (id === ACTION_IDS.stock) {
|
|
114
|
+
if (!toggleStock()) host.log(text.noStock);
|
|
115
|
+
} else if (id === ACTION_IDS.underbarrel) switchUnderbarrel();
|
|
116
|
+
else return false;
|
|
117
|
+
return true;
|
|
118
|
+
};
|
|
119
|
+
const zoomOf = (tool) => {
|
|
120
|
+
const optic = (tool.attachments ?? []).map((part) => arsenal.attachments.get(part.id)?.optic).find((entry) => entry !== void 0);
|
|
121
|
+
return optic?.zoom[0] ?? 1;
|
|
122
|
+
};
|
|
123
|
+
const isFirearm = (tool) => arsenal.weapons.get(tool.weapon)?.action.kind === "firearm";
|
|
124
|
+
const setAim = (on) => {
|
|
125
|
+
if (on === aiming) return;
|
|
126
|
+
aiming = on;
|
|
127
|
+
host.command?.({ type: "stance", aiming: on });
|
|
128
|
+
};
|
|
129
|
+
const follow = (dt) => {
|
|
130
|
+
const tool = host.tool();
|
|
131
|
+
const seconds = Math.max(0.05, tool.aimSeconds ?? 0.2);
|
|
132
|
+
aimProgress = Math.max(0, Math.min(1, aimProgress + (aiming ? dt : -dt) / seconds));
|
|
133
|
+
const zoom = 1 + (zoomOf(tool) - 1) * aimProgress;
|
|
134
|
+
if (Math.abs(zoom - appliedZoom) < 1e-4) return;
|
|
135
|
+
if (appliedZoom === 1) baseFov = camera.fov;
|
|
136
|
+
appliedZoom = zoom;
|
|
137
|
+
camera.fov = zoom === 1 ? baseFov : 2 * Math.atan(Math.tan(baseFov * Math.PI / 360) / zoom) * 180 / Math.PI;
|
|
138
|
+
camera.updateProjectionMatrix();
|
|
139
|
+
};
|
|
140
|
+
const onKeyDown = (event) => {
|
|
141
|
+
if (event.repeat || event.ctrlKey || event.metaKey || event.altKey || TEXT_FIELDS.has(event.target?.tagName ?? "")) return;
|
|
142
|
+
const handled2 = event.code === ACCESSORY_KEYS.light ? act(ACTION_IDS.light) : event.code === ACCESSORY_KEYS.laser ? act(ACTION_IDS.laser) : event.code === ACCESSORY_KEYS.underbarrel ? act(ACTION_IDS.underbarrel) : event.code === ACCESSORY_KEYS.stock && toggleStock();
|
|
143
|
+
if (!handled2) return;
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
event.stopImmediatePropagation();
|
|
146
|
+
};
|
|
147
|
+
const isOnRoom = (event) => Boolean(document.pointerLockElement) || event.target instanceof HTMLCanvasElement;
|
|
148
|
+
const onMouseDown = (event) => {
|
|
149
|
+
if (event.button !== SECONDARY_BUTTON || !isOnRoom(event) || !isFirearm(host.tool())) return;
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
event.stopImmediatePropagation();
|
|
152
|
+
setAim(true);
|
|
153
|
+
};
|
|
154
|
+
const onMouseUp = (event) => {
|
|
155
|
+
if (event.button === SECONDARY_BUTTON) setAim(false);
|
|
156
|
+
};
|
|
157
|
+
const onBlur = () => setAim(false);
|
|
158
|
+
window.addEventListener("keydown", onKeyDown, true);
|
|
159
|
+
window.addEventListener("mousedown", onMouseDown, true);
|
|
160
|
+
window.addEventListener("mouseup", onMouseUp);
|
|
161
|
+
window.addEventListener("blur", onBlur);
|
|
162
|
+
const offFrame = host.onFrame((dt) => {
|
|
163
|
+
refresh();
|
|
164
|
+
settle();
|
|
165
|
+
follow(dt);
|
|
166
|
+
});
|
|
167
|
+
const offEvents = host.on((event) => {
|
|
168
|
+
if (event.type === "attachment") {
|
|
169
|
+
const template = event.action === "fit" ? text.fitting : event.action === "remove" ? text.removing : text.broke;
|
|
170
|
+
host.log(fillText(template, { attachment: nameOf(event.attachment), weapon: arsenal.name(event.weapon), seconds: String(event.duration ?? 0) }), { attachment: event.attachment, action: event.action });
|
|
171
|
+
}
|
|
172
|
+
if (event.type !== "tool") return;
|
|
173
|
+
pending.clear();
|
|
174
|
+
setAim(false);
|
|
175
|
+
bench?.patch(SUPPORTED_CONTROL_ID, { value: false });
|
|
176
|
+
signature = "";
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
handleChange(change) {
|
|
180
|
+
if (change.kind === "action") return act(change.id);
|
|
181
|
+
if (change.kind === "control" && change.id === SUPPORTED_CONTROL_ID && typeof change.value === "boolean") {
|
|
182
|
+
host.command?.({ type: "stance", supported: change.value });
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
if (change.kind !== "control" || !change.id.startsWith(FIT_PREFIX) || typeof change.value !== "string") return false;
|
|
186
|
+
const mount = change.id.slice(FIT_PREFIX.length);
|
|
187
|
+
pending.set(mount, change.value);
|
|
188
|
+
settle();
|
|
189
|
+
return true;
|
|
190
|
+
},
|
|
191
|
+
dispose() {
|
|
192
|
+
offFrame();
|
|
193
|
+
offEvents();
|
|
194
|
+
window.removeEventListener("keydown", onKeyDown, true);
|
|
195
|
+
window.removeEventListener("mousedown", onMouseDown, true);
|
|
196
|
+
window.removeEventListener("mouseup", onMouseUp);
|
|
197
|
+
window.removeEventListener("blur", onBlur);
|
|
198
|
+
if (appliedZoom !== 1) {
|
|
199
|
+
camera.fov = baseFov;
|
|
200
|
+
camera.updateProjectionMatrix();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// src/accessory-text.ts
|
|
207
|
+
var ACCESSORY_TEXT = {
|
|
208
|
+
en: {
|
|
209
|
+
none: "None",
|
|
210
|
+
mounts: { muzzle: "Muzzle", rail_top: "Top rail", rail_left: "Left rail", rail_right: "Right rail", rail_bottom: "Bottom rail", underbarrel: "Under the barrel", magazine: "Magazine", feed: "Feed (backpack)", sling: "Sling", barrel: "Barrel" },
|
|
211
|
+
actions: {
|
|
212
|
+
light: { name: "Light on/off (L)", hint: "Turns the weapon light on or off" },
|
|
213
|
+
laser: { name: "Laser on/off (K)", hint: "Turns the laser sight on or off" },
|
|
214
|
+
stock: { name: "Fold or open the stock (V)", hint: "Folds or slides the stock in, or opens it" },
|
|
215
|
+
underbarrel: { name: "Launcher under the barrel (U)", hint: "Switches the trigger and the reload between the weapon and the launcher under its barrel" }
|
|
216
|
+
},
|
|
217
|
+
layers: { attachments: "Accessory models", beams: "Light cone and laser beam" },
|
|
218
|
+
supported: { name: "Supported (deployed / prone)", hint: "The weapon rests on something: a bipod then takes its recoil and spread effect" },
|
|
219
|
+
metrics: { mass: "Weapon mass", aim: "Time to the sights", fitted: "Accessories on the weapon", suppressed: "Suppressed" },
|
|
220
|
+
yes: "yes",
|
|
221
|
+
no: "no",
|
|
222
|
+
attachments: {},
|
|
223
|
+
fitRefused: "Could not change {mount} of {weapon}: {reason}",
|
|
224
|
+
fitting: "Fitting {attachment} on {weapon} ({seconds} s)",
|
|
225
|
+
removing: "Taking {attachment} off {weapon} ({seconds} s)",
|
|
226
|
+
broke: "{attachment} broke on {weapon}",
|
|
227
|
+
noLight: "No light on this weapon",
|
|
228
|
+
noLaser: "No laser on this weapon",
|
|
229
|
+
noStock: "This weapon has no stock that folds or slides",
|
|
230
|
+
noLauncher: "No launcher under the barrel of this weapon"
|
|
231
|
+
},
|
|
232
|
+
"pt-BR": {
|
|
233
|
+
none: "Nenhum",
|
|
234
|
+
mounts: { muzzle: "Boca do cano", rail_top: "Trilho superior", rail_left: "Trilho esquerdo", rail_right: "Trilho direito", rail_bottom: "Trilho inferior", underbarrel: "Sob o cano", magazine: "Carregador", feed: "Alimenta\xE7\xE3o (mochila)", sling: "Bandoleira", barrel: "Cano" },
|
|
235
|
+
actions: {
|
|
236
|
+
light: { name: "Ligar/desligar a lanterna (L)", hint: "Liga ou desliga a lanterna da arma" },
|
|
237
|
+
laser: { name: "Ligar/desligar o laser (K)", hint: "Liga ou desliga o laser da arma" },
|
|
238
|
+
stock: { name: "Dobrar ou abrir a coronha (V)", hint: "Dobra ou recolhe a coronha, ou a abre" },
|
|
239
|
+
underbarrel: { name: "Lan\xE7a-granada sob o cano (U)", hint: "Passa o gatilho e a recarga entre a arma e o lan\xE7a-granada sob o cano" }
|
|
240
|
+
},
|
|
241
|
+
layers: { attachments: "Modelos dos acess\xF3rios", beams: "Cone da lanterna e feixe do laser" },
|
|
242
|
+
supported: { name: "Apoiado (bip\xE9 aberto / deitado)", hint: "A arma est\xE1 apoiada: o bip\xE9 passa a valer no recuo e na dispers\xE3o" },
|
|
243
|
+
metrics: { mass: "Massa da arma", aim: "Tempo at\xE9 a mira", fitted: "Acess\xF3rios na arma", suppressed: "Silenciada" },
|
|
244
|
+
yes: "sim",
|
|
245
|
+
no: "n\xE3o",
|
|
246
|
+
attachments: {
|
|
247
|
+
suppressor: "Supressor",
|
|
248
|
+
oil_filter_suppressor: "Supressor de filtro de \xF3leo",
|
|
249
|
+
integral_suppressor: "Supressor integrado",
|
|
250
|
+
muzzle_brake: "Freio de boca",
|
|
251
|
+
muzzle_brake_heavy: "Freio de boca pesado",
|
|
252
|
+
compensator: "Compensador",
|
|
253
|
+
red_dot: "Mira red dot",
|
|
254
|
+
holographic_sight: "Mira hologr\xE1fica",
|
|
255
|
+
acog_4x: "ACOG 4\xD7",
|
|
256
|
+
scope_3_9x: "Luneta 3\u20139\xD7",
|
|
257
|
+
scope_6_24x: "Luneta 6\u201324\xD7",
|
|
258
|
+
night_vision_scope: "Mira noturna",
|
|
259
|
+
thermal_scope: "Mira t\xE9rmica",
|
|
260
|
+
integral_optic_1_5x: "Mira integrada 1,5\xD7",
|
|
261
|
+
integral_optic_3x: "Mira integrada 3\xD7",
|
|
262
|
+
pso_1: "Mira PSO-1",
|
|
263
|
+
fixed_scope_10x: "Luneta fixa 10\xD7",
|
|
264
|
+
vertical_grip: "Empunhadura vertical",
|
|
265
|
+
bipod: "Bip\xE9",
|
|
266
|
+
m203: "Lan\xE7a-granada M203",
|
|
267
|
+
gp_25: "Lan\xE7a-granada GP-25",
|
|
268
|
+
ag36: "Lan\xE7a-granada AG36",
|
|
269
|
+
weapon_light: "Lanterna",
|
|
270
|
+
laser_sight: "Laser",
|
|
271
|
+
extended_magazine: "Carregador estendido",
|
|
272
|
+
drum_magazine: "Carregador tambor",
|
|
273
|
+
dual_coupler: "Acoplador de carregadores",
|
|
274
|
+
sawn_off_barrel: "Cano serrado",
|
|
275
|
+
long_barrel: "Cano longo",
|
|
276
|
+
sling: "Bandoleira",
|
|
277
|
+
ammo_backpack: "Mochila de muni\xE7\xE3o",
|
|
278
|
+
tripod: "Trip\xE9"
|
|
279
|
+
},
|
|
280
|
+
fitRefused: "N\xE3o deu para mudar {mount} de {weapon}: {reason}",
|
|
281
|
+
fitting: "Encaixando {attachment} em {weapon} ({seconds} s)",
|
|
282
|
+
removing: "Tirando {attachment} de {weapon} ({seconds} s)",
|
|
283
|
+
broke: "{attachment} quebrou em {weapon}",
|
|
284
|
+
noLight: "Esta arma n\xE3o tem lanterna",
|
|
285
|
+
noLaser: "Esta arma n\xE3o tem laser",
|
|
286
|
+
noStock: "Esta arma n\xE3o tem coronha que dobre ou recolha",
|
|
287
|
+
noLauncher: "Esta arma n\xE3o tem lan\xE7a-granada sob o cano"
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var accessoryTextOf = (locale) => ACCESSORY_TEXT[locale] ?? ACCESSORY_TEXT.en;
|
|
291
|
+
|
|
4
292
|
// src/audio.ts
|
|
5
293
|
import { createRng } from "@adatechnology/game-weapons";
|
|
6
294
|
|
|
@@ -21,6 +309,9 @@ var MECHANICAL_SOUNDS = {
|
|
|
21
309
|
jam: [metal(0, 700, 0.4), metal(0.08, 500, 0.35)],
|
|
22
310
|
clear_start: [metal(0, 1e3, 0.3)],
|
|
23
311
|
clear_end: [metal(0, 1300, 0.35)],
|
|
312
|
+
stock_fold: [metal(0, 800, 0.3), metal(0.18, 1200, 0.3), thud(0.3, 0.2, 600)],
|
|
313
|
+
light_click: [metal(0, 2400, 0.2), metal(0.03, 1800, 0.12)],
|
|
314
|
+
attach: [metal(0, 1400, 0.35), thud(0.04, 0.2, 700), metal(0.12, 1900, 0.25)],
|
|
24
315
|
magazine_out: [metal(0, 1500, 0.35), sound(0, "reload", 0.6)],
|
|
25
316
|
magazine_in: seat,
|
|
26
317
|
charge: slide,
|
|
@@ -67,6 +358,14 @@ var mechanicalSoundOf = (event) => {
|
|
|
67
358
|
return "jam";
|
|
68
359
|
case "clear":
|
|
69
360
|
return event.phase === "start" ? "clear_start" : "clear_end";
|
|
361
|
+
case "attachment":
|
|
362
|
+
return event.action === "break" ? "jam" : "attach";
|
|
363
|
+
case "toggle":
|
|
364
|
+
return "light_click";
|
|
365
|
+
case "stock":
|
|
366
|
+
return "stock_fold";
|
|
367
|
+
case "underbarrel":
|
|
368
|
+
return "mode";
|
|
70
369
|
default:
|
|
71
370
|
return void 0;
|
|
72
371
|
}
|
|
@@ -315,7 +614,42 @@ function createWeaponAudio(volume = 0.6, options = {}) {
|
|
|
315
614
|
import { createRng as createRng2, muzzleEnergy as muzzleEnergy2 } from "@adatechnology/game-weapons";
|
|
316
615
|
|
|
317
616
|
// src/fx/layers.ts
|
|
318
|
-
var createLayers = () => ({ casings: true, magazines: true, muzzleFlash: true });
|
|
617
|
+
var createLayers = () => ({ casings: true, magazines: true, muzzleFlash: true, attachments: true, beams: true });
|
|
618
|
+
|
|
619
|
+
// src/laser-raycast.ts
|
|
620
|
+
var VIEWMODEL_NODE_NAME = "weapon-viewmodel";
|
|
621
|
+
var IN_HAND_NODE_NAME = "weapon-in-hand";
|
|
622
|
+
var LASER_RAYCAST_HZ = 30;
|
|
623
|
+
var isHeld = (object) => {
|
|
624
|
+
for (let node = object; node; node = node.parent) if (node.name === VIEWMODEL_NODE_NAME || node.name === IN_HAND_NODE_NAME) return true;
|
|
625
|
+
return false;
|
|
626
|
+
};
|
|
627
|
+
var isVisibleInScene = (object) => {
|
|
628
|
+
for (let node = object; node; node = node.parent) if (!node.visible) return false;
|
|
629
|
+
return true;
|
|
630
|
+
};
|
|
631
|
+
var createLaserRaycast = (THREE, scene, camera, options = {}) => {
|
|
632
|
+
const now = options.now ?? (() => performance.now());
|
|
633
|
+
const interval = 1e3 / LASER_RAYCAST_HZ;
|
|
634
|
+
const raycaster = new THREE.Raycaster();
|
|
635
|
+
raycaster.camera = camera;
|
|
636
|
+
let lastAt = -Infinity;
|
|
637
|
+
let lastReach = null;
|
|
638
|
+
return (origin, direction, maxDistance) => {
|
|
639
|
+
const at = now();
|
|
640
|
+
if (at - lastAt < interval) return lastReach;
|
|
641
|
+
lastAt = at;
|
|
642
|
+
raycaster.set(origin, direction);
|
|
643
|
+
raycaster.far = maxDistance;
|
|
644
|
+
const hit = raycaster.intersectObjects(scene.children, true).find((entry) => {
|
|
645
|
+
const mesh = entry.object;
|
|
646
|
+
const material = mesh.material;
|
|
647
|
+
return mesh.isMesh && isVisibleInScene(mesh) && material?.transparent !== true && !isHeld(mesh);
|
|
648
|
+
});
|
|
649
|
+
lastReach = hit ? hit.distance : null;
|
|
650
|
+
return lastReach;
|
|
651
|
+
};
|
|
652
|
+
};
|
|
319
653
|
|
|
320
654
|
// src/merge-by-material.ts
|
|
321
655
|
var ATTRIBUTES = ["position", "normal", "uv"];
|
|
@@ -1510,29 +1844,417 @@ var models_default = {
|
|
|
1510
1844
|
lt: "flameDIY"
|
|
1511
1845
|
}
|
|
1512
1846
|
},
|
|
1513
|
-
browning_m2: {
|
|
1514
|
-
builder: "m2",
|
|
1515
|
-
caliber: ".50 BMG",
|
|
1516
|
-
length: 1650,
|
|
1517
|
-
barrel: 1140,
|
|
1847
|
+
browning_m2: {
|
|
1848
|
+
builder: "m2",
|
|
1849
|
+
caliber: ".50 BMG",
|
|
1850
|
+
length: 1650,
|
|
1851
|
+
barrel: 1140,
|
|
1852
|
+
options: {
|
|
1853
|
+
rpm: 550
|
|
1854
|
+
}
|
|
1855
|
+
},
|
|
1856
|
+
remington_700_300wm: {
|
|
1857
|
+
builder: "rifle",
|
|
1858
|
+
caliber: ".308",
|
|
1859
|
+
length: 1100,
|
|
1860
|
+
barrel: 610,
|
|
1861
|
+
options: {
|
|
1862
|
+
wood: 1,
|
|
1863
|
+
stock: "trad",
|
|
1864
|
+
mag: "internal",
|
|
1865
|
+
scope: 3,
|
|
1866
|
+
bolt: 1,
|
|
1867
|
+
round: 1
|
|
1868
|
+
},
|
|
1869
|
+
variantOf: "remington_700"
|
|
1870
|
+
},
|
|
1871
|
+
hk_mp5sd3: {
|
|
1872
|
+
builder: "rifle",
|
|
1873
|
+
caliber: "9\xD719",
|
|
1874
|
+
length: 805,
|
|
1875
|
+
barrel: 146,
|
|
1876
|
+
options: {
|
|
1877
|
+
mag: "curved",
|
|
1878
|
+
magLen: 0.72,
|
|
1879
|
+
stock: "retract",
|
|
1880
|
+
stockLen: 0.22,
|
|
1881
|
+
hR: 0.055,
|
|
1882
|
+
hg: "poly",
|
|
1883
|
+
hgFrac: 0.85,
|
|
1884
|
+
g3: 1,
|
|
1885
|
+
rpm: 700,
|
|
1886
|
+
sel: 1,
|
|
1887
|
+
jacket: 1,
|
|
1888
|
+
noFH: 1,
|
|
1889
|
+
br: 0.0115
|
|
1890
|
+
},
|
|
1891
|
+
variantOf: "hk_mp5a3"
|
|
1892
|
+
},
|
|
1893
|
+
fn_parafal: {
|
|
1894
|
+
builder: "rifle",
|
|
1895
|
+
caliber: "7,62\xD751",
|
|
1896
|
+
length: 1020,
|
|
1897
|
+
barrel: 440,
|
|
1898
|
+
options: {
|
|
1899
|
+
stock: "fold",
|
|
1900
|
+
mag: "box",
|
|
1901
|
+
curve: 0.05,
|
|
1902
|
+
magLen: 1.05,
|
|
1903
|
+
hg: "poly",
|
|
1904
|
+
hgFrac: 0.45,
|
|
1905
|
+
carry: 1,
|
|
1906
|
+
brake: 1,
|
|
1907
|
+
charge: 1,
|
|
1908
|
+
rpm: 650,
|
|
1909
|
+
stockLen: 0.25
|
|
1910
|
+
},
|
|
1911
|
+
variantOf: "fn_fal"
|
|
1912
|
+
},
|
|
1913
|
+
taurus_t4_civil: {
|
|
1914
|
+
builder: "rifle",
|
|
1915
|
+
caliber: "5,56\xD745",
|
|
1916
|
+
length: 878,
|
|
1917
|
+
barrel: 406,
|
|
1918
|
+
options: {
|
|
1919
|
+
stock: "retract",
|
|
1920
|
+
mag: "box",
|
|
1921
|
+
curve: 0.07,
|
|
1922
|
+
magLen: 1.3,
|
|
1923
|
+
hg: "rail",
|
|
1924
|
+
hgFrac: 0.7,
|
|
1925
|
+
rail: 1,
|
|
1926
|
+
scope: 1,
|
|
1927
|
+
ar: 1,
|
|
1928
|
+
rpm: 120
|
|
1929
|
+
},
|
|
1930
|
+
variantOf: "taurus_t4"
|
|
1931
|
+
},
|
|
1932
|
+
taurus_th380: {
|
|
1933
|
+
builder: "pistol",
|
|
1934
|
+
caliber: ".380 ACP",
|
|
1935
|
+
length: 165,
|
|
1936
|
+
barrel: 90,
|
|
1937
|
+
options: {
|
|
1938
|
+
st: "g2c",
|
|
1939
|
+
gl: 0.085,
|
|
1940
|
+
rel: 1.5
|
|
1941
|
+
},
|
|
1942
|
+
variantOf: "taurus_g2c"
|
|
1943
|
+
},
|
|
1944
|
+
mossberg_500_cruiser: {
|
|
1945
|
+
builder: "rifle",
|
|
1946
|
+
caliber: "12 ga",
|
|
1947
|
+
length: 720,
|
|
1948
|
+
barrel: 470,
|
|
1949
|
+
options: {
|
|
1950
|
+
stock: "none",
|
|
1951
|
+
mag: "none",
|
|
1952
|
+
tube: 1,
|
|
1953
|
+
pump: 1,
|
|
1954
|
+
hg: "none",
|
|
1955
|
+
br: 0.0105,
|
|
1956
|
+
hR: 0.055,
|
|
1957
|
+
noFH: 1
|
|
1958
|
+
},
|
|
1959
|
+
variantOf: "mossberg_500"
|
|
1960
|
+
},
|
|
1961
|
+
hk_mp5k: {
|
|
1962
|
+
builder: "rifle",
|
|
1963
|
+
caliber: "9\xD719",
|
|
1964
|
+
length: 325,
|
|
1965
|
+
barrel: 115,
|
|
1966
|
+
options: {
|
|
1967
|
+
mag: "curved",
|
|
1968
|
+
magLen: 0.72,
|
|
1969
|
+
stock: "none",
|
|
1970
|
+
stockLen: 0.22,
|
|
1971
|
+
hR: 0.055,
|
|
1972
|
+
hg: "poly",
|
|
1973
|
+
hgFrac: 0.5,
|
|
1974
|
+
g3: 1,
|
|
1975
|
+
rpm: 900,
|
|
1976
|
+
sel: 1
|
|
1977
|
+
},
|
|
1978
|
+
variantOf: "hk_mp5a3"
|
|
1979
|
+
},
|
|
1980
|
+
hk_ump9: {
|
|
1981
|
+
builder: "rifle",
|
|
1982
|
+
caliber: "9\xD719",
|
|
1983
|
+
length: 690,
|
|
1984
|
+
barrel: 200,
|
|
1985
|
+
options: {
|
|
1986
|
+
mag: "curved",
|
|
1987
|
+
magLen: 1,
|
|
1988
|
+
stock: "skeleton",
|
|
1989
|
+
stockLen: 0.22,
|
|
1990
|
+
hR: 0.07,
|
|
1991
|
+
hg: "poly",
|
|
1992
|
+
hgFrac: 0.85,
|
|
1993
|
+
rpm: 700,
|
|
1994
|
+
sel: 1,
|
|
1995
|
+
rail: 1
|
|
1996
|
+
},
|
|
1997
|
+
variantOf: "hk_ump_45"
|
|
1998
|
+
},
|
|
1999
|
+
mini_uzi: {
|
|
2000
|
+
builder: "rifle",
|
|
2001
|
+
caliber: "9\xD719",
|
|
2002
|
+
length: 600,
|
|
2003
|
+
barrel: 197,
|
|
2004
|
+
options: {
|
|
2005
|
+
mag: "box",
|
|
2006
|
+
magAt: 0.06,
|
|
2007
|
+
magLen: 0.9,
|
|
2008
|
+
stock: "fold",
|
|
2009
|
+
stockLen: 0.2,
|
|
2010
|
+
hR: 0.065,
|
|
2011
|
+
hg: "poly",
|
|
2012
|
+
hgFrac: 0.4,
|
|
2013
|
+
grip: "none",
|
|
2014
|
+
rpm: 950
|
|
2015
|
+
},
|
|
2016
|
+
variantOf: "uzi"
|
|
2017
|
+
},
|
|
2018
|
+
hk_mp7a1: {
|
|
2019
|
+
builder: "rifle",
|
|
2020
|
+
caliber: "4,6\xD730",
|
|
2021
|
+
length: 638,
|
|
2022
|
+
barrel: 180,
|
|
2023
|
+
options: {
|
|
2024
|
+
mag: "box",
|
|
2025
|
+
magAt: 0.1,
|
|
2026
|
+
magLen: 0.8,
|
|
2027
|
+
stock: "retract",
|
|
2028
|
+
stockLen: 0.2,
|
|
2029
|
+
hR: 0.07,
|
|
2030
|
+
hg: "rail",
|
|
2031
|
+
hgFrac: 0.45,
|
|
2032
|
+
grip: "none",
|
|
2033
|
+
rail: 1,
|
|
2034
|
+
rpm: 950,
|
|
2035
|
+
sel: 1
|
|
2036
|
+
}
|
|
2037
|
+
},
|
|
2038
|
+
hk_g36: {
|
|
2039
|
+
builder: "rifle",
|
|
2040
|
+
caliber: "5,56\xD745",
|
|
2041
|
+
length: 999,
|
|
2042
|
+
barrel: 480,
|
|
2043
|
+
options: {
|
|
2044
|
+
stock: "fold",
|
|
2045
|
+
stockLen: 0.24,
|
|
2046
|
+
mag: "box",
|
|
2047
|
+
curve: 0.05,
|
|
2048
|
+
magLen: 1,
|
|
2049
|
+
hg: "poly",
|
|
2050
|
+
hgFrac: 0.5,
|
|
2051
|
+
hR: 0.065,
|
|
2052
|
+
charge: 1,
|
|
2053
|
+
sel: 1,
|
|
2054
|
+
rpm: 750,
|
|
2055
|
+
carry: 1,
|
|
2056
|
+
scope: 2
|
|
2057
|
+
}
|
|
2058
|
+
},
|
|
2059
|
+
hk_g36k: {
|
|
2060
|
+
builder: "rifle",
|
|
2061
|
+
caliber: "5,56\xD745",
|
|
2062
|
+
length: 860,
|
|
2063
|
+
barrel: 318,
|
|
2064
|
+
options: {
|
|
2065
|
+
stock: "fold",
|
|
2066
|
+
stockLen: 0.24,
|
|
2067
|
+
mag: "box",
|
|
2068
|
+
curve: 0.05,
|
|
2069
|
+
magLen: 1,
|
|
2070
|
+
hg: "poly",
|
|
2071
|
+
hgFrac: 0.5,
|
|
2072
|
+
hR: 0.065,
|
|
2073
|
+
charge: 1,
|
|
2074
|
+
sel: 1,
|
|
2075
|
+
rpm: 750,
|
|
2076
|
+
carry: 1,
|
|
2077
|
+
scope: 2
|
|
2078
|
+
}
|
|
2079
|
+
},
|
|
2080
|
+
hk_g36c: {
|
|
2081
|
+
builder: "rifle",
|
|
2082
|
+
caliber: "5,56\xD745",
|
|
2083
|
+
length: 720,
|
|
2084
|
+
barrel: 228,
|
|
2085
|
+
options: {
|
|
2086
|
+
stock: "fold",
|
|
2087
|
+
stockLen: 0.24,
|
|
2088
|
+
mag: "box",
|
|
2089
|
+
curve: 0.05,
|
|
2090
|
+
magLen: 1,
|
|
2091
|
+
hg: "poly",
|
|
2092
|
+
hgFrac: 0.5,
|
|
2093
|
+
hR: 0.065,
|
|
2094
|
+
charge: 1,
|
|
2095
|
+
sel: 1,
|
|
2096
|
+
rpm: 750,
|
|
2097
|
+
rail: 1
|
|
2098
|
+
}
|
|
2099
|
+
},
|
|
2100
|
+
fn_m249: {
|
|
2101
|
+
builder: "rifle",
|
|
2102
|
+
caliber: "5,56\xD745",
|
|
2103
|
+
length: 1040,
|
|
2104
|
+
barrel: 465,
|
|
2105
|
+
options: {
|
|
2106
|
+
stock: "fixed",
|
|
2107
|
+
mag: "belt",
|
|
2108
|
+
hg: "poly",
|
|
2109
|
+
hgFrac: 0.35,
|
|
2110
|
+
bipod: 1,
|
|
2111
|
+
hR: 0.08,
|
|
2112
|
+
carry: 1,
|
|
2113
|
+
charge: 1,
|
|
2114
|
+
rpm: 850
|
|
2115
|
+
},
|
|
2116
|
+
variantOf: "fn_minimi"
|
|
2117
|
+
},
|
|
2118
|
+
fn_minimi_para: {
|
|
2119
|
+
builder: "rifle",
|
|
2120
|
+
caliber: "5,56\xD745",
|
|
2121
|
+
length: 893,
|
|
2122
|
+
barrel: 349,
|
|
2123
|
+
options: {
|
|
2124
|
+
stock: "retract",
|
|
2125
|
+
mag: "belt",
|
|
2126
|
+
hg: "poly",
|
|
2127
|
+
hgFrac: 0.35,
|
|
2128
|
+
bipod: 1,
|
|
2129
|
+
hR: 0.08,
|
|
2130
|
+
carry: 1,
|
|
2131
|
+
charge: 1,
|
|
2132
|
+
rpm: 850,
|
|
2133
|
+
stockLen: 0.22
|
|
2134
|
+
},
|
|
2135
|
+
variantOf: "fn_minimi"
|
|
2136
|
+
},
|
|
2137
|
+
fn_mag: {
|
|
2138
|
+
builder: "rifle",
|
|
2139
|
+
caliber: "7,62\xD751",
|
|
2140
|
+
length: 1263,
|
|
2141
|
+
barrel: 630,
|
|
2142
|
+
options: {
|
|
2143
|
+
stock: "fixed",
|
|
2144
|
+
mag: "belt",
|
|
2145
|
+
hg: "poly",
|
|
2146
|
+
hgFrac: 0.35,
|
|
2147
|
+
bipod: 1,
|
|
2148
|
+
carry: 1,
|
|
2149
|
+
hR: 0.085,
|
|
2150
|
+
br: 0.011,
|
|
2151
|
+
charge: 1,
|
|
2152
|
+
rpm: 750
|
|
2153
|
+
}
|
|
2154
|
+
},
|
|
2155
|
+
rpk: {
|
|
2156
|
+
builder: "rifle",
|
|
2157
|
+
caliber: "7,62\xD739",
|
|
2158
|
+
length: 1040,
|
|
2159
|
+
barrel: 590,
|
|
2160
|
+
options: {
|
|
2161
|
+
wood: 1,
|
|
2162
|
+
stock: "wood",
|
|
2163
|
+
mag: "curved",
|
|
2164
|
+
hg: "wood",
|
|
2165
|
+
hgFrac: 0.42,
|
|
2166
|
+
gas: 1,
|
|
2167
|
+
charge: 1,
|
|
2168
|
+
ak: 1,
|
|
2169
|
+
rod: 1,
|
|
2170
|
+
bayo: 0,
|
|
2171
|
+
rpm: 600,
|
|
2172
|
+
rel: 0.4,
|
|
2173
|
+
bipod: 1,
|
|
2174
|
+
magLen: 1.4
|
|
2175
|
+
},
|
|
2176
|
+
variantOf: "ak_47"
|
|
2177
|
+
},
|
|
2178
|
+
sig_mpx: {
|
|
2179
|
+
builder: "rifle",
|
|
2180
|
+
caliber: "9\xD719",
|
|
2181
|
+
length: 610,
|
|
2182
|
+
barrel: 203,
|
|
2183
|
+
options: {
|
|
2184
|
+
mag: "box",
|
|
2185
|
+
curve: 0.05,
|
|
2186
|
+
magLen: 1,
|
|
2187
|
+
stock: "retract",
|
|
2188
|
+
stockLen: 0.2,
|
|
2189
|
+
hR: 0.06,
|
|
2190
|
+
hg: "rail",
|
|
2191
|
+
hgFrac: 0.7,
|
|
2192
|
+
rail: 1,
|
|
2193
|
+
charge: 1,
|
|
2194
|
+
sel: 1,
|
|
2195
|
+
rpm: 850
|
|
2196
|
+
}
|
|
2197
|
+
},
|
|
2198
|
+
mg3: {
|
|
2199
|
+
builder: "rifle",
|
|
2200
|
+
caliber: "7,62\xD751",
|
|
2201
|
+
length: 1225,
|
|
2202
|
+
barrel: 565,
|
|
2203
|
+
options: {
|
|
2204
|
+
stock: "fixed",
|
|
2205
|
+
mag: "belt",
|
|
2206
|
+
hg: "poly",
|
|
2207
|
+
hgFrac: 0.3,
|
|
2208
|
+
bipod: 1,
|
|
2209
|
+
carry: 1,
|
|
2210
|
+
hR: 0.085,
|
|
2211
|
+
br: 0.011,
|
|
2212
|
+
charge: 1,
|
|
2213
|
+
rpm: 1100
|
|
2214
|
+
}
|
|
2215
|
+
},
|
|
2216
|
+
pp19_bizon: {
|
|
2217
|
+
builder: "rifle",
|
|
2218
|
+
caliber: "9\xD718",
|
|
2219
|
+
length: 660,
|
|
2220
|
+
barrel: 195,
|
|
1518
2221
|
options: {
|
|
1519
|
-
|
|
2222
|
+
mag: "helical",
|
|
2223
|
+
magLen: 1,
|
|
2224
|
+
stock: "fold",
|
|
2225
|
+
stockLen: 0.2,
|
|
2226
|
+
hR: 0.06,
|
|
2227
|
+
hg: "poly",
|
|
2228
|
+
hgFrac: 0.5,
|
|
2229
|
+
ak: 1,
|
|
2230
|
+
charge: 1,
|
|
2231
|
+
rpm: 680
|
|
1520
2232
|
}
|
|
1521
2233
|
},
|
|
1522
|
-
|
|
2234
|
+
vz61: {
|
|
1523
2235
|
builder: "rifle",
|
|
1524
|
-
caliber: ".
|
|
1525
|
-
length:
|
|
1526
|
-
barrel:
|
|
2236
|
+
caliber: ".32 ACP",
|
|
2237
|
+
length: 517,
|
|
2238
|
+
barrel: 115,
|
|
1527
2239
|
options: {
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
2240
|
+
mag: "curved",
|
|
2241
|
+
magLen: 0.6,
|
|
2242
|
+
stock: "fold",
|
|
2243
|
+
stockLen: 0.2,
|
|
2244
|
+
hR: 0.05,
|
|
2245
|
+
hg: "none",
|
|
2246
|
+
rpm: 850,
|
|
2247
|
+
sel: 1
|
|
2248
|
+
}
|
|
2249
|
+
},
|
|
2250
|
+
m134: {
|
|
2251
|
+
builder: "m134",
|
|
2252
|
+
caliber: "7,62\xD751",
|
|
2253
|
+
length: 801,
|
|
2254
|
+
barrel: 559,
|
|
2255
|
+
options: {
|
|
2256
|
+
rpm: 3e3
|
|
2257
|
+
}
|
|
1536
2258
|
}
|
|
1537
2259
|
};
|
|
1538
2260
|
|
|
@@ -1750,6 +2472,80 @@ function buildBreakAction(kit, options) {
|
|
|
1750
2472
|
return { group, parts };
|
|
1751
2473
|
}
|
|
1752
2474
|
|
|
2475
|
+
// src/models/attachments/mount-frames.ts
|
|
2476
|
+
var DEFAULT_BARREL_RADIUS = 85e-4;
|
|
2477
|
+
var DEFAULT_RECEIVER_WIDTH = 0.034;
|
|
2478
|
+
var RECEIVER_START_FRACTION = 0.25;
|
|
2479
|
+
var RECEIVER_END_FRACTION = 0.55;
|
|
2480
|
+
var RECEIVER_HEIGHT_FRACTION = 0.4;
|
|
2481
|
+
var DEFAULT_GRIP_OFFSET = 0.07;
|
|
2482
|
+
var HANDGUARD_FRACTION = 0.5;
|
|
2483
|
+
function deriveFrames(THREE, model) {
|
|
2484
|
+
const { root, inner, parts } = model;
|
|
2485
|
+
root.updateMatrixWorld(true);
|
|
2486
|
+
const box2 = new THREE.Box3().setFromObject(root);
|
|
2487
|
+
const span = box2.max.x - box2.min.x;
|
|
2488
|
+
const toInnerX = (x) => (x - inner.position.x) / inner.scale.x;
|
|
2489
|
+
const muzzle = parts.muzzle?.position;
|
|
2490
|
+
const length = muzzle ? muzzle.x - 0.01 : toInnerX(box2.max.x);
|
|
2491
|
+
const receiverStart = toInnerX(box2.min.x + span * RECEIVER_START_FRACTION);
|
|
2492
|
+
const receiverEnd = toInnerX(box2.min.x + span * RECEIVER_END_FRACTION);
|
|
2493
|
+
return {
|
|
2494
|
+
length,
|
|
2495
|
+
barrelY: muzzle?.y ?? (box2.max.y + box2.min.y) / 2,
|
|
2496
|
+
barrelRadius: DEFAULT_BARREL_RADIUS,
|
|
2497
|
+
receiverStart,
|
|
2498
|
+
receiverEnd,
|
|
2499
|
+
receiverHeight: (box2.max.y - box2.min.y) * RECEIVER_HEIGHT_FRACTION,
|
|
2500
|
+
receiverWidth: Math.min(DEFAULT_RECEIVER_WIDTH, box2.max.z - box2.min.z),
|
|
2501
|
+
handguardEnd: receiverEnd + (length - receiverEnd) * HANDGUARD_FRACTION,
|
|
2502
|
+
magazineX: parts.mag ? parts.mag.position.x : (receiverStart + receiverEnd) / 2,
|
|
2503
|
+
gripX: receiverStart + DEFAULT_GRIP_OFFSET,
|
|
2504
|
+
hasRail: false
|
|
2505
|
+
};
|
|
2506
|
+
}
|
|
2507
|
+
function mountFrame(model, mount) {
|
|
2508
|
+
const frames = model.parts.frames;
|
|
2509
|
+
if (!frames) return void 0;
|
|
2510
|
+
const { inner } = model;
|
|
2511
|
+
const toRoot = (x, y, z) => [inner.position.x + x * inner.scale.x, y, z];
|
|
2512
|
+
const handguardHeight = frames.receiverHeight * 0.8;
|
|
2513
|
+
const railTopY = frames.receiverHeight / 2 + (frames.hasRail ? 6e-3 : 0);
|
|
2514
|
+
const forendMiddle = (frames.receiverEnd + frames.handguardEnd) / 2;
|
|
2515
|
+
const base = { mount, barrelRadius: frames.barrelRadius, receiverWidth: frames.receiverWidth, receiverHeight: frames.receiverHeight, hasRail: frames.hasRail };
|
|
2516
|
+
switch (mount) {
|
|
2517
|
+
case "muzzle":
|
|
2518
|
+
return { ...base, position: toRoot(frames.length, frames.barrelY, 0), side: 0 };
|
|
2519
|
+
case "rail_top":
|
|
2520
|
+
return { ...base, position: toRoot((frames.receiverStart + frames.receiverEnd) / 2 + 0.03, railTopY, 0), side: 0 };
|
|
2521
|
+
case "rail_bottom":
|
|
2522
|
+
return { ...base, position: toRoot(forendMiddle, frames.barrelY - handguardHeight / 2, 0), side: 0 };
|
|
2523
|
+
case "rail_left":
|
|
2524
|
+
return { ...base, position: toRoot(forendMiddle, frames.barrelY, -(frames.receiverWidth * 0.54 + 4e-3)), side: -1 };
|
|
2525
|
+
case "rail_right":
|
|
2526
|
+
return { ...base, position: toRoot(forendMiddle, frames.barrelY, frames.receiverWidth * 0.54 + 4e-3), side: 1 };
|
|
2527
|
+
case "underbarrel": {
|
|
2528
|
+
const [x, y] = frames.underbarrel ?? [frames.receiverEnd + (frames.handguardEnd - frames.receiverEnd) * 0.25, frames.barrelY - handguardHeight / 2 - 0.02];
|
|
2529
|
+
return { ...base, position: toRoot(x, y, 0), side: 0 };
|
|
2530
|
+
}
|
|
2531
|
+
case "magazine":
|
|
2532
|
+
return { ...base, position: toRoot(frames.magazineX, -frames.receiverHeight / 2, 0), side: 0 };
|
|
2533
|
+
case "feed": {
|
|
2534
|
+
const belt = model.parts.belt?.position;
|
|
2535
|
+
return { ...base, position: belt ? toRoot(belt.x, belt.y, belt.z) : toRoot(frames.magazineX + 0.02, -frames.receiverHeight / 2 - 0.05, -0.045), side: -1 };
|
|
2536
|
+
}
|
|
2537
|
+
case "sling":
|
|
2538
|
+
return {
|
|
2539
|
+
...base,
|
|
2540
|
+
position: toRoot(frames.receiverEnd + (frames.length - frames.receiverEnd) * 0.45, -frames.receiverHeight / 2 - 0.03, 0),
|
|
2541
|
+
secondary: toRoot(0.08, -0.09, 0),
|
|
2542
|
+
side: 0
|
|
2543
|
+
};
|
|
2544
|
+
default:
|
|
2545
|
+
return void 0;
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
|
|
1753
2549
|
// src/models/details.ts
|
|
1754
2550
|
var PI2 = Math.PI;
|
|
1755
2551
|
function rail(kit, parent, x, length, y, z, width, rotationX = 0) {
|
|
@@ -2092,6 +2888,90 @@ function buildLauncher(kit, options) {
|
|
|
2092
2888
|
return { group, parts };
|
|
2093
2889
|
}
|
|
2094
2890
|
|
|
2891
|
+
// src/models/m134.ts
|
|
2892
|
+
var PI4 = Math.PI;
|
|
2893
|
+
var BARRELS = 6;
|
|
2894
|
+
var CLUSTER_RADIUS = 0.03;
|
|
2895
|
+
var BARREL_RADIUS = 78e-4;
|
|
2896
|
+
var BARREL_END = 0.8;
|
|
2897
|
+
function buildBarrelCluster(kit, parts, group) {
|
|
2898
|
+
const { M, cylinderX } = kit;
|
|
2899
|
+
const barrels = kit.pivot(group, 0, 0, 0);
|
|
2900
|
+
parts.barrels = barrels;
|
|
2901
|
+
const length = BARREL_END - 0.3;
|
|
2902
|
+
let heated;
|
|
2903
|
+
for (let i = 0; i < BARRELS; i++) {
|
|
2904
|
+
const angle = i * 2 * PI4 / BARRELS;
|
|
2905
|
+
const y = Math.cos(angle) * CLUSTER_RADIUS;
|
|
2906
|
+
const z = Math.sin(angle) * CLUSTER_RADIUS;
|
|
2907
|
+
const barrel = cylinderX(barrels, BARREL_RADIUS, length, M.metal, 0.3 + length / 2, y, z, 14);
|
|
2908
|
+
if (heated) barrel.material = heated;
|
|
2909
|
+
else heated = heatify(parts.glow, barrel).material;
|
|
2910
|
+
cylinderX(barrels, BARREL_RADIUS * 0.55, 4e-3, M.hole, BARREL_END + 5e-4, y, z, 10);
|
|
2911
|
+
}
|
|
2912
|
+
cylinderX(barrels, 0.012, length, M.steel, 0.3 + length / 2, 0, 0, 12);
|
|
2913
|
+
for (const x of [0.42, 0.58, 0.74]) cylinderX(barrels, CLUSTER_RADIUS + BARREL_RADIUS * 1.6, 0.014, M.steel, x, 0, 0, 24);
|
|
2914
|
+
cylinderX(barrels, CLUSTER_RADIUS + BARREL_RADIUS * 1.8, 0.03, M.dark, BARREL_END - 0.015, 0, 0, 24);
|
|
2915
|
+
}
|
|
2916
|
+
function buildHousing(kit, parts, group) {
|
|
2917
|
+
const { M, box: box2, cylinderX, cylinderZ } = kit;
|
|
2918
|
+
box2(group, 0.3, 0.14, 0.15, M.metal, 0.18, 0, 0);
|
|
2919
|
+
cylinderX(group, 0.052, 0.12, M.dark, 0.06, 0, 0, 20);
|
|
2920
|
+
cylinderX(group, 0.062, 0.2, M.dark, 0.38, 0, 0, 22);
|
|
2921
|
+
for (let i = 0; i < 4; i++) cylinderX(group, 0.064, 8e-3, M.steel, 0.3 + i * 0.05, 0, 0, 22);
|
|
2922
|
+
box2(group, 0.04, 0.05, 0.2, M.dark, 0.1, 0.095, 0);
|
|
2923
|
+
box2(group, 0.2, 0.012, 0.045, M.dark, 0.22, 0.158, 0);
|
|
2924
|
+
box2(group, 0.012, 0.05, 0.045, M.dark, 0.13, 0.13, 0);
|
|
2925
|
+
box2(group, 0.012, 0.05, 0.045, M.dark, 0.31, 0.13, 0);
|
|
2926
|
+
for (const side of [-1, 1]) {
|
|
2927
|
+
box2(group, 0.03, 0.03, 0.1, M.poly, 0.06, -0.01, side * 0.14);
|
|
2928
|
+
cylinderZ(group, 0.016, 0.07, M.poly, 0.06, -0.01, side * 0.2, 14);
|
|
2929
|
+
}
|
|
2930
|
+
parts.trigger = box2(group, 0.012, 0.02, 0.016, M.dark, 0.1, 0.085, 0.14, 0.3);
|
|
2931
|
+
parts.safety = box2(group, 0.014, 6e-3, 0.01, M.dark, 0.2, 0.073, -0.076, 0.4);
|
|
2932
|
+
}
|
|
2933
|
+
function buildFeed(kit, parts, group) {
|
|
2934
|
+
const { M, box: box2, cylinderX } = kit;
|
|
2935
|
+
const belt = kit.pivot(group, 0.2, -0.13, -0.16);
|
|
2936
|
+
parts.belt = belt;
|
|
2937
|
+
box2(belt, 0.16, 0.12, 0.1, M.olive, 0, 0, 0);
|
|
2938
|
+
box2(belt, 0.162, 0.012, 0.102, M.dark, 0, 0.056, 0);
|
|
2939
|
+
for (let i = 0; i < 8; i++) cylinderX(belt, 46e-4, 0.04, M.brass, -0.07 + i * 0.02, 0.075, 0.04, 10);
|
|
2940
|
+
box2(group, 0.1, 0.03, 0.1, M.metal, 0.22, -0.06, -0.1, 0, 0, 0.5);
|
|
2941
|
+
const cover = kit.pivot(group, 0.1, 0.075, 0);
|
|
2942
|
+
parts.cover = cover;
|
|
2943
|
+
box2(cover, 0.24, 0.012, 0.15, M.metal, 0.12, 6e-3, 0);
|
|
2944
|
+
const bolt = kit.pivot(group);
|
|
2945
|
+
parts.bolt = bolt;
|
|
2946
|
+
parts.stroke = 0.03;
|
|
2947
|
+
box2(bolt, 0.03, 0.012, 0.012, M.steelL, 0.28, 0.02, 0.078);
|
|
2948
|
+
}
|
|
2949
|
+
function buildM134(kit) {
|
|
2950
|
+
const group = kit.group();
|
|
2951
|
+
const parts = { family: "belt", kick: 0.9, glow: [] };
|
|
2952
|
+
buildHousing(kit, parts, group);
|
|
2953
|
+
buildBarrelCluster(kit, parts, group);
|
|
2954
|
+
buildFeed(kit, parts, group);
|
|
2955
|
+
parts.frames = {
|
|
2956
|
+
length: BARREL_END,
|
|
2957
|
+
barrelY: 0,
|
|
2958
|
+
barrelRadius: BARREL_RADIUS,
|
|
2959
|
+
receiverStart: 0.06,
|
|
2960
|
+
receiverEnd: 0.48,
|
|
2961
|
+
receiverHeight: 0.14,
|
|
2962
|
+
receiverWidth: 0.15,
|
|
2963
|
+
handguardEnd: 0.6,
|
|
2964
|
+
magazineX: 0.2,
|
|
2965
|
+
gripX: 0.06,
|
|
2966
|
+
hasRail: false,
|
|
2967
|
+
// the tripod head, where the gun balances (the tripod of the counters joins here too)
|
|
2968
|
+
underbarrel: [0.4, -0.1]
|
|
2969
|
+
};
|
|
2970
|
+
parts.muzzle = kit.mark(group, BARREL_END + 0.01, 0, 0);
|
|
2971
|
+
parts.eject = kit.mark(group, 0.3, -0.07, 0.08);
|
|
2972
|
+
return { group, parts };
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2095
2975
|
// src/models/m2.ts
|
|
2096
2976
|
function buildM2(kit) {
|
|
2097
2977
|
const { M, box: box2, cylinderX, cylinderZ } = kit;
|
|
@@ -2157,7 +3037,7 @@ function buildP90(kit) {
|
|
|
2157
3037
|
}
|
|
2158
3038
|
|
|
2159
3039
|
// 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"];
|
|
3040
|
+
var SINGLE_PARTS = ["slide", "bolt", "boltHandle", "mag", "trigger", "hammer", "lever", "pump", "belt", "cover", "crane", "cylinder", "barrels", "safety", "muzzle", "eject", "loadPort", "topLoad", "rear", "round", "gauge", "stock", "ugl"];
|
|
2161
3041
|
function partRegistry(parts) {
|
|
2162
3042
|
const registry = /* @__PURE__ */ new Map();
|
|
2163
3043
|
for (const key of SINGLE_PARTS) {
|
|
@@ -2170,7 +3050,7 @@ function partRegistry(parts) {
|
|
|
2170
3050
|
}
|
|
2171
3051
|
|
|
2172
3052
|
// src/models/pistol-slide.ts
|
|
2173
|
-
var
|
|
3053
|
+
var PI5 = Math.PI;
|
|
2174
3054
|
function buildSlide(kit, slide2, parts, options, style, size, metal2) {
|
|
2175
3055
|
const { M, box: box2, cylinderX } = kit;
|
|
2176
3056
|
const { length: L, width: w } = size;
|
|
@@ -2198,8 +3078,8 @@ function buildSlide(kit, slide2, parts, options, style, size, metal2) {
|
|
|
2198
3078
|
if (style.tri) {
|
|
2199
3079
|
const tube = new kit.THREE.Mesh(new kit.THREE.CylinderGeometry(0.016, 0.016, L * 0.6, 3), metal2);
|
|
2200
3080
|
tube.castShadow = tube.receiveShadow = true;
|
|
2201
|
-
tube.rotation.set(0, 0, -
|
|
2202
|
-
tube.rotateY(
|
|
3081
|
+
tube.rotation.set(0, 0, -PI5 / 2);
|
|
3082
|
+
tube.rotateY(PI5 / 6);
|
|
2203
3083
|
tube.position.set(L * 0.7, slideHeight * 0.08, 0);
|
|
2204
3084
|
slide2.add(tube);
|
|
2205
3085
|
heatify(parts.glow, tube);
|
|
@@ -2251,7 +3131,7 @@ var STYLES = {
|
|
|
2251
3131
|
};
|
|
2252
3132
|
|
|
2253
3133
|
// src/models/pistol.ts
|
|
2254
|
-
var
|
|
3134
|
+
var PI6 = Math.PI;
|
|
2255
3135
|
function buildFrame(kit, group, parts, options, style, size, materials) {
|
|
2256
3136
|
const { M, box: box2, cylinderZ, sphere } = kit;
|
|
2257
3137
|
const { length: L, height: h, width: w } = size;
|
|
@@ -2342,8 +3222,8 @@ function buildPistol(kit, options) {
|
|
|
2342
3222
|
box2(group, 4e-3, 0.03, 9e-3, frameMetal, gx + 0.073, ty - 0.02, 0, 0.2);
|
|
2343
3223
|
box2(group, 8e-3, 6e-3, 9e-3, frameMetal, gx + 0.074, ty - 0.037, 0, 0.4);
|
|
2344
3224
|
} else {
|
|
2345
|
-
const guard = kit.torus(group, new kit.THREE.TorusGeometry(0.019, 22e-4, 6, 16,
|
|
2346
|
-
guard.rotation.z =
|
|
3225
|
+
const guard = kit.torus(group, new kit.THREE.TorusGeometry(0.019, 22e-4, 6, 16, PI6), frameMetal, gx + 0.05, ty - 0.014, 0);
|
|
3226
|
+
guard.rotation.z = PI6;
|
|
2347
3227
|
guard.scale.set(1.2, 1, 1);
|
|
2348
3228
|
}
|
|
2349
3229
|
const isGlock = options.st === "glock" || !options.st;
|
|
@@ -2367,7 +3247,7 @@ function buildPistol(kit, options) {
|
|
|
2367
3247
|
}
|
|
2368
3248
|
|
|
2369
3249
|
// src/models/revolver.ts
|
|
2370
|
-
var
|
|
3250
|
+
var PI7 = Math.PI;
|
|
2371
3251
|
var DEFAULT_CHAMBERS = 6;
|
|
2372
3252
|
function buildRevolver(kit, options) {
|
|
2373
3253
|
const { M, box: box2, cylinderX } = kit;
|
|
@@ -2385,12 +3265,12 @@ function buildRevolver(kit, options) {
|
|
|
2385
3265
|
parts.cylinder = cylinder;
|
|
2386
3266
|
cylinderX(cylinder, 0.0215, 0.042, metal2, 0, 0, 0, 24);
|
|
2387
3267
|
for (let i = 0; i < chambers; i++) {
|
|
2388
|
-
const angle = i * 2 *
|
|
3268
|
+
const angle = i * 2 * PI7 / chambers;
|
|
2389
3269
|
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
3270
|
}
|
|
2391
3271
|
parts.chambers = [];
|
|
2392
3272
|
for (let i = 0; i < chambers; i++) {
|
|
2393
|
-
const angle = i * 2 *
|
|
3273
|
+
const angle = i * 2 * PI7 / chambers + PI7 / chambers;
|
|
2394
3274
|
parts.chambers.push(kit.mark(cylinder, -0.022, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013));
|
|
2395
3275
|
cylinderX(cylinder, 46e-4, 2e-3, M.brass, -0.0215, Math.cos(angle) * 0.013, Math.sin(angle) * 0.013, 12);
|
|
2396
3276
|
}
|
|
@@ -2414,27 +3294,55 @@ function buildRevolver(kit, options) {
|
|
|
2414
3294
|
return { group, parts };
|
|
2415
3295
|
}
|
|
2416
3296
|
|
|
3297
|
+
// src/models/attachments/muzzle-brake.ts
|
|
3298
|
+
function buildBrake(kit, parent, kind, x, y, barrelRadius, heat = () => void 0) {
|
|
3299
|
+
const { M, box: box2, cylinderX } = kit;
|
|
3300
|
+
const scale = kind === 2 ? 3.2 : 2;
|
|
3301
|
+
const length = kind === 2 ? 0.12 : 0.06;
|
|
3302
|
+
heat(cylinderX(parent, barrelRadius * scale, length, M.dark, x - length / 2, y, 0, kind === 2 ? 8 : 16));
|
|
3303
|
+
for (let i = 0; i < 3; i++) box2(parent, length * 0.16, barrelRadius * scale * 1.3, barrelRadius * scale * 2.2, M.hole, x - length * 0.25 - i * length * 0.25, y, 0);
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
// src/models/capture.ts
|
|
3307
|
+
function captured(parent, draw) {
|
|
3308
|
+
const before = parent.children.length;
|
|
3309
|
+
draw();
|
|
3310
|
+
return parent.children.slice(before);
|
|
3311
|
+
}
|
|
3312
|
+
function recordFitting(parts, id, parent, draw) {
|
|
3313
|
+
parts.fittings = { ...parts.fittings, [id]: captured(parent, draw) };
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
// src/models/attachments/bipod.ts
|
|
3317
|
+
function buildBipod(kit, parent, x, y) {
|
|
3318
|
+
const { M, box: box2, cylinderX } = kit;
|
|
3319
|
+
cylinderX(parent, 8e-3, 0.02, M.dark, x, y, 0, 12);
|
|
3320
|
+
for (const side of [-1, 1]) {
|
|
3321
|
+
const leg = kit.pivot(parent, x, y, side * 0.012);
|
|
3322
|
+
leg.rotation.set(side * 0.15, 0, -0.35);
|
|
3323
|
+
box2(leg, 6e-3, 0.2, 6e-3, M.dark, 0, -0.1, 0);
|
|
3324
|
+
box2(leg, 0.01, 6e-3, 0.01, M.rubber, 0, -0.2, 0);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
var buildBipodModel = ({ kit }) => {
|
|
3328
|
+
const group = kit.group();
|
|
3329
|
+
buildBipod(kit, group, 0, 0);
|
|
3330
|
+
return group;
|
|
3331
|
+
};
|
|
3332
|
+
|
|
2417
3333
|
// src/models/rifle-fittings.ts
|
|
3334
|
+
var SCOPE_IDS = { 1: "red_dot", 2: "acog_4x", 3: "scope_3_9x", 4: "scope_6_24x" };
|
|
2418
3335
|
function buildFittings(context, gripX) {
|
|
2419
3336
|
const { kit, group, parts, options: o, furniture: furn, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
|
|
2420
3337
|
const { M, box: box2, cylinderX, cylinderZ, sphere } = kit;
|
|
2421
3338
|
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);
|
|
3339
|
+
if (o.scope) recordFitting(parts, SCOPE_IDS[o.scope] ?? "scope", group, () => scope(kit, group, o.scope, middle + 0.03, hR / 2 + (o.rail ? 6e-3 : 0), o.scope === 4 ? -0.02 : 0));
|
|
2423
3340
|
if (o.carry) {
|
|
2424
3341
|
box2(group, 0.15, 0.026, 0.014, M.metal, middle, hR / 2 + 0.03, 0);
|
|
2425
3342
|
box2(group, 0.012, 0.03, 0.014, M.metal, middle + 0.07, hR / 2 + 0.014, 0);
|
|
2426
3343
|
box2(group, 0.012, 0.03, 0.014, M.metal, middle - 0.07, hR / 2 + 0.014, 0);
|
|
2427
3344
|
}
|
|
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
|
-
}
|
|
3345
|
+
if (o.bipod) recordFitting(parts, "bipod", group, () => buildBipod(kit, group, x1 + (L - x1) * 0.45 + 0.04, by - 0.012));
|
|
2438
3346
|
if (o.bolt) {
|
|
2439
3347
|
const handle = kit.pivot(group, x1 - 0.045, by, 0);
|
|
2440
3348
|
parts.boltHandle = handle;
|
|
@@ -2472,7 +3380,7 @@ function buildFittings(context, gripX) {
|
|
|
2472
3380
|
}
|
|
2473
3381
|
|
|
2474
3382
|
// src/models/rifle-forend.ts
|
|
2475
|
-
var
|
|
3383
|
+
var PI8 = Math.PI;
|
|
2476
3384
|
function buildForend(context) {
|
|
2477
3385
|
const { kit, group, options: o, furniture: furn, length: L, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, barrelRadius: r, barrelY: by } = context;
|
|
2478
3386
|
const { M, box: box2 } = kit;
|
|
@@ -2484,9 +3392,9 @@ function buildForend(context) {
|
|
|
2484
3392
|
if (o.hg === "rail") {
|
|
2485
3393
|
box2(group, handguardLength, hh, wR * 1.04, M.dark, hx, by - 2e-3, 0);
|
|
2486
3394
|
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,
|
|
2488
|
-
rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, -wR * 0.54, wR * 0.4, -
|
|
2489
|
-
rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - hh / 2, 0, wR * 0.6,
|
|
3395
|
+
rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, wR * 0.54, wR * 0.4, PI8 / 2);
|
|
3396
|
+
rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - 2e-3, -wR * 0.54, wR * 0.4, -PI8 / 2);
|
|
3397
|
+
rail(kit, group, x1 + 0.01, handguardLength - 0.02, by - hh / 2, 0, wR * 0.6, PI8);
|
|
2490
3398
|
} else {
|
|
2491
3399
|
const material = o.hg === "wood" ? M.wood : furn;
|
|
2492
3400
|
if (o.ak) {
|
|
@@ -2516,6 +3424,46 @@ function buildForend(context) {
|
|
|
2516
3424
|
} else box2(group, 0.012, 0.024, 0.012, M.dark, sightX - 0.04, by + hR * 0.4 + 0.012, 0);
|
|
2517
3425
|
}
|
|
2518
3426
|
|
|
3427
|
+
// src/models/attachments/belt-box.ts
|
|
3428
|
+
function buildBeltBox(kit, belt) {
|
|
3429
|
+
const { M, box: box2, cylinderX } = kit;
|
|
3430
|
+
box2(belt, 0.11, 0.1, 0.08, M.olive, 0, 0, 0);
|
|
3431
|
+
box2(belt, 0.112, 0.012, 0.082, M.dark, 0, 0.046, 0);
|
|
3432
|
+
for (let i = 0; i < 7; i++) {
|
|
3433
|
+
cylinderX(belt, 42e-4, 0.034, M.brass, -0.04 + i * 0.012, 0.07, 0.035, 10);
|
|
3434
|
+
box2(belt, 6e-3, 6e-3, 0.012, M.metal, -0.04 + i * 0.012, 0.066, 0.035);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
// src/models/attachments/drum.ts
|
|
3439
|
+
function buildDrum(kit, magazine) {
|
|
3440
|
+
const { M, box: box2, cylinderZ } = kit;
|
|
3441
|
+
cylinderZ(magazine, 0.075, 0.07, M.dark, 0.02, -0.085, 0, 22);
|
|
3442
|
+
cylinderZ(magazine, 0.05, 0.074, M.metal, 0.02, -0.085, 0, 18);
|
|
3443
|
+
box2(magazine, 0.05, 0.04, 0.03, M.dark, 0, -0.02, 0);
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
// src/models/helical-magazine.ts
|
|
3447
|
+
var PI9 = Math.PI;
|
|
3448
|
+
var LENGTH = 0.26;
|
|
3449
|
+
var RADIUS = 0.026;
|
|
3450
|
+
var TURNS = 11;
|
|
3451
|
+
function buildHelicalMagazine(context) {
|
|
3452
|
+
const { kit, group, parts, receiverEnd, barrelY, barrelRadius } = context;
|
|
3453
|
+
const { M, cylinderX, box: box2 } = kit;
|
|
3454
|
+
const x = receiverEnd + LENGTH / 2 + 0.02;
|
|
3455
|
+
const y = barrelY - barrelRadius - RADIUS - 0.012;
|
|
3456
|
+
const magazine = kit.pivot(group, x, y, 0);
|
|
3457
|
+
parts.mag = magazine;
|
|
3458
|
+
cylinderX(magazine, RADIUS, LENGTH, M.dark, 0, 0, 0, 20);
|
|
3459
|
+
cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, -LENGTH / 2, 0, 0, 20);
|
|
3460
|
+
cylinderX(magazine, RADIUS * 1.06, 0.01, M.metal, LENGTH / 2, 0, 0, 20);
|
|
3461
|
+
for (let i = 0; i < TURNS; i++) box2(magazine, 0.012, RADIUS * 2.15, 0.014, M.metal, -LENGTH / 2 + (i + 0.5) * LENGTH / TURNS, 0, 0, 0, 0, i * PI9 / 4);
|
|
3462
|
+
box2(magazine, 0.05, 0.03, 0.03, M.dark, -LENGTH / 2 - 0.02, RADIUS * 0.4, 0);
|
|
3463
|
+
parts.magDrop = 0.16;
|
|
3464
|
+
return x;
|
|
3465
|
+
}
|
|
3466
|
+
|
|
2519
3467
|
// src/models/rifle-controls.ts
|
|
2520
3468
|
function buildGrip(context) {
|
|
2521
3469
|
const { kit, group, parts, options: o, furniture: furn, receiverStart: x0, receiverHeight: hR, receiverWidth: wR } = context;
|
|
@@ -2536,7 +3484,8 @@ function buildGrip(context) {
|
|
|
2536
3484
|
}
|
|
2537
3485
|
function buildMagazine(context, gx) {
|
|
2538
3486
|
const { kit, group, parts, options: o, receiverStart: x0, receiverHeight: hR, receiverWidth: wR, receiverLength: RL } = context;
|
|
2539
|
-
const { M, box: box2
|
|
3487
|
+
const { M, box: box2 } = kit;
|
|
3488
|
+
if (o.mag === "helical") return buildHelicalMagazine(context);
|
|
2540
3489
|
const mx = o.magAt != null ? x0 + o.magAt : gx + 0.075;
|
|
2541
3490
|
const scale = o.magLen || 1;
|
|
2542
3491
|
const magazine = kit.pivot(group, mx, -hR / 2, 0);
|
|
@@ -2577,9 +3526,7 @@ function buildMagazine(context, gx) {
|
|
|
2577
3526
|
box2(magazine, 0.056, 7e-3, 0.028, material, px, py - 2e-3, 0, angle);
|
|
2578
3527
|
parts.magDrop = length + 0.03;
|
|
2579
3528
|
} else if (o.mag === "drum") {
|
|
2580
|
-
|
|
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);
|
|
3529
|
+
buildDrum(kit, magazine);
|
|
2583
3530
|
parts.magDrop = 0.2;
|
|
2584
3531
|
} else if (o.mag === "internal") {
|
|
2585
3532
|
box2(group, 0.08, 0.025, 0.03, context.isTraditional ? context.furniture : M.metal, mx, -hR / 2 - 0.012, 0);
|
|
@@ -2588,12 +3535,7 @@ function buildMagazine(context, gx) {
|
|
|
2588
3535
|
if (o.mag === "belt") {
|
|
2589
3536
|
const belt = kit.pivot(group, mx + 0.02, -hR / 2 - 0.05, -0.045);
|
|
2590
3537
|
parts.belt = belt;
|
|
2591
|
-
|
|
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
|
-
}
|
|
3538
|
+
buildBeltBox(kit, belt);
|
|
2597
3539
|
const cover = kit.pivot(group, x0 + 0.01, hR / 2, 0);
|
|
2598
3540
|
parts.cover = cover;
|
|
2599
3541
|
box2(cover, RL - 0.03, 0.014, wR * 1.05, M.metal, (RL - 0.03) / 2, 7e-3, 0);
|
|
@@ -2643,7 +3585,7 @@ function buildStock(context, magazineX) {
|
|
|
2643
3585
|
}
|
|
2644
3586
|
|
|
2645
3587
|
// src/models/rifle.ts
|
|
2646
|
-
var
|
|
3588
|
+
var PI10 = Math.PI;
|
|
2647
3589
|
function buildReceiver(context) {
|
|
2648
3590
|
const { kit, group, parts, options: o, length: L, receiverStart: x0, receiverEnd: x1, receiverHeight: hR, receiverWidth: wR, receiverLength: RL, barrelY: by } = context;
|
|
2649
3591
|
const { M, box: box2, cylinderX, cylinderY, cylinderZ, sphere } = kit;
|
|
@@ -2696,16 +3638,16 @@ function buildBarrel(context) {
|
|
|
2696
3638
|
}
|
|
2697
3639
|
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
3640
|
if (o.brake) {
|
|
2699
|
-
const
|
|
2700
|
-
|
|
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);
|
|
3641
|
+
const kind = o.brake === 2 ? 2 : 1;
|
|
3642
|
+
recordFitting(parts, kind === 2 ? "muzzle_brake_heavy" : "muzzle_brake", group, () => buildBrake(kit, group, kind, L, by, r, (body) => void heatify(parts.glow, body)));
|
|
2703
3643
|
} else if (!o.noFH) {
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
3644
|
+
recordFitting(parts, "muzzle_default", group, () => {
|
|
3645
|
+
heatify(parts.glow, cylinderX(group, r * 1.45, 0.042, M.dark, L - 0.021, by, 0, 16));
|
|
3646
|
+
for (let i = 0; i < 4; i++) {
|
|
3647
|
+
const angle = i * PI10 / 2 + PI10 / 4;
|
|
3648
|
+
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);
|
|
3649
|
+
}
|
|
3650
|
+
});
|
|
2709
3651
|
}
|
|
2710
3652
|
cylinderX(group, r * 0.55, 4e-3, M.hole, L + 5e-4, by, 0, 12);
|
|
2711
3653
|
}
|
|
@@ -2751,8 +3693,23 @@ function buildRifle(kit, options) {
|
|
|
2751
3693
|
buildForend(context);
|
|
2752
3694
|
const gripX = buildGrip(context);
|
|
2753
3695
|
const magazineX = buildMagazine(context, gripX);
|
|
2754
|
-
buildStock(context, magazineX);
|
|
3696
|
+
parts.stockMeshes = context.isTraditional ? [] : captured(group, () => buildStock(context, magazineX));
|
|
3697
|
+
if (context.isTraditional) buildStock(context, magazineX);
|
|
2755
3698
|
buildFittings(context, gripX);
|
|
3699
|
+
const hasHandguard = !context.isTraditional && options.hg !== "none";
|
|
3700
|
+
parts.frames = {
|
|
3701
|
+
length,
|
|
3702
|
+
barrelY: context.barrelY,
|
|
3703
|
+
barrelRadius: context.barrelRadius,
|
|
3704
|
+
receiverStart,
|
|
3705
|
+
receiverEnd,
|
|
3706
|
+
receiverHeight,
|
|
3707
|
+
receiverWidth: context.receiverWidth,
|
|
3708
|
+
handguardEnd: receiverEnd + (length - receiverEnd) * (hasHandguard ? options.hgFrac || 0.5 : 0.4),
|
|
3709
|
+
magazineX,
|
|
3710
|
+
gripX,
|
|
3711
|
+
hasRail: Boolean(options.rail)
|
|
3712
|
+
};
|
|
2756
3713
|
const { barrelY: by, receiverWidth: wR } = context;
|
|
2757
3714
|
const ejectX = receiverEnd - (options.ejectBack || 0.07);
|
|
2758
3715
|
const ejectY = options.ar ? receiverHeight * 0.2 : by;
|
|
@@ -2784,6 +3741,7 @@ var BUILDERS = {
|
|
|
2784
3741
|
p90: (kit) => buildP90(kit),
|
|
2785
3742
|
break: buildBreakAction,
|
|
2786
3743
|
m2: (kit) => buildM2(kit),
|
|
3744
|
+
m134: (kit) => buildM134(kit),
|
|
2787
3745
|
launcher: buildLauncher
|
|
2788
3746
|
};
|
|
2789
3747
|
var nameParts = (parts) => {
|
|
@@ -2814,9 +3772,26 @@ function buildWeaponModelById(THREE, id) {
|
|
|
2814
3772
|
built.group.position.x = -new THREE.Box3().setFromObject(built.group).min.x;
|
|
2815
3773
|
} else root = fitLength(kit, built.group, entry.length / 1e3);
|
|
2816
3774
|
root.name = id;
|
|
2817
|
-
|
|
3775
|
+
const model = { id, root, inner: built.group, parts: built.parts };
|
|
3776
|
+
if (!built.parts.frames) built.parts.frames = deriveFrames(THREE, model);
|
|
3777
|
+
return model;
|
|
2818
3778
|
}
|
|
2819
3779
|
|
|
3780
|
+
// src/models/attachments/tripod.ts
|
|
3781
|
+
var LEG = { length: 0.62, width: 0.012 };
|
|
3782
|
+
function buildTripodLegs(kit, parent) {
|
|
3783
|
+
const { length, width } = LEG;
|
|
3784
|
+
kit.box(parent, 0.03, 0.06, 0.03, kit.M.metal, 0, 0, 0);
|
|
3785
|
+
kit.box(parent, width, length, width, kit.M.metal, -0.2, -0.25, 0, 0.75);
|
|
3786
|
+
kit.box(parent, width, length, width, kit.M.metal, 0.22, -0.25, 0.16, -0.75, 0, 0.3);
|
|
3787
|
+
kit.box(parent, width, length, width, kit.M.metal, 0.22, -0.25, -0.16, -0.75, 0, -0.3);
|
|
3788
|
+
}
|
|
3789
|
+
var buildTripod = ({ kit }) => {
|
|
3790
|
+
const group = kit.group();
|
|
3791
|
+
buildTripodLegs(kit, group);
|
|
3792
|
+
return group;
|
|
3793
|
+
};
|
|
3794
|
+
|
|
2820
3795
|
// src/props.ts
|
|
2821
3796
|
var box = (id, material, size, position, extra = {}) => ({ id, material, shape: { kind: "box", size }, position, ...extra });
|
|
2822
3797
|
var joint = (a, b, kind) => ({ a, b, kind });
|
|
@@ -2889,9 +3864,10 @@ function buildWeaponModel(THREE, def) {
|
|
|
2889
3864
|
return buildProp(THREE, weaponProp(def));
|
|
2890
3865
|
}
|
|
2891
3866
|
var MODEL_BODY = "body";
|
|
2892
|
-
function buildModeledProp(THREE, def, name) {
|
|
3867
|
+
function buildModeledProp(THREE, def, name, decorate) {
|
|
2893
3868
|
const model = buildWeaponModelById(THREE, def.id);
|
|
2894
3869
|
if (!model) return void 0;
|
|
3870
|
+
decorate?.(model.root);
|
|
2895
3871
|
const group = new THREE.Group();
|
|
2896
3872
|
group.name = name;
|
|
2897
3873
|
const box2 = new THREE.Box3().setFromObject(model.root);
|
|
@@ -2908,6 +3884,13 @@ function buildModeledProp(THREE, def, name) {
|
|
|
2908
3884
|
group.userData.footprint = [size.x, size.y, size.z];
|
|
2909
3885
|
return group;
|
|
2910
3886
|
}
|
|
3887
|
+
var TRIPOD_HEAD = { x: 0.4, y: -0.1 };
|
|
3888
|
+
function buildTripodMountedProp(THREE, def, name) {
|
|
3889
|
+
return buildModeledProp(THREE, def, name, (root) => {
|
|
3890
|
+
const kit = modelKit(THREE);
|
|
3891
|
+
buildTripodLegs(kit, kit.pivot(root, TRIPOD_HEAD.x, TRIPOD_HEAD.y, 0));
|
|
3892
|
+
});
|
|
3893
|
+
}
|
|
2911
3894
|
function modeledPropSpec(root, def, name) {
|
|
2912
3895
|
const footprint = root.userData.footprint ?? [0.3, 0.1, 0.1];
|
|
2913
3896
|
const size = [Math.max(0.02, footprint[0]), Math.max(0.02, footprint[1]), Math.max(0.02, footprint[2])];
|
|
@@ -2934,7 +3917,7 @@ function createBoxFx(options) {
|
|
|
2934
3917
|
const shell = new THREE.MeshStandardMaterial({ color: 11022891, roughness: 0.6 });
|
|
2935
3918
|
const steel = new THREE.MeshStandardMaterial({ color: 3093047, metalness: 0.5, roughness: 0.5 });
|
|
2936
3919
|
const hand = new THREE.Group();
|
|
2937
|
-
hand.name =
|
|
3920
|
+
hand.name = IN_HAND_NODE_NAME;
|
|
2938
3921
|
scene.add(hand);
|
|
2939
3922
|
let model = null;
|
|
2940
3923
|
let inHand = null;
|
|
@@ -3154,6 +4137,42 @@ function composeClip(id, tweens, events = [], minDuration = 0) {
|
|
|
3154
4137
|
}
|
|
3155
4138
|
var renamed = (clip, id) => ({ ...clip, id });
|
|
3156
4139
|
|
|
4140
|
+
// src/animation/accessory-clips.ts
|
|
4141
|
+
var PI11 = Math.PI;
|
|
4142
|
+
var STOCK_SECONDS = 0.6;
|
|
4143
|
+
var TOGGLE_SECONDS = 0.14;
|
|
4144
|
+
var TOGGLE_TILT = 0.02;
|
|
4145
|
+
var LAUNCHER_SLIDE = 0.05;
|
|
4146
|
+
var LAUNCHER_RELOAD = { open: 0, loaded: 0.4, close: 0.8, end: 1.05, slide: 0.3, back: 0.25 };
|
|
4147
|
+
var LAUNCHER_KICK = 0.03;
|
|
4148
|
+
function stockClips(context) {
|
|
4149
|
+
const { stock, stockMove } = context.parts;
|
|
4150
|
+
if (!stock || !stockMove) return [];
|
|
4151
|
+
if (stockMove.kind === "fold") {
|
|
4152
|
+
return [
|
|
4153
|
+
composeClip("stock_fold", [turn("stock", stockMove.axis, 0, STOCK_SECONDS, 0, PI11)]),
|
|
4154
|
+
composeClip("stock_unfold", [turn("stock", stockMove.axis, 0, STOCK_SECONDS, PI11, 0)])
|
|
4155
|
+
];
|
|
4156
|
+
}
|
|
4157
|
+
return [
|
|
4158
|
+
composeClip("stock_collapse", [move("stock", "x", 0, STOCK_SECONDS, 0, stockMove.travel)]),
|
|
4159
|
+
composeClip("stock_extend", [move("stock", "x", 0, STOCK_SECONDS, stockMove.travel, 0)])
|
|
4160
|
+
];
|
|
4161
|
+
}
|
|
4162
|
+
var toggleClip = () => composeClip("toggle", [turn(ROOT, "z", 0, TOGGLE_SECONDS / 2, 0, TOGGLE_TILT), turn(ROOT, "z", TOGGLE_SECONDS / 2, TOGGLE_SECONDS / 2, TOGGLE_TILT, 0)]);
|
|
4163
|
+
function launcherClips(context) {
|
|
4164
|
+
if (!context.parts.ugl) return [];
|
|
4165
|
+
const { open, loaded, close, end, slide: slide2, back } = LAUNCHER_RELOAD;
|
|
4166
|
+
const reload = composeClip(
|
|
4167
|
+
"reload_ugl",
|
|
4168
|
+
[move("ugl", "x", open, slide2, 0, LAUNCHER_SLIDE), move("ugl", "x", close, back, LAUNCHER_SLIDE, 0)],
|
|
4169
|
+
[stage("breech_open", open), stage("round_in", loaded), stage("breech_close", close)],
|
|
4170
|
+
end
|
|
4171
|
+
);
|
|
4172
|
+
const shot = composeClip("shot_ugl", [move(ROOT, "x", 0, 0.04, 0, -LAUNCHER_KICK), move(ROOT, "x", 0.04, 0.2, -LAUNCHER_KICK, 0)], [cue(0, "muzzle_flash", { at: "ugl" })]);
|
|
4173
|
+
return [reload, shot];
|
|
4174
|
+
}
|
|
4175
|
+
|
|
3157
4176
|
// src/animation/magazine-clips.ts
|
|
3158
4177
|
var MAGAZINE_SWAP_SECONDS = 0.82;
|
|
3159
4178
|
var MAGAZINE_IN_AT = 0.45;
|
|
@@ -3377,10 +4396,11 @@ var recoil = (context) => {
|
|
|
3377
4396
|
turn(ROOT, "z", 0.03, 0.18, 0.045 * kick, 0)
|
|
3378
4397
|
];
|
|
3379
4398
|
};
|
|
4399
|
+
var spinBarrels = (context) => context.parts.barrels && context.family === "belt" ? [turn("barrels", "x", 0, 0.03, 0, Math.PI / 3)] : [];
|
|
3380
4400
|
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
4401
|
function shotClip(id, context, locked) {
|
|
3382
4402
|
const { parts, family } = context;
|
|
3383
|
-
const tweens = [...pullTrigger(context), ...recoil(context), ...hammerFall(context)];
|
|
4403
|
+
const tweens = [...pullTrigger(context), ...recoil(context), ...hammerFall(context), ...spinBarrels(context)];
|
|
3384
4404
|
const events = [cue(0, "muzzle_flash")];
|
|
3385
4405
|
if (BLOWBACK.has(family)) {
|
|
3386
4406
|
const part = blowbackPart(context);
|
|
@@ -3436,7 +4456,10 @@ function buildToolClips(context) {
|
|
|
3436
4456
|
...faultClips(context),
|
|
3437
4457
|
...clearClips(context),
|
|
3438
4458
|
...safetyClips(context),
|
|
3439
|
-
drawClip()
|
|
4459
|
+
drawClip(),
|
|
4460
|
+
...stockClips(context),
|
|
4461
|
+
toggleClip(),
|
|
4462
|
+
...launcherClips(context)
|
|
3440
4463
|
];
|
|
3441
4464
|
const set = {};
|
|
3442
4465
|
for (const clip of clips) if (clip) set[clip.id] = clip;
|
|
@@ -3463,11 +4486,494 @@ var weaponEventOf = (event) => {
|
|
|
3463
4486
|
return { type: "clear", fault: event.fault, phase: event.phase, at: 0 };
|
|
3464
4487
|
case "safety":
|
|
3465
4488
|
return { type: "safety", on: event.on, at: 0 };
|
|
4489
|
+
case "attachment":
|
|
4490
|
+
return { type: "attachment", action: event.action, id: event.attachment, mount: event.mount, at: 0 };
|
|
4491
|
+
case "toggle":
|
|
4492
|
+
return { type: "toggle", id: event.attachment, on: event.on, at: 0 };
|
|
4493
|
+
case "stock":
|
|
4494
|
+
return { type: "stock", state: event.state, kind: event.kind ?? "fold", at: 0 };
|
|
4495
|
+
case "underbarrel":
|
|
4496
|
+
return { type: "underbarrel", on: event.on, at: 0 };
|
|
3466
4497
|
default:
|
|
3467
4498
|
return void 0;
|
|
3468
4499
|
}
|
|
3469
4500
|
};
|
|
3470
4501
|
|
|
4502
|
+
// src/models/attachments/ammo-backpack.ts
|
|
4503
|
+
import { catenary, createCord } from "@adatechnology/game-interactables-three";
|
|
4504
|
+
var PACK_AT = [-0.55, -0.4, 0.1];
|
|
4505
|
+
var PACK_SIZE = [0.3, 0.42, 0.2];
|
|
4506
|
+
var BELT_SAG = 0.14;
|
|
4507
|
+
var BELT_POINTS = 16;
|
|
4508
|
+
var BELT_ROUND_EVERY = 2;
|
|
4509
|
+
var buildAmmoBackpack = ({ kit, frame, model }) => {
|
|
4510
|
+
const { THREE, M, box: box2, cylinderZ } = kit;
|
|
4511
|
+
const group = kit.group();
|
|
4512
|
+
const pack = [PACK_AT[0] - frame.position[0], PACK_AT[1] - frame.position[1], PACK_AT[2] - frame.position[2]];
|
|
4513
|
+
box2(group, ...PACK_SIZE, M.olive, ...pack);
|
|
4514
|
+
box2(group, PACK_SIZE[0] * 1.02, 0.03, PACK_SIZE[2] * 1.02, M.dark, pack[0], pack[1] + PACK_SIZE[1] / 2, pack[2]);
|
|
4515
|
+
box2(group, 0.04, PACK_SIZE[1] * 0.9, 0.012, M.tan, pack[0] + PACK_SIZE[0] / 2, pack[1], pack[2] - 0.04);
|
|
4516
|
+
const feed = kit.pivot(group, 0, 0, 0);
|
|
4517
|
+
buildBeltBox(kit, feed);
|
|
4518
|
+
const points = catenary([pack[0] + PACK_SIZE[0] / 2, pack[1] + PACK_SIZE[1] * 0.3, pack[2]], [0, 0, 0], BELT_SAG, BELT_POINTS);
|
|
4519
|
+
const strip = createCord(THREE, points, 35e-4, M.metal);
|
|
4520
|
+
group.add(strip);
|
|
4521
|
+
points.forEach((point, index) => {
|
|
4522
|
+
if (index % BELT_ROUND_EVERY === 0) cylinderZ(group, 45e-4, 0.02, M.brass, point[0], point[1], point[2], 8);
|
|
4523
|
+
});
|
|
4524
|
+
return {
|
|
4525
|
+
object: group,
|
|
4526
|
+
...model.parts.belt ? { hides: [model.parts.belt] } : {},
|
|
4527
|
+
dispose() {
|
|
4528
|
+
strip.geometry.dispose();
|
|
4529
|
+
}
|
|
4530
|
+
};
|
|
4531
|
+
};
|
|
4532
|
+
|
|
4533
|
+
// src/models/attachments/foregrip.ts
|
|
4534
|
+
var RIBS = 4;
|
|
4535
|
+
var buildForegrip = ({ kit, attachment }) => {
|
|
4536
|
+
const { M, box: box2, cylinderY } = kit;
|
|
4537
|
+
const length = attachment.dims?.length ?? 0.1;
|
|
4538
|
+
const group = kit.group();
|
|
4539
|
+
box2(group, 0.045, 0.014, 0.034, M.dark, 0, -7e-3, 0);
|
|
4540
|
+
cylinderY(group, 0.0135, length, M.poly, 0, -0.014 - length / 2, 0, 14);
|
|
4541
|
+
for (let i = 0; i < RIBS; i++) cylinderY(group, 0.0145, 4e-3, M.rubber, 0, -0.03 - i * (length / (RIBS + 1)), 0, 14);
|
|
4542
|
+
cylinderY(group, 0.0155, 0.01, M.dark, 0, -0.014 - length, 0, 14);
|
|
4543
|
+
return group;
|
|
4544
|
+
};
|
|
4545
|
+
|
|
4546
|
+
// src/models/attachments/laser.ts
|
|
4547
|
+
var DEFAULT_RANGE = 120;
|
|
4548
|
+
var DEFAULT_COLOR = 16711680;
|
|
4549
|
+
var BEAM_WIDTH = 25e-4;
|
|
4550
|
+
var DOT_RADIUS = 0.012;
|
|
4551
|
+
var buildLaser = ({ kit, attachment, frame, raycast }) => {
|
|
4552
|
+
const { THREE, M, box: box2, cylinderX } = kit;
|
|
4553
|
+
const length = attachment.dims?.length ?? 0.08;
|
|
4554
|
+
const half = (attachment.dims?.diameter ?? 0.025) / 2;
|
|
4555
|
+
const range = attachment.laser?.range ?? DEFAULT_RANGE;
|
|
4556
|
+
const color = attachment.laser?.color ?? DEFAULT_COLOR;
|
|
4557
|
+
const group = kit.group();
|
|
4558
|
+
const offset = frame.side === 0 ? { y: -(half + 4e-3), z: 0 } : { y: 0, z: frame.side * (half + 4e-3) };
|
|
4559
|
+
box2(group, 0.03, 0.012, 0.028, M.dark, 0, frame.side === 0 ? -2e-3 : 0, 0);
|
|
4560
|
+
box2(group, length, half * 2, half * 2, M.dark, 0, offset.y, offset.z);
|
|
4561
|
+
const emitter = new THREE.MeshBasicMaterial({ color: 5574929 });
|
|
4562
|
+
cylinderX(group, half * 0.5, 4e-3, emitter, length / 2 + 1e-3, offset.y, offset.z, 10);
|
|
4563
|
+
const glow = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.7, blending: THREE.AdditiveBlending, depthWrite: false });
|
|
4564
|
+
const beam = new THREE.Mesh(new THREE.BoxGeometry(1, BEAM_WIDTH, BEAM_WIDTH), glow);
|
|
4565
|
+
const dot = new THREE.Mesh(new THREE.SphereGeometry(DOT_RADIUS, 10, 8), new THREE.MeshBasicMaterial({ color }));
|
|
4566
|
+
const front = length / 2 + 4e-3;
|
|
4567
|
+
beam.position.set(front, offset.y, offset.z);
|
|
4568
|
+
dot.position.set(front, offset.y, offset.z);
|
|
4569
|
+
beam.visible = false;
|
|
4570
|
+
dot.visible = false;
|
|
4571
|
+
group.add(beam, dot);
|
|
4572
|
+
const origin = new THREE.Vector3();
|
|
4573
|
+
const direction = new THREE.Vector3();
|
|
4574
|
+
const scale = new THREE.Vector3();
|
|
4575
|
+
let isOn = false;
|
|
4576
|
+
return {
|
|
4577
|
+
object: group,
|
|
4578
|
+
setOn(on) {
|
|
4579
|
+
isOn = on;
|
|
4580
|
+
emitter.color.setHex(on ? color : 5574929);
|
|
4581
|
+
beam.visible = on;
|
|
4582
|
+
if (!on) dot.visible = false;
|
|
4583
|
+
},
|
|
4584
|
+
update() {
|
|
4585
|
+
if (!isOn) return;
|
|
4586
|
+
group.updateWorldMatrix(true, false);
|
|
4587
|
+
group.getWorldPosition(origin);
|
|
4588
|
+
direction.set(1, 0, 0).transformDirection(group.matrixWorld);
|
|
4589
|
+
group.getWorldScale(scale);
|
|
4590
|
+
const hit = raycast?.(origin, direction, range) ?? null;
|
|
4591
|
+
const reach = (hit ?? range) / Math.max(scale.x, 1e-6);
|
|
4592
|
+
beam.scale.x = reach;
|
|
4593
|
+
beam.position.x = front + reach / 2;
|
|
4594
|
+
dot.visible = hit !== null;
|
|
4595
|
+
dot.position.x = front + reach;
|
|
4596
|
+
},
|
|
4597
|
+
dispose() {
|
|
4598
|
+
beam.geometry.dispose();
|
|
4599
|
+
dot.geometry.dispose();
|
|
4600
|
+
glow.dispose();
|
|
4601
|
+
emitter.dispose();
|
|
4602
|
+
dot.material.dispose();
|
|
4603
|
+
}
|
|
4604
|
+
};
|
|
4605
|
+
};
|
|
4606
|
+
|
|
4607
|
+
// src/models/attachments/light.ts
|
|
4608
|
+
var LIGHT_INTENSITY = 150;
|
|
4609
|
+
var LENS_GLOW = 3;
|
|
4610
|
+
var DEFAULT_CONE_DEG = 30;
|
|
4611
|
+
var DEFAULT_RANGE2 = 60;
|
|
4612
|
+
var buildLight = ({ kit, attachment, frame }) => {
|
|
4613
|
+
const { THREE, M, box: box2, cylinderX } = kit;
|
|
4614
|
+
const length = attachment.dims?.length ?? 0.1;
|
|
4615
|
+
const radius = (attachment.dims?.diameter ?? 0.03) / 2;
|
|
4616
|
+
const group = kit.group();
|
|
4617
|
+
const offset = frame.side === 0 ? { y: -(radius + 4e-3), z: 0 } : { y: 0, z: frame.side * (radius + 4e-3) };
|
|
4618
|
+
box2(group, 0.035, 0.012, 0.03, M.dark, 0, frame.side === 0 ? -2e-3 : 0, 0);
|
|
4619
|
+
cylinderX(group, radius, length, M.dark, 0, offset.y, offset.z, 14);
|
|
4620
|
+
cylinderX(group, radius * 1.08, 0.012, M.metal, length / 2 - 6e-3, offset.y, offset.z, 14);
|
|
4621
|
+
const lens = new THREE.MeshStandardMaterial({ color: 16773832, roughness: 0.1, metalness: 0.2, emissive: 16773832, emissiveIntensity: 0 });
|
|
4622
|
+
cylinderX(group, radius * 0.82, 4e-3, lens, length / 2 + 1e-3, offset.y, offset.z, 14);
|
|
4623
|
+
const cone = (attachment.light?.coneDeg ?? DEFAULT_CONE_DEG) * (Math.PI / 180);
|
|
4624
|
+
const spot = new THREE.SpotLight(16773584, LIGHT_INTENSITY, attachment.light?.range ?? DEFAULT_RANGE2, cone / 2, 0.35, 1.2);
|
|
4625
|
+
spot.position.set(length / 2 + 0.01, offset.y, offset.z);
|
|
4626
|
+
spot.intensity = 0;
|
|
4627
|
+
const target = new THREE.Object3D();
|
|
4628
|
+
target.position.set(length / 2 + 1, offset.y, offset.z);
|
|
4629
|
+
group.add(spot, target);
|
|
4630
|
+
spot.target = target;
|
|
4631
|
+
return {
|
|
4632
|
+
object: group,
|
|
4633
|
+
setOn(on) {
|
|
4634
|
+
spot.intensity = on ? LIGHT_INTENSITY : 0;
|
|
4635
|
+
lens.emissiveIntensity = on ? LENS_GLOW : 0;
|
|
4636
|
+
},
|
|
4637
|
+
dispose() {
|
|
4638
|
+
lens.dispose();
|
|
4639
|
+
spot.dispose();
|
|
4640
|
+
}
|
|
4641
|
+
};
|
|
4642
|
+
};
|
|
4643
|
+
|
|
4644
|
+
// src/models/attachments/magazine.ts
|
|
4645
|
+
var EXTENSION_FRACTION = 0.5;
|
|
4646
|
+
var MAGAZINE_LEAN = 0.08;
|
|
4647
|
+
var buildDrumMagazine = ({ kit, factoryMagazine }) => {
|
|
4648
|
+
const group = kit.group();
|
|
4649
|
+
buildDrum(kit, group);
|
|
4650
|
+
return { object: group, hangsFrom: "magazine", hides: factoryMagazine };
|
|
4651
|
+
};
|
|
4652
|
+
var buildExtendedMagazine = ({ kit, model }) => {
|
|
4653
|
+
const { M, box: box2 } = kit;
|
|
4654
|
+
const length = Math.max(0.1, (model.parts.magDrop ?? 0.16) - 0.03);
|
|
4655
|
+
const extension = length * EXTENSION_FRACTION;
|
|
4656
|
+
const group = kit.group();
|
|
4657
|
+
const lean = Math.sin(MAGAZINE_LEAN) * (length + extension / 2);
|
|
4658
|
+
box2(group, 0.05, extension + 4e-3, 0.024, M.dark, lean, -(Math.cos(MAGAZINE_LEAN) * (length + extension / 2)), 0, MAGAZINE_LEAN);
|
|
4659
|
+
box2(group, 0.056, 7e-3, 0.028, M.dark, lean + Math.sin(MAGAZINE_LEAN) * extension / 2, -(Math.cos(MAGAZINE_LEAN) * (length + extension)) - 3e-3, 0);
|
|
4660
|
+
return { object: group, hangsFrom: "magazine" };
|
|
4661
|
+
};
|
|
4662
|
+
var buildDualCoupler = ({ kit, factoryMagazine }) => {
|
|
4663
|
+
const { M, box: box2 } = kit;
|
|
4664
|
+
const group = kit.group();
|
|
4665
|
+
const spare = kit.group();
|
|
4666
|
+
spare.position.set(0, 0, 0.032);
|
|
4667
|
+
for (const piece of factoryMagazine) spare.add(piece.clone(true));
|
|
4668
|
+
group.add(spare);
|
|
4669
|
+
box2(group, 0.05, 0.012, 0.04, M.tape, 0, -0.03, 0.016);
|
|
4670
|
+
return { object: group, hangsFrom: "magazine" };
|
|
4671
|
+
};
|
|
4672
|
+
|
|
4673
|
+
// src/models/attachments/muzzle-device.ts
|
|
4674
|
+
var buildMuzzleBrake = (kind) => ({ kit, frame }) => {
|
|
4675
|
+
const group = kit.group();
|
|
4676
|
+
buildBrake(kit, group, kind, 0, 0, frame.barrelRadius);
|
|
4677
|
+
return group;
|
|
4678
|
+
};
|
|
4679
|
+
var buildCompensator = ({ kit, frame }) => {
|
|
4680
|
+
const { M, box: box2, cylinderX } = kit;
|
|
4681
|
+
const radius = frame.barrelRadius * 1.9;
|
|
4682
|
+
const length = 0.07;
|
|
4683
|
+
const group = kit.group();
|
|
4684
|
+
cylinderX(group, radius, length, M.dark, -length / 2, 0, 0, 16);
|
|
4685
|
+
for (let i = 0; i < 3; i++) box2(group, length * 0.14, radius * 0.9, radius * 0.7, M.hole, -length * 0.2 - i * length * 0.25, radius * 0.75, 0);
|
|
4686
|
+
return group;
|
|
4687
|
+
};
|
|
4688
|
+
|
|
4689
|
+
// src/models/attachments/optic.ts
|
|
4690
|
+
var OPTICS = {
|
|
4691
|
+
red_dot: { kind: 1 },
|
|
4692
|
+
holographic_sight: { kind: 1 },
|
|
4693
|
+
acog_4x: { kind: 2 },
|
|
4694
|
+
scope_3_9x: { kind: 3 },
|
|
4695
|
+
scope_6_24x: { kind: 4 },
|
|
4696
|
+
night_vision_scope: { kind: 3, lens: 3538794 },
|
|
4697
|
+
thermal_scope: { kind: 3, lens: 16747066 },
|
|
4698
|
+
pso_1: { kind: 2 },
|
|
4699
|
+
fixed_scope_10x: { kind: 4 }
|
|
4700
|
+
};
|
|
4701
|
+
var ADAPTER_LENGTH = 0.1;
|
|
4702
|
+
var ADAPTER_BACK = 0.05;
|
|
4703
|
+
var tinted = (context, group, color) => {
|
|
4704
|
+
const { THREE, M } = context.kit;
|
|
4705
|
+
const lens = new THREE.MeshStandardMaterial({ color, roughness: 0.05, metalness: 0.3, transparent: true, opacity: 0.9, emissive: color, emissiveIntensity: 0.6 });
|
|
4706
|
+
group.traverse((node) => {
|
|
4707
|
+
const mesh = node;
|
|
4708
|
+
if (mesh.isMesh && mesh.material === M.lens) mesh.material = lens;
|
|
4709
|
+
});
|
|
4710
|
+
return lens;
|
|
4711
|
+
};
|
|
4712
|
+
var buildOptic = (context) => {
|
|
4713
|
+
const look = OPTICS[context.attachment.model];
|
|
4714
|
+
if (!look) return null;
|
|
4715
|
+
const { kit, frame } = context;
|
|
4716
|
+
const group = kit.group();
|
|
4717
|
+
scope(kit, group, look.kind, 0, 0, 0);
|
|
4718
|
+
if (!frame.hasRail && frame.mount === "rail_top") rail(kit, group, -ADAPTER_BACK, ADAPTER_LENGTH, -3e-3, 0, frame.receiverWidth * 0.68);
|
|
4719
|
+
if (look.lens === void 0) return group;
|
|
4720
|
+
const lens = tinted(context, group, look.lens);
|
|
4721
|
+
return { object: group, dispose: () => lens.dispose() };
|
|
4722
|
+
};
|
|
4723
|
+
var OPTIC_MODEL_IDS = Object.keys(OPTICS);
|
|
4724
|
+
|
|
4725
|
+
// src/models/attachments/sling.ts
|
|
4726
|
+
import { catenary as catenary2, createCord as createCord2 } from "@adatechnology/game-interactables-three";
|
|
4727
|
+
var SAG = 0.2;
|
|
4728
|
+
var STRAP_RADIUS = 7e-3;
|
|
4729
|
+
var CORD_POINTS = 12;
|
|
4730
|
+
var buildSling = ({ kit, frame }) => {
|
|
4731
|
+
if (!frame.secondary) return null;
|
|
4732
|
+
const { THREE, M } = kit;
|
|
4733
|
+
const group = kit.group();
|
|
4734
|
+
const rear = [frame.secondary[0] - frame.position[0], frame.secondary[1] - frame.position[1], frame.secondary[2] - frame.position[2]];
|
|
4735
|
+
const swivels = [];
|
|
4736
|
+
for (const at of [[0, 0, 0], rear]) {
|
|
4737
|
+
const swivel2 = kit.torus(group, new THREE.TorusGeometry(6e-3, 15e-4, 6, 12), M.metal, at[0], at[1], at[2]);
|
|
4738
|
+
swivel2.rotation.y = Math.PI / 2;
|
|
4739
|
+
swivels.push(swivel2);
|
|
4740
|
+
}
|
|
4741
|
+
const strap = createCord2(THREE, catenary2(rear, [0, 0, 0], SAG, CORD_POINTS), STRAP_RADIUS, M.olive);
|
|
4742
|
+
group.add(strap);
|
|
4743
|
+
return {
|
|
4744
|
+
object: group,
|
|
4745
|
+
dispose() {
|
|
4746
|
+
strap.geometry.dispose();
|
|
4747
|
+
for (const swivel2 of swivels) swivel2.geometry.dispose();
|
|
4748
|
+
}
|
|
4749
|
+
};
|
|
4750
|
+
};
|
|
4751
|
+
|
|
4752
|
+
// src/models/attachments/suppressor.ts
|
|
4753
|
+
var BAFFLES = 5;
|
|
4754
|
+
var buildSuppressor = ({ kit, attachment, fitted }) => {
|
|
4755
|
+
const { M, cylinderX } = kit;
|
|
4756
|
+
const length = attachment.dims?.length ?? 0.2;
|
|
4757
|
+
const radius = (attachment.dims?.diameter ?? 0.045) / 2;
|
|
4758
|
+
const isImprovised = attachment.id === "oil_filter_suppressor";
|
|
4759
|
+
const group = kit.group();
|
|
4760
|
+
const middle = fitted.integral === true ? -length / 2 : length / 2;
|
|
4761
|
+
cylinderX(group, radius, length, isImprovised ? M.rust : M.dark, middle, 0, 0, 18);
|
|
4762
|
+
if (isImprovised) {
|
|
4763
|
+
for (const along of [0.2, 0.8]) cylinderX(group, radius * 1.04, 0.012, M.tape, middle - length / 2 + length * along, 0, 0, 18);
|
|
4764
|
+
cylinderX(group, radius * 0.7, 8e-3, M.rustD, middle + length / 2 - 4e-3, 0, 0, 14);
|
|
4765
|
+
} else {
|
|
4766
|
+
for (let i = 0; i < BAFFLES; i++) cylinderX(group, radius * 1.03, 4e-3, M.metal, middle - length / 2 + length * (i + 0.5) / BAFFLES, 0, 0, 18);
|
|
4767
|
+
cylinderX(group, radius * 0.9, 6e-3, M.steel, middle - length / 2 + 3e-3, 0, 0, 18);
|
|
4768
|
+
}
|
|
4769
|
+
cylinderX(group, radius * 0.22, 4e-3, M.hole, middle + length / 2 + 5e-4, 0, 0, 12);
|
|
4770
|
+
return { object: group, ...fitted.integral === true ? {} : { muzzleOffset: length } };
|
|
4771
|
+
};
|
|
4772
|
+
|
|
4773
|
+
// src/models/attachments/ugl.ts
|
|
4774
|
+
var LAUNCHERS = {
|
|
4775
|
+
m203: { tubeRadius: 0.0215, tubeFraction: 0.8, housing: [0.1, 0.05, 0.05], hasFrontGrip: false },
|
|
4776
|
+
gp_25: { tubeRadius: 0.023, tubeFraction: 0.78, housing: [0.08, 0.055, 0.05], hasFrontGrip: false },
|
|
4777
|
+
ag36: { tubeRadius: 0.02, tubeFraction: 0.8, housing: [0.13, 0.06, 0.05], hasFrontGrip: true }
|
|
4778
|
+
};
|
|
4779
|
+
var drawFixed = (kit, group, look, length) => {
|
|
4780
|
+
const { M, box: box2, cylinderY } = kit;
|
|
4781
|
+
const [housingLength, housingHeight, housingWidth] = look.housing;
|
|
4782
|
+
box2(group, housingLength, housingHeight, housingWidth, M.dark, housingLength / 2 - 0.02, -look.tubeRadius, 0);
|
|
4783
|
+
box2(group, 0.05, 4e-3, 0.012, M.metal, housingLength * 0.45, -look.tubeRadius - housingHeight / 2 - 4e-3, 0);
|
|
4784
|
+
box2(group, 4e-3, 0.02, 8e-3, M.dark, housingLength * 0.6, -look.tubeRadius - housingHeight / 2 - 0.016, 0);
|
|
4785
|
+
if (look.hasFrontGrip) {
|
|
4786
|
+
cylinderY(group, 0.012, 0.07, M.poly, length * 0.55, -look.tubeRadius - 0.04, 0, 12);
|
|
4787
|
+
box2(group, 0.04, 0.01, 0.03, M.dark, length * 0.55, -look.tubeRadius - 4e-3, 0);
|
|
4788
|
+
}
|
|
4789
|
+
};
|
|
4790
|
+
var buildUnderbarrelLauncher = ({ kit, attachment, slide: slide2 }) => {
|
|
4791
|
+
const look = LAUNCHERS[attachment.model];
|
|
4792
|
+
if (!look || !slide2) return null;
|
|
4793
|
+
const { M, cylinderX } = kit;
|
|
4794
|
+
const length = attachment.dims?.length ?? 0.35;
|
|
4795
|
+
const fixed = kit.group();
|
|
4796
|
+
drawFixed(kit, fixed, look, length);
|
|
4797
|
+
const tubeLength = length * look.tubeFraction;
|
|
4798
|
+
const start = -0.02;
|
|
4799
|
+
cylinderX(slide2, look.tubeRadius, tubeLength, M.metal, start + tubeLength / 2, -look.tubeRadius, 0, 18);
|
|
4800
|
+
cylinderX(slide2, look.tubeRadius * 1.12, 0.012, M.dark, start + tubeLength - 6e-3, -look.tubeRadius, 0, 18);
|
|
4801
|
+
cylinderX(slide2, look.tubeRadius * 0.7, 4e-3, M.hole, start + tubeLength + 5e-4, -look.tubeRadius, 0, 14);
|
|
4802
|
+
const muzzle = kit.mark(slide2, start + tubeLength + 0.01, -look.tubeRadius, 0);
|
|
4803
|
+
muzzle.name = "ugl_muzzle";
|
|
4804
|
+
return {
|
|
4805
|
+
object: fixed,
|
|
4806
|
+
muzzle,
|
|
4807
|
+
dispose() {
|
|
4808
|
+
slide2.remove(...slide2.children);
|
|
4809
|
+
}
|
|
4810
|
+
};
|
|
4811
|
+
};
|
|
4812
|
+
|
|
4813
|
+
// src/models/attachments/registry.ts
|
|
4814
|
+
var ATTACHMENT_MODELS = {
|
|
4815
|
+
...Object.fromEntries(OPTIC_MODEL_IDS.map((id) => [id, buildOptic])),
|
|
4816
|
+
bipod: buildBipodModel,
|
|
4817
|
+
muzzle_brake: buildMuzzleBrake(1),
|
|
4818
|
+
muzzle_brake_heavy: buildMuzzleBrake(2),
|
|
4819
|
+
compensator: buildCompensator,
|
|
4820
|
+
suppressor: buildSuppressor,
|
|
4821
|
+
oil_filter_suppressor: buildSuppressor,
|
|
4822
|
+
integral_suppressor: buildSuppressor,
|
|
4823
|
+
vertical_grip: buildForegrip,
|
|
4824
|
+
weapon_light: buildLight,
|
|
4825
|
+
laser_sight: buildLaser,
|
|
4826
|
+
m203: buildUnderbarrelLauncher,
|
|
4827
|
+
gp_25: buildUnderbarrelLauncher,
|
|
4828
|
+
ag36: buildUnderbarrelLauncher,
|
|
4829
|
+
ammo_backpack: buildAmmoBackpack,
|
|
4830
|
+
tripod: buildTripod,
|
|
4831
|
+
sling: buildSling,
|
|
4832
|
+
drum_magazine: buildDrumMagazine,
|
|
4833
|
+
extended_magazine: buildExtendedMagazine,
|
|
4834
|
+
dual_coupler: buildDualCoupler
|
|
4835
|
+
};
|
|
4836
|
+
|
|
4837
|
+
// src/models/attachments/fit.ts
|
|
4838
|
+
var toModel = (made) => "object" in made ? made : { object: made };
|
|
4839
|
+
var MUZZLE_DEFAULT = "muzzle_default";
|
|
4840
|
+
function fitAttachmentModels(model, fitted, kit, options) {
|
|
4841
|
+
const registry = { ...ATTACHMENT_MODELS, ...options.overrides };
|
|
4842
|
+
const { parts, root, inner } = model;
|
|
4843
|
+
const factoryMagazine = parts.mag ? [...parts.mag.children] : [];
|
|
4844
|
+
const muzzleRest = parts.muzzle ? parts.muzzle.position.x : 0;
|
|
4845
|
+
const drawn = /* @__PURE__ */ new Map();
|
|
4846
|
+
const hiddenBy = /* @__PURE__ */ new Map();
|
|
4847
|
+
const switchedOn = /* @__PURE__ */ new Map();
|
|
4848
|
+
const hide = (id, objects) => {
|
|
4849
|
+
for (const object of objects) {
|
|
4850
|
+
const owners = hiddenBy.get(object) ?? /* @__PURE__ */ new Set();
|
|
4851
|
+
owners.add(id);
|
|
4852
|
+
hiddenBy.set(object, owners);
|
|
4853
|
+
object.visible = false;
|
|
4854
|
+
}
|
|
4855
|
+
};
|
|
4856
|
+
const unhide = (id, objects) => {
|
|
4857
|
+
for (const object of objects) {
|
|
4858
|
+
const owners = hiddenBy.get(object);
|
|
4859
|
+
owners?.delete(id);
|
|
4860
|
+
if (!owners || owners.size === 0) {
|
|
4861
|
+
hiddenBy.delete(object);
|
|
4862
|
+
object.visible = true;
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
};
|
|
4866
|
+
const take = (id) => {
|
|
4867
|
+
const entry = drawn.get(id);
|
|
4868
|
+
if (!entry) return;
|
|
4869
|
+
drawn.delete(id);
|
|
4870
|
+
switchedOn.delete(id);
|
|
4871
|
+
entry.model.object.removeFromParent();
|
|
4872
|
+
unhide(id, entry.model.hides ?? []);
|
|
4873
|
+
if (parts.uglMuzzle && entry.model.muzzle === parts.uglMuzzle) delete parts.uglMuzzle;
|
|
4874
|
+
entry.model.dispose?.();
|
|
4875
|
+
};
|
|
4876
|
+
const place = (part, attachment) => {
|
|
4877
|
+
const builder = registry[attachment.model] ?? registry[attachment.id];
|
|
4878
|
+
const frame = mountFrame(model, part.mount);
|
|
4879
|
+
if (!builder || !frame) return;
|
|
4880
|
+
const made = builder({ kit, model, attachment, fitted: part, frame, raycast: options.raycast, factoryMagazine, slide: parts.ugl });
|
|
4881
|
+
if (!made) return;
|
|
4882
|
+
const entry = toModel(made);
|
|
4883
|
+
const parent = entry.hangsFrom === "magazine" && parts.mag ? parts.mag : root;
|
|
4884
|
+
if (parent === root) entry.object.position.set(...frame.position);
|
|
4885
|
+
parent.add(entry.object);
|
|
4886
|
+
hide(attachment.id, entry.hides ?? []);
|
|
4887
|
+
if (entry.muzzle) parts.uglMuzzle = entry.muzzle;
|
|
4888
|
+
drawn.set(attachment.id, { model: entry, mount: part.mount });
|
|
4889
|
+
};
|
|
4890
|
+
const shine = () => {
|
|
4891
|
+
const isDrawn = options.isDrawn?.() ?? true;
|
|
4892
|
+
const beams = options.areBeamsOn?.() ?? true;
|
|
4893
|
+
for (const [id, entry] of drawn) {
|
|
4894
|
+
entry.model.object.visible = isDrawn;
|
|
4895
|
+
entry.model.setOn?.(beams && switchedOn.get(id) === true);
|
|
4896
|
+
}
|
|
4897
|
+
};
|
|
4898
|
+
const sync = (list) => {
|
|
4899
|
+
const wanted = new Map(list.map((part) => [part.id, part]));
|
|
4900
|
+
for (const id of [...drawn.keys()]) if (!wanted.has(id) || drawn.get(id)?.mount !== wanted.get(id)?.mount) take(id);
|
|
4901
|
+
for (const [id, objects] of Object.entries(parts.fittings ?? {})) {
|
|
4902
|
+
if (id === MUZZLE_DEFAULT) continue;
|
|
4903
|
+
for (const object of objects) object.visible = wanted.has(id);
|
|
4904
|
+
}
|
|
4905
|
+
for (const object of parts.fittings?.[MUZZLE_DEFAULT] ?? []) object.visible = !list.some((part) => part.mount === "muzzle");
|
|
4906
|
+
for (const part of list) {
|
|
4907
|
+
const attachment = options.catalogue.get(part.id);
|
|
4908
|
+
if (!attachment || drawn.has(part.id) || parts.fittings?.[part.id]) continue;
|
|
4909
|
+
if (part.integral === true && attachment.kind === "optic") continue;
|
|
4910
|
+
place(part, attachment);
|
|
4911
|
+
}
|
|
4912
|
+
for (const id of drawn.keys()) switchedOn.set(id, wanted.get(id)?.on === true);
|
|
4913
|
+
shine();
|
|
4914
|
+
if (parts.muzzle) parts.muzzle.position.x = muzzleRest + [...drawn.values()].reduce((sum, entry) => sum + (entry.model.muzzleOffset ?? 0), 0) / inner.scale.x;
|
|
4915
|
+
};
|
|
4916
|
+
sync(fitted);
|
|
4917
|
+
return {
|
|
4918
|
+
sync,
|
|
4919
|
+
update() {
|
|
4920
|
+
shine();
|
|
4921
|
+
for (const entry of drawn.values()) entry.model.update?.();
|
|
4922
|
+
},
|
|
4923
|
+
get drawn() {
|
|
4924
|
+
return [...drawn.keys()];
|
|
4925
|
+
},
|
|
4926
|
+
dispose() {
|
|
4927
|
+
for (const id of [...drawn.keys()]) take(id);
|
|
4928
|
+
}
|
|
4929
|
+
};
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4932
|
+
// src/models/attachments/rig-underbarrel.ts
|
|
4933
|
+
var LAUNCHER_INTERFACES = /* @__PURE__ */ new Set(["m203", "gp25", "ag36"]);
|
|
4934
|
+
function rigUnderbarrel(THREE, model, def) {
|
|
4935
|
+
const place = def.mounts?.find((mount) => mount.kind === "underbarrel" && mount.interfaces.some((name) => LAUNCHER_INTERFACES.has(name)));
|
|
4936
|
+
const frame = place ? mountFrame(model, "underbarrel") : void 0;
|
|
4937
|
+
if (!frame) return;
|
|
4938
|
+
const pivot = new THREE.Group();
|
|
4939
|
+
pivot.name = "ugl";
|
|
4940
|
+
pivot.position.set(...frame.position);
|
|
4941
|
+
model.root.add(pivot);
|
|
4942
|
+
model.parts.ugl = pivot;
|
|
4943
|
+
}
|
|
4944
|
+
|
|
4945
|
+
// src/models/rig-stock.ts
|
|
4946
|
+
var FOLD_AXIS_BY_SIDE = { right: "y", left: "y", under: "z", over: "z" };
|
|
4947
|
+
var hingeOf = (model, side) => {
|
|
4948
|
+
const frames = model.parts.frames;
|
|
4949
|
+
const x = frames?.receiverStart ?? 0;
|
|
4950
|
+
const halfWidth = (frames?.receiverWidth ?? 0.034) / 2;
|
|
4951
|
+
const halfHeight = (frames?.receiverHeight ?? 0.06) / 2;
|
|
4952
|
+
if (side === "right") return [x, 0, halfWidth];
|
|
4953
|
+
if (side === "left") return [x, 0, -halfWidth];
|
|
4954
|
+
if (side === "under") return [x, -halfHeight, 0];
|
|
4955
|
+
return [x, halfHeight, 0];
|
|
4956
|
+
};
|
|
4957
|
+
function rigStock(THREE, model, def) {
|
|
4958
|
+
const stock = def.stock;
|
|
4959
|
+
const meshes = model.parts.stockMeshes;
|
|
4960
|
+
if (!stock || stock.kind === "fixed" || stock.kind === "none" || !meshes || meshes.length === 0) return;
|
|
4961
|
+
const isFolding = stock.kind === "fold" || stock.kind === "fold_retract";
|
|
4962
|
+
const side = stock.foldAxis ?? "right";
|
|
4963
|
+
const hinge = isFolding ? hingeOf(model, side) : [0, 0, 0];
|
|
4964
|
+
const pivot = new THREE.Group();
|
|
4965
|
+
pivot.name = "stock";
|
|
4966
|
+
pivot.position.set(...hinge);
|
|
4967
|
+
model.inner.add(pivot);
|
|
4968
|
+
model.inner.updateMatrixWorld(true);
|
|
4969
|
+
for (const mesh of meshes) pivot.attach(mesh);
|
|
4970
|
+
const dimensions = def.dimensions;
|
|
4971
|
+
const travel = dimensions?.lengthCollapsed === void 0 ? 0 : (dimensions.length - dimensions.lengthCollapsed) / model.inner.scale.x;
|
|
4972
|
+
const move2 = isFolding ? { kind: "fold", axis: FOLD_AXIS_BY_SIDE[side] ?? "y", travel } : { kind: "retract", axis: "x", travel };
|
|
4973
|
+
model.parts.stock = pivot;
|
|
4974
|
+
model.parts.stockMove = move2;
|
|
4975
|
+
}
|
|
4976
|
+
|
|
3471
4977
|
// src/models/condition.ts
|
|
3472
4978
|
var substitutions = (kit, condition) => {
|
|
3473
4979
|
const { M } = kit;
|
|
@@ -3574,7 +5080,7 @@ function buildRound(kit, dims, shape) {
|
|
|
3574
5080
|
}
|
|
3575
5081
|
|
|
3576
5082
|
// src/fx/effects.ts
|
|
3577
|
-
var
|
|
5083
|
+
var PI12 = Math.PI;
|
|
3578
5084
|
var FLASH_BY_CASE = { shell: 1.6, big: 2.6, tiny: 0.5, pistol: 0.8, rifle: 1.2 };
|
|
3579
5085
|
var FLASH_SECONDS = 0.055;
|
|
3580
5086
|
function createEffects(kit, scene, particles, random) {
|
|
@@ -3585,15 +5091,15 @@ function createEffects(kit, scene, particles, random) {
|
|
|
3585
5091
|
const buildFlash = (size) => {
|
|
3586
5092
|
const group = new THREE.Group();
|
|
3587
5093
|
const cone = new THREE.Mesh(new THREE.ConeGeometry(0.013 * size, 0.085 * size, 10, 1, true), M.flashA);
|
|
3588
|
-
cone.rotation.z = -
|
|
5094
|
+
cone.rotation.z = -PI12 / 2;
|
|
3589
5095
|
cone.position.x = 0.042 * size;
|
|
3590
5096
|
group.add(cone);
|
|
3591
5097
|
for (let i = 0; i < 4; i++) {
|
|
3592
|
-
const angle = i *
|
|
5098
|
+
const angle = i * PI12 / 2 + random() * 0.6;
|
|
3593
5099
|
const spike = new THREE.Mesh(new THREE.ConeGeometry(5e-3 * size, 0.04 * size, 6), M.flashA);
|
|
3594
5100
|
spike.position.set(0.012 * size, Math.cos(angle) * 0.016 * size, Math.sin(angle) * 0.016 * size);
|
|
3595
5101
|
spike.rotation.set(angle, 0, 0);
|
|
3596
|
-
spike.rotateZ(-
|
|
5102
|
+
spike.rotateZ(-PI12 / 2);
|
|
3597
5103
|
group.add(spike);
|
|
3598
5104
|
}
|
|
3599
5105
|
const core = new THREE.Mesh(new THREE.SphereGeometry(0.016 * size, 12, 8), M.flashB);
|
|
@@ -3617,7 +5123,7 @@ function createEffects(kit, scene, particles, random) {
|
|
|
3617
5123
|
const group = buildFlash(size);
|
|
3618
5124
|
group.position.copy(position);
|
|
3619
5125
|
group.quaternion.copy(orientation);
|
|
3620
|
-
group.rotateX(random() *
|
|
5126
|
+
group.rotateX(random() * PI12);
|
|
3621
5127
|
scene.add(group);
|
|
3622
5128
|
flash = { group, age: 0 };
|
|
3623
5129
|
light.position.copy(position);
|
|
@@ -3649,7 +5155,7 @@ function createEffects(kit, scene, particles, random) {
|
|
|
3649
5155
|
}
|
|
3650
5156
|
|
|
3651
5157
|
// src/fx/heavy.ts
|
|
3652
|
-
var
|
|
5158
|
+
var PI13 = Math.PI;
|
|
3653
5159
|
var flameMaterials = (kit) => {
|
|
3654
5160
|
const { THREE } = kit;
|
|
3655
5161
|
return [
|
|
@@ -3692,7 +5198,7 @@ function launchRocket(context) {
|
|
|
3692
5198
|
rocket.position.copy(muzzle);
|
|
3693
5199
|
scene.add(rocket);
|
|
3694
5200
|
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 =
|
|
5201
|
+
exhaust.rotation.z = PI13 / 2;
|
|
3696
5202
|
exhaust.position.x = -0.08;
|
|
3697
5203
|
rocket.add(exhaust);
|
|
3698
5204
|
const light = new THREE.PointLight(16751168, 3, 2.5);
|
|
@@ -3888,7 +5394,8 @@ var toWorld = (session, vector, scaled = false) => {
|
|
|
3888
5394
|
};
|
|
3889
5395
|
|
|
3890
5396
|
// src/fx/viewmodel-events.ts
|
|
3891
|
-
var
|
|
5397
|
+
var PI14 = Math.PI;
|
|
5398
|
+
var UGL_FLASH = 1.6;
|
|
3892
5399
|
var restFlat = (object) => {
|
|
3893
5400
|
object.rotation.x = 0;
|
|
3894
5401
|
object.rotation.z = 0;
|
|
@@ -3961,14 +5468,14 @@ function createClipEventHandler(world, session) {
|
|
|
3961
5468
|
root.updateWorldMatrix(true, true);
|
|
3962
5469
|
const copy = parts.mag.clone(true);
|
|
3963
5470
|
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,
|
|
5471
|
+
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, PI14 / 2) });
|
|
3965
5472
|
};
|
|
3966
5473
|
const stuckCase = () => {
|
|
3967
5474
|
if (!session.cartridge || !parts.eject) return;
|
|
3968
5475
|
const casing = buildCasing(kit, session.cartridge.dims, session.cartridge.shape);
|
|
3969
5476
|
const at = root.worldToLocal(offsetFrom(positionOf(parts.eject), 0, casing.length * 0.35, 0));
|
|
3970
5477
|
casing.group.position.copy(at);
|
|
3971
|
-
casing.group.rotation.set(0, 0,
|
|
5478
|
+
casing.group.rotation.set(0, 0, PI14 / 2 - 0.25);
|
|
3972
5479
|
root.add(casing.group);
|
|
3973
5480
|
session.stuckCase = casing.group;
|
|
3974
5481
|
};
|
|
@@ -3990,7 +5497,9 @@ function createClipEventHandler(world, session) {
|
|
|
3990
5497
|
const data = event.data ?? {};
|
|
3991
5498
|
switch (event.type) {
|
|
3992
5499
|
case "muzzle_flash":
|
|
3993
|
-
if (
|
|
5500
|
+
if (data.at === "ugl") {
|
|
5501
|
+
if (parts.uglMuzzle && world.layers.muzzleFlash) effects.muzzleFlash(positionOf(parts.uglMuzzle), root.getWorldQuaternion(new THREE.Quaternion()), UGL_FLASH);
|
|
5502
|
+
} else 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
5503
|
break;
|
|
3995
5504
|
case "eject_casing":
|
|
3996
5505
|
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 }));
|
|
@@ -4045,10 +5554,16 @@ function createClipEventHandler(world, session) {
|
|
|
4045
5554
|
}
|
|
4046
5555
|
|
|
4047
5556
|
// src/fx/viewmodel.ts
|
|
4048
|
-
var
|
|
5557
|
+
var PI15 = Math.PI;
|
|
4049
5558
|
var HAND_OFFSET2 = [0.2, -0.2, -0.42];
|
|
4050
5559
|
var HEAT_DISPLAY_MAX_FALLBACK = 120;
|
|
4051
5560
|
var LAUNCH_VOLUME = 0.6;
|
|
5561
|
+
var fittedOf2 = (parts) => parts.map((part) => ({ id: part.id, mount: part.mount, ...part.integral ? { integral: true } : {}, ...part.on === void 0 ? {} : { on: part.on } }));
|
|
5562
|
+
var collapsedStockPose = (move2) => {
|
|
5563
|
+
if (move2.kind === "retract") return { position: [move2.travel, 0, 0] };
|
|
5564
|
+
return { rotation: [move2.axis === "x" ? PI15 : 0, move2.axis === "y" ? PI15 : 0, move2.axis === "z" ? PI15 : 0] };
|
|
5565
|
+
};
|
|
5566
|
+
var underbarrelStep = (step, tool) => tool.underbarrelActive && step.type === "play" && (step.clip === "shot" || step.clip === "reload") ? { ...step, variant: "ugl" } : step;
|
|
4052
5567
|
var caliberOf = (arsenal, def) => def.action.kind === "firearm" ? arsenal.calibers.get(def.action.caliber) : void 0;
|
|
4053
5568
|
function createViewmodel(options) {
|
|
4054
5569
|
const { THREE, scene, camera, arsenal, groundAt, audio } = options;
|
|
@@ -4060,10 +5575,10 @@ function createViewmodel(options) {
|
|
|
4060
5575
|
particles.onBounce = (kind) => kind === "casing" ? audio?.clink() : kind === "magazine" ? audio?.thud() : void 0;
|
|
4061
5576
|
const effects = createEffects(kit, scene, particles, random);
|
|
4062
5577
|
const hand = new THREE.Group();
|
|
4063
|
-
hand.name =
|
|
5578
|
+
hand.name = VIEWMODEL_NODE_NAME;
|
|
4064
5579
|
scene.add(hand);
|
|
4065
5580
|
const holder = new THREE.Group();
|
|
4066
|
-
holder.quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0),
|
|
5581
|
+
holder.quaternion.setFromAxisAngle(new THREE.Vector3(0, 1, 0), PI15 / 2);
|
|
4067
5582
|
hand.add(holder);
|
|
4068
5583
|
let tasks = [];
|
|
4069
5584
|
let session = null;
|
|
@@ -4085,6 +5600,7 @@ function createViewmodel(options) {
|
|
|
4085
5600
|
const heatMax = arsenal.rules.heat.displayMax || HEAT_DISPLAY_MAX_FALLBACK;
|
|
4086
5601
|
const release = () => {
|
|
4087
5602
|
if (!session) return;
|
|
5603
|
+
session.attachments?.dispose();
|
|
4088
5604
|
session.model.root.removeFromParent();
|
|
4089
5605
|
player = null;
|
|
4090
5606
|
session = null;
|
|
@@ -4108,6 +5624,8 @@ function createViewmodel(options) {
|
|
|
4108
5624
|
const model = def ? buildWeaponModelById(THREE, weapon) : void 0;
|
|
4109
5625
|
if (!def || !model) return false;
|
|
4110
5626
|
release();
|
|
5627
|
+
rigStock(THREE, model, def);
|
|
5628
|
+
rigUnderbarrel(THREE, model, def);
|
|
4111
5629
|
const length = new THREE.Box3().setFromObject(model.root).getSize(new THREE.Vector3()).x;
|
|
4112
5630
|
const scale = length > 0.8 ? 0.55 : length > 0.4 ? 0.75 : 1;
|
|
4113
5631
|
holder.scale.setScalar(scale);
|
|
@@ -4125,21 +5643,37 @@ function createViewmodel(options) {
|
|
|
4125
5643
|
kit,
|
|
4126
5644
|
cartridge: caliber && dims ? { dims, shape: cartridgeShape(caliber, dims) } : null,
|
|
4127
5645
|
stuckCase: null,
|
|
4128
|
-
nextChamber: 0
|
|
5646
|
+
nextChamber: 0,
|
|
5647
|
+
attachments: null,
|
|
5648
|
+
attachmentsSignature: ""
|
|
4129
5649
|
};
|
|
5650
|
+
const hostTool = options.tool?.();
|
|
5651
|
+
const onTool = hostTool?.weapon === weapon ? hostTool : void 0;
|
|
5652
|
+
const fitted = onTool?.attachments ? fittedOf2(onTool.attachments) : [...def.fitted ?? []];
|
|
5653
|
+
session.attachments = fitAttachmentModels(model, fitted, kit, { catalogue: arsenal.attachments, isDrawn: () => layers.attachments, areBeamsOn: () => layers.beams, ...options.attachmentModels ? { overrides: options.attachmentModels } : {}, ...options.raycast ? { raycast: options.raycast } : {} });
|
|
5654
|
+
session.attachmentsSignature = attachmentsKey(onTool?.attachments);
|
|
4130
5655
|
const action = def.action;
|
|
4131
5656
|
const reloadFamily = action.kind === "firearm" || action.kind === "flame" || action.kind === "launcher" ? action.family : void 0;
|
|
4132
5657
|
const rpm = action.kind === "firearm" ? action.rpm : 0;
|
|
5658
|
+
const rig = createModelRig(model);
|
|
4133
5659
|
player = createToolClipPlayer({
|
|
4134
5660
|
clips: buildToolClips({ parts: model.parts, family: model.parts.family, caseType: session.caseType, reloadFamily }),
|
|
4135
|
-
rig
|
|
5661
|
+
rig,
|
|
4136
5662
|
onEvent: createClipEventHandler(world, session),
|
|
4137
5663
|
repeatInterval: () => rpm > 0 ? 60 / rpm : 0.1
|
|
4138
5664
|
});
|
|
5665
|
+
if (model.parts.stockMove && onTool?.stock === "collapsed") rig.setPartPose("stock", collapsedStockPose(model.parts.stockMove));
|
|
4139
5666
|
applyCondition(kit, model, condition);
|
|
4140
5667
|
lastAmmo = options.tool?.().ammo ?? 0;
|
|
4141
5668
|
return true;
|
|
4142
5669
|
};
|
|
5670
|
+
const syncAttachments = (current, tool) => {
|
|
5671
|
+
if (!current.attachments || tool?.weapon !== current.id || !tool.attachments) return;
|
|
5672
|
+
const signature = attachmentsKey(tool.attachments);
|
|
5673
|
+
if (signature === current.attachmentsSignature) return;
|
|
5674
|
+
current.attachmentsSignature = signature;
|
|
5675
|
+
current.attachments.sync(fittedOf2(tool.attachments));
|
|
5676
|
+
};
|
|
4143
5677
|
const place = () => {
|
|
4144
5678
|
const [x, y, z] = HAND_OFFSET2;
|
|
4145
5679
|
hand.position.set(x, y, z).applyQuaternion(camera.quaternion).add(camera.position);
|
|
@@ -4156,7 +5690,11 @@ function createViewmodel(options) {
|
|
|
4156
5690
|
material.emissiveIntensity = 2.2 * level;
|
|
4157
5691
|
}
|
|
4158
5692
|
};
|
|
4159
|
-
const handleShot = (current) => {
|
|
5693
|
+
const handleShot = (current, event, tool) => {
|
|
5694
|
+
if (tool.underbarrelActive && current.model.parts.uglMuzzle) {
|
|
5695
|
+
audio?.play("explosion", LAUNCH_VOLUME, distance());
|
|
5696
|
+
return;
|
|
5697
|
+
}
|
|
4160
5698
|
const action = current.def.action;
|
|
4161
5699
|
if (action.kind === "launcher") {
|
|
4162
5700
|
launchRocket(heavy(current));
|
|
@@ -4165,7 +5703,7 @@ function createViewmodel(options) {
|
|
|
4165
5703
|
if (action.kind !== "firearm") return;
|
|
4166
5704
|
const caliber = caliberOf(arsenal, current.def);
|
|
4167
5705
|
if (!caliber) return;
|
|
4168
|
-
audio?.shot(muzzleEnergy3(caliber) * (caliber.pellets ?? 1), current.def.category, { caliber, distance: distance() });
|
|
5706
|
+
audio?.shot(muzzleEnergy3(caliber) * (caliber.pellets ?? 1), current.def.category, { caliber, distance: distance(), ...event.suppressed === true ? { suppressed: true } : {} });
|
|
4169
5707
|
};
|
|
4170
5708
|
return {
|
|
4171
5709
|
equip,
|
|
@@ -4175,7 +5713,7 @@ function createViewmodel(options) {
|
|
|
4175
5713
|
if (event.type === "tool") equip(event.weapon);
|
|
4176
5714
|
if (!session || !player || event.weapon !== session.id) return;
|
|
4177
5715
|
const current = session;
|
|
4178
|
-
if (event.type === "shot") handleShot(current);
|
|
5716
|
+
if (event.type === "shot") handleShot(current, event, tool);
|
|
4179
5717
|
else if (event.type === "reload" && event.phase === "start") current.nextChamber = 0;
|
|
4180
5718
|
else if (event.type === "clear" && event.phase === "end") current.stuckCase = null;
|
|
4181
5719
|
const mechanical = weaponEventOf(event);
|
|
@@ -4183,13 +5721,15 @@ function createViewmodel(options) {
|
|
|
4183
5721
|
const step = toolAnimationStep(event);
|
|
4184
5722
|
if (!step) return;
|
|
4185
5723
|
const last = step.type === "play" && step.clip === "shot" && current.model.parts.family === "pistol" && tool.ammo === 0;
|
|
4186
|
-
player.apply(last ? { ...step, variant: "last" } : step);
|
|
5724
|
+
player.apply(last ? { ...step, variant: "last" } : underbarrelStep(step, tool));
|
|
4187
5725
|
},
|
|
4188
5726
|
update(dt) {
|
|
4189
5727
|
if (session) {
|
|
4190
5728
|
place();
|
|
4191
5729
|
player?.update(dt);
|
|
4192
5730
|
const tool = options.tool?.();
|
|
5731
|
+
syncAttachments(session, tool);
|
|
5732
|
+
session.attachments?.update();
|
|
4193
5733
|
glow(tool);
|
|
4194
5734
|
if (tool && session.def.action.kind === "flame") {
|
|
4195
5735
|
if (tool.ammo < lastAmmo) flameJet(heavy(session), dt, session.model.parts.launcherType === "flame" ? 1 : 0.6);
|
|
@@ -4254,7 +5794,9 @@ function createWeaponFx(options) {
|
|
|
4254
5794
|
audio: options.audio ?? null,
|
|
4255
5795
|
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
4256
5796
|
...options.tool ? { tool: options.tool } : {},
|
|
4257
|
-
...options.condition ? { condition: options.condition } : {}
|
|
5797
|
+
...options.condition ? { condition: options.condition } : {},
|
|
5798
|
+
...options.attachmentModels ? { attachmentModels: options.attachmentModels } : {},
|
|
5799
|
+
...options.raycast ? { raycast: options.raycast } : {}
|
|
4258
5800
|
});
|
|
4259
5801
|
return {
|
|
4260
5802
|
handle(event, tool) {
|
|
@@ -4340,11 +5882,13 @@ var SOUND_DISTANCE_MAX = 200;
|
|
|
4340
5882
|
var FUSE_LENGTH_MAX = 1;
|
|
4341
5883
|
var DEFAULT_FUSE_LENGTH = 0.03;
|
|
4342
5884
|
var COUNTER = { length: 4.8, depth: 1.6, height: 0.9 };
|
|
4343
|
-
var COUNTER_X = [-6.5, -9, -11.5];
|
|
5885
|
+
var COUNTER_X = [-6.5, -9, -11.5, -14];
|
|
4344
5886
|
var COUNTER_Z = [-1, 5.5];
|
|
5887
|
+
var MOUNTED_M134_ITEM_ID = "m134_tripod";
|
|
5888
|
+
var MOUNTED_M134 = { weaponId: "m134", at: [-3.5, 0, 2], rotationY: Math.PI / 2 };
|
|
4345
5889
|
var END_MARGIN = 0.5;
|
|
4346
5890
|
var DEFAULT_PITCH = 0.16;
|
|
4347
|
-
var PITCH_BY_ID = { browning_m2: 0.5, m2_flamethrower: 0.45, bow: 0.3, crossbow: 0.3 };
|
|
5891
|
+
var PITCH_BY_ID = { browning_m2: 0.5, m134: 0.45, m2_flamethrower: 0.45, bow: 0.3, crossbow: 0.3 };
|
|
4348
5892
|
var WORLD_EXPLOSIVES = [
|
|
4349
5893
|
{ id: "fuel_barrel", at: [10, 0, -8] },
|
|
4350
5894
|
{ id: "fuel_barrel", at: [11.2, 0, -8] },
|
|
@@ -4356,6 +5900,7 @@ var TARGETS = [
|
|
|
4356
5900
|
{ id: "target_plywood", at: [0, 0, -7] },
|
|
4357
5901
|
{ id: "target_concrete", at: [1.5, 0, -7] }
|
|
4358
5902
|
];
|
|
5903
|
+
var EMPTY_TOOL = { weapon: "", category: "", ammo: 0, capacity: 0, reserve: 0, reloading: false };
|
|
4359
5904
|
var TEXT = {
|
|
4360
5905
|
en: {
|
|
4361
5906
|
group: "Weapons",
|
|
@@ -4365,6 +5910,7 @@ var TEXT = {
|
|
|
4365
5910
|
reloadStart: "Reload started: {weapon} ({ammo}/{capacity})",
|
|
4366
5911
|
reloadEnd: "Reload done: {weapon} ({ammo}/{capacity})",
|
|
4367
5912
|
worldExplosives: { fuel_barrel: "Fuel barrel", propane_p13: "Propane cylinder (P13)", car_fuel_tank: "Car fuel tank" },
|
|
5913
|
+
mountedM134: "M134 Minigun on its tripod",
|
|
4368
5914
|
condition: "Weapon condition",
|
|
4369
5915
|
conditionHint: "Wear of the weapon: it jams more and the metal shows it",
|
|
4370
5916
|
conditions: { new: "New", used: "Used", worn: "Worn", rusty: "Rusty" },
|
|
@@ -4386,6 +5932,7 @@ var TEXT = {
|
|
|
4386
5932
|
reloadStart: "Recarga iniciada: {weapon} ({ammo}/{capacity})",
|
|
4387
5933
|
reloadEnd: "Recarga completa: {weapon} ({ammo}/{capacity})",
|
|
4388
5934
|
worldExplosives: { fuel_barrel: "Barril de combust\xEDvel", propane_p13: "Botij\xE3o de g\xE1s (P13)", car_fuel_tank: "Tanque de combust\xEDvel do carro" },
|
|
5935
|
+
mountedM134: "M134 Minigun no trip\xE9",
|
|
4389
5936
|
condition: "Estado da arma",
|
|
4390
5937
|
conditionHint: "Desgaste da arma: trava mais e o metal mostra",
|
|
4391
5938
|
conditions: { new: "Nova", used: "Usada", worn: "Gasta", rusty: "Enferrujada" },
|
|
@@ -4451,6 +5998,14 @@ var weaponItem = (def, name) => {
|
|
|
4451
5998
|
movable: true
|
|
4452
5999
|
};
|
|
4453
6000
|
};
|
|
6001
|
+
var mountedM134Item = (def, name) => ({
|
|
6002
|
+
id: MOUNTED_M134_ITEM_ID,
|
|
6003
|
+
name,
|
|
6004
|
+
build: (THREE) => buildTripodMountedProp(THREE, def, name),
|
|
6005
|
+
spec: (root) => modeledPropSpec(root, def, name),
|
|
6006
|
+
weapon: def.id,
|
|
6007
|
+
movable: false
|
|
6008
|
+
});
|
|
4454
6009
|
var asItem = (id, name, prop, extra = {}) => ({
|
|
4455
6010
|
id,
|
|
4456
6011
|
name,
|
|
@@ -4485,6 +6040,7 @@ function createWeaponsTestModule(options = {}) {
|
|
|
4485
6040
|
const locale = options.locale ?? "pt-BR";
|
|
4486
6041
|
const arsenal = options.arsenal ?? createArsenal({ locale });
|
|
4487
6042
|
const text = textOf(locale);
|
|
6043
|
+
const accessoryText = accessoryTextOf(locale);
|
|
4488
6044
|
const audio = options.sound === false ? null : createWeaponAudio(void 0, options.seed === void 0 ? {} : { seed: options.seed });
|
|
4489
6045
|
const weaponDefs = [...arsenal.weapons.values()].filter((def) => !BODY_ACTIONS.has(def.id));
|
|
4490
6046
|
const weaponIds = weaponDefs.filter((def) => !(def.id in ARCHETYPE_MODELS)).map((def) => `${WEAPON_ITEM_PREFIX}${def.id}`);
|
|
@@ -4495,6 +6051,8 @@ function createWeaponsTestModule(options = {}) {
|
|
|
4495
6051
|
return item ? [item] : [];
|
|
4496
6052
|
});
|
|
4497
6053
|
const worldExplosiveIds = new Set(worldExplosives.map((item) => item.id));
|
|
6054
|
+
const mountedM134Def = arsenal.weapons.get(MOUNTED_M134.weaponId);
|
|
6055
|
+
const mountedM134 = mountedM134Def && hasWeaponModel(mountedM134Def.id) ? [mountedM134Item(mountedM134Def, text.mountedM134)] : [];
|
|
4498
6056
|
let fx = null;
|
|
4499
6057
|
let host = null;
|
|
4500
6058
|
let soundDistance = 0;
|
|
@@ -4511,10 +6069,14 @@ function createWeaponsTestModule(options = {}) {
|
|
|
4511
6069
|
...weaponDefs.map((def) => weaponItem(def, arsenal.name(def.id))),
|
|
4512
6070
|
asItem(COUNTER_ITEM_ID, text.counter, counterProp(text.counter), { movable: false }),
|
|
4513
6071
|
...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
|
|
6072
|
+
...worldExplosives,
|
|
6073
|
+
...mountedM134
|
|
4515
6074
|
],
|
|
4516
6075
|
area: () => ({
|
|
4517
|
-
placements:
|
|
6076
|
+
placements: [
|
|
6077
|
+
...weaponsLayout(weaponIds, pitchOf).filter((placement) => !placement.item.startsWith(WORLD_EXPLOSIVE_PREFIX) || worldExplosiveIds.has(placement.item)),
|
|
6078
|
+
...mountedM134.map((item) => ({ item: item.id, position: MOUNTED_M134.at, rotationY: MOUNTED_M134.rotationY, asleep: true }))
|
|
6079
|
+
],
|
|
4518
6080
|
view: { eye: [-3.4, 1.9, 9.2], target: [-9, 0.9, 1.6] }
|
|
4519
6081
|
}),
|
|
4520
6082
|
controls: () => [
|
|
@@ -4535,13 +6097,15 @@ function createWeaponsTestModule(options = {}) {
|
|
|
4535
6097
|
default: arsenal.rules.defaultAmmoQuality
|
|
4536
6098
|
},
|
|
4537
6099
|
{ 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 }
|
|
6100
|
+
{ 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 },
|
|
6101
|
+
...accessoryControls(accessoryText)
|
|
4539
6102
|
],
|
|
4540
|
-
layers: () => ["muzzle_flash", "casings", "magazines"].map((id) => ({ id, name: text.layers[id] })),
|
|
6103
|
+
layers: () => [...["muzzle_flash", "casings", "magazines"].map((id) => ({ id, name: text.layers[id] })), ...accessoryLayers(accessoryText)],
|
|
4541
6104
|
metrics: () => [
|
|
4542
6105
|
{ id: "casings_live", name: text.metrics.casings_live, read: () => fx?.casings ?? 0 },
|
|
4543
6106
|
{ 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 }
|
|
6107
|
+
{ id: "heat", name: text.metrics.heat, unit: "%", max: 100, digits: 0, read: heatPercent },
|
|
6108
|
+
...accessoryMetrics(accessoryText, () => host?.tool() ?? EMPTY_TOOL)
|
|
4545
6109
|
],
|
|
4546
6110
|
attach(lent) {
|
|
4547
6111
|
host = lent;
|
|
@@ -4554,14 +6118,20 @@ function createWeaponsTestModule(options = {}) {
|
|
|
4554
6118
|
audio,
|
|
4555
6119
|
tool: lent.tool,
|
|
4556
6120
|
soundDistance: () => soundDistance,
|
|
6121
|
+
raycast: createLaserRaycast(lent.THREE, lent.scene, lent.camera),
|
|
6122
|
+
...options.attachmentModels ? { attachmentModels: options.attachmentModels } : {},
|
|
4557
6123
|
...options.condition ? { condition: options.condition } : {},
|
|
4558
6124
|
...options.seed === void 0 ? {} : { seed: options.seed }
|
|
4559
6125
|
});
|
|
4560
6126
|
fx = created;
|
|
4561
6127
|
const camera = lent.camera;
|
|
6128
|
+
const accessories = createAccessoryBench({ arsenal, host: lent, text: accessoryText });
|
|
4562
6129
|
const offBench = lent.bench?.onChange((change) => {
|
|
6130
|
+
if (accessories.handleChange(change)) return;
|
|
4563
6131
|
if (change.kind === "layer") {
|
|
4564
|
-
if (change.id === "
|
|
6132
|
+
if (change.id === "attachments") created.layers.attachments = change.on;
|
|
6133
|
+
else if (change.id === "beams") created.layers.beams = change.on;
|
|
6134
|
+
else if (change.id === "muzzle_flash") created.layers.muzzleFlash = change.on;
|
|
4565
6135
|
else if (change.id === "casings") created.layers.casings = change.on;
|
|
4566
6136
|
else if (change.id === "magazines") created.layers.magazines = change.on;
|
|
4567
6137
|
} else if (change.kind === "control" && typeof change.value === "string") {
|
|
@@ -4585,6 +6155,7 @@ function createWeaponsTestModule(options = {}) {
|
|
|
4585
6155
|
const offFrame = lent.onFrame((dt) => created.update(dt));
|
|
4586
6156
|
created.handle({ type: "tool", weapon: lent.tool().weapon }, lent.tool());
|
|
4587
6157
|
return () => {
|
|
6158
|
+
accessories.dispose();
|
|
4588
6159
|
offBench?.();
|
|
4589
6160
|
offEvents();
|
|
4590
6161
|
offFrame();
|
|
@@ -4598,6 +6169,7 @@ function createWeaponsTestModule(options = {}) {
|
|
|
4598
6169
|
}
|
|
4599
6170
|
export {
|
|
4600
6171
|
ARCHETYPE_MODELS,
|
|
6172
|
+
ATTACHMENT_MODELS,
|
|
4601
6173
|
BODY_ACTIONS,
|
|
4602
6174
|
CLASS_REFERENCE_ENERGY,
|
|
4603
6175
|
COUNTER_ITEM_ID,
|
|
@@ -4606,6 +6178,7 @@ export {
|
|
|
4606
6178
|
GAME_SOUND_CLASSES,
|
|
4607
6179
|
MECHANICAL_SOUNDS,
|
|
4608
6180
|
MODEL_ENTRIES,
|
|
6181
|
+
MOUNTED_M134_ITEM_ID,
|
|
4609
6182
|
WEAPON_ITEM_PREFIX,
|
|
4610
6183
|
WORLD_EXPLOSIVE_IDS,
|
|
4611
6184
|
WORLD_EXPLOSIVE_PREFIX,
|
|
@@ -4622,10 +6195,15 @@ export {
|
|
|
4622
6195
|
createWeaponAudio,
|
|
4623
6196
|
createWeaponFx,
|
|
4624
6197
|
createWeaponsTestModule,
|
|
6198
|
+
deriveFrames,
|
|
6199
|
+
fitAttachmentModels,
|
|
4625
6200
|
hasWeaponModel,
|
|
4626
6201
|
mechanicalSoundOf,
|
|
4627
6202
|
modelKit,
|
|
6203
|
+
mountFrame,
|
|
4628
6204
|
partRegistry,
|
|
6205
|
+
rigStock,
|
|
6206
|
+
rigUnderbarrel,
|
|
4629
6207
|
weaponProp,
|
|
4630
6208
|
weaponsLayout,
|
|
4631
6209
|
worldExplosiveItem
|