@adatechnology/game-weapons-three 0.7.1 → 0.8.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
@@ -4,6 +4,16 @@ three.js presentation for `@adatechnology/game-weapons`: the real models of the
4
4
 
5
5
  The muzzle flash, the smoke, the backblast of a launched rocket and the flame jet of the weapon in the hands are not built here: they are recipes of `@adatechnology/game-vfx` (`viewmodel_muzzle_flash`, `weapon_smoke_puff`, `launcher_backblast`, `flame_jet`) that draw them as the gallery does, as solids (an open cone, four small cones and a core for 55 ms growing from 0.6 to 1.5 times their size, a light of `4 k` that holds, spheres of smoke that rise and grow, icosahedra of fire), with the gallery's numbers and the barrel's whole turn (`orientation`, pitch and roll included). `createWeaponFx({ effects })` plays them through the host's effect sink (the test area passes `host.effects`); with no sink the viewer makes a world of effects of its own, stepped and drawn with the weapon. Casings, magazines, the rocket itself and the burning droplets stay meshes of this package.
6
6
 
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
+
9
+ **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.
10
+
11
+ ## Geometry
12
+
13
+ The models' geometry comes from [`game-shapes`](../game-shapes): the model kit's `THREE` is `shapeGeometries(host)` (box, cylinder, cone, sphere,
14
+ torus with the same bytes and classes as three's, from a template cache; icosahedron and tube are still three's). `tests/exact-golden.test.ts`
15
+ holds every bit of the models, accessories, cartridges and props as the old path made them (`UPDATE_EXACT=1` rewrites it).
16
+
7
17
  ## Licenses
8
18
 
9
19
  The code is MIT. The `assets/weapon-svg/` thumbnails are not code and have their own licenses, which is why the `license` field is an expression:
@@ -14,3 +24,28 @@ The code is MIT. The `assets/weapon-svg/` thumbnails are not code and have their
14
24
  Read `assets/weapon-svg/LICENSE-NOTE.md` before redistributing the folder and keep `assets/weapon-svg/CREDITS.md` (author, license, link and modifications of every file) and `manifest.json` with it. The kit's demo site shows the same credits at `weapon-svg/credits.html`.
15
25
 
16
26
  Unused generated fallbacks: every weapon keeps its `generated/` file even when it has a `commons/` one; the manifest does not reference those, they are what the weapon returns to if the Commons file is removed.
27
+
28
+ ## Models of thrown and fired explosives
29
+
30
+ `projectileModel(THREE, { weapon, explosive, kind })` gives a host the model of the round a launcher fires (the RPG-7's PG-7V, the Carl Gustaf's 84 mm round, the rocket of the disposable tubes; the same code the weapon in the hands uses to fly it), long axis +x, or `null`. It is the hook the test room's `projectileModel` option takes. The gallery has no model yet for the throwables (M67, molotov, pipe bomb, dynamite, C4, Claymore, flashbang) nor for the grenade launchers (M203, GP-25, AG36: their rounds), so those answer `null`.
31
+
32
+ `createWeaponsTestModule({ flyRocket: false })` (and `createWeaponFx`) keeps the viewmodel from flying a rocket of its own out of the launcher, for a host that draws the projectile itself (the test room does, with `projectileModel`): one round flies, not two. The backblast stays.
33
+
34
+ ## Área de explosivos (`explosives_area`)
35
+ O botijão P13 de cozinha com o modelo real do time (`lpg_cylinder_13kg`, 0,353 x 0,49 x 0,358 m), numa quina de cozinha junto da parede de tijolo: geladeira, balcão, fogão e mesa, um zumbi a 3 m, um sobrevivente a 8 m, anéis no chão a 5 m (letal) e 14 m (ferimento) e o jogador a cerca de 6 m (fora do anel letal). Abre por `?area=explosives_area` (ou `?area=Explosivos`; em inglês `Explosives`); a página padrão da sala já a carrega.
36
+ | Id | Tipo | O que faz |
37
+ |---|---|---|
38
+ | `shoot` | botão | "Atirar no botijão": 9 mm no casco; jato branco, tocha em 0,8 s, explosão em 1,4 s |
39
+ | `valve` | botão | "Abrir o registro": vaza sem fogo; sem chama não explode |
40
+ | `light` | botão | "Acender (isqueiro)": a chama acende o vazamento, vira tocha e explode 1,4 s depois |
41
+ | `fire` | botão | "Jogar no fogo": poça de fogo a 700 °C; o casco passa de 300 °C e explode |
42
+ | `reset` | botão | "Reiniciar": refaz a cena |
43
+ | `blast_scale` | número 0,25 a 3 (padrão 1) | "Força da explosão (multiplicador de jogo)": multiplica energia e raios do botijão a cada novo botijão |
44
+ | `rings` | camada | os anéis de 5 m e 14 m |
45
+ | `gas`, `clock`, `phase` | métricas | gás que resta, relógio desde o gatilho e fase |
46
+
47
+ Cada fase conta no registro da sala, em pt-BR e en, e vira JSON no registro de comportamento. Depois do estouro o casco voa com rastro de fogo e o fogo residual dura 7 s; 2 s depois o registro diz quem morreu e o que a parede e os móveis perderam.
48
+
49
+ **Onde ajustar a força:** o multiplicador da bancada é só do teste. No jogo, energia, raios, tempos e gás ficam no arsenal: `createArsenal({ patch: { explosives: { propane_p13: { energy, lethalRadius, injuryRadius, chain: { shot, fire, explosion }, leak: { igniteDelay, liters, hissSeconds }, shell: { launchSpeed, trailSeconds } } } } })` (`ArsenalOptions.patch.explosives`). `createExplosivesTestModule({ blastScale })` muda o ponto de partida da bancada.
50
+
51
+ Cenários: `explosives_p13_shot`, `explosives_p13_valve`, `explosives_p13_fire`, `explosives_p13_chain`.
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 200" fill="currentColor" fill-rule="evenodd"><path fill-opacity=".6" d="M345 12 455 12 455 188 345 188Z"/></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 200" fill="currentColor" fill-rule="evenodd"><path fill-opacity=".6" d="M366.5 12 433.5 12 436.5 13.5 437.5 18 436 20.5 433 21.5 433 37.5 438 38.5 450 49.5 454 52 460.5 74 460.5 141 457 170.5 454.5 174.5 463.5 174.5 463.5 188 336.5 188 336.5 174.5 345.5 174.5 343 170.5 339.5 141 339.5 74 346 52 350 49.5 362 38.5 367 37.5 367 21.5 364 20.5 362.5 18 363.5 13.5ZM372.5 21.5 372.5 36 382 34 383.5 33 391 33 391 21.5ZM409 21.5 409 33 416.5 33 418 34 427.5 36 427.5 21.5Z"/></svg>
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { HostTool, EffectSink, HostEvent, TestableModule, AreaPlacement, TestableItem, DestructibleSpec, ToolClipSet } from '@adatechnology/game-contracts';
1
+ import { HostTool, EffectSink, HostEvent, HandTargets, TestableModule, AreaPlacement, DestructionEvent, Hit, Vec3 as Vec3$1, ModuleControl, ModuleLayer, ModuleMetric, ModuleHost, TestableItem, DestructibleSpec, ToolClipSet } from '@adatechnology/game-contracts';
2
2
  import { CaliberDef, ReloadStageId, WeaponEvent, MountKind, AttachmentDef, FittedAttachment, Arsenal, ExplosiveDef, WeaponDef, CartridgeDims, CaseType, ReloadFamily } from '@adatechnology/game-weapons';
3
3
  import { AudioEngine } from '@adatechnology/game-audio';
4
4
  import * as THREE_NS from 'three';
@@ -169,6 +169,41 @@ declare const MECHANICAL_SOUNDS: Readonly<Record<MechanicalSoundId, readonly Sou
169
169
  /** Which mechanical sound a weapon event asks for, if any (`shot`, `draw` and `reload` start/end have their own handling). */
170
170
  declare const mechanicalSoundOf: (event: WeaponEvent) => MechanicalSoundId | undefined;
171
171
 
172
+ var profiles = {
173
+ weight: {
174
+ light: {
175
+ pitch: 1.3,
176
+ gain: 0.85
177
+ },
178
+ medium: {
179
+ pitch: 1,
180
+ gain: 1
181
+ },
182
+ heavy: {
183
+ pitch: 0.78,
184
+ gain: 1.2
185
+ }
186
+ },
187
+ material: {
188
+ steel: {
189
+ pitch: 1,
190
+ gain: 1
191
+ },
192
+ polymer: {
193
+ pitch: 0.82,
194
+ gain: 0.8
195
+ }
196
+ }
197
+ };
198
+ var data = {
199
+ profiles: profiles};
200
+
201
+ /** How a weapon sounds: the weight of its metal (pitch and level) and what it is made of. */
202
+ interface SoundProfile {
203
+ readonly weight: keyof typeof data.profiles.weight;
204
+ readonly material: keyof typeof data.profiles.material;
205
+ }
206
+
172
207
  /** What a shot adds to the energy and the category: where the listener is and how the weapon is set. */
173
208
  type ShotExtras = Pick<ShotSoundInput, 'caliber' | 'distance' | 'volume' | 'suppressed' | 'indoors'>;
174
209
  interface WeaponAudioOptions extends ShotSoundOptions {
@@ -176,6 +211,8 @@ interface WeaponAudioOptions extends ShotSoundOptions {
176
211
  readonly seed?: number;
177
212
  /** Replaces or adds mechanical sounds by id. */
178
213
  readonly mechanical?: Readonly<Partial<Record<MechanicalSoundId, readonly SoundStep[]>>>;
214
+ /** Replaces or adds the sounds of the reload clip cues by id (`reload-sounds.json` has the ones the kit ships). */
215
+ readonly reloadCues?: Readonly<Record<string, readonly SoundStep[]>>;
179
216
  /** Where the AudioContext comes from (default: the global one); lets a host or a test lend its own. Not used with `engine`. */
180
217
  readonly createContext?: () => AudioContext;
181
218
  /**
@@ -195,6 +232,8 @@ interface WeaponAudio {
195
232
  click(): void;
196
233
  /** A mechanical sound by id: a reload stage, `dry_fire`, `jam`, `safety_on`... */
197
234
  mechanical(id: MechanicalSoundId): void;
235
+ /** A cue of a reload clip (`mag_seat`, `charging_handle_pull`, `pump_back`...) for a weapon's profile (the weight of its metal and what it is made of): played now, where the clip says. */
236
+ cue(id: string, profile?: SoundProfile): void;
198
237
  /** The mechanical sound of a weapon event, if it has one (reload stages, empty, misfire, fault, clear, safety, mode, feed). */
199
238
  event(event: WeaponEvent): void;
200
239
  /** A sound class of the game by name (`impact`, `flesh`, `glass`...), with the game's distance muffling. */
@@ -339,6 +378,8 @@ interface ModelParts {
339
378
  chambers?: THREE_NS.Object3D[];
340
379
  barrels?: THREE_NS.Object3D;
341
380
  breech?: THREE_NS.Object3D[];
381
+ /** The pivot of the pistol grip (its y runs along the grip, tilted as the grip is): the right hand holds here. Models without a drawn grip leave it out and the hand is placed from `frames`. */
382
+ grip?: THREE_NS.Object3D;
342
383
  /** The existing selector or safety mesh; the gallery draws it but does not move it. */
343
384
  safety?: THREE_NS.Object3D;
344
385
  muzzle?: THREE_NS.Object3D;
@@ -463,8 +504,11 @@ interface ModelKit {
463
504
  mark(parent: Parent, x: number, y: number, z: number): THREE_NS.Object3D;
464
505
  pivot(parent: Parent, x?: number, y?: number, z?: number): THREE_NS.Group;
465
506
  }
466
- /** One kit per three.js namespace: the materials and the geometry cache are shared by every model built with it. */
467
- declare function modelKit(THREE: Three): ModelKit;
507
+ /**
508
+ * One kit per three.js namespace: the materials and the geometry cache are shared by every model built with it. The kit's `THREE` is
509
+ * `shapeGeometries(host)`: box, cylinder, cone, sphere and torus come from game-shapes (the same bytes, the same classes); the rest is the host's.
510
+ */
511
+ declare function modelKit(host: Three): ModelKit;
468
512
 
469
513
  type Vec3 = [number, number, number];
470
514
  /** The shape of a model that carries its `inner` and `root` as `buildWeaponModelById` makes them. */
@@ -542,6 +586,8 @@ interface WeaponFxOptions {
542
586
  audio?: WeaponAudio | null;
543
587
  /** Seed of the scatter of the effects of the gallery models. */
544
588
  seed?: number;
589
+ /** The rocket of a launcher flies out of the viewmodel along the barrel (default true). A host that draws the projectile itself (the test room does) sets it to false, so one round flies, not two. */
590
+ flyRocket?: boolean;
545
591
  /** The tool as it is now (barrel heat, fuel burning). */
546
592
  tool?: () => HostTool;
547
593
  /** Wear of the weapon: `new`, `used`, `worn` or `rusty`. */
@@ -550,10 +596,18 @@ interface WeaponFxOptions {
550
596
  layers?: FxLayers;
551
597
  /** Metres between the shooter and the listener, for the distance muffling of the shot sound (default 0). */
552
598
  soundDistance?: () => number;
599
+ /** Told at every sound cue of a reload clip, at the moment the clip says it (also with no audio): the weapon, the cue id and the profile it sounds with. */
600
+ onSound?: (cue: {
601
+ weapon: string;
602
+ cue: string;
603
+ profile: SoundProfile;
604
+ }) => void;
553
605
  /** Builders of accessory models that replace or add to the ones the kit ships (by the `model` of the accessory). */
554
606
  attachmentModels?: AttachmentModelRegistry;
555
607
  /** How far a laser beam goes from a point along a direction (metres), or null when it hits nothing: the dot lands where it ends. */
556
608
  raycast?: LaserRaycast;
609
+ /** Where the world is (distance from a point along a direction to the first surface, or null): the weapon in the hands comes back from a wall it would go through. */
610
+ contact?: LaserRaycast;
557
611
  /** The host's effects: the muzzle flash, smoke, backblast and flame jet of the gallery models are recipes it plays; without it the viewmodel makes a world of its own for them. */
558
612
  effects?: EffectSink;
559
613
  }
@@ -565,10 +619,17 @@ interface WeaponFx {
565
619
  update(dt: number): void;
566
620
  /** Dresses the weapon for its wear (`new`, `used`, `worn`, `rusty`); the gallery models show it in their metal. */
567
621
  setCondition(condition: string): void;
622
+ /** Where the hands take the weapon (world space, read every frame after `update`): the grip and the forend of the model, or the handle of the generic prop; null with nothing in the hands. */
623
+ handTargets(): HandTargets | null;
568
624
  /** What is drawn (casings, magazines, muzzle flash): change a field and the next event follows it. */
569
625
  readonly layers: FxLayers;
570
626
  /** Weapon shown in the hands, or null. */
571
627
  readonly inHand: string | null;
628
+ /** The clips of the model playing now with the seconds each lasts (scaled to the rule's duration): which reload variant went (`reload` or `reload_empty`...). */
629
+ readonly clips: ReadonlyArray<{
630
+ name: string;
631
+ seconds: number;
632
+ }>;
572
633
  /** Casings and magazines currently on the floor or in the air. */
573
634
  readonly casings: number;
574
635
  readonly magazines: number;
@@ -595,6 +656,8 @@ interface WeaponsTestModuleOptions {
595
656
  attachmentModels?: AttachmentModelRegistry;
596
657
  /** Folder (ending with a slash) the weapon thumbnails are served from, relative to the page (default `weapon-svg/`); `false` leaves the items without a thumbnail. */
597
658
  thumbnailBase?: string | false;
659
+ /** The rocket of a launcher flies out of the weapon in the hands (default true). A host that draws the projectile (the test room's `projectileModel`) passes false: one round flies. */
660
+ flyRocket?: boolean;
598
661
  }
599
662
  /** Prefix of the item ids of the weapon props (`weapon_pistol_9mm`). */
600
663
  declare const WEAPON_ITEM_PREFIX = "weapon_";
@@ -631,12 +694,334 @@ declare const DEFAULT_THUMBNAIL_BASE = "weapon-svg/";
631
694
  /** The URL of a weapon's thumbnail under `base` (which ends with a slash), or undefined when it has none. */
632
695
  declare function weaponThumbnailUrl(id: string, base?: string): string | undefined;
633
696
 
697
+ /** The words of the explosives area (the P13 cylinder bench and its narration), by locale (`en` and `pt-BR`). `{name}` marks a value the module fills in. */
698
+ interface ExplosivesText {
699
+ group: string;
700
+ name: string;
701
+ cylinder: string;
702
+ actions: Record<'shoot' | 'valve' | 'light' | 'fire' | 'reset', {
703
+ name: string;
704
+ hint: string;
705
+ }>;
706
+ blastScale: {
707
+ name: string;
708
+ hint: string;
709
+ };
710
+ layers: Record<'rings', string>;
711
+ metrics: Record<'gas' | 'clock' | 'phase', string>;
712
+ phases: Record<'rest' | 'leaking' | 'torch' | 'primed' | 'exploded', string>;
713
+ rings: {
714
+ lethal: string;
715
+ injury: string;
716
+ };
717
+ triggers: Record<'shot' | 'fire' | 'explosion', string>;
718
+ things: Record<string, string>;
719
+ verdicts: {
720
+ wall_pieces: string;
721
+ lost_parts: string;
722
+ dead: string;
723
+ hurt: string;
724
+ unhurt: string;
725
+ wall_broken: string;
726
+ wall_cracked: string;
727
+ wall_intact: string;
728
+ burning: string;
729
+ standing: string;
730
+ };
731
+ log: {
732
+ leak_shot: string;
733
+ leak_valve: string;
734
+ valve_closed: string;
735
+ valve_closed_late: string;
736
+ primed: string;
737
+ jet_lit: string;
738
+ jet_lit_by: string;
739
+ lighter_none: string;
740
+ lighter_lit: string;
741
+ fire_set: string;
742
+ heating: string;
743
+ explode: string;
744
+ shell: string;
745
+ shell_landed: string;
746
+ residual: string;
747
+ aftermath_person: string;
748
+ aftermath_wall: string;
749
+ aftermath_object: string;
750
+ reset: string;
751
+ scale: string;
752
+ no_room: string;
753
+ shoot: string;
754
+ no_vessel: string;
755
+ };
756
+ sheet: {
757
+ kicker: string;
758
+ title: string;
759
+ body: string;
760
+ facts: Record<'energy' | 'lethal' | 'injury' | 'gas' | 'mass' | 'torch' | 'shot_chain' | 'fire_chain', string>;
761
+ steps: Array<{
762
+ at: string;
763
+ text: string;
764
+ }>;
765
+ segments: Record<'jet' | 'torch' | 'blast', string>;
766
+ note: string;
767
+ };
768
+ scenarios: {
769
+ shot: string;
770
+ shot_leak: string;
771
+ shot_ignite: string;
772
+ shot_explode: string;
773
+ shot_zombie: string;
774
+ shot_survivor: string;
775
+ shot_wall: string;
776
+ valve: string;
777
+ valve_leak: string;
778
+ valve_quiet: string;
779
+ valve_lit: string;
780
+ valve_explode: string;
781
+ fire: string;
782
+ fire_explode: string;
783
+ chain: string;
784
+ chain_explode: string;
785
+ push_p13: string;
786
+ push_grenade: string;
787
+ push_found: string;
788
+ push_told: string;
789
+ push_outward: string;
790
+ push_falloff: string;
791
+ push_person: string;
792
+ push_heavy: string;
793
+ };
794
+ }
795
+ declare const EXPLOSIVES_TEXT: Record<string, ExplosivesText>;
796
+ declare const explosivesTextOf: (locale: string) => ExplosivesText;
797
+
798
+ /** Id of the explosives module (`?area=explosives_area` opens it). */
799
+ declare const EXPLOSIVES_AREA_ID = "explosives_area";
800
+ /** The parts of a part state the bench reads (structural: `PartState` of the destruction world fits). */
801
+ interface PartView {
802
+ readonly liters?: number;
803
+ readonly temperature: number;
804
+ readonly status: string;
805
+ readonly holes: ReadonlyArray<{
806
+ readonly diameter: number;
807
+ readonly through: boolean;
808
+ }>;
809
+ readonly cracks: readonly unknown[];
810
+ readonly burning: boolean;
811
+ }
812
+ /** What the explosives module needs from the room it drives (structural: the `TestRoom` of game-test-room fits). */
813
+ interface ExplosivesRoom {
814
+ readonly world: {
815
+ readonly time: number;
816
+ ids(): string[];
817
+ on(type: '*', fn: (event: DestructionEvent) => void): () => void;
818
+ hit(objectId: string, hit: Hit): unknown;
819
+ vent(objectId: string, partId: string, opening?: {
820
+ point?: Vec3$1;
821
+ normal?: Vec3$1;
822
+ diameter?: number;
823
+ }): boolean;
824
+ seal(objectId: string, partId: string): number;
825
+ part(objectId: string, partId: string): PartView | null;
826
+ state(objectId: string): {
827
+ readonly parts: readonly (PartView & {
828
+ readonly id: string;
829
+ readonly hp: number;
830
+ readonly maxHp: number;
831
+ })[];
832
+ } | null;
833
+ };
834
+ readonly renderer: {
835
+ impulse(objectId: string, impulse: Vec3$1, point?: Vec3$1): void;
836
+ };
837
+ readonly vfx: {
838
+ spawn(kind: string, point: Vec3$1, velocity: Vec3$1, options?: {
839
+ color?: number;
840
+ size?: number;
841
+ life?: number;
842
+ alpha?: number;
843
+ }): void;
844
+ burst(kind: string, point: Vec3$1, normal: Vec3$1, count: number, speed?: number): void;
845
+ fire(options: {
846
+ point: Vec3$1;
847
+ radius: number;
848
+ life: number;
849
+ }): number;
850
+ stop(handle: number): void;
851
+ };
852
+ spawned(): ReadonlyArray<{
853
+ readonly objectId: string;
854
+ readonly itemId: string;
855
+ readonly root: unknown;
856
+ }>;
857
+ openArea(group: string): unknown;
858
+ clear(): void;
859
+ spawn(itemId: string, position?: Vec3$1, rotationY?: number): {
860
+ readonly objectId: string;
861
+ readonly itemId: string;
862
+ readonly root: unknown;
863
+ };
864
+ setTool(weaponId: string): void;
865
+ waitUntilReady(maxSeconds?: number): number;
866
+ look(position: Vec3$1, target: Vec3$1): void;
867
+ use(hold?: number): void;
868
+ on(type: 'spawn', fn: (data: unknown) => void): () => void;
869
+ }
870
+ /** The bench of the explosives area as the module and its scenarios see it. */
871
+ interface ExplosivesBench {
872
+ readonly room: ExplosivesRoom | undefined;
873
+ readonly def: ExplosiveDef;
874
+ /** The strength multiplier now (the next cylinder laid takes it). */
875
+ blastScale(): number;
876
+ /** Called by the module's `area`: the cylinders spawned from now on belong to the area (they get their rings). */
877
+ areaOpened(): void;
878
+ controls(): ModuleControl[];
879
+ layers(): ModuleLayer[];
880
+ metrics(): ModuleMetric[];
881
+ attach(host: ModuleHost): () => void;
882
+ /** The actions of the bench, by the id of their button. Each answers whether it did something. */
883
+ readonly actions: Readonly<Record<'shoot' | 'valve' | 'light' | 'fire' | 'reset', () => boolean>>;
884
+ /** The cylinder of the scene (the first one alive), with what the bench knows about it. */
885
+ vessel(): VesselView | undefined;
886
+ /** The wall of the scene, the zombie and the survivor, as the aftermath reads them. */
887
+ /** The last explosion the host told (a part of the world or a thrown explosive): where, how strong and how far; undefined before the first, and after the area is cleared. */
888
+ lastBlast(): {
889
+ readonly point: Vec3$1;
890
+ readonly energy: number;
891
+ readonly radius: number;
892
+ readonly cause: string;
893
+ readonly explosive?: string;
894
+ } | undefined;
895
+ witnesses(): Array<{
896
+ kind: 'person' | 'wall' | 'object';
897
+ id: string;
898
+ itemId: string;
899
+ distance: number;
900
+ alive?: boolean;
901
+ vitality?: number;
902
+ broken?: number;
903
+ marks?: number;
904
+ pieces?: number;
905
+ }>;
906
+ }
907
+ interface VesselView {
908
+ readonly objectId: string;
909
+ /** World time of the first leak, of the jet catching fire and of the blast (seconds); undefined until they happen. */
910
+ readonly leakAt: number | undefined;
911
+ readonly litAt: number | undefined;
912
+ readonly explodedAt: number | undefined;
913
+ readonly cause: 'shot' | 'valve' | undefined;
914
+ readonly valveOpen: boolean;
915
+ readonly lit: boolean;
916
+ readonly exploded: boolean;
917
+ readonly point: Vec3$1 | undefined;
918
+ }
919
+ interface ExplosivesBenchOptions {
920
+ readonly locale: string;
921
+ readonly text: ExplosivesText;
922
+ readonly def: ExplosiveDef;
923
+ readonly scene: {
924
+ readonly cylinder: Vec3$1;
925
+ };
926
+ readonly room: ExplosivesRoom | undefined;
927
+ readonly blastScale: number;
928
+ readonly profile: {
929
+ readonly visualRadius?: number;
930
+ readonly residualFires?: ReadonlyArray<{
931
+ readonly radius: number;
932
+ readonly life: number;
933
+ }>;
934
+ } | undefined;
935
+ }
936
+ /**
937
+ * The state and the actions of the explosives area: it follows the world's events of the cylinder (leak, primed, explode), tells each phase in the room's log, plays the torch and the flight
938
+ * of the shell (the Laboratório de Explosões, `SC.p13`) and drives the destruction world for the bench actions. The module wraps it in a `TestableModule`.
939
+ */
940
+ declare function createExplosivesBench(options: ExplosivesBenchOptions): ExplosivesBench;
941
+
942
+ /** Where the things of the scene stand (metres, in the room's world; the brick wall is the room's own at z = -11). The kitchen corner is against the wall; the player looks at it from about 6 m. */
943
+ interface ExplosivesScene {
944
+ /** Item ids and positions of the kitchen furniture (items of the household module); one that the room does not know is left out. */
945
+ readonly furniture: ReadonlyArray<{
946
+ readonly item: string;
947
+ readonly at: Vec3$1;
948
+ readonly rotationY?: number;
949
+ }>;
950
+ /** The cylinder. */
951
+ readonly cylinder: Vec3$1;
952
+ /** The zombie (about 3 m from the cylinder) and the survivor (about 8 m): item ids of the characters module. */
953
+ readonly zombie: {
954
+ readonly item: string;
955
+ readonly at: Vec3$1;
956
+ };
957
+ readonly survivor: {
958
+ readonly item: string;
959
+ readonly at: Vec3$1;
960
+ };
961
+ /** The player's eye (about 6 m from the cylinder: outside the 5 m lethal ring, the lamp rig of the weather module is behind it). */
962
+ readonly eye: Vec3$1;
963
+ }
964
+ /** The kitchen corner of the default scene: fridge, cabinet, the cylinder and the stove side by side against the wall, a table 2 m away. */
965
+ declare const DEFAULT_EXPLOSIVES_SCENE: ExplosivesScene;
966
+ interface ExplosivesTestModuleOptions {
967
+ /** `en` or `pt-BR` (default). */
968
+ locale?: string;
969
+ /** The arsenal to read the P13 from (default `createArsenal` with the locale). */
970
+ arsenal?: Arsenal;
971
+ /** The room the module drives: its destruction world, effects and tools. Without it the module still lays the area and the bench shows, but the actions only say they need the room. */
972
+ room?: ExplosivesRoom;
973
+ /** Overrides of the default scene (see `ExplosivesScene`). */
974
+ scene?: Partial<ExplosivesScene>;
975
+ /** Strength multiplier the bench starts at (default 1: the arsenal's numbers; a game value, see the README). */
976
+ blastScale?: number;
977
+ }
978
+ /**
979
+ * The explosives test area as a `TestableModule`: the P13 cooking gas cylinder in a kitchen corner against a brick wall, with furniture, a zombie at 3 m, a survivor at 8 m and rings on the floor at the
980
+ * lethal and injury radii. The bench shoots it, opens its valve, lights it with a lighter, throws it in a fire or restarts the scene; every phase is told in the room's log in the locale of the room.
981
+ * The module reads the sequence (the torch delay, the gas, the flight of the shell) from the arsenal def of `propane_p13` and the blast from its `chain`, so a game changes them with `ArsenalOptions.patch.explosives`.
982
+ */
983
+ declare function createExplosivesTestModule(options?: ExplosivesTestModuleOptions): TestableModule & {
984
+ readonly bench: ReturnType<typeof createExplosivesBench>;
985
+ };
986
+
634
987
  /** Prefix of the item ids of the explosives that sit in the world (`world_fuel_barrel`). */
635
988
  declare const WORLD_EXPLOSIVE_PREFIX = "world_";
989
+ /** Name of the mesh of the brass valve on the P13 model (the team's `botijao`: the 0xb8913a cylinder on the neck). */
990
+ declare const P13_VALVE_MESH = "valve";
636
991
  /** The ids of the explosives of the arsenal that the area puts in the world. */
637
992
  declare const WORLD_EXPLOSIVE_IDS: readonly string[];
993
+ /** What the bench (or a game) changes about a world explosive without touching the arsenal: the multiplier of its energy and reach, read each time one is spawned. */
994
+ interface WorldExplosiveOptions {
995
+ /** Multiplies `energy`, `lethalRadius` and `injuryRadius` of the explosive (default 1: the arsenal's numbers). It is a game value, not a physical table; the arsenal's own place for it is `ArsenalOptions.patch.explosives`. */
996
+ readonly scale?: () => number;
997
+ }
998
+ /** Where the valve is on a spawned cylinder, in the world (the centre of the brass mesh), or undefined when the object has none. */
999
+ declare const worldExplosiveValve: (THREE: typeof THREE_NS, root: THREE_NS.Object3D) => Vec3$1 | undefined;
1000
+ /**
1001
+ * What is left of a world explosive after the blast, as an object to fly: the same model, worn and darkened (the torn shell of the P13 in the Laboratório de Explosões, `SC.p13`),
1002
+ * with no part mapped. Undefined for the explosives that have no model of the team.
1003
+ */
1004
+ declare const worldExplosiveShell: (THREE: typeof THREE_NS, defId: string) => THREE_NS.Group | undefined;
638
1005
  /** An explosive of the arsenal standing in the world: it goes off by a bullet, fire or another blast, after the delay its `chain` gives. */
639
- declare const worldExplosiveItem: (def: ExplosiveDef, name: string) => TestableItem | undefined;
1006
+ declare const worldExplosiveItem: (def: ExplosiveDef, name: string, options?: WorldExplosiveOptions) => TestableItem | undefined;
1007
+
1008
+ /** What a host asks for a thrown or set down explosive: the weapon in the hand, the explosive it carries and how it is used (`rocket`, `grenade`, `molotov`, `charge`). */
1009
+ interface ProjectileQuery {
1010
+ readonly weapon: string;
1011
+ readonly explosive?: string | undefined;
1012
+ readonly kind: string;
1013
+ }
1014
+ /** The model of a projectile and the axis along its length. */
1015
+ interface ProjectileModel {
1016
+ readonly model: THREE_NS.Object3D;
1017
+ readonly axis: [number, number, number];
1018
+ }
1019
+ /**
1020
+ * The model of what a weapon throws or fires, drawn by the same code as the weapon itself: the round of a rocket launcher (the RPG-7's PG-7V, the Carl Gustaf's 84 mm round, the rocket of
1021
+ * the disposable tubes), long axis +x. Answers `null` where the gallery has no model: the throwables (grenades, molotovs, dynamite, C4, the Claymore) and the grenade launchers are
1022
+ * still the plain props of `weaponProp`, and a host keeps its primitive for them.
1023
+ */
1024
+ declare function projectileModel(THREE: typeof THREE_NS, query: ProjectileQuery): ProjectileModel | null;
640
1025
 
641
1026
  /** The effect ids a weapon of a case type shows when it is seen from outside: its muzzle flash and its ejected case (the host's catalogue names them). */
642
1027
  interface WorldEffectIds {
@@ -754,4 +1139,4 @@ declare function rigUnderbarrel(THREE: typeof THREE_NS, model: WeaponModel, def:
754
1139
  */
755
1140
  declare function rigStock(THREE: typeof THREE_NS, model: WeaponModel, def: Pick<WeaponDef, 'stock' | 'dimensions'>): void;
756
1141
 
757
- export { ARCHETYPE_MODELS, ATTACHMENT_MODELS, type AttachmentBuildContext, type AttachmentModel, type AttachmentModelBuilder, type AttachmentModelRegistry, type AttachmentRig, BODY_ACTIONS, CLASS_REFERENCE_ENERGY, COUNTER_ITEM_ID, type CartridgeShape, type ClipContext, DEFAULT_ENERGY_CURVE, DEFAULT_THUMBNAIL_BASE, type DistanceCurve, type EnergyCurve, type FitAttachmentOptions, type FxLayers, GAME_DISTANCE_CURVE, GAME_SOUND_CLASSES, type LaserRaycast, MECHANICAL_SOUNDS, MODEL_ENTRIES, MOUNTED_M134_ITEM_ID, type MechanicalSoundId, type ModelEntry, type ModelFamily, type ModelKit, type ModelMaterials, type ModelOptions, type ModelParts, type MountFrame, type ShotExtras, type ShotSound, type ShotSoundClass, type ShotSoundInput, type ShotSoundOptions, type SoundClassDef, type SoundStep, WEAPON_ITEM_PREFIX, WEAPON_THUMBNAILS, WORLD_EFFECTS, WORLD_EXPLOSIVE_IDS, WORLD_EXPLOSIVE_PREFIX, type WeaponAudio, type WeaponAudioOptions, type WeaponFx, type WeaponFxOptions, type WeaponModel, type WeaponProp, type WeaponThumbnailEntry, type WeaponsTestModuleOptions, type WorldEffectIds, type WorldEffectsOptions, applyCondition, buildCartridge, buildCasing, buildProp, buildRound, buildToolClips, buildWeaponModel, buildWeaponModelById, cartridgeShape, computeShotSound, createWeaponAudio, createWeaponFx, createWeaponsTestModule, createWorldEffects, deriveFrames, fitAttachmentModels, hasWeaponModel, mechanicalSoundOf, modelKit, mountFrame, partRegistry, rigStock, rigUnderbarrel, weaponProp, weaponThumbnailCredit, weaponThumbnailUrl, weaponsLayout, worldExplosiveItem };
1142
+ export { ARCHETYPE_MODELS, ATTACHMENT_MODELS, type AttachmentBuildContext, type AttachmentModel, type AttachmentModelBuilder, type AttachmentModelRegistry, type AttachmentRig, BODY_ACTIONS, CLASS_REFERENCE_ENERGY, COUNTER_ITEM_ID, type CartridgeShape, type ClipContext, DEFAULT_ENERGY_CURVE, DEFAULT_EXPLOSIVES_SCENE, DEFAULT_THUMBNAIL_BASE, type DistanceCurve, EXPLOSIVES_AREA_ID, EXPLOSIVES_TEXT, type EnergyCurve, type ExplosivesRoom, type ExplosivesScene, type ExplosivesTestModuleOptions, type ExplosivesText, type FitAttachmentOptions, type FxLayers, GAME_DISTANCE_CURVE, GAME_SOUND_CLASSES, type LaserRaycast, MECHANICAL_SOUNDS, MODEL_ENTRIES, MOUNTED_M134_ITEM_ID, type MechanicalSoundId, type ModelEntry, type ModelFamily, type ModelKit, type ModelMaterials, type ModelOptions, type ModelParts, type MountFrame, P13_VALVE_MESH, type ProjectileModel, type ProjectileQuery, type ShotExtras, type ShotSound, type ShotSoundClass, type ShotSoundInput, type ShotSoundOptions, type SoundClassDef, type SoundStep, WEAPON_ITEM_PREFIX, WEAPON_THUMBNAILS, WORLD_EFFECTS, WORLD_EXPLOSIVE_IDS, WORLD_EXPLOSIVE_PREFIX, type WeaponAudio, type WeaponAudioOptions, type WeaponFx, type WeaponFxOptions, type WeaponModel, type WeaponProp, type WeaponThumbnailEntry, type WeaponsTestModuleOptions, type WorldEffectIds, type WorldEffectsOptions, type WorldExplosiveOptions, applyCondition, buildCartridge, buildCasing, buildProp, buildRound, buildToolClips, buildWeaponModel, buildWeaponModelById, cartridgeShape, computeShotSound, createExplosivesTestModule, createWeaponAudio, createWeaponFx, createWeaponsTestModule, createWorldEffects, deriveFrames, explosivesTextOf, fitAttachmentModels, hasWeaponModel, mechanicalSoundOf, modelKit, mountFrame, partRegistry, projectileModel, rigStock, rigUnderbarrel, weaponProp, weaponThumbnailCredit, weaponThumbnailUrl, weaponsLayout, worldExplosiveItem, worldExplosiveShell, worldExplosiveValve };