@adatechnology/game-weapons-three 0.4.0 → 0.5.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.
@@ -35,6 +35,7 @@ var GameWeaponsThree = (() => {
35
35
  MOUNTED_M134_ITEM_ID: () => MOUNTED_M134_ITEM_ID,
36
36
  WEAPON_ITEM_PREFIX: () => WEAPON_ITEM_PREFIX,
37
37
  WEAPON_THUMBNAILS: () => WEAPON_THUMBNAILS,
38
+ WORLD_EFFECTS: () => WORLD_EFFECTS,
38
39
  WORLD_EXPLOSIVE_IDS: () => WORLD_EXPLOSIVE_IDS,
39
40
  WORLD_EXPLOSIVE_PREFIX: () => WORLD_EXPLOSIVE_PREFIX,
40
41
  applyCondition: () => applyCondition,
@@ -50,6 +51,7 @@ var GameWeaponsThree = (() => {
50
51
  createWeaponAudio: () => createWeaponAudio,
51
52
  createWeaponFx: () => createWeaponFx,
52
53
  createWeaponsTestModule: () => createWeaponsTestModule,
54
+ createWorldEffects: () => createWorldEffects,
53
55
  deriveFrames: () => deriveFrames,
54
56
  fitAttachmentModels: () => fitAttachmentModels,
55
57
  hasWeaponModel: () => hasWeaponModel,
@@ -66,6 +68,199 @@ var GameWeaponsThree = (() => {
66
68
  worldExplosiveItem: () => worldExplosiveItem
67
69
  });
68
70
 
71
+ // ../game-contracts/dist/index.js
72
+ var TOOL_CLIP = {
73
+ DRAW: "draw",
74
+ SHOT: "shot",
75
+ MISFIRE: "misfire",
76
+ EMPTY: "empty",
77
+ CYCLE: "cycle",
78
+ RELOAD: "reload",
79
+ FEED: "feed",
80
+ FAULT: "fault",
81
+ CLEAR: "clear",
82
+ SAFETY_ON: "safety_on",
83
+ SAFETY_OFF: "safety_off",
84
+ MODE: "mode",
85
+ /** A throwable with the pin out, held until it is thrown. */
86
+ COOK: "cook",
87
+ /** The stock folds, unfolds, slides in or out (variants `fold`, `unfold`, `collapse`, `extend`). */
88
+ STOCK: "stock",
89
+ /** A light or a laser is switched (variants `on`, `off`). */
90
+ TOGGLE: "toggle"
91
+ };
92
+ var stockVariant = (event) => {
93
+ const isFolding = event.kind === void 0 ? false : event.kind.includes("fold");
94
+ if (event.state === "collapsed") return isFolding ? "fold" : "collapse";
95
+ return isFolding ? "unfold" : "extend";
96
+ };
97
+ var toolAnimationStep = (event) => {
98
+ switch (event.type) {
99
+ case "tool":
100
+ return { type: "play", clip: TOOL_CLIP.DRAW, ...event.duration === void 0 ? {} : { duration: event.duration } };
101
+ case "shot":
102
+ return { type: "play", clip: TOOL_CLIP.SHOT, repeat: Math.max(1, event.rounds ?? 1) };
103
+ case "misfire":
104
+ return { type: "play", clip: TOOL_CLIP.MISFIRE };
105
+ case "empty":
106
+ return { type: "play", clip: TOOL_CLIP.EMPTY };
107
+ case "mode":
108
+ return { type: "play", clip: TOOL_CLIP.MODE, variant: event.mode };
109
+ case "fault":
110
+ return { type: "play", clip: TOOL_CLIP.FAULT, variant: event.fault };
111
+ case "clear":
112
+ if (event.phase === "end") return { type: "finish", clip: TOOL_CLIP.CLEAR };
113
+ return { type: "play", clip: TOOL_CLIP.CLEAR, variant: event.fault, ...event.duration === void 0 ? {} : { duration: event.duration } };
114
+ case "safety":
115
+ return { type: "play", clip: event.on ? TOOL_CLIP.SAFETY_ON : TOOL_CLIP.SAFETY_OFF };
116
+ case "cycle":
117
+ if (event.phase === "end") return { type: "finish", clip: TOOL_CLIP.CYCLE };
118
+ return { type: "play", clip: TOOL_CLIP.CYCLE, ...event.duration === void 0 ? {} : { duration: event.duration } };
119
+ case "reload":
120
+ if (event.phase === "end") return { type: "finish", clip: TOOL_CLIP.RELOAD };
121
+ return { type: "play", clip: TOOL_CLIP.RELOAD, ...event.kind === void 0 ? {} : { variant: event.kind }, ...event.duration === void 0 ? {} : { duration: event.duration } };
122
+ case "stage":
123
+ return { type: "sync", stage: event.stage };
124
+ case "feed":
125
+ return { type: "play", clip: TOOL_CLIP.FEED };
126
+ case "cook":
127
+ if (event.phase === "start") return { type: "play", clip: TOOL_CLIP.COOK, duration: event.fuse };
128
+ return { type: "finish", clip: TOOL_CLIP.COOK };
129
+ case "stock":
130
+ return { type: "play", clip: TOOL_CLIP.STOCK, variant: stockVariant(event), ...event.duration === void 0 ? {} : { duration: event.duration } };
131
+ case "toggle":
132
+ return { type: "play", clip: TOOL_CLIP.TOGGLE, variant: event.on ? "on" : "off" };
133
+ default:
134
+ return null;
135
+ }
136
+ };
137
+ var STAGE_EVENT = "stage";
138
+ var scaleClip = (clip, duration) => {
139
+ const factor = clip.duration > 0 ? duration / clip.duration : 1;
140
+ return {
141
+ ...clip,
142
+ duration,
143
+ keys: clip.keys.map((key) => ({ ...key, t: key.t * factor })),
144
+ ...clip.events ? { events: clip.events.map((event) => ({ ...event, t: event.t * factor })) } : {}
145
+ };
146
+ };
147
+ var ZERO = [0, 0, 0];
148
+ var mix = (from, to, fraction) => [
149
+ (from[0] ?? 0) + ((to[0] ?? 0) - (from[0] ?? 0)) * fraction,
150
+ (from[1] ?? 0) + ((to[1] ?? 0) - (from[1] ?? 0)) * fraction,
151
+ (from[2] ?? 0) + ((to[2] ?? 0) - (from[2] ?? 0)) * fraction
152
+ ];
153
+ var sampleClip = (clip, partId, time) => {
154
+ const points = clip.keys.map((key) => {
155
+ const pose = partId === void 0 ? key : key.parts?.[partId];
156
+ return { t: key.t, position: pose?.position ?? ZERO, rotation: pose?.rotation ?? ZERO };
157
+ });
158
+ const first = points[0];
159
+ if (!first || first.t > 0) points.unshift({ t: 0, position: ZERO, rotation: ZERO });
160
+ const last = points[points.length - 1];
161
+ if (!last) return { position: ZERO, rotation: ZERO };
162
+ if (time >= last.t) return { position: mix(last.position, last.position, 0), rotation: mix(last.rotation, last.rotation, 0) };
163
+ const index = Math.max(0, points.findIndex((point, i) => time >= point.t && time < (points[i + 1]?.t ?? Infinity)));
164
+ const from = points[index];
165
+ const to = points[index + 1];
166
+ if (!from || !to) return { position: ZERO, rotation: ZERO };
167
+ const fraction = to.t > from.t ? (time - from.t) / (to.t - from.t) : 0;
168
+ return { position: mix(from.position, to.position, fraction), rotation: mix(from.rotation, to.rotation, fraction) };
169
+ };
170
+ var createToolClipPlayer = (options) => {
171
+ const { clips, rig, onEvent } = options;
172
+ let instances = [];
173
+ const find = (clip, variant) => {
174
+ const name = variant !== void 0 && clips[`${clip}_${variant}`] ? `${clip}_${variant}` : clip;
175
+ const found = clips[name];
176
+ return found ? { name, clip: found } : void 0;
177
+ };
178
+ const render = (instance) => {
179
+ const time = Math.min(instance.time, instance.clip.duration);
180
+ const root = sampleClip(instance.clip, void 0, time);
181
+ if (instance.clip.keys.some((key) => key.position || key.rotation)) rig.setRootPose?.(root);
182
+ const touched = new Set(instance.clip.keys.flatMap((key) => Object.keys(key.parts ?? {})));
183
+ for (const id of touched) rig.setPartPose(id, sampleClip(instance.clip, id, time));
184
+ };
185
+ const fireEvents = (instance, upTo) => {
186
+ const events = instance.clip.events ?? [];
187
+ while (instance.fired < events.length && (events[instance.fired]?.t ?? Infinity) <= upTo + 1e-9) {
188
+ const event = events[instance.fired];
189
+ instance.fired += 1;
190
+ if (event) onEvent?.(event, instance.name);
191
+ }
192
+ };
193
+ return {
194
+ apply(step) {
195
+ if (step.type === "sync") {
196
+ for (const instance of instances) {
197
+ const marker = (instance.clip.events ?? []).find((event) => event.type === STAGE_EVENT && event.data?.stage === step.stage);
198
+ if (marker && instance.time < marker.t) instance.time = marker.t;
199
+ }
200
+ return;
201
+ }
202
+ if (step.type === "finish") {
203
+ for (const instance of instances) {
204
+ if (instance.name !== step.clip && !instance.name.startsWith(`${step.clip}_`)) continue;
205
+ fireEvents(instance, instance.clip.duration);
206
+ instance.time = instance.clip.duration;
207
+ }
208
+ return;
209
+ }
210
+ const found = find(step.clip, step.variant);
211
+ if (!found) return;
212
+ const clip = step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip;
213
+ const interval = options.repeatInterval?.() ?? 0.1;
214
+ instances = instances.filter((existing) => existing.name !== found.name || existing.delay > 0);
215
+ for (let round2 = 0; round2 < (step.repeat ?? 1); round2++) {
216
+ instances.push({ name: found.name, clip, time: 0, delay: round2 * interval, fired: 0 });
217
+ }
218
+ },
219
+ update(dt) {
220
+ for (const instance of instances) {
221
+ const carried = instance.delay > 0 ? dt - instance.delay : dt;
222
+ if (instance.delay > 0) {
223
+ instance.delay = Math.max(0, instance.delay - dt);
224
+ if (instance.delay > 0) continue;
225
+ }
226
+ instance.time += carried;
227
+ fireEvents(instance, instance.time);
228
+ render(instance);
229
+ }
230
+ instances = instances.filter((instance) => instance.delay > 0 || instance.time < instance.clip.duration);
231
+ },
232
+ get playing() {
233
+ return instances.map((instance) => instance.name);
234
+ }
235
+ };
236
+ };
237
+ var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
238
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
239
+ var deepMerge = (base, patch) => {
240
+ if (!patch) return base;
241
+ const result2 = { ...base };
242
+ for (const [key, value] of Object.entries(patch)) {
243
+ if (value === void 0 || UNSAFE_KEYS.has(key)) continue;
244
+ const current = result2[key];
245
+ result2[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value;
246
+ }
247
+ return result2;
248
+ };
249
+ var mergeById = (shipped, extra) => {
250
+ const entries = /* @__PURE__ */ new Map();
251
+ for (const entry of [...shipped, ...extra ?? []]) entries.set(entry.id, entry);
252
+ return entries;
253
+ };
254
+ var applyPatches = (entries, patches) => {
255
+ for (const [id, patch] of Object.entries(patches ?? {})) {
256
+ const entry = entries.get(id);
257
+ if (entry) entries.set(id, deepMerge(entry, patch));
258
+ }
259
+ };
260
+ var removeIds = (entries, ids) => {
261
+ for (const id of ids ?? []) entries.delete(id);
262
+ };
263
+
69
264
  // ../game-weapons/dist/index.js
70
265
  var __defProp2 = Object.defineProperty;
71
266
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -11138,69 +11333,270 @@ var GameWeaponsThree = (() => {
11138
11333
  if (context.isCompatible && !context.isCompatible(def, attachment, fitted)) return { ok: false, reason: "rejected" };
11139
11334
  return { ok: true, mount: place.kind };
11140
11335
  };
11141
- var findDuplicateIds = (kind, entries) => {
11142
- const seen = /* @__PURE__ */ new Set();
11143
- const problems = [];
11144
- for (const { id } of entries ?? []) {
11145
- if (seen.has(id)) problems.push(`${kind} "${id}" is listed more than once`);
11146
- seen.add(id);
11147
- }
11148
- return problems;
11149
- };
11150
- var checkRotary = (id, action) => {
11151
- const problems = [];
11152
- const { rpm, rpmRange, spin } = action;
11153
- if (rpmRange && !(rpmRange[0] > 0 && rpmRange[0] <= rpmRange[1])) problems.push(`weapon "${id}" has an empty rate range ${rpmRange[0]}-${rpmRange[1]} rpm`);
11154
- if (rpmRange && (rpm < rpmRange[0] || rpm > rpmRange[1])) problems.push(`weapon "${id}" fires at ${rpm} rpm, outside its range ${rpmRange[0]}-${rpmRange[1]}`);
11155
- if (spin && !(spin.upSeconds >= 0 && spin.holdSeconds >= 0)) problems.push(`weapon "${id}" has a negative spin time`);
11156
- return problems;
11157
- };
11158
- var checkFirearm = (def, calibers) => {
11159
- if (def.action.kind !== "firearm") return [];
11160
- const problems = [];
11161
- for (const caliber of [def.action.caliber, ...def.action.acceptsCalibers ?? []]) {
11162
- if (!calibers.has(caliber)) problems.push(`weapon "${def.id}" uses unknown calibre "${caliber}"`);
11163
- }
11164
- if (def.action.modes.length === 0) problems.push(`weapon "${def.id}" has no fire modes`);
11165
- if (!(def.action.rpm > 0)) problems.push(`weapon "${def.id}" has a rate of fire of ${def.action.rpm} rpm`);
11166
- if (!(def.action.reload > 0)) problems.push(`weapon "${def.id}" has a reload time of ${def.action.reload} s`);
11167
- problems.push(...checkRotary(def.id, def.action));
11168
- return problems;
11169
- };
11170
- var checkTimings = (def) => {
11171
- const timings = "timings" in def.action ? def.action.timings : void 0;
11172
- return Object.entries(timings ?? {}).filter(([, seconds]) => typeof seconds === "number" && !(seconds > 0)).map(([name, seconds]) => `weapon "${def.id}" has a ${name} time of ${seconds} s`);
11173
- };
11174
- var checkExplosiveReference = (def, explosives) => {
11175
- const id = def.action.kind === "launcher" ? def.action.projectile : def.action.kind === "throwable" ? def.action.explosive : void 0;
11176
- return id !== void 0 && !explosives.has(id) ? [`weapon "${def.id}" uses unknown explosive "${id}"`] : [];
11177
- };
11178
- var checkExplosiveNumbers = (explosive) => {
11179
- const problems = [];
11180
- const [low, high] = explosive.fuseRange ?? [1, 1];
11181
- if (!(low > 0 && high >= low)) problems.push(`explosive "${explosive.id}" has a fuse range of ${low} to ${high} s`);
11182
- if (explosive.fuseLength !== void 0 && !(explosive.fuseLength > 0)) problems.push(`explosive "${explosive.id}" has a fuse length of ${explosive.fuseLength} m`);
11183
- if (explosive.burnRate !== void 0 && !(explosive.burnRate > 0)) problems.push(`explosive "${explosive.id}" has a burn rate of ${explosive.burnRate} s/m`);
11184
- if (explosive.burnTolerance !== void 0 && !(explosive.burnTolerance >= 0 && explosive.burnTolerance < 1)) problems.push(`explosive "${explosive.id}" has a burn tolerance of ${explosive.burnTolerance}`);
11185
- if (explosive.tripwire && !(explosive.tripwire.length > 0)) problems.push(`explosive "${explosive.id}" has a trip wire of ${explosive.tripwire.length} m`);
11186
- return problems;
11187
- };
11188
- var STATS = /* @__PURE__ */ new Set(["noise", "spread", "hipSpreadExcess", "recoilVertical", "recoilHorizontal", "range", "muzzleSpeed", "mass", "capacity", "reload", "reloadEmpty", "draw", "aim", "faultChance", "heatPerShot"]);
11189
- var INSTALLS = /* @__PURE__ */ new Set(["field", "tool", "bench"]);
11190
- var checkAttachment = (attachment, weapons) => {
11191
- const problems = [];
11192
- if (attachment.launcher !== void 0 && weapons.get(attachment.launcher)?.action.kind !== "launcher") problems.push(`attachment "${attachment.id}" names "${attachment.launcher}" as its launcher, which is not a launcher weapon`);
11193
- const label = `attachment "${attachment.id}"`;
11194
- if (!INSTALLS.has(attachment.install)) problems.push(`${label} installs "${attachment.install}"`);
11195
- if (attachment.mounts.length === 0) problems.push(`${label} has no mount`);
11196
- if (attachment.interfaces.length === 0) problems.push(`${label} has no interface`);
11197
- for (const effect of attachment.effects) {
11198
- if (!STATS.has(effect.stat)) problems.push(`${label} changes the unknown stat "${effect.stat}"`);
11199
- if (effect.mul !== void 0 && !(effect.mul > 0)) problems.push(`${label} multiplies ${effect.stat} by ${effect.mul}`);
11200
- }
11201
- return problems;
11202
- };
11203
- var checkFitted = (def, attachments) => {
11336
+ var melee_weapons_default = [
11337
+ {
11338
+ id: "tactical_knife",
11339
+ name: "Tactical knife",
11340
+ category: "melee_blade",
11341
+ action: {
11342
+ kind: "melee",
11343
+ damage: "slash",
11344
+ energy: 45,
11345
+ radius: 0.03,
11346
+ reach: 0.6,
11347
+ rpm: 100,
11348
+ noise: 1
11349
+ },
11350
+ mass: 0.3
11351
+ },
11352
+ {
11353
+ id: "meat_cleaver",
11354
+ name: "Meat cleaver",
11355
+ category: "melee_blade",
11356
+ action: {
11357
+ kind: "melee",
11358
+ damage: "slash",
11359
+ energy: 120,
11360
+ radius: 0.05,
11361
+ reach: 0.6,
11362
+ rpm: 70,
11363
+ noise: 2
11364
+ },
11365
+ mass: 0.6
11366
+ },
11367
+ {
11368
+ id: "hatchet",
11369
+ name: "Hatchet",
11370
+ category: "melee_blade",
11371
+ action: {
11372
+ kind: "melee",
11373
+ damage: "slash",
11374
+ energy: 180,
11375
+ radius: 0.06,
11376
+ reach: 0.7,
11377
+ rpm: 55,
11378
+ noise: 2
11379
+ },
11380
+ mass: 0.8
11381
+ },
11382
+ {
11383
+ id: "nail_bat",
11384
+ name: "Nail bat",
11385
+ category: "melee_blunt",
11386
+ action: {
11387
+ kind: "melee",
11388
+ damage: "blunt",
11389
+ energy: 170,
11390
+ radius: 0.1,
11391
+ reach: 1,
11392
+ rpm: 60,
11393
+ noise: 3
11394
+ },
11395
+ mass: 1.2
11396
+ },
11397
+ {
11398
+ id: "iron_pipe",
11399
+ name: "Iron pipe",
11400
+ category: "melee_blunt",
11401
+ action: {
11402
+ kind: "melee",
11403
+ damage: "blunt",
11404
+ energy: 140,
11405
+ radius: 0.06,
11406
+ reach: 1,
11407
+ rpm: 60,
11408
+ noise: 4
11409
+ },
11410
+ mass: 1.2
11411
+ },
11412
+ {
11413
+ id: "pickaxe",
11414
+ name: "Pickaxe",
11415
+ category: "melee_blade",
11416
+ action: {
11417
+ kind: "melee",
11418
+ damage: "slash",
11419
+ energy: 260,
11420
+ radius: 0.05,
11421
+ reach: 1,
11422
+ rpm: 40,
11423
+ noise: 4
11424
+ },
11425
+ mass: 2.8,
11426
+ tags: [
11427
+ "heavy"
11428
+ ]
11429
+ },
11430
+ {
11431
+ id: "sickle",
11432
+ name: "Sickle",
11433
+ category: "melee_blade",
11434
+ action: {
11435
+ kind: "melee",
11436
+ damage: "slash",
11437
+ energy: 70,
11438
+ radius: 0.05,
11439
+ reach: 1.1,
11440
+ rpm: 70,
11441
+ noise: 2
11442
+ },
11443
+ mass: 1.2
11444
+ },
11445
+ {
11446
+ id: "police_baton",
11447
+ name: "Police baton",
11448
+ category: "melee_blunt",
11449
+ action: {
11450
+ kind: "melee",
11451
+ damage: "blunt",
11452
+ energy: 70,
11453
+ radius: 0.04,
11454
+ reach: 0.7,
11455
+ rpm: 90,
11456
+ noise: 3
11457
+ },
11458
+ mass: 0.6
11459
+ },
11460
+ {
11461
+ id: "cast_iron_skillet",
11462
+ name: "Cast iron skillet",
11463
+ category: "melee_blunt",
11464
+ action: {
11465
+ kind: "melee",
11466
+ damage: "blunt",
11467
+ energy: 110,
11468
+ radius: 0.12,
11469
+ reach: 0.6,
11470
+ rpm: 60,
11471
+ noise: 8
11472
+ },
11473
+ mass: 1.2
11474
+ },
11475
+ {
11476
+ id: "improvised_spear",
11477
+ name: "Improvised spear",
11478
+ category: "melee_blade",
11479
+ action: {
11480
+ kind: "melee",
11481
+ damage: "slash",
11482
+ energy: 90,
11483
+ radius: 0.02,
11484
+ reach: 1.6,
11485
+ rpm: 50,
11486
+ noise: 1
11487
+ },
11488
+ mass: 1.1
11489
+ },
11490
+ {
11491
+ id: "chainsaw",
11492
+ name: "Chainsaw",
11493
+ category: "melee_blade",
11494
+ action: {
11495
+ kind: "melee",
11496
+ damage: "slash",
11497
+ energy: 400,
11498
+ radius: 0.1,
11499
+ reach: 0.9,
11500
+ rpm: 300,
11501
+ noise: 120
11502
+ },
11503
+ mass: 6,
11504
+ tags: [
11505
+ "heavy"
11506
+ ]
11507
+ },
11508
+ {
11509
+ id: "recurve_bow",
11510
+ name: "Recurve bow",
11511
+ category: "bow",
11512
+ action: {
11513
+ kind: "firearm",
11514
+ caliber: "arrow",
11515
+ rpm: 20,
11516
+ modes: [
11517
+ "single"
11518
+ ],
11519
+ magazine: 1,
11520
+ reload: 1.5,
11521
+ barrel: 1,
11522
+ spreadDeg: 0.4,
11523
+ bloomDeg: 0.4,
11524
+ recoveryDegPerS: 2,
11525
+ recoil: [
11526
+ 0.3,
11527
+ 0.1
11528
+ ],
11529
+ range: 40
11530
+ },
11531
+ mass: 1.2,
11532
+ tags: [
11533
+ "silent"
11534
+ ]
11535
+ }
11536
+ ];
11537
+ var findDuplicateIds = (kind, entries) => {
11538
+ const seen = /* @__PURE__ */ new Set();
11539
+ const problems = [];
11540
+ for (const { id } of entries ?? []) {
11541
+ if (seen.has(id)) problems.push(`${kind} "${id}" is listed more than once`);
11542
+ seen.add(id);
11543
+ }
11544
+ return problems;
11545
+ };
11546
+ var checkRotary = (id, action) => {
11547
+ const problems = [];
11548
+ const { rpm, rpmRange, spin } = action;
11549
+ if (rpmRange && !(rpmRange[0] > 0 && rpmRange[0] <= rpmRange[1])) problems.push(`weapon "${id}" has an empty rate range ${rpmRange[0]}-${rpmRange[1]} rpm`);
11550
+ if (rpmRange && (rpm < rpmRange[0] || rpm > rpmRange[1])) problems.push(`weapon "${id}" fires at ${rpm} rpm, outside its range ${rpmRange[0]}-${rpmRange[1]}`);
11551
+ if (spin && !(spin.upSeconds >= 0 && spin.holdSeconds >= 0)) problems.push(`weapon "${id}" has a negative spin time`);
11552
+ return problems;
11553
+ };
11554
+ var checkFirearm = (def, calibers) => {
11555
+ if (def.action.kind !== "firearm") return [];
11556
+ const problems = [];
11557
+ for (const caliber of [def.action.caliber, ...def.action.acceptsCalibers ?? []]) {
11558
+ if (!calibers.has(caliber)) problems.push(`weapon "${def.id}" uses unknown calibre "${caliber}"`);
11559
+ }
11560
+ if (def.action.modes.length === 0) problems.push(`weapon "${def.id}" has no fire modes`);
11561
+ if (!(def.action.rpm > 0)) problems.push(`weapon "${def.id}" has a rate of fire of ${def.action.rpm} rpm`);
11562
+ if (!(def.action.reload > 0)) problems.push(`weapon "${def.id}" has a reload time of ${def.action.reload} s`);
11563
+ problems.push(...checkRotary(def.id, def.action));
11564
+ return problems;
11565
+ };
11566
+ var checkTimings = (def) => {
11567
+ const timings = "timings" in def.action ? def.action.timings : void 0;
11568
+ return Object.entries(timings ?? {}).filter(([, seconds]) => typeof seconds === "number" && !(seconds > 0)).map(([name, seconds]) => `weapon "${def.id}" has a ${name} time of ${seconds} s`);
11569
+ };
11570
+ var checkExplosiveReference = (def, explosives) => {
11571
+ const id = def.action.kind === "launcher" ? def.action.projectile : def.action.kind === "throwable" ? def.action.explosive : void 0;
11572
+ return id !== void 0 && !explosives.has(id) ? [`weapon "${def.id}" uses unknown explosive "${id}"`] : [];
11573
+ };
11574
+ var checkExplosiveNumbers = (explosive) => {
11575
+ const problems = [];
11576
+ const [low, high] = explosive.fuseRange ?? [1, 1];
11577
+ if (!(low > 0 && high >= low)) problems.push(`explosive "${explosive.id}" has a fuse range of ${low} to ${high} s`);
11578
+ if (explosive.fuseLength !== void 0 && !(explosive.fuseLength > 0)) problems.push(`explosive "${explosive.id}" has a fuse length of ${explosive.fuseLength} m`);
11579
+ if (explosive.burnRate !== void 0 && !(explosive.burnRate > 0)) problems.push(`explosive "${explosive.id}" has a burn rate of ${explosive.burnRate} s/m`);
11580
+ if (explosive.burnTolerance !== void 0 && !(explosive.burnTolerance >= 0 && explosive.burnTolerance < 1)) problems.push(`explosive "${explosive.id}" has a burn tolerance of ${explosive.burnTolerance}`);
11581
+ if (explosive.tripwire && !(explosive.tripwire.length > 0)) problems.push(`explosive "${explosive.id}" has a trip wire of ${explosive.tripwire.length} m`);
11582
+ return problems;
11583
+ };
11584
+ var STATS = /* @__PURE__ */ new Set(["noise", "spread", "hipSpreadExcess", "recoilVertical", "recoilHorizontal", "range", "muzzleSpeed", "mass", "capacity", "reload", "reloadEmpty", "draw", "aim", "faultChance", "heatPerShot"]);
11585
+ var INSTALLS = /* @__PURE__ */ new Set(["field", "tool", "bench"]);
11586
+ var checkAttachment = (attachment, weapons) => {
11587
+ const problems = [];
11588
+ if (attachment.launcher !== void 0 && weapons.get(attachment.launcher)?.action.kind !== "launcher") problems.push(`attachment "${attachment.id}" names "${attachment.launcher}" as its launcher, which is not a launcher weapon`);
11589
+ const label = `attachment "${attachment.id}"`;
11590
+ if (!INSTALLS.has(attachment.install)) problems.push(`${label} installs "${attachment.install}"`);
11591
+ if (attachment.mounts.length === 0) problems.push(`${label} has no mount`);
11592
+ if (attachment.interfaces.length === 0) problems.push(`${label} has no interface`);
11593
+ for (const effect of attachment.effects) {
11594
+ if (!STATS.has(effect.stat)) problems.push(`${label} changes the unknown stat "${effect.stat}"`);
11595
+ if (effect.mul !== void 0 && !(effect.mul > 0)) problems.push(`${label} multiplies ${effect.stat} by ${effect.mul}`);
11596
+ }
11597
+ return problems;
11598
+ };
11599
+ var checkFitted = (def, attachments) => {
11204
11600
  if (!attachments) return [];
11205
11601
  return (def.fitted ?? []).flatMap((part) => {
11206
11602
  const problems = [];
@@ -11222,18 +11618,6 @@ var GameWeaponsThree = (() => {
11222
11618
  ...[...explosives.values()].flatMap(checkExplosiveNumbers),
11223
11619
  ...checkRuleDefaults(rules)
11224
11620
  ];
11225
- var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
11226
- var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
11227
- var deepMerge = (base, patch) => {
11228
- if (!patch) return base;
11229
- const result2 = { ...base };
11230
- for (const [key, value] of Object.entries(patch)) {
11231
- if (value === void 0 || UNSAFE_KEYS.has(key)) continue;
11232
- const current = result2[key];
11233
- result2[key] = isPlainObject(current) && isPlainObject(value) ? deepMerge(current, value) : value;
11234
- }
11235
- return result2;
11236
- };
11237
11621
  var modesEn = { single: "single action", semi: "semi-automatic", burst: "burst", auto: "automatic", pump: "pump action", bolt: "bolt action", lever: "lever action", break: "break action", volley: "volley" };
11238
11622
  var categoriesEn = {
11239
11623
  pistol: "pistol",
@@ -11425,20 +11809,6 @@ var GameWeaponsThree = (() => {
11425
11809
  if (explosive.fuseRange) return explosive.fuseRange[0] + (explosive.fuseRange[1] - explosive.fuseRange[0]) * random();
11426
11810
  return explosive.fuse;
11427
11811
  };
11428
- var mergeById = (shipped, extra) => {
11429
- const entries = /* @__PURE__ */ new Map();
11430
- for (const entry of [...shipped, ...extra ?? []]) entries.set(entry.id, entry);
11431
- return entries;
11432
- };
11433
- var applyPatches = (entries, patches) => {
11434
- for (const [id, patch] of Object.entries(patches ?? {})) {
11435
- const entry = entries.get(id);
11436
- if (entry) entries.set(id, deepMerge(entry, patch));
11437
- }
11438
- };
11439
- var removeIds = (entries, ids) => {
11440
- for (const id of ids ?? []) entries.delete(id);
11441
- };
11442
11812
  var resolveThrowables = (weapons, explosives) => {
11443
11813
  for (const [id, def] of weapons) {
11444
11814
  if (def.action.kind !== "throwable" || def.action.explosive === void 0) continue;
@@ -12674,6 +13044,7 @@ var GameWeaponsThree = (() => {
12674
13044
  caliber: cal.diameter,
12675
13045
  caliberId: cal.id,
12676
13046
  speed,
13047
+ mass: cal.bulletGrams,
12677
13048
  energyAt,
12678
13049
  pellet: p,
12679
13050
  weapon: this.def.id
@@ -12859,7 +13230,7 @@ var GameWeaponsThree = (() => {
12859
13230
  var EXPLOSIVES = explosives_default;
12860
13231
  var ARCHETYPES = archetypes_default;
12861
13232
  var ARCHETYPE_IDS = ARCHETYPES.map((def) => def.id);
12862
- var WEAPONS = [...ARCHETYPES, ...firearms_default, ...launchers_default, ...explosive_weapons_default];
13233
+ var WEAPONS = [...ARCHETYPES, ...firearms_default, ...launchers_default, ...explosive_weapons_default, ...melee_weapons_default];
12863
13234
  var explosiveOf = (def, explosives) => {
12864
13235
  const id = def.action.kind === "launcher" ? def.action.projectile : def.action.kind === "throwable" ? def.action.explosive : void 0;
12865
13236
  return id === void 0 ? null : explosives.get(id) ?? null;
@@ -13605,7 +13976,68 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
13605
13976
  };
13606
13977
  };
13607
13978
 
13608
- // src/merge-by-material.ts
13979
+ // ../game-props3d/dist/index.js
13980
+ var T = (id, interior, w, d, h, y, solid2, group) => ({ id, interior, w, d, h, y, solid: solid2, group });
13981
+ var TYPE_DEFS = [
13982
+ T("ceiling_light", true, 0.4, 0.4, 0.07, 0, false, "ceiling"),
13983
+ T("pendant", true, 0.34, 0.34, 1, 0, false, "ceiling"),
13984
+ T("track_spot", true, 1.2, 0.1, 0.2, 0, false, "ceiling"),
13985
+ T("chandelier", true, 0.7, 0.7, 0.7, 0, false, "ceiling"),
13986
+ T("wall_ac", true, 0.9, 0.2, 0.28, 2.15, false, "wall"),
13987
+ T("picture_small", true, 0.35, 0.03, 0.45, 1.3, false, "wall"),
13988
+ T("picture_large", true, 1, 0.04, 0.7, 1.2, false, "wall"),
13989
+ T("mirror", true, 0.6, 0.03, 1.2, 0.9, false, "wall"),
13990
+ T("curtain", true, 1.6, 0.12, 2.3, 0.1, false, "wall"),
13991
+ T("blinds", true, 1.2, 0.08, 1.3, 1, false, "wall"),
13992
+ T("flat_tv", true, 1.2, 0.06, 0.7, 1, false, "wall"),
13993
+ T("floor_plant", true, 0.5, 0.5, 1.1, 0, true, "plants"),
13994
+ T("table_plant", true, 0.2, 0.2, 0.3, 0, false, "plants"),
13995
+ T("hanging_plant", true, 0.5, 0.5, 1, 0, false, "plants"),
13996
+ T("tv_stand", true, 1.6, 0.42, 1.05, 0, true, "furniture"),
13997
+ T("breakfast_bar", true, 2, 1.6, 0.95, 0, true, "furniture"),
13998
+ T("sofa_l", true, 2.8, 1.7, 0.85, 0, true, "furniture"),
13999
+ T("double_bed_modern", true, 1.6, 2.1, 1.2, 0, true, "furniture"),
14000
+ T("home_office", true, 1.4, 0.75, 1.2, 0, true, "furniture"),
14001
+ T("closet", true, 2.2, 0.6, 2.3, 0, true, "furniture"),
14002
+ T("vanity", true, 0.6, 0.45, 1.5, 0, true, "furniture"),
14003
+ T("ac_condenser", false, 0.8, 0.3, 0.55, 0.5, true, "yard"),
14004
+ T("lawn", false, 4, 4, 0.02, 0, false, "floor"),
14005
+ T("tile_floor", false, 4, 3, 0.03, 0, false, "floor"),
14006
+ T("concrete", false, 4, 4, 0.03, 0, false, "floor"),
14007
+ T("deck_floor", false, 4, 3, 0.04, 0, false, "floor"),
14008
+ T("dirt", false, 4, 4, 0.02, 0, false, "floor"),
14009
+ T("gourmet_counter", false, 4.2, 3, 2.9, 0, true, "yard"),
14010
+ T("sauna", false, 2, 1.8, 2.1, 0, true, "yard"),
14011
+ T("soaking_tub", false, 1.3, 1.3, 0.8, 0, true, "yard"),
14012
+ T("pool", false, 4.5, 2.8, 0.1, 0, true, "yard"),
14013
+ T("deck", false, 3.5, 3, 0.22, 0, true, "yard"),
14014
+ T("sun_lounger", false, 0.62, 1.9, 0.5, 0, true, "yard"),
14015
+ T("garden_bed", false, 2, 1, 0.9, 0, true, "yard"),
14016
+ T("fruit_tree", false, 2.4, 2.4, 3.2, 0, true, "yard"),
14017
+ T("palm_tree", false, 2, 2, 4.5, 0, true, "yard"),
14018
+ T("pergola", false, 3.5, 3, 2.4, 0, true, "yard"),
14019
+ T("slide", false, 0.8, 2.6, 1.5, 0, true, "yard"),
14020
+ T("swing", false, 2.2, 1.7, 2.2, 0, true, "yard"),
14021
+ T("bbq_simple", false, 0.9, 0.6, 1, 0, true, "yard"),
14022
+ T("garage_car", false, 1.8, 4.2, 1.45, 0, true, "yard"),
14023
+ T("window_boards", true, 1.2, 0.06, 1.15, 1, false, "house_state"),
14024
+ T("barricade", true, 1.5, 0.7, 1.8, 0, true, "house_state"),
14025
+ T("sandbags", false, 1.6, 0.5, 0.6, 0, true, "house_state"),
14026
+ T("barbed_wire", false, 4, 0.2, 0.6, 0, false, "house_state"),
14027
+ T("razor_wire", false, 4, 0.6, 0.6, 0, false, "house_state"),
14028
+ T("water_bottle_crates", true, 0.9, 0.6, 0.65, 0, true, "house_state"),
14029
+ T("canned_food", true, 0.4, 0.3, 0.3, 0, false, "house_state"),
14030
+ T("water_jug", true, 0.3, 0.3, 0.5, 0, true, "house_state"),
14031
+ T("floor_mattress", true, 0.9, 1.9, 0.3, 0, false, "house_state"),
14032
+ T("lantern", true, 0.16, 0.16, 0.3, 0, false, "house_state"),
14033
+ T("candles", true, 0.25, 0.25, 0.2, 0, false, "house_state"),
14034
+ T("open_suitcases", true, 1.5, 0.7, 0.6, 0, false, "house_state"),
14035
+ T("clothes_on_floor", true, 1.2, 1, 0.05, 0, false, "house_state"),
14036
+ T("fallen_chair", true, 0.5, 0.5, 0.5, 0, false, "house_state"),
14037
+ T("blood_stains", true, 1.2, 1.2, 0.01, 0, false, "house_state"),
14038
+ T("open_door", true, 0.9, 0.15, 2.15, 0, false, "house_state")
14039
+ ];
14040
+ var DEFS = Object.fromEntries(TYPE_DEFS.map((t) => [t.id, t]));
13609
14041
  var ATTRIBUTES = ["position", "normal", "uv"];
13610
14042
  var isVisible = (object) => {
13611
14043
  for (let node = object; node; node = node.parent) if (!node.visible) return false;
@@ -17026,173 +17458,6 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
17026
17458
  };
17027
17459
  }
17028
17460
 
17029
- // ../game-contracts/dist/index.js
17030
- var TOOL_CLIP = {
17031
- DRAW: "draw",
17032
- SHOT: "shot",
17033
- MISFIRE: "misfire",
17034
- EMPTY: "empty",
17035
- CYCLE: "cycle",
17036
- RELOAD: "reload",
17037
- FEED: "feed",
17038
- FAULT: "fault",
17039
- CLEAR: "clear",
17040
- SAFETY_ON: "safety_on",
17041
- SAFETY_OFF: "safety_off",
17042
- MODE: "mode",
17043
- /** A throwable with the pin out, held until it is thrown. */
17044
- COOK: "cook",
17045
- /** The stock folds, unfolds, slides in or out (variants `fold`, `unfold`, `collapse`, `extend`). */
17046
- STOCK: "stock",
17047
- /** A light or a laser is switched (variants `on`, `off`). */
17048
- TOGGLE: "toggle"
17049
- };
17050
- var stockVariant = (event) => {
17051
- const isFolding = event.kind === void 0 ? false : event.kind.includes("fold");
17052
- if (event.state === "collapsed") return isFolding ? "fold" : "collapse";
17053
- return isFolding ? "unfold" : "extend";
17054
- };
17055
- var toolAnimationStep = (event) => {
17056
- switch (event.type) {
17057
- case "tool":
17058
- return { type: "play", clip: TOOL_CLIP.DRAW, ...event.duration === void 0 ? {} : { duration: event.duration } };
17059
- case "shot":
17060
- return { type: "play", clip: TOOL_CLIP.SHOT, repeat: Math.max(1, event.rounds ?? 1) };
17061
- case "misfire":
17062
- return { type: "play", clip: TOOL_CLIP.MISFIRE };
17063
- case "empty":
17064
- return { type: "play", clip: TOOL_CLIP.EMPTY };
17065
- case "mode":
17066
- return { type: "play", clip: TOOL_CLIP.MODE, variant: event.mode };
17067
- case "fault":
17068
- return { type: "play", clip: TOOL_CLIP.FAULT, variant: event.fault };
17069
- case "clear":
17070
- if (event.phase === "end") return { type: "finish", clip: TOOL_CLIP.CLEAR };
17071
- return { type: "play", clip: TOOL_CLIP.CLEAR, variant: event.fault, ...event.duration === void 0 ? {} : { duration: event.duration } };
17072
- case "safety":
17073
- return { type: "play", clip: event.on ? TOOL_CLIP.SAFETY_ON : TOOL_CLIP.SAFETY_OFF };
17074
- case "cycle":
17075
- if (event.phase === "end") return { type: "finish", clip: TOOL_CLIP.CYCLE };
17076
- return { type: "play", clip: TOOL_CLIP.CYCLE, ...event.duration === void 0 ? {} : { duration: event.duration } };
17077
- case "reload":
17078
- if (event.phase === "end") return { type: "finish", clip: TOOL_CLIP.RELOAD };
17079
- return { type: "play", clip: TOOL_CLIP.RELOAD, ...event.kind === void 0 ? {} : { variant: event.kind }, ...event.duration === void 0 ? {} : { duration: event.duration } };
17080
- case "stage":
17081
- return { type: "sync", stage: event.stage };
17082
- case "feed":
17083
- return { type: "play", clip: TOOL_CLIP.FEED };
17084
- case "cook":
17085
- if (event.phase === "start") return { type: "play", clip: TOOL_CLIP.COOK, duration: event.fuse };
17086
- return { type: "finish", clip: TOOL_CLIP.COOK };
17087
- case "stock":
17088
- return { type: "play", clip: TOOL_CLIP.STOCK, variant: stockVariant(event), ...event.duration === void 0 ? {} : { duration: event.duration } };
17089
- case "toggle":
17090
- return { type: "play", clip: TOOL_CLIP.TOGGLE, variant: event.on ? "on" : "off" };
17091
- default:
17092
- return null;
17093
- }
17094
- };
17095
- var STAGE_EVENT = "stage";
17096
- var scaleClip = (clip, duration) => {
17097
- const factor = clip.duration > 0 ? duration / clip.duration : 1;
17098
- return {
17099
- ...clip,
17100
- duration,
17101
- keys: clip.keys.map((key) => ({ ...key, t: key.t * factor })),
17102
- ...clip.events ? { events: clip.events.map((event) => ({ ...event, t: event.t * factor })) } : {}
17103
- };
17104
- };
17105
- var ZERO = [0, 0, 0];
17106
- var mix = (from, to, fraction) => [
17107
- (from[0] ?? 0) + ((to[0] ?? 0) - (from[0] ?? 0)) * fraction,
17108
- (from[1] ?? 0) + ((to[1] ?? 0) - (from[1] ?? 0)) * fraction,
17109
- (from[2] ?? 0) + ((to[2] ?? 0) - (from[2] ?? 0)) * fraction
17110
- ];
17111
- var sampleClip = (clip, partId, time) => {
17112
- const points = clip.keys.map((key) => {
17113
- const pose = partId === void 0 ? key : key.parts?.[partId];
17114
- return { t: key.t, position: pose?.position ?? ZERO, rotation: pose?.rotation ?? ZERO };
17115
- });
17116
- const first = points[0];
17117
- if (!first || first.t > 0) points.unshift({ t: 0, position: ZERO, rotation: ZERO });
17118
- const last = points[points.length - 1];
17119
- if (!last) return { position: ZERO, rotation: ZERO };
17120
- if (time >= last.t) return { position: mix(last.position, last.position, 0), rotation: mix(last.rotation, last.rotation, 0) };
17121
- const index = Math.max(0, points.findIndex((point, i) => time >= point.t && time < (points[i + 1]?.t ?? Infinity)));
17122
- const from = points[index];
17123
- const to = points[index + 1];
17124
- if (!from || !to) return { position: ZERO, rotation: ZERO };
17125
- const fraction = to.t > from.t ? (time - from.t) / (to.t - from.t) : 0;
17126
- return { position: mix(from.position, to.position, fraction), rotation: mix(from.rotation, to.rotation, fraction) };
17127
- };
17128
- var createToolClipPlayer = (options) => {
17129
- const { clips, rig, onEvent } = options;
17130
- let instances = [];
17131
- const find = (clip, variant) => {
17132
- const name = variant !== void 0 && clips[`${clip}_${variant}`] ? `${clip}_${variant}` : clip;
17133
- const found = clips[name];
17134
- return found ? { name, clip: found } : void 0;
17135
- };
17136
- const render = (instance) => {
17137
- const time = Math.min(instance.time, instance.clip.duration);
17138
- const root = sampleClip(instance.clip, void 0, time);
17139
- if (instance.clip.keys.some((key) => key.position || key.rotation)) rig.setRootPose?.(root);
17140
- const touched = new Set(instance.clip.keys.flatMap((key) => Object.keys(key.parts ?? {})));
17141
- for (const id of touched) rig.setPartPose(id, sampleClip(instance.clip, id, time));
17142
- };
17143
- const fireEvents = (instance, upTo) => {
17144
- const events = instance.clip.events ?? [];
17145
- while (instance.fired < events.length && (events[instance.fired]?.t ?? Infinity) <= upTo + 1e-9) {
17146
- const event = events[instance.fired];
17147
- instance.fired += 1;
17148
- if (event) onEvent?.(event, instance.name);
17149
- }
17150
- };
17151
- return {
17152
- apply(step) {
17153
- if (step.type === "sync") {
17154
- for (const instance of instances) {
17155
- const marker = (instance.clip.events ?? []).find((event) => event.type === STAGE_EVENT && event.data?.stage === step.stage);
17156
- if (marker && instance.time < marker.t) instance.time = marker.t;
17157
- }
17158
- return;
17159
- }
17160
- if (step.type === "finish") {
17161
- for (const instance of instances) {
17162
- if (instance.name !== step.clip && !instance.name.startsWith(`${step.clip}_`)) continue;
17163
- fireEvents(instance, instance.clip.duration);
17164
- instance.time = instance.clip.duration;
17165
- }
17166
- return;
17167
- }
17168
- const found = find(step.clip, step.variant);
17169
- if (!found) return;
17170
- const clip = step.duration && step.duration > 0 ? scaleClip(found.clip, step.duration) : found.clip;
17171
- const interval = options.repeatInterval?.() ?? 0.1;
17172
- instances = instances.filter((existing) => existing.name !== found.name || existing.delay > 0);
17173
- for (let round2 = 0; round2 < (step.repeat ?? 1); round2++) {
17174
- instances.push({ name: found.name, clip, time: 0, delay: round2 * interval, fired: 0 });
17175
- }
17176
- },
17177
- update(dt) {
17178
- for (const instance of instances) {
17179
- const carried = instance.delay > 0 ? dt - instance.delay : dt;
17180
- if (instance.delay > 0) {
17181
- instance.delay = Math.max(0, instance.delay - dt);
17182
- if (instance.delay > 0) continue;
17183
- }
17184
- instance.time += carried;
17185
- fireEvents(instance, instance.time);
17186
- render(instance);
17187
- }
17188
- instances = instances.filter((instance) => instance.delay > 0 || instance.time < instance.clip.duration);
17189
- },
17190
- get playing() {
17191
- return instances.map((instance) => instance.name);
17192
- }
17193
- };
17194
- };
17195
-
17196
17461
  // src/animation/clip-builder.ts
17197
17462
  var ROOT = "root";
17198
17463
  var AXIS_INDEX = { x: 0, y: 1, z: 2 };
@@ -19087,6 +19352,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19087
19352
  source: "generated",
19088
19353
  file: "generated/carl_gustaf_m4.svg"
19089
19354
  },
19355
+ cast_iron_skillet: {
19356
+ source: "generated",
19357
+ file: "generated/cast_iron_skillet.svg"
19358
+ },
19090
19359
  cbc_122: {
19091
19360
  source: "generated",
19092
19361
  file: "generated/cbc_122.svg"
@@ -19099,6 +19368,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19099
19368
  source: "generated",
19100
19369
  file: "generated/cbc_pump_military_3_0.svg"
19101
19370
  },
19371
+ chainsaw: {
19372
+ source: "generated",
19373
+ file: "generated/chainsaw.svg"
19374
+ },
19102
19375
  cheytac_m200: {
19103
19376
  source: "generated",
19104
19377
  file: "generated/cheytac_m200.svg"
@@ -19255,6 +19528,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19255
19528
  source: "generated",
19256
19529
  file: "generated/hammer.svg"
19257
19530
  },
19531
+ hatchet: {
19532
+ source: "generated",
19533
+ file: "generated/hatchet.svg"
19534
+ },
19258
19535
  hk416: {
19259
19536
  source: "generated",
19260
19537
  file: "generated/hk416.svg"
@@ -19323,6 +19600,14 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19323
19600
  source: "generated",
19324
19601
  file: "generated/improvised_flamethrower.svg"
19325
19602
  },
19603
+ improvised_spear: {
19604
+ source: "generated",
19605
+ file: "generated/improvised_spear.svg"
19606
+ },
19607
+ iron_pipe: {
19608
+ source: "generated",
19609
+ file: "generated/iron_pipe.svg"
19610
+ },
19326
19611
  kel_tec_ksg: {
19327
19612
  source: "generated",
19328
19613
  file: "generated/kel_tec_ksg.svg"
@@ -19391,6 +19676,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19391
19676
  source: "generated",
19392
19677
  file: "generated/makarov_pm.svg"
19393
19678
  },
19679
+ meat_cleaver: {
19680
+ source: "generated",
19681
+ file: "generated/meat_cleaver.svg"
19682
+ },
19394
19683
  mg3: {
19395
19684
  source: "generated",
19396
19685
  file: "generated/mg3.svg"
@@ -19415,6 +19704,14 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19415
19704
  source: "generated",
19416
19705
  file: "generated/mossberg_500_cruiser.svg"
19417
19706
  },
19707
+ nail_bat: {
19708
+ source: "generated",
19709
+ file: "generated/nail_bat.svg"
19710
+ },
19711
+ pickaxe: {
19712
+ source: "generated",
19713
+ file: "generated/pickaxe.svg"
19714
+ },
19418
19715
  pipe_bomb: {
19419
19716
  source: "generated",
19420
19717
  file: "generated/pipe_bomb.svg"
@@ -19435,6 +19732,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19435
19732
  source: "generated",
19436
19733
  file: "generated/pkm.svg"
19437
19734
  },
19735
+ police_baton: {
19736
+ source: "generated",
19737
+ file: "generated/police_baton.svg"
19738
+ },
19438
19739
  pp19_bizon: {
19439
19740
  source: "generated",
19440
19741
  file: "generated/pp19_bizon.svg"
@@ -19447,6 +19748,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19447
19748
  source: "generated",
19448
19749
  file: "generated/pry_bar.svg"
19449
19750
  },
19751
+ recurve_bow: {
19752
+ source: "generated",
19753
+ file: "generated/recurve_bow.svg"
19754
+ },
19450
19755
  remington_700: {
19451
19756
  source: "generated",
19452
19757
  file: "generated/remington_700.svg"
@@ -19523,6 +19828,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19523
19828
  source: "generated",
19524
19829
  file: "generated/shotgun_semi_12.svg"
19525
19830
  },
19831
+ sickle: {
19832
+ source: "generated",
19833
+ file: "generated/sickle.svg"
19834
+ },
19526
19835
  sig_mpx: {
19527
19836
  source: "generated",
19528
19837
  file: "generated/sig_mpx.svg"
@@ -19575,6 +19884,10 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19575
19884
  source: "generated",
19576
19885
  file: "generated/sw_model_29.svg"
19577
19886
  },
19887
+ tactical_knife: {
19888
+ source: "generated",
19889
+ file: "generated/tactical_knife.svg"
19890
+ },
19578
19891
  taurus_82: {
19579
19892
  source: "generated",
19580
19893
  file: "generated/taurus_82.svg"
@@ -19673,7 +19986,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19673
19986
  shape: shape.shape,
19674
19987
  position: shape.position,
19675
19988
  ...SHELL,
19676
- explodes: { energy: def.energy, radius: def.injuryRadius, ...def.chain ? { chain: { ...def.chain } } : {} }
19989
+ explodes: { energy: def.energy, radius: def.injuryRadius, effect: def.id, ...def.chain ? { chain: { ...def.chain } } : {} }
19677
19990
  });
19678
19991
  var meshOf = (THREE, shape) => {
19679
19992
  const geometry = shape.shape.kind === "cylinder" ? new THREE.CylinderGeometry(shape.shape.radius, shape.shape.radius, shape.shape.height, 16) : shape.shape.kind === "box" ? new THREE.BoxGeometry(...shape.shape.size) : new THREE.SphereGeometry(0.2);
@@ -19701,6 +20014,51 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19701
20014
  };
19702
20015
  };
19703
20016
 
20017
+ // src/data/world-effects.json
20018
+ var world_effects_default = {
20019
+ pistol: { muzzle: "muzzle_glock", casing: "casing_brass" },
20020
+ tiny: { muzzle: "muzzle_glock", casing: "casing_brass" },
20021
+ rifle: { muzzle: "muzzle_ak", casing: "casing_brass" },
20022
+ shell: { muzzle: "muzzle_r870", casing: "casing_shell" },
20023
+ big: { muzzle: "muzzle_barrett", casing: "casing_50" }
20024
+ };
20025
+
20026
+ // src/world-effects.ts
20027
+ var WORLD_EFFECTS = world_effects_default;
20028
+ var PORT_BACK = 0.35;
20029
+ var PORT_RIGHT = 0.04;
20030
+ function createWorldEffects(options) {
20031
+ const ids = options.ids ?? WORLD_EFFECTS;
20032
+ const caseTypeOf = options.caseTypeOf ?? ((weapon) => {
20033
+ const def = options.arsenal.weapons.get(weapon);
20034
+ return def && def.action.kind === "firearm" ? options.arsenal.calibers.get(def.action.caliber)?.caseType : void 0;
20035
+ });
20036
+ let counter = 0;
20037
+ return {
20038
+ /** Plays what the event asks for and answers how many effects started. */
20039
+ handle(event, sink) {
20040
+ if (!sink || event.type !== "shot" || !event.origin) return 0;
20041
+ const caseType = caseTypeOf(event.weapon) ?? "rifle";
20042
+ const entry = ids[caseType];
20043
+ if (!entry) return 0;
20044
+ const direction = event.direction ?? [0, 0, -1];
20045
+ const seed = ++counter;
20046
+ let started = 0;
20047
+ for (let shot = 0; shot < Math.max(1, event.rounds ?? 1); shot++) {
20048
+ if (sink.play({ effect: entry.muzzle, point: event.origin, direction, seed: seed * 8 + shot }) !== null) started++;
20049
+ }
20050
+ const side = [direction[2], 0, -direction[0]];
20051
+ const port = [
20052
+ event.origin[0] - direction[0] * PORT_BACK + side[0] * PORT_RIGHT,
20053
+ event.origin[1] - direction[1] * PORT_BACK,
20054
+ event.origin[2] - direction[2] * PORT_BACK + side[2] * PORT_RIGHT
20055
+ ];
20056
+ if (sink.play({ effect: entry.casing, point: port, direction, seed: seed * 8 + 7 }) !== null) started++;
20057
+ return started;
20058
+ }
20059
+ };
20060
+ }
20061
+
19704
20062
  // src/area.ts
19705
20063
  var WEAPON_ITEM_PREFIX = "weapon_";
19706
20064
  var COUNTER_ITEM_ID = "counter_weapons";
@@ -19977,7 +20335,9 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
19977
20335
  } else if (change.kind === "control" && change.id === "sound_distance" && typeof change.value === "number") soundDistance = change.value;
19978
20336
  else if (change.kind === "control" && change.id === "fuse_length" && typeof change.value === "number") lent.configureTool?.({ fuse_length: String(change.value) });
19979
20337
  });
20338
+ const worldEffects = createWorldEffects({ arsenal });
19980
20339
  const offEvents = lent.on((event) => {
20340
+ worldEffects.handle(event, lent.effects);
19981
20341
  if (event.type === "explosion") {
19982
20342
  const away = Math.hypot(event.point[0] - camera.position.x, event.point[1] - camera.position.y, event.point[2] - camera.position.z);
19983
20343
  audio?.play("explosion", Math.min(1, EXPLOSION_VOLUME_BASE + event.radius * EXPLOSION_VOLUME_PER_METRE), away);