@adatechnology/game-weapons 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anderson Fernandes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,90 @@
1
- # Temporary Holding Version
1
+ # @adatechnology/game-weapons
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Engine-agnostic weapons and ammunition. Real calibres as data (muzzle energy from `0.5 × m × v²`), generic real-world weapon archetypes (no brands),
4
+ and a tiny deterministic runtime: magazines, fire modes, reload, spread and bloom, shotgun pellets, recoil, noise, plus melee, flame, heat, throwables,
5
+ tools and a fire extinguisher. A trigger pull returns plain-data `Shot`s and `Use`s, so any damage model can consume them. It knows nothing about any game.
6
+
7
+ Units: metres, seconds, joules, mm (calibre diameter), grams (bullet mass), m/s, degrees, °C. Every random choice comes from a seeded generator, so the
8
+ same seed gives the same shots on every machine.
9
+
10
+ Contracts live in [`@adatechnology/game-contracts`](../game-contracts); the types are re-exported here, so existing imports keep working.
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ pnpm add @adatechnology/game-weapons
16
+ ```
17
+
18
+ A plain `<script src=".../dist/index.global.js">` exposes the `GameWeapons` global. No runtime dependencies.
19
+
20
+ ## Usage
21
+
22
+ ```ts
23
+ import { createArsenal, DEFAULT_LOADOUT } from '@adatechnology/game-weapons'
24
+
25
+ const arsenal = createArsenal({ seed: 42, locale: 'pt-BR' })
26
+ const gun = arsenal.create('shotgun_pump_12', { reserve: 24 })
27
+ arsenal.name('shotgun_pump_12') // "espingarda de repetição calibre 12"
28
+
29
+ // every frame
30
+ gun.update(dt)
31
+ if (mouseDown) {
32
+ const r = gun.trigger(cameraPos, cameraDir, dt, mouseHeldSinceLastFrame)
33
+ for (const shot of r.shots) {
34
+ // feed the shots to @adatechnology/game-destruction
35
+ world.shoot(shot.origin, shot.direction, shot.energy, shot.caliber)
36
+ }
37
+ for (const use of r.uses) handleUse(use) // melee swing, flame tick, throw, tool...
38
+ camera.kick(...r.recoil)
39
+ horde.hear(cameraPos, r.noise)
40
+ } else gun.release()
41
+ if (reloadKey) gun.reload()
42
+ ```
43
+
44
+ Semi / single / pump / bolt weapons fire once per pull: call `trigger(..., held = true)` while the button stays down and they fire only the first
45
+ time (status `cooldown` afterwards) until `release()`, or pass `held = false` for every new click. `auto` fires at the cyclic rpm while held (several
46
+ shots per `dt` if needed). `burst` emits its rounds together in one call. Reloading is never automatic: the host calls `reload()`; a tube-fed weapon
47
+ loads one round per `reload` seconds and the trigger interrupts it. `trigger(dt)` and `update(dt)` can both be called in the same frame without
48
+ counting `dt` twice.
49
+
50
+ Non-firearms: `flame`/`heat` drain `fuelPerSecond × dt` of fuel (`no_fuel` when dry), `tool` ticks with `duration: dt`, `extinguisher` spends its
51
+ charge in seconds, `throwable` throws once per pull (`velocity = direction × throwSpeed` plus a small upward arc, three by default), `melee` swings at
52
+ its rpm, `hand` produces one `hand` use per pull.
53
+
54
+ ## Data
55
+
56
+ `CALIBERS` (`src/data/calibers.json`): `22lr`, `9x19`, `38spl`, `357mag`, `40sw`, `45acp`, `380acp`, `5_56x45`, `7_62x39`, `7_62x51`, `338lapua`, `50bmg`,
57
+ `12ga_00buck` (9 pellets), `12ga_slug`, `12ga_birdshot` (90 pellets), `20ga_buck`, `arrow`, `crossbow_bolt`. Muzzle energy is per projectile (per pellet
58
+ for shotguns), e.g. 9×19 ~500 J, 5.56 ~1750 J, .308 ~3500 J, .50 BMG ~18 kJ, 00 buck ~300 J per pellet.
59
+
60
+ `WEAPONS` (`src/data/weapons.json`):
61
+
62
+ | Category | Ids |
63
+ |---|---|
64
+ | Firearms | `pistol_9mm`, `pistol_45`, `pistol_22`, `revolver_357`, `revolver_38`, `smg_9mm`, `carbine_22`, `rifle_5_56`, `rifle_7_62x39`, `rifle_bolt_308`, `sniper_338`, `anti_materiel_50`, `shotgun_pump_12`, `shotgun_double_12`, `shotgun_semi_12`, `bow_compound`, `crossbow` |
65
+ | Melee | `hammer`, `sledgehammer`, `baseball_bat`, `crowbar`, `machete`, `axe`, `knife`, `fists` |
66
+ | Fire and heat | `lighter`, `flamethrower`, `cutting_torch`, `blowtorch`, `molotov` |
67
+ | Explosives | `frag_grenade`, `pipe_bomb` |
68
+ | Tools | `screwdriver`, `wrench`, `pry_bar`, `hacksaw`, `utility_knife`, `claw_hammer`, `extinguisher`, `hand` |
69
+
70
+ `DEFAULT_LOADOUT` lists a starter kit of 19 ids. Pass `calibers` / `weapons` to `createArsenal` to add or override entries by id.
71
+
72
+ ## API
73
+
74
+ - `createArsenal(options?)` returns an `Arsenal`: `calibers`, `weapons` (maps), `create(id, { seed?, reserve? })` (unknown id throws), `muzzleEnergy(caliberId, barrel?)`,
75
+ `name(id)` (weapon, fire mode or category, translated), `list(category?)`.
76
+ - `WeaponState`: `def`, `caliber`, `ammo`, `capacity`, `reserve`, `mode`, `reloading`, `spread`, `trigger(origin, direction, dt, held?, source?)`, `release()`,
77
+ `reload()`, `cycleMode()`, `update(dt)`, `refill()`.
78
+ - `Shot`: `origin`, `direction`, `energy` (J), `caliber` (mm), `caliberId`, `speed`, `energyAt(distance)` (energy × speed factor², drag per 100 m), `pellet`, `weapon`, `source`.
79
+ - `Use`: `melee`, `flame`, `heat`, `throw`, `tool`, `extinguish`, `hand`. `TriggerResult`: `shots`, `uses`, `status` (`ok`, `empty`, `reloading`, `cooldown`, `no_fuel`, `safe`), `recoil`, `noise`.
80
+ - `muzzleEnergy(caliber, barrel?)`, `CALIBERS`, `WEAPONS`, `DEFAULT_LOADOUT`, `locales`, `resolveStrings`.
81
+
82
+ Locales: `en` and `pt-BR` (`locale` option; unknown falls back to `en`). Calibre labels are technical names and are not translated.
83
+
84
+ ## Português
85
+
86
+ Armas e munição independentes de engine: calibres reais como dados (energia de boca = `0,5 × m × v²`), arquétipos genéricos de armas (sem marcas) e um
87
+ runtime determinístico (carregador, modos de tiro, recarga, dispersão, chumbos de espingarda, recuo, ruído), além de corpo a corpo, chama, calor,
88
+ arremessáveis, ferramentas e extintor. Cada disparo vira um `Shot` (energia em joules, calibre em mm, direção) que se passa direto para
89
+ `world.shoot(origem, direção, shot.energy, shot.caliber)` do `@adatechnology/game-destruction`. Use `createArsenal({ locale: 'pt-BR' })` para os nomes
90
+ em português (`arsenal.name('rifle_5_56')` devolve "fuzil 5,56"). A recarga nunca é automática; o jogo chama `reload()`.
@@ -0,0 +1,214 @@
1
+ import { Vec3, Shot, Use } from '@adatechnology/game-contracts';
2
+ export { Shot, Use, Vec3 } from '@adatechnology/game-contracts';
3
+
4
+ /**
5
+ * Public types of @adatechnology/game-weapons.
6
+ *
7
+ * Weapons and ammunition as data plus a tiny runtime: a `WeaponState` holds the magazine, chamber, fire mode, heat and recoil; `trigger()` turns
8
+ * a trigger pull into `Shot`s (one per pellet) with muzzle energy, calibre, direction with spread, and range falloff. Shots are plain data, so
9
+ * any damage model can consume them (@adatechnology/game-destruction takes `energy` and `caliber` as is). Melee, flame, heat, throwables and
10
+ * tools use the same shape so a test room or a game can treat every tool in the hand the same way.
11
+ *
12
+ * Units: metres, seconds, joules, millimetres for calibres, grams for bullets, metres per second for speeds, °C.
13
+ * Every random choice (spread) comes from the state's seed: same inputs, same shots on every machine.
14
+ */
15
+
16
+ /** snake_case calibre id, e.g. `9x19`, `5_56x45`, `12ga_00buck`. */
17
+ type CaliberId = string;
18
+ /** snake_case weapon id. */
19
+ type WeaponId = string;
20
+ interface CaliberDef {
21
+ id: CaliberId;
22
+ /** Display label, e.g. `9×19 mm`, `12 ga 00 buck`. Not translated (it is a technical name). */
23
+ label: string;
24
+ /** Bullet (or each pellet) diameter in mm. */
25
+ diameter: number;
26
+ /** Bullet or pellet mass in grams. */
27
+ bulletGrams: number;
28
+ /** Typical muzzle speed from a reference barrel, m/s. Muzzle energy = 0.5 × mass × speed². */
29
+ muzzleSpeed: number;
30
+ /** Pellets per shell (shotgun), default 1. */
31
+ pellets?: number;
32
+ /** Speed lost per 100 m as a fraction (default 0.1 rifle, 0.2 pistol, 0.45 buckshot). */
33
+ dragPer100m?: number;
34
+ /** Armour-piercing or hollow-point flavour (a damage model may use it). */
35
+ kind?: 'fmj' | 'hp' | 'ap' | 'buck' | 'slug' | 'birdshot' | 'rimfire' | 'arrow' | 'bolt';
36
+ tags?: string[];
37
+ }
38
+ type FireMode = 'single' | 'semi' | 'burst' | 'auto' | 'pump' | 'bolt' | 'lever' | 'break';
39
+ type WeaponCategory = 'pistol' | 'revolver' | 'smg' | 'rifle' | 'carbine' | 'sniper' | 'shotgun' | 'bow' | 'crossbow' | 'melee_blunt' | 'melee_blade' | 'flame' | 'heat' | 'throwable' | 'tool' | 'extinguisher' | 'hand';
40
+ /** What using the item does. Firearms produce ballistic shots; the rest produce one `Use` of their kind. */
41
+ type WeaponAction = {
42
+ kind: 'firearm';
43
+ caliber: CaliberId;
44
+ /** Rounds per minute (cyclic for auto, practical for the rest). */
45
+ rpm: number;
46
+ modes: FireMode[];
47
+ /** Rounds in the burst mode (default 3). */
48
+ burst?: number;
49
+ magazine: number;
50
+ /** Seconds to reload a full magazine (tube-fed: per round, see `perRound`). */
51
+ reload: number;
52
+ perRound?: boolean;
53
+ /** Barrel speed factor vs. the calibre reference (short barrel 0.9, long 1.05), default 1. */
54
+ barrel?: number;
55
+ /** Base spread in degrees (cone half-angle) and extra degrees per consecutive shot (bloom), with recovery per second. */
56
+ spreadDeg: number;
57
+ bloomDeg?: number;
58
+ recoveryDegPerS?: number;
59
+ /** Pellet spread for shotguns in degrees (default 3). */
60
+ pelletSpreadDeg?: number;
61
+ /** Camera kick per shot in degrees (pitch, yaw jitter). */
62
+ recoil: [number, number];
63
+ /** Effective range in metres (shots past it keep going with drag; the host may cut them). */
64
+ range: number;
65
+ /** Audible distance in metres (a horde game uses it to attract zombies). */
66
+ noise: number;
67
+ } | {
68
+ kind: 'melee';
69
+ damage: 'blunt' | 'slash';
70
+ energy: number;
71
+ radius: number;
72
+ reach: number;
73
+ rpm: number;
74
+ noise: number;
75
+ stamina?: number;
76
+ } | {
77
+ kind: 'flame';
78
+ temperature: number;
79
+ radius: number;
80
+ range: number;
81
+ fuelPerSecond: number;
82
+ fuel: number;
83
+ cone?: boolean;
84
+ noise: number;
85
+ } | {
86
+ kind: 'heat';
87
+ temperature: number;
88
+ radius: number;
89
+ range: number;
90
+ fuelPerSecond: number;
91
+ fuel: number;
92
+ noise: number;
93
+ } | {
94
+ kind: 'throwable'; /** Throwables carried (default 3). */
95
+ count?: number;
96
+ energy: number;
97
+ radius: number;
98
+ fuse: number;
99
+ throwSpeed: number;
100
+ fire?: {
101
+ temperature: number;
102
+ radius: number;
103
+ seconds: number;
104
+ };
105
+ noise: number;
106
+ } | {
107
+ kind: 'tool';
108
+ tool: 'screwdriver' | 'wrench' | 'pry_bar' | 'saw' | 'cutting_torch' | 'knife' | 'hammer';
109
+ reach: number;
110
+ noise: number;
111
+ } | {
112
+ kind: 'extinguisher';
113
+ range: number;
114
+ coolC: number;
115
+ charge: number;
116
+ noise: number;
117
+ } | {
118
+ kind: 'hand';
119
+ reach: number;
120
+ };
121
+ interface WeaponDef {
122
+ id: WeaponId;
123
+ /** Generic real-world description used as the default name, e.g. `9 mm service pistol`. Hosts translate with the locale or rename. */
124
+ name?: string;
125
+ category: WeaponCategory;
126
+ action: WeaponAction;
127
+ /** Mass in kg (carry weight, and thrown melee weapons). */
128
+ mass: number;
129
+ tags?: string[];
130
+ }
131
+ interface TriggerResult {
132
+ /** Shots fired this call (firearms). */
133
+ shots: Shot[];
134
+ /** Uses this call (everything else). */
135
+ uses: Use[];
136
+ /** `ok`, or why nothing happened. */
137
+ status: 'ok' | 'empty' | 'reloading' | 'cooldown' | 'no_fuel' | 'safe';
138
+ /** Camera kick to apply, degrees [pitch, yaw]. */
139
+ recoil: [number, number];
140
+ /** Audible radius of what happened, metres (0 when nothing). */
141
+ noise: number;
142
+ }
143
+ interface WeaponState {
144
+ readonly def: WeaponDef;
145
+ readonly caliber: CaliberDef | null;
146
+ /** Rounds in the magazine, fuel or charge left. */
147
+ readonly ammo: number;
148
+ readonly capacity: number;
149
+ /** Rounds in reserve (the host fills it from the inventory). */
150
+ reserve: number;
151
+ mode: FireMode;
152
+ readonly reloading: boolean;
153
+ /** Current spread in degrees (base + bloom). */
154
+ readonly spread: number;
155
+ /**
156
+ * Pull or hold the trigger for `dt` seconds from `origin` towards `direction`. `held` = the trigger stays down (auto fire, continuous
157
+ * flame); a new pull with `held: false` fires semi/single once.
158
+ */
159
+ trigger(origin: Vec3, direction: Vec3, dt: number, held?: boolean, source?: string): TriggerResult;
160
+ release(): void;
161
+ reload(): boolean;
162
+ cycleMode(): FireMode;
163
+ /** Advance reload, cooldown and spread recovery. */
164
+ update(dt: number): void;
165
+ /** Fill the magazine and reserve (test rooms). */
166
+ refill(): void;
167
+ }
168
+ interface ArsenalOptions {
169
+ /** Extra or overriding calibres and weapons (merged by id over `CALIBERS` and `WEAPONS`). */
170
+ calibers?: CaliberDef[];
171
+ weapons?: WeaponDef[];
172
+ seed?: number;
173
+ locale?: string;
174
+ }
175
+ interface Arsenal {
176
+ readonly calibers: ReadonlyMap<CaliberId, CaliberDef>;
177
+ readonly weapons: ReadonlyMap<WeaponId, WeaponDef>;
178
+ /** Make a runtime state for a weapon (each holder gets its own). Unknown id throws. */
179
+ create(id: WeaponId, options?: {
180
+ seed?: number;
181
+ reserve?: number;
182
+ }): WeaponState;
183
+ /** Muzzle energy of a calibre (optionally with a barrel factor). */
184
+ muzzleEnergy(caliber: CaliberId, barrel?: number): number;
185
+ /** Translated name of a weapon / calibre category / fire mode. */
186
+ name(id: WeaponId): string;
187
+ list(category?: WeaponCategory): WeaponDef[];
188
+ }
189
+
190
+ /** Muzzle energy in joules of ONE projectile (a bullet, or one pellet): 0.5 × mass × speed², speed scaled by the barrel factor. */
191
+ declare const muzzleEnergy: (caliber: CaliberDef, barrel?: number) => number;
192
+
193
+ declare const CALIBERS: CaliberDef[];
194
+ declare const WEAPONS: WeaponDef[];
195
+ /** A reasonable starting kit for a test room or a sandbox. */
196
+ declare const DEFAULT_LOADOUT: WeaponId[];
197
+
198
+ /** Create an arsenal: the built-in calibres and weapons merged (by id) with the ones in `options`. */
199
+ declare const createArsenal: (options?: ArsenalOptions) => Arsenal;
200
+
201
+ /** Locales shipped with the package. Unknown locales fall back to `en`. */
202
+ type WeaponsLocale = 'en' | 'pt-BR';
203
+ interface WeaponsStrings {
204
+ /** Display name of each weapon id. */
205
+ weapons: Record<string, string>;
206
+ /** Display name of each fire mode. */
207
+ modes: Record<string, string>;
208
+ /** Display name of each weapon category. */
209
+ categories: Record<string, string>;
210
+ }
211
+ declare const locales: Record<WeaponsLocale, WeaponsStrings>;
212
+ declare const resolveStrings: (locale: string | undefined) => WeaponsStrings;
213
+
214
+ export { type Arsenal, type ArsenalOptions, CALIBERS, type CaliberDef, type CaliberId, DEFAULT_LOADOUT, type FireMode, type TriggerResult, WEAPONS, type WeaponAction, type WeaponCategory, type WeaponDef, type WeaponId, type WeaponState, type WeaponsLocale, type WeaponsStrings, createArsenal, locales, muzzleEnergy, resolveStrings };