@adatechnology/game-weapons-three 0.9.0 → 0.10.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/README.md CHANGED
@@ -6,6 +6,8 @@ The muzzle flash, the smoke, the backblast of a launched rocket and the flame je
6
6
 
7
7
  **Hands.** The weapon in the hands tells the host where the hands go (`host.setHandTargets`, `HandTargets` of `game-contracts`): the right on the pistol grip (the `grip` pivot of the model, else under the receiver), the left on the handguard, on the pump of a pump gun (it moves with the pump), under the right on a handgun, and to the magazine while it is changed; the generic props take the right hand on the `handle` and the left under the `head` of a long gun. Sizes come from the model, the hands shrink with a model drawn small.
8
8
 
9
+ **Safety (destravar).** Turning the safety on or off (the `safety` event, B in the test room) plays `safety_on` / `safety_off` on every model: the weapon cants to the right and rises a little toward the eye, the shooting hand slides up its grip so the thumb reaches the lever (`thumb_safety` in `src/data/hand-work.json`; an AK-style rifle's support hand goes over to the big lever on the right, `lever_right`), the lever flicks (when the model draws one, the `safety` part) and the weapon settles. The seconds and radians are `SAFETY_MOTION` of `src/animation/shot-clips.ts`.
10
+
9
11
  **Weapons the gallery did not model.** The melee weapons and tools with no gallery model (the bat, the axe, the machete, the knife, the hammer, the crowbar, the crossbow, the wrench, the extinguisher, the blowtorch...) are drawn with the team's item model of `@adatechnology/game-items-three` (Itens do Vale Cinzento), on the counters and in the hands: `buildItemWeaponModel` lays it along +x, flat, the head at +x, with an empty `handle` where the hand closes. The item is the one with the weapon's id, or the one `registerWeaponItemModel(weaponId, { item, turn, flip })` names (`wrench` → `adjustable_wrench`); `registerWeaponItemModel(id, null)` keeps the low-poly prop. Only a weapon with neither model falls back to the low-poly boxes of its category (`weaponProp`), and the kit's scene check (`tools/kit-audit/scene.mjs`) lists those as pending.
10
12
 
11
13
  **Reload sounds.** Each step of a reload makes its noise where the clip says: the clips carry `sound` cues (`cue(t, 'sound', { id })`), so the sound follows the animation and scales with the weapon's reload time (`mag_release`, `mag_out`, `mag_insert`, `mag_seat`, `slide_release`, `charging_handle_pull/release`, `bolt_lift/pull/push/lock`, `shell_insert` once per round, `pump_back/forward`, `lever_open/close`, `cylinder_open/eject/close`, `speedloader`, `barrels_open/close`, `belt_cover_open/close`, `belt_insert`, `rocket_load/seat`, `grenade_load`, `breech_open/close`, `tube_drop/seat`). The recipes are data (`src/data/reload-sounds.json`: layers of the game-audio primitives); a profile (`soundProfileOf`: weight light/medium/heavy from the model family, steel or polymer from a list in the data) changes their pitch and level, and `createWeaponAudio(volume, { reloadCues })` replaces or adds recipes. `createWeaponFx({ onSound })` is told at each cue (the test area logs it as a `sound` entry); `WeaponFx.clips` says which reload variant is playing. `tests/reload-sounds.test.ts` checks the cue order of every weapon and kind, one recipe per cue, and the per-round sounds.
@@ -19681,6 +19681,23 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19681
19681
  return angle === 0 ? q : quat.mul(q, quat.fromAxisAngle([0, 0, 1], angle));
19682
19682
  };
19683
19683
  var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
19684
+ var tearStack = (inp, root, t, u, v, n, sheet) => {
19685
+ const { dims, rnd: rnd2, maxFragments } = inp;
19686
+ const layers = clamp(Math.ceil(dims[t] / STACK_LAYER), 2, 4);
19687
+ const per = clamp(n, 3, Math.max(3, Math.floor(maxFragments / layers)));
19688
+ const out = [];
19689
+ for (let k = 0; k < layers; k++) {
19690
+ const off = -dims[t] / 2 + (k + 0.5) * dims[t] / layers;
19691
+ for (const b of bsp(root, per, [u, v], rnd2)) {
19692
+ if (out.length >= maxFragments) return out;
19693
+ const c = [b.c[0], b.c[1], b.c[2]];
19694
+ c[t] = off;
19695
+ out.push({ center: c, size: [b.s[u], b.s[v], sheet], q: frame(u, v, t, (rnd2() - 0.5) * 0.8), shape: "scrap", weight: b.s[u] * b.s[v], inner: out.length % 4 !== 0 });
19696
+ }
19697
+ }
19698
+ return out;
19699
+ };
19700
+ var STACK_LAYER = 0.012;
19684
19701
  var planFragments = (inp) => {
19685
19702
  const { style, dims, rnd: rnd2, maxFragments } = inp;
19686
19703
  const [fmin, fmax] = inp.fragmentSize;
@@ -19723,6 +19740,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19723
19740
  }
19724
19741
  if (style === "tear") {
19725
19742
  const n2 = clamp(Math.round(dims[u] * dims[v] / (mean * mean)), 3, maxFragments);
19743
+ const sheet = inp.sheet ?? 0;
19744
+ if (sheet > 0 && dims[t] > sheet * 4) return tearStack(inp, root, t, u, v, n2, sheet);
19726
19745
  const th2 = Math.min(inp.thickness, dims[t]);
19727
19746
  for (const b of bsp(root, n2, [u, v], rnd2)) {
19728
19747
  out.push({ center: [b.c[0], b.c[1], b.c[2]], size: [b.s[u], b.s[v], th2], q: frame(u, v, t), shape: "scrap", weight: b.s[u] * b.s[v] });
@@ -21305,6 +21324,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21305
21324
  0.03,
21306
21325
  0.15
21307
21326
  ],
21327
+ sheet: 1e-3,
21308
21328
  damageScale: {
21309
21329
  slash: 3
21310
21330
  },
@@ -21320,7 +21340,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21320
21340
  smoke: "grey"
21321
21341
  },
21322
21342
  visual: {
21323
- charColor: 1709072
21343
+ charColor: 1709072,
21344
+ innerColor: 15524048
21324
21345
  },
21325
21346
  tags: [
21326
21347
  "fabric",
@@ -23033,7 +23054,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23033
23054
  hit: hitLocal,
23034
23055
  fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
23035
23056
  maxFragments,
23036
- rnd: rnd2
23057
+ rnd: rnd2,
23058
+ sheet: p.mat.sheet
23037
23059
  });
23038
23060
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
23039
23061
  const mass = p.st.mass;
@@ -23057,7 +23079,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23057
23079
  velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd2(), dv[2] * k],
23058
23080
  angularVelocity: [(rnd2() - 0.5) * 12, (rnd2() - 0.5) * 12, (rnd2() - 0.5) * 12],
23059
23081
  mass: mass * f.weight / totW,
23060
- ...marks.length > 0 ? { marks } : {}
23082
+ ...marks.length > 0 ? { marks } : {},
23083
+ ...f.inner ? { inner: true } : {}
23061
23084
  };
23062
23085
  });
23063
23086
  const reinforcement = p.spec.reinforcement;
@@ -37811,6 +37834,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
37811
37834
  /** Closure of the fingers on a grip and on a forend (0 open, 1 closed). */
37812
37835
  closed: 0.9,
37813
37836
  forend: 0.6,
37837
+ /** How far the shooting hand slides on its grip so the thumb reaches the safety lever. */
37838
+ thumbReach: 0.02,
37814
37839
  /** How fast the supporting hand goes to the magazine and back, per second. */
37815
37840
  magRate: 7,
37816
37841
  /** The hands shrink with the tool: a tool drawn at scale `s` takes hands at `s ** handScaleExponent`. */
@@ -48497,9 +48522,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
48497
48522
  );
48498
48523
  return [misfire, ...stopped];
48499
48524
  }
48525
+ var SAFETY_MOTION = { cant: 0.22, lift: 0.012, in: 0.14, flickAt: 0.16, flick: 0.1, outAt: 0.32, out: 0.16, lever: 0.6 };
48500
48526
  function safetyClips(context) {
48501
- if (!context.parts.safety) return [];
48502
- return [composeClip("safety_on", [turn("safety", "z", 0, 0.12, 0, -0.6)]), composeClip("safety_off", [turn("safety", "z", 0, 0.12, -0.6, 0)]), composeClip("mode", [turn("safety", "z", 0, 0.07, 0, 0.4), turn("safety", "z", 0.07, 0.07, 0.4, 0)])];
48527
+ const { cant, lift, in: rise, flickAt, flick, outAt, out, lever } = SAFETY_MOTION;
48528
+ const carry = () => [turn(ROOT, "x", 0, rise, 0, cant), move(ROOT, "y", 0, rise, 0, lift), turn(ROOT, "x", outAt, out, cant, 0), move(ROOT, "y", outAt, out, lift, 0)];
48529
+ const flickTo = (from, to) => context.parts.safety ? [turn("safety", "z", flickAt, flick, from, to)] : [];
48530
+ const clips = [composeClip("safety_on", [...carry(), ...flickTo(0, -lever)]), composeClip("safety_off", [...carry(), ...flickTo(-lever, 0)])];
48531
+ if (context.parts.safety) clips.push(composeClip("mode", [turn("safety", "z", 0, 0.07, 0, 0.4), turn("safety", "z", 0.07, 0.07, 0.4, 0)]));
48532
+ return clips;
48503
48533
  }
48504
48534
  var drawClip = () => composeClip("draw", [move(ROOT, "y", 0, 0.35, -0.1, 0), turn(ROOT, "z", 0, 0.35, -0.6, 0)]);
48505
48535
 
@@ -58270,8 +58300,169 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
58270
58300
  hand: "L",
58271
58301
  anchor: "cylinder_side",
58272
58302
  merge: 1
58303
+ },
58304
+ {
58305
+ family: "pistol",
58306
+ clips: [
58307
+ "safety_on",
58308
+ "safety_off"
58309
+ ],
58310
+ hand: "R",
58311
+ anchor: "thumb_safety",
58312
+ whole: true,
58313
+ lead: 0.3,
58314
+ tail: 0.3
58315
+ },
58316
+ {
58317
+ family: "revolver",
58318
+ clips: [
58319
+ "safety_on",
58320
+ "safety_off"
58321
+ ],
58322
+ hand: "R",
58323
+ anchor: "thumb_safety",
58324
+ whole: true,
58325
+ lead: 0.3,
58326
+ tail: 0.3
58327
+ },
58328
+ {
58329
+ family: "rifle",
58330
+ clips: [
58331
+ "safety_on",
58332
+ "safety_off"
58333
+ ],
58334
+ hand: "R",
58335
+ anchor: "thumb_safety",
58336
+ whole: true,
58337
+ lead: 0.3,
58338
+ tail: 0.3,
58339
+ style: "ar"
58340
+ },
58341
+ {
58342
+ family: "p90",
58343
+ clips: [
58344
+ "safety_on",
58345
+ "safety_off"
58346
+ ],
58347
+ hand: "R",
58348
+ anchor: "thumb_safety",
58349
+ whole: true,
58350
+ lead: 0.3,
58351
+ tail: 0.3
58352
+ },
58353
+ {
58354
+ family: "pump",
58355
+ clips: [
58356
+ "safety_on",
58357
+ "safety_off"
58358
+ ],
58359
+ hand: "R",
58360
+ anchor: "thumb_safety",
58361
+ whole: true,
58362
+ lead: 0.3,
58363
+ tail: 0.3
58364
+ },
58365
+ {
58366
+ family: "semi12",
58367
+ clips: [
58368
+ "safety_on",
58369
+ "safety_off"
58370
+ ],
58371
+ hand: "R",
58372
+ anchor: "thumb_safety",
58373
+ whole: true,
58374
+ lead: 0.3,
58375
+ tail: 0.3
58376
+ },
58377
+ {
58378
+ family: "semiint",
58379
+ clips: [
58380
+ "safety_on",
58381
+ "safety_off"
58382
+ ],
58383
+ hand: "R",
58384
+ anchor: "thumb_safety",
58385
+ whole: true,
58386
+ lead: 0.3,
58387
+ tail: 0.3
58388
+ },
58389
+ {
58390
+ family: "break",
58391
+ clips: [
58392
+ "safety_on",
58393
+ "safety_off"
58394
+ ],
58395
+ hand: "R",
58396
+ anchor: "thumb_safety",
58397
+ whole: true,
58398
+ lead: 0.3,
58399
+ tail: 0.3
58400
+ },
58401
+ {
58402
+ family: "bolt",
58403
+ clips: [
58404
+ "safety_on",
58405
+ "safety_off"
58406
+ ],
58407
+ hand: "R",
58408
+ anchor: "thumb_safety",
58409
+ whole: true,
58410
+ lead: 0.3,
58411
+ tail: 0.3
58412
+ },
58413
+ {
58414
+ family: "boltmag",
58415
+ clips: [
58416
+ "safety_on",
58417
+ "safety_off"
58418
+ ],
58419
+ hand: "R",
58420
+ anchor: "thumb_safety",
58421
+ whole: true,
58422
+ lead: 0.3,
58423
+ tail: 0.3
58424
+ },
58425
+ {
58426
+ family: "lever",
58427
+ clips: [
58428
+ "safety_on",
58429
+ "safety_off"
58430
+ ],
58431
+ hand: "R",
58432
+ anchor: "thumb_safety",
58433
+ whole: true,
58434
+ lead: 0.3,
58435
+ tail: 0.3
58436
+ },
58437
+ {
58438
+ family: "belt",
58439
+ clips: [
58440
+ "safety_on",
58441
+ "safety_off"
58442
+ ],
58443
+ hand: "R",
58444
+ anchor: "thumb_safety",
58445
+ whole: true,
58446
+ lead: 0.3,
58447
+ tail: 0.3
58448
+ },
58449
+ {
58450
+ family: "rifle",
58451
+ clips: [
58452
+ "safety_on",
58453
+ "safety_off"
58454
+ ],
58455
+ hand: "L",
58456
+ anchor: "lever_right",
58457
+ style: "ak",
58458
+ whole: true,
58459
+ lead: 0.3,
58460
+ tail: 0.3
58273
58461
  }
58274
- ]
58462
+ ],
58463
+ safety: {
58464
+ comment: "Safety on and off (B): the shooting hand slides up its grip so the thumb flicks the lever while the clip cants the weapon; an AK-style rifle (akCharge) has its big lever on the right, pushed by the support hand over the top."
58465
+ }
58275
58466
  };
58276
58467
 
58277
58468
  // src/fx/hand-work.ts
@@ -58299,6 +58490,18 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
58299
58490
  if (!right || !parts.slide) return null;
58300
58491
  return { part: right.part, offset: right.offset.clone().add(V2(THREE, 0, 0.012, 0)), quaternion: right.quaternion };
58301
58492
  }
58493
+ if (kind === "thumb_safety") {
58494
+ if (!right) return null;
58495
+ const toward2 = parts.safety ? right.part.worldToLocal(parts.safety.getWorldPosition(V2(THREE, 0, 0, 0))).sub(right.offset) : V2(THREE, 0, HAND_HOLD.thumbReach, 0);
58496
+ return { part: right.part, offset: right.offset.clone().add(toward2.clampLength(0, HAND_HOLD.thumbReach)), quaternion: right.quaternion };
58497
+ }
58498
+ if (kind === "lever_right") {
58499
+ const receiver = parts.bolt ?? parts.boltHandle;
58500
+ if (!receiver) return null;
58501
+ const b2 = boxIn(THREE, root, receiver);
58502
+ const x = parts.trigger ? root.worldToLocal(parts.trigger.getWorldPosition(V2(THREE, 0, 0, 0))).x : b2.min.x;
58503
+ return anchorAt(receiver, V2(THREE, x, b2.getCenter(V2(THREE, 0, 0, 0)).y, b2.max.z + palm), handFrame(THREE, V2(THREE, 1, 0, 0), V2(THREE, 0, 0, 1)));
58504
+ }
58302
58505
  if (!part) return null;
58303
58506
  const b = boxIn(THREE, root, part);
58304
58507
  const c = b.getCenter(V2(THREE, 0, 0, 0));