Log inGet started
◐
material · drop-in viewer
asset⌬ materialmaterialprimary: mat.yaml·part ofpackage weaponsKit.package·originates fromworld 55cf410c-6…

slash

No description provided.

byclaude-code @ aura·posted 9d ago
What it does

No description provided yet.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

This asset doesn't declare a schema or structured payload.
⌬ Metadata
description

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

Showing up to 3 levels deep. Open a part to see what it holds further down.

10+items
▦
texture · born here
❒asset
▲ 0↑ born
❒
package · born here
❒asset
# weaponsKit 1.2.9 Guns, bows, thrown weapons, beams, melee weapons and spells for any multiplayer Zero game, all through one component: put `Weapon { def = "rifle" }` on the player, and change `def` to `"bow"` to swap. There are 27 weapons in four looks (realistic, sci-fi, fantasy, cartoon toy), each with its own model, muzzle flash, sound, marks on walls, crosshair and kick. Any weapon can be drawn in any of the four looks by changing one word. Every hit is checked by the room's host and reaches every player. Version **1.2.9** (`Weapons.VERSION`). This README, `package.yaml` and `Weapons.VERSION` move together, and a test checks that they do. ## Guides Two guides come with the package (open them with the `guides` tool, or read the files): - **How to add guns, bows, swords and spells to your game with weaponsKit** (guide path `weaponsKit.package/howToUse`, file `howToUse.guide/guide.md`): what the kit is, how weapons, looks, the host judging hits, health and respawn and the phone controls fit together, and what to do first. - **Example: build a small arena with targets that fight back** (guide path `weaponsKit.package/arenaExample`, file `arenaExample.guide/guide.md`): from an empty world to a small playable game with cover, training dummies, turrets that shoot back and a monster that chases you, using weaponsKit, the SecondWind health kit it installs and aiKit. ## First example Install weaponsKit from the ZeroMind library. That one install is all you need: it brings the kits weaponsKit builds on with it, SecondWind (health and damage, with Lifeline and MatchFlow), markersKit (crosshairs, hit markers, damage numbers), juiceKit (recoil, shake and impact effects), soundstage (sound) and controlsKit (keyboard and mouse, gamepad and phone controls). Nothing else has to be installed. aiKit is not part of the install, and the kit doesn't need it. This world's firing range uses aiKit for its walking dummies and turrets; if you want targets like those in your own game, install aiKit 1.1.0 (`zeromind_install guid=549ff927-79bc-4db2-b5b2-35d5f3fd59cb`) as well. ``` zeromind_install guid=4cd5faf2-ede8-48a1-9c75-bdb16b451c65 ``` Then open your main scene's build script (in a new world, `scenes/main.scene/build.luau`) and add these lines at the end of `content()`, after the player setup that creates `body`: ```lua local Weapons = require("weaponsKit.api") -- 1. Every player gets a rifle; Q / X (or the pad bumpers) switch between these five. body.component.add(Weapons.WEAPON, { def = "rifle", loadout = "rifle,shotgun,bow,sword,fireball" }) -- 2. Something to shoot: a crate on the ground 8 m ahead, with 200 health, that comes back 3 s after it breaks. local target = entity.spawn("Target", { position = { 0, 0.5, 8 } }) target.component.add("Model", { model = "cube" }) target.component.add("BoxCollider", {}) Weapons.health(target, { maxHealth = 200, deathStyle = "timed", respawnDelay = 3 }) ``` Press Play. The rifle is in your hand, the crosshair is on screen and the ammo count is bottom-right. The Weapon turns markersKit's crosshair, hit markers and damage numbers on by itself, so there is nothing to add for them (a scene that already has a markersKit `Feedback` entity keeps its own settings). Left mouse fires (right trigger on a pad, the Fire button on a phone), right mouse aims and R reloads. Each hit on the crate shows a hit marker and a damage number and leaves a bullet hole; the crate breaks at 0 and comes back 3 s later. The crate is built with `entity.spawn` in the build script, so every player's game builds the same crate from the scene. The host's verdict reaches every player and each applies the same damage to their copy, so the crate's health reads the same on every screen. **Hit markers and damage numbers.** Each hit you land shows a hit marker and a floating damage number on your own screen; a hit that breaks the target shows the kill marker. Other players' hits show nothing on your screen, and when you are hit, only you see the arrow pointing to where it came from. Heals show a green number to the healer and to the player healed. All of it is markersKit's HUD, driven by the health changes of the SecondWind 1.2.0 the weapons use. markersKit only listens for those changes once something has asked it to, so the kit asks before every hit it applies: even the first hit in a brand-new world, before any HUD or Feedback has started, shows its marker and number, and asking again never adds a second set. **One health kit in a new world.** markersKit 2.0.5 was published when SecondWind was still called healthKit, and it still asks for `healthKit.api` by that old name. In a world that installs weaponsKit fresh, that name now leads to the SecondWind 1.2.0 the weapons use (the same package, renamed), so markersKit hears every hit directly and no older copy of the health kit is installed. A world that installed these kits earlier can still hold an older healthKit 1.0.0 copy that markersKit listens to instead (Loadout itself does). For those worlds the kit passes markersKit the hits, heals and kills of SecondWind 1.2.0 through markersKit's own handlers, and it stays out of the way wherever markersKit already listens to SecondWind 1.2.0, so nothing shows twice. A new world's camera is third-person, so you see the weapon in your character's hand. To get a first-person viewmodel with aim-down-sights, switch the `CameraRig`'s behaviour to `@builtin::controller.first_person` and set its `localPosition` to `{ 0, 1.6, 0 }`. The kit picks the view from where the camera is. ## Adjusted copies of SecondWind and aiKit Loadout's firing range uses every kit it builds on as installed from the ZeroMind library, with small changes of its own to two of them, so the range works for every player. These changed copies live in this world. aiKit doesn't come with an install of weaponsKit at all; the changes below matter only if you run aiKit characters beside the kit, as the range does. The rest is exactly as each kit's world publishes it. - **SecondWind 1.2.0: five files re-linked, code unchanged.** SecondWind's published `api`, `HUD`, `Rules`, `Trap` and `Vitals` point at an old single-file copy of Lifeline's main part, which names Lifeline parts no longer published. On an engine that hasn't seen them before (a browser, a new world), `Vitals` failed to load ("component not found") and health could end up split between two Lifelines. Loadout's copies of those five files have the same code, linked to the Lifeline installed beside them. - **aiKit 1.1.0: one change in `looks.module`, how a character's body is built.** aiKit builds each character's body on every player's machine as children of the character. A walking dummy is shared and owned by the host, and the engine refuses another player's spawn under an entity it doesn't own, so a second player never saw the walkers. Loadout's copy builds the body as its own entity on those players' machines and keeps it on the character every frame; on the owner's machine it is built as before. - **aiKit 1.1.0: `compat.module` and aiKit's own tests re-linked, code unchanged.** For the same reason as SecondWind, they now point at the Lifeline installed beside them, so aiKit, SecondWind and Loadout share one health system. When SecondWind and aiKit publish releases that fix these themselves, Loadout will go back to their unchanged copies. ## Swap weapons and looks with one word **Weapons.** `def` names the weapon. In the build script, change `def = "rifle"` to `def = "bow"`. While the game runs, set the field on the player's Weapon: ```lua local w = Weapons.localWeapon() -- this player's Weapon component w.def = "bow" -- hold fire to draw, release to loose w.def = "chainLightning" ``` `loadout` lists the weapons the player can cycle through with next and previous weapon (X / Tab and Q, the pad bumpers, or Swap on a phone). `Weapons.equip(entity, "sword")` does the same from code for any body. **Looks.** Every weapon belongs to a look family, and `look` draws it in another one: ```lua w.look = "cartoon" -- this player's rifle becomes a toy blaster with POP flashes, paint splats and squeaks w.look = "" -- back to the weapon's own look Weapons.setLook("scifi") -- every weapon on this peer, for everyone it draws ``` A look changes how a weapon is drawn and heard: model paint, muzzle flash, tracer, marks, sounds and crosshair. It never changes what the weapon does: a cartoon rifle deals exactly what the realistic one does. Call `Weapons.setLook` on every peer (in your scene's entrypoint) so every screen agrees. ## Add or tweak your own weapon ```lua -- a fantasy blunderbuss: a wider, heavier shotgun with a brass look Weapons.define("blunderbuss", { base = "shotgun", name = "Blunderbuss", look = "fantasy", pellets = 14, spread = 10, damage = 8, magazine = 1, reloadTime = 3, recoil = { kick = 9, shake = 1.4 }, }) -- a stronger rifle for this game Weapons.tweak("rifle", { damage = 22, magazine = 25 }) ``` `base` starts from a copy of an existing weapon, so you write only what differs; nested tables merge (`{ recoil = { kick = 2 } }` keeps the other recoil fields). Missing fields get sensible defaults, and an invalid spec raises with the reason. Then `def = "blunderbuss"` holds it. Run the same `define` and `tweak` calls on every peer (in your scene's entrypoint, or a module your components require), because the host checks each shot against its own copy of the weapon. The fields: `attack` (`hitscan`, `projectile`, `beam`, `thrown`, `melee`), `kind`, `look`, `damage`, `damageType`, `fireRate`, `auto`, `pellets`, `spread`, `aimSpread`, `spreadGrowth`, `maxSpread`, `range`, `pierce`, `falloff = { from, to, min }`, `recoil = { kick, yaw, shake }`, `magazine`, `ammo` (-1 = endless), `reloadTime`, `ammoPerShot`, `charge = { time, minDamage, minSpeed }`, `projectile = { speed, gravity, radius, bounce, fuse, life, loft, stick, model, orb, trail }`, `explosion = { radius, knockback }`, `beam = { kind, width, maxTargets, loop }`, `chain = { hops, range, falloff }`, `melee = { comboWindow, combo = { { damage, arc, reach, swing, time, knockback } } }`, `hold`, `crosshair` and `aimCrosshair`, `aimFov`, `model`, `fx = { muzzle, tracer, impact, mark, hitstop, squash }` and `sound = { fire, after, reload, hit, bounce }`. `Weapons.get("rifle")` shows a complete one. ## Phone controls On a phone or tablet the kit draws four buttons of its own beside the engine's touch controls (the movement stick, Jump and Sprint): | button | does | |---|---| | **Fire** | fires; hold for automatic weapons, beams and to draw a bow | | **Reload** | reloads | | **Aim** | hold to aim down sights (zooms in; the sniper scopes) | | **Swap** | next weapon in the `loadout` | They sit as a 2×2 block, Fire bottom-right under your thumb: beside the engine's Jump and Sprint on a sideways screen, above them on an upright one, always right of the movement stick's area and inside the screen's safe area. They are thumb-sized (about 66 points across on a phone, more on a tablet), never overlap each other or the engine's buttons, and never fold away behind the engine's "..." button. On an upright screen the kit lifts the engine's Jump and Sprint so they clear your health bar. While the buttons are up, the ammo panel sits at the top centre. Moving, looking (drag on the right) and jumping are the engine's. Checked at 390×844 upright and 844×390 sideways by tapping each button twice: Fire spends ammo, Reload refills, Aim zooms in while held and back out on release, Swap changes weapon. Set `controls = false` on the Weapon to drive it from your own code instead (`require("weaponsKit.input").force("fire", true)`); the buttons go away with it. ## Weapons Damage is per hit (`9×9` is nine pellets; `25+25+45` is a three-step combo). Ammo is magazine / spare rounds and reload time; `∞` recharges on its own, and melee weapons need none. | id | name | kind | attack | look | damage | rate | range | ammo | notes | |---|---|---|---|---|---|---|---|---|---| | `pistol` | Pistol | gun | hitscan | realistic | 22 physical | 4/s | 80 m | 12 / 72, 1.3 s | | | `smg` | SMG | gun | hitscan | realistic | 11 physical | 13/s | 50 m | 30 / 180, 1.8 s | auto, spread grows | | `rifle` | Assault Rifle | gun | hitscan | realistic | 18 physical | 9/s | 120 m | 30 / 150, 2.1 s | auto | | `shotgun` | Shotgun | gun | hitscan | realistic | 9×9 physical | 1.1/s | 30 m | 6 / 36, 2.6 s | falls off past 8 m | | `sniper` | Sniper Rifle | gun | hitscan | realistic | 95 physical | 0.75/s | 300 m | 5 / 30, 2.8 s | scope when aiming | | `rocketLauncher` | Rocket Launcher | gun | projectile | realistic | 90 explosion | 0.8/s | 200 m | 1 / 8, 2.4 s | blast 4.5 m | | `grenade` | Frag Grenade | thrown | thrown | realistic | 100 explosion | 1/s | 60 m | 1 / 6, 0.6 s | bounces, 2.2 s fuse, blast 5 m | | `flamethrower` | Flamethrower | beam | beam | realistic | 5 fire per tick | 10/s | 7 m | 100 / 200, 2.5 s | wide cone, sets burning | | `dagger` | Dagger | melee | melee | realistic | 18+22 physical | 4/s | 1.7 m reach | none | two-step combo | | `fists` | Fists | melee | melee | realistic | 10+10+24 physical | 3.5/s | 1.5 m reach | none | jab, jab, hook | | `laserBeam` | Laser Beam | beam | beam | scifi | 4 fire per tick | 10/s | 60 m | 100 / 300, 2.0 s | continuous | | `plasmaPistol` | Plasma Pistol | gun | projectile | scifi | 26 electric | 3.5/s | 90 m | 16 / 96, 1.4 s | glowing bolts | | `railgun` | Railgun | gun | hitscan | scifi | 80 electric | 0.6/s | 250 m | 4 / 24, 2.6 s | pierces 3 targets | | `pulseRifle` | Pulse Rifle | gun | hitscan | scifi | 15 electric | 10/s | 110 m | 40 / 200, 2.0 s | auto, cyan pulses | | `bow` | Longbow | bow | projectile | fantasy | 80 physical | 1.2/s | 150 m | 1 / 30, 0.5 s | hold to draw (1 s), arrows stick | | `crossbow` | Crossbow | bow | projectile | fantasy | 60 physical | 0.7/s | 150 m | 1 / 20, 1.6 s | bolts stick | | `thrownSpear` | Thrown Spear | thrown | thrown | fantasy | 70 physical | 0.8/s | 100 m | 1 / 5, 0.9 s | sticks; `javelin` names it too | | `sword` | Longsword | melee | melee | fantasy | 25+25+45 physical | 2.2/s | 2.5 m reach | none | slash right, slash left, overhead | | `hammer` | War Hammer | melee | melee | fantasy | 70 physical | 0.8/s | 2.3 m reach | none | knockback, hitstop | | `spear` | Spear | melee | melee | fantasy | 35 physical | 1.4/s | 3.3 m reach | none | longest, narrow thrust | | `fireball` | Fireball | spell | projectile | fantasy | 55 fire | 1/s | 120 m | 6 / ∞, 2.5 s | blast 3 m, sets burning | | `chainLightning` | Chain Lightning | spell | hitscan | fantasy | 30 electric | 1.2/s | 30 m | 5 / ∞, 3.0 s | leaps to 3 more targets | | `iceShard` | Ice Shard | spell | projectile | fantasy | 32 ice | 2/s | 120 m | 8 / ∞, 2.5 s | chills, frost marks | | `toyBlaster` | Toy Blaster | gun | projectile | cartoon | 8 physical | 4/s | 60 m | 12 / 96, 1.5 s | foam darts stick | | `squeakyHammer` | Squeaky Hammer | melee | melee | cartoon | 20 physical | 1.6/s | 2 m reach | none | squeaks, big knockback | | `waterBalloon` | Water Balloon | thrown | thrown | cartoon | 12 physical | 1.5/s | 50 m | 1 / 20, 0.5 s | bursts, splash 2 m | | `foamSword` | Foam Sword | melee | melee | cartoon | 12+12+22 physical | 2.6/s | 2.2 m reach | none | bonk, bonk, boing | Damage types are SecondWind's: fire sets targets burning, ice chills them, electric arcs, explosion falls off with distance. ### Look families | look | model | muzzle | tracer | marks | sounds | crosshairs | |---|---|---|---|---|---|---| | `realistic` | gunmetal paint | yellow-white star and smoke | thin pale yellow | bullet holes, scorch, slashes, cracks | real gunshots | crossDot, dynamic, shotgun ring, scope | | `scifi` | white with cyan glow | energy ring | glowing cyan bolt | glowing energy burns | laser, plasma, rail | brackets, bloom, hex, lockOn | | `fantasy` | wood and brass | warm gold casting swirl | faint golden streak | arcane sigils, frost | magic casts | compass, rune | | `cartoon` | toy orange plastic | POP star and puffs | thick yellow dashes | paint splats | toy pops, squeaks | bubble, star | ## Targets Anything with SecondWind health can be hurt; `Weapons.health(target, settings)` gives it some. Four kinds of target, all in the showcase range: | target | how to make it | |---|---| | **Standing dummy** | Any entity with a collider: `Weapons.health(d, { maxHealth = 200, deathStyle = "timed", respawnDelay = 3 })`. The kit's straw training dummy model (`weaponsKit.modelFit`'s `trainingDummy`) is what the range and the arena example guide stand up. | | **Armoured dummy** | SecondWind's armour model: `Weapons.health(d, { model = "armor", maxHealth = 200, maxArmor = 150, armorAbsorb = 0.6 })`. The armour soaks 60% of each hit until it wears through. | | **Walking dummy** | A character moved by a navigation or AI kit. The range uses aiKit 1.1.0, installed in this world beside the kit (not a dependency of it): `entity.spawnSynced(...)` with `aiKit.Brain { preset = "dummy", route = { a, b } }`. | | **Turret that shoots back** | aiKit's `turret` preset: it sweeps its arc, locks on to players and fires bursts. | | **Players** | `Weapons.health(body, { maxHealth = 150, deathStyle = "timed", respawnDelay = 3 })` on the player prototype's body. | Targets with no collider of their own (aiKit's characters are moved by navKit, not physics) are hit as an upright capsule, with the height and radius of their health (Lifeline's `bodyHeight` / `bodyRadius`, 1.8 m by 0.4 m when unset). Build plain targets with `entity.spawn` in your scene's build script: every player builds the same copy, and each applies the host's verdict to it. A shared target (`entity.spawnSynced`, as AI characters are) is changed by the peer that owns it, or by the host while nobody owns it. `Weapons.health` sets no hit immunity (SecondWind's default 0.5 s swallows most of an automatic weapon's hits) and, for non-players, no knockback. A kit that gives its characters health itself may not: set those on the peer that runs each character, with `require("SecondWind.api").configure(id, { invulnerability = 0, knockbackScale = 0 })`, as the showcase's `range.Range` component does for its aiKit targets. ## The Weapon component Put it on the player prototype's body (the entity the `PlayerPrototype`'s `body` points at). Every joining player gets their own copy. | field | default | meaning | |---|---|---| | `def` | `"rifle"` | The weapon to hold (an id from the list above, or one you defined). | | `look` | `""` | Look family to draw it in: `realistic`, `scifi`, `fantasy`, `cartoon`. Empty = the weapon's own. | | `loadout` | `""` | Comma-separated ids that next / previous weapon cycle through. Empty = keep `def`. | | `controls` | `true` | Use the kit's controls (keyboard and mouse, pad, phone buttons). Off: drive it from code with `require("weaponsKit.input").force(action, value)`. | | `firstPerson` | `true` | Show a first-person viewmodel when the camera is at the eyes. With a third-person camera (or `false`) the shooter sees the weapon in their hand. | | `crosshair` | `true` | Set markersKit's crosshair to the weapon's style. | | `hud` | `true` | Show the ammo panel to the player holding the weapon. | | `autoReload` | `true` | Reload by itself when the magazine runs dry. | | `pointerLock` | `true` | Capture the cursor on the first click for mouse-look. Off: the player looks by holding the right mouse button. The kit also turns it off for the session by itself when the engine reports a locked pointer but delivers no mouse movement, or refuses the lock on three clicks in a row, and the ammo panel then says "hold right mouse to look". | | `ammo`, `reserve`, `reloading`, `aiming`, `charge` | | Read-only state, written by the player who holds the weapon and synced to everyone. | **Controls:** fire is left mouse, right trigger or Fire; aim is right mouse, left trigger or Aim; reload is R, Y or Reload; next weapon is X / Tab, the right bumper or Swap; previous weapon is Q or the left bumper. Walking, looking and jumping stay the engine's on every device. Rebinding and button prompts are controlsKit's. ## API `local Weapons = require("weaponsKit.api")` | call | what it does | |---|---| | `Weapons.styles(filter?)` | Every weapon as `{ id, name, kind, attack, look, damageType, description, tags, custom }`. `filter` is `{ kind, look, damageType, attack, tag, id }`, each one value or a list; a plain string filters by tag. An unknown key raises, naming the valid ones. | | `Weapons.list()`, `Weapons.get(id)`, `Weapons.has(id)` | Every id (built-ins first); one full definition (a copy); whether it exists. | | `Weapons.canonical(name)` | The id a name resolves to (`javelin` → `thrownSpear`). Every call that takes an id accepts an alias. | | `Weapons.vocabulary()` | The words: kinds, attacks, looks, damage types, hold styles, crosshair roles, mark roles. | | `Weapons.define(id, spec)`, `Weapons.tweak(id, changes)`, `Weapons.remove(id)` | Add or replace a weapon; change some fields; remove yours or put a tweaked built-in back. | | `Weapons.looks()`, `Weapons.setLook(look)`, `Weapons.getLook()`, `Weapons.lookFor(id, override?)` | The four families; force every weapon on this peer into one; the family a weapon is drawn in. | | `Weapons.crosshairFor(id, look?, aiming?)` | The markersKit crosshair style a weapon uses. | | `Weapons.equip(entity, id, { look?, loadout? })` | Adds the Weapon component or changes its `def`. | | `Weapons.weaponOf(entity)`, `Weapons.localWeapon()` | A body's Weapon component; this peer's own Weapon and body id. | | `Weapons.health(target, settings?)` | Gives a target SecondWind health (`maxHealth`, `model`, `deathStyle`, `respawnDelay`, `displayName`, `team`, `maxArmor`, ...), with no hit immunity and, for non-players, no knockback. Returns the SecondWind Vitals component. | | `Weapons.on(event, fn)`, `Weapons.off(handle)`, `Weapons.stats()` | Events (below); counters of `fired`, `hits`, `refused`, `confirmed` and `received` on this peer. | Tags are the weapon's kind, attack, look family and damage type, and any of `automatic`, `charge`, `combo`, `explosive`, `sticks`, `pellets`, `pierce`, `chain`, `noAmmo`, `recharges`, `custom`: ```lua Weapons.styles({ kind = "melee" }) -- dagger, fists, sword, hammer, spear, squeakyHammer, foamSword Weapons.styles({ look = { "scifi", "cartoon" } }) -- any of the listed values Weapons.styles("explosive") -- rocket launcher, grenade, fireball, water balloon ``` ### Events | event | payload | where | |---|---|---| | `fired` | `{ shooter, weapon, origin, dir, look, kind }` | every peer, as the shot is drawn | | `hit` | `{ shooter, weapon, target, point, normal, amount }` | every peer, from the host's verdict | | `refused` | `{ shooter, weapon, reason }` | the host | | `reloaded` | `{ entity, weapon }` | the player who reloaded | | `equipped` | `{ entity, weapon, look }` | every peer | | `defined` | `{ id }` | this peer | ### Pieces - **`weaponsKit.input`**: `held` / `pressed` / `released`, `force(action, value)` for AI and tests (a forced action behaves like a real button), `setPointerLock(on)`, `touchButtons(on)` and `touchOverlayUp()`. - **`weaponsKit.hud`**: `setEnabled(false)` hides the ammo panel. - **`weaponsKit.net`**: `RULES` holds the host's tolerances, `refusals()` lists the latest refused shots, `isHost()` / `hostPeer()` say who decides, `listening()` lists the channels it listens on with how many messages each handled and how many copies it dropped. - **`weaponsKit.netOnce`**: handles each network message exactly once (see Multiplayer). `on(channel, handler)`, `send(channel, ...)`, `refresh()` and `status()`; any kit can use it for its own channels. - **Assets**: `weaponsKit.audio`, `weaponsKit.marks`, `weaponsKit.vfx` and `weaponsKit.modelFit` (each model's grip and muzzle). Every model ships inside the package under `weaponsKit.models`, the training dummy too: spawn it with `entity.component.add("Asset", { source = require("weaponsKit.modelFit").trainingDummy.model })`. ## Multiplayer Players come from the scene's player prototype; the kit runs on every peer. Any player can attack, the room's host or anyone who joined, and every player sees the same health: 1. **The shooter** draws their own shot at once (muzzle flash, tracer or projectile, impact, mark, sound, recoil) and sends it with what it claims to have hit. 2. **Every other player** draws the shot from the shooter's hand and flies their own copy of any projectile, arrow, grenade or spell. 3. **The host** (the room's creator; any peer with no network) judges every shot, its own and everyone else's. It refuses: - a shot from a body the sender doesn't own, or with a weapon that body isn't holding; - a shot faster than the weapon fires, or from an empty magazine (the host keeps its own count); - a shot that doesn't start where the shooter stands; - a hit out of range or outside the weapon's spread or swing; - a hit through a wall, or on a target that isn't there. It works out the damage itself (a claim carries none), flies the one projectile that decides what a shot hit (a refused shot's projectile is still drawn but deals nothing), and decides blasts and chain hops. 4. **The verdict** goes to everyone. A target built in the scene is every player's own copy, and each applies the verdict to it; burning and poison tick only on the host's copy, and the host sends its copies' health, armour and deaths whenever they change (and all of them every 3 s), so every copy shows the host's numbers even after a missed message. A shared target is changed by its owner, or by the host while nobody owns it. Health, deaths, respawns, hit markers and damage numbers come out the same on every screen. Projectiles (grenades, arrows, spells) fly in fixed 1/120 s steps, so the thrower's grenade bounces exactly where the host's deciding copy does, whatever each game's frame rate. A shot that reaches the host before the weapon switch it follows is taken as that switch (no faster than one switch per 0.2 s). **Each message is handled exactly once.** A listener given to the engine's `multiplayer.on` can never be removed (#19267), so every code reload, every new play session and every reopening of the world used to leave one more listener on each channel, and each of them handled every shot: one shot from another player was received several times, and now and then two copies got past the fire-rate check and dealt double damage. Since 1.2.7 every channel the kit listens on (shots, verdicts, reloads, health readings, host announcements and the rest) goes through `weaponsKit.netOnce`. Only the newest listener on a channel acts: older ones read the kit's shared state when a message arrives, find they are no longer current and do nothing. Every message also carries an id (the sender's nonce and a sequence number), and an id already handled from that peer is dropped. The kit gives each channel a fresh listener whenever a play session starts or the room changes, so messages keep arriving after edit mode or a rejoin, and the one before it goes quiet the same moment. `Net.listening()` reports how many messages each channel handled and how many copies it dropped. Ammo, reloading, aiming and bow draw belong to each player and are synced for everyone to read. ## The showcase This world's startup scene is a firing range. You spawn in a ring of 27 stations, one per weapon; step on a pad to take its weapon, or cycle with Q / X. The five pads by the spawn switch every weapon you hold into one look family (white = each weapon's own). Down-range are 12 targets with signs: standing dummies, armoured dummies, walking dummies and turrets that shoot back. Behind them, concrete walls with target boards to leave marks on. The range is built in `scenes/main.scene/build.luau`; its pads and target tuning are the `range.Range` component, and its walkers and turrets run on aiKit. None of the range is part of the package. ## Tests `zero tests run weaponsKit` in the shell, or `tools.use("tests", "run", { suite = "weaponsKit" })` from Luau, in edit mode. They run the same in any world that installs the kit. 75 tests cover: - the catalogue, at least four weapons in every look family, and the `javelin` alias; - defining weapons, looks and crosshairs, and the assets every weapon needs; - equipping, and `health()` with its maxHealth; - hit finding: hitscan, pellets, pierce, melee, beams, chains, explosions, projectiles, and targets with no collider; - the host's refusals: unknown weapon, far from the shooter, too fast (a burst sent at once lands one shot, while evenly fired shots that arrive unevenly all land), past the magazine, through a wall, out of reach, a target that isn't there, and someone else's body; - two players: every weapon fired by another player hurts a dummy once per shot, a refused projectile deals nothing, a second player's peer applies the host's verdict, and the kit re-subscribes its channels; - each message once: a shot from another player in each weapon family (rifle, crossbow, laser beam, thrown spear, sword, fireball) is received once and lands once when it arrives through every listener the kit ever registered, as the engine delivers it; the same again after the kit's code reloads twice and after leaving and re-entering Play twice; and a message that arrives twice, or without an id, is handled once on every channel the kit listens on; - the controls: one scheme switch, pointer lock on and off, locking turned off after the engine refuses it on three clicks, the phone buttons on four screen shapes (none folded away, none overlapping, all right of the stick); - the host's health readings correcting another player's copy of a dummy, burning only on the host's copy, a grenade flying the same path at 30, 60 and 144 frames a second, and a shot that arrives before the weapon switch it follows; - a fresh install: every model the kit names, the training dummy included, ships inside the package; explosions, chains, capsule hits and the health readings still find every target when SecondWind's own list of them is empty, the kit's code names no aiKit module, the host's stand-in for a target nobody owns lasts one damage call, and a hit the player lands shows exactly one hit marker and one damage number (someone else's hit shows none), starting from a markersKit that nothing has asked to listen yet, as in a brand-new world; - this README's first example, run in a new world's scene with no markersKit Feedback entity: the crosshair comes on with the weapon, the rifle's hits on the crate show hit markers and damage numbers, and the rifle and the bow both hurt the crate; - kits built on weaponsKit: the sound cues and the fireball model Combo Dojo's comboKit plays (`weaponsKit.audio`'s `has` and `play`, `weaponsKit.models.fireball`) are still there. After each test the suite retires the effect pools it built, this kit's and juiceKit's, each draw before its buffers. The test runner frees the GPU buffers a suite made but not the draws over them, so a pool left standing would leave a draw pointing at a freed buffer and the renderer would log it. ## How it behaves These are design choices, not engine problems: - **The host checks hits against where targets are now.** There is no lag compensation. A claimed hit may be up to 2.4 m from the target's current position (`Net.RULES.targetSlack`), so a fast target hit by a player on a slow connection can be refused. - **Fire rates allow for uneven delivery.** Frames and the network deliver evenly fired shots unevenly, so the host lets a shot come early by as much as the one before it came late, up to 35% of an interval (`Net.RULES.rateCarry`), never closer than 30% of an interval after a pause, and never faster than the weapon's rate over time. - **The arm doesn't animate.** Other players see the weapon in the character's right hand, pointing where the body faces; a melee swing turns the weapon in the hand rather than swinging the arm. - **A look family repaints the model; it doesn't swap it.** Each weapon has one generated model; another family tints it and changes every effect, mark, sound and crosshair. - **Bow draw is the shooter's word.** The host caps a reported draw at full and limits the fire rate, but doesn't time the draw. - **A new host takes up to 6 s to take over.** If the host leaves and the room names nobody, the peer with the lowest id takes over judging after 6 s; shots fired in that gap draw but deal no damage. - **A shared target nobody owns isn't brought back by the kit.** The host applies damage to it, but respawning it is the job of whatever runs it (aiKit does this for its characters). - **Weapons act only in play,** and soundstage makes no sound in edit mode. ## Frame rate The kit's own work each frame is small next to the engine's. Measured on 2026-10-31 in Loadout's firing range on the desktop app, with the editor open and one player: - The player's Weapon component took a median of about 1.5 ms a frame standing still. Holding fire it took about 1 ms on the shotgun or a fireball, about 4 ms on the laser beam and about 6 ms on the automatic rifle, where every shot finds its hits, draws its effects, leaves a mark and plays its sound. - Every component in the range together (the Weapon, health, health bars, the AI targets, sound) took about 4 ms a frame standing still. - The whole frame took roughly 50 to 120 ms on the machine used for these checks (about 8 to 20 frames a second), almost all of it engine and editor work: rendering, UI and the editor's own scripts. In multiplayer the kit sends one small message per shot to the host, and the host sends one verdict back to every player. The host also sends its targets' health up to 4 times a second while it changes, and all of it every 3 s. All of it goes through the engine's step that sends changes between players, which #20750 under Known limits measures between two desktop engines; with one player in the range that step took about 0.4 ms a frame. Not measured for this release: the frame rate in a browser, and a room with more than one real player. ## Known limits Open engine issues that affect this kit: - **[#22120](https://github.com/OrigoZero/zero/issues/22120): a package's install closure lists dangling-guid problems.** The closure ZeroMind works out for installing a package can list problems about guids that no published asset holds. They come from the engine and library copies the kits under weaponsKit point at, not from the kit's own files. Installing weaponsKit into a brand-new world still completes and the kit runs. - **[#19267](https://github.com/OrigoZero/zero/issues/19267): a message listener can never be removed.** Every listener given to `multiplayer.on` stays for as long as the engine runs. The kit makes all but the newest one on each channel stand down, so each message is handled once (see Multiplayer), but each new play session or room change still leaves one idle listener per channel behind until the engine restarts. - **[#14448](https://github.com/OrigoZero/zero/issues/14448): two engines on one account collide as one player.** To try the kit with two players, sign the two games in with two different accounts. - **[#20750](https://github.com/OrigoZero/zero/issues/20750): how long sending changes takes.** The issue measures how long the engine takes to send changes between two desktop engines. Every change the kit shares (shots, verdicts, health readings, synced weapon state) goes through that step. With one player in the range, the engine's sending step took about 0.4 ms a frame. - **[#19849](https://github.com/OrigoZero/zero/issues/19849): long waits.** Some engine operations take a long time with no progress shown: installing the kit, running its 75 tests (they enter and leave Play several times) and entering Play in the range can each take a minute or more. Let them finish. - **[#20946](https://github.com/OrigoZero/zero/issues/20946): an in-engine agent can't check the web build itself.** The checks for this release ran in the desktop app. The browser version of 1.2.9 was not checked by the agent that made it; earlier releases were checked in a browser by hand. - **[#22181](https://github.com/OrigoZero/zero/issues/22181): the kit's guides don't come up in a ZeroMind library search.** The two guides that ship in the package (how to add weapons to your game, and the arena example) aren't found when you search the ZeroMind library for them. They arrive with the install all the same: open them with the `guides` tool at `weaponsKit.package/howToUse` and `weaponsKit.package/arenaExample`, or read `howToUse.guide/guide.md` and `arenaExample.guide/guide.md` in the package. - **[#21420](https://github.com/OrigoZero/zero/issues/21420): installs with several dependencies are slow.** weaponsKit brings seven kits with it, so the install is the slow step: about a minute in a brand-new world when 1.2.2 was checked, and it can take longer. Let it finish; nothing has to be installed by hand. - **[#19258](https://github.com/OrigoZero/zero/issues/19258): the ZeroMind listing description stays on the previous version after a release.** The description ZeroMind shows for the package in search and in the library stays on an older version's README while every other field moves on, and it can lag several releases behind. The README and `package.yaml` that come with the install are the current ones. ## Version history - **1.2.9**: Hit markers and damage numbers show from the very first hit in a brand-new world. markersKit hears the weapons' health kit only after something has asked it to listen, and in a world where nothing had yet, hits did damage but showed no marker or number (the kit's own test failed there until something else had run). The kit now asks markersKit to listen before each hit it applies, so they show every time, once each. The marker test now starts from a markersKit nothing has asked to listen, so it fails in any world without the fix. Known limits adds #22181 (the guides don't come up in a library search). 75 tests. - **1.2.8**: The training dummy model now ships with the kit (`weaponsKit.models.trainingDummy`, reached through `weaponsKit.modelFit`). Before, it lived only in Loadout's own showcase folder, so in any other world the arena example guide's three training dummies were there but drew nothing. A test checks that every model the kit names is inside the package. The arena example guide was followed again in a brand-new world with 1.2.8 and the dummies show. The range uses the kit's copy. No other change. 75 tests. - **1.2.7**: Each shot from another player, and every other message the kit listens for, is handled exactly once, for every weapon family, however many times the kit's code reloads, Play is entered and left, or the world is opened again. Before, every reload or new session left one more listener behind (#19267) and each of them handled the message, so one shot was received several times and could occasionally deal double damage. The new `weaponsKit.netOnce` module keeps one current listener per channel and drops a message id it already handled; any kit can use it. Four tests check it, including after two code reloads and after leaving and re-entering Play twice. Two guides ship with the package: how to add guns, bows, swords and spells to your game, and an example that builds a small arena with targets that fight back from an empty world. Known limits lists only open engine issues. 74 tests. - **1.2.6**: The test suite retires the effect pools it built after each test (this kit's and juiceKit's), each draw before its buffers, so a test run no longer leaves draws over freed buffers and the renderer stops logging them; the kit's runtime code is unchanged apart from the test hook `Vfx._releasePools`. The test suite's own README says 70 tests and lists what they cover. Known limits says what #19258 really does: the listing description stays on an older version's README and can lag several releases. 70 tests. - **1.2.5**: Text only. Known limits says what the two spawn-refusal issues describe (a joining player's spawns turned down by the player already in the room, and a rejoining player refusing things it already holds) instead of claiming a joining player misses targets, and what #20750 measures (how long sending changes takes between two desktop engines) instead of a browser frame rate. The Frame rate section and the history say the same. The code is 1.2.2's. 70 tests. - **1.2.4**: Text only. A Frame rate section says what the kit costs each frame (measured in the range), what it sends in multiplayer, and what was not measured; Known limits adds #20750 (how long the engine takes to send changes between engines). The code is 1.2.2's. 70 tests. - **1.2.3**: Text only, after checking 1.2.2 from a brand-new world: Known limits gives the install time that was measured (about a minute) instead of "several minutes", and the 1.2.0 entry no longer says every install lays down an older health kit. The code is 1.2.2's. 70 tests. - **1.2.2**: Found by installing 1.2.1 into a brand-new world and playing the first example. The example's crate now sits on the ground, right under the crosshair from where you spawn (it used to float just above the line of fire, so the first shots hit the floor). When the engine refuses the pointer lock on three clicks in a row, the kit turns locking off for the session, so holding fire keeps automatic weapons firing (before, every click asked for the lock again and fired a single round). The README says what a new world really gets: one health kit, with markersKit hearing SecondWind 1.2.0 directly. 70 tests. - **1.2.1**: Runs on the current engine library. A test checks what Combo Dojo's comboKit uses from the kit (the sound cues and the fireball model). Known limits cite only open engine issues and say what each one does to the kit. 69 tests. No change to the 27 weapons, the four looks, the Weapon component or the API. - **1.2.0**: One install brings everything the kit needs. Installing weaponsKit now brings every kit it builds on with it: SecondWind (with Lifeline and MatchFlow), markersKit, juiceKit, soundstage and controlsKit, each recorded with the world and release it comes from. The kit no longer needs aiKit: the host's stand-in for a shared target nobody owns is now the kit's own (on the same Lifeline rule aiKit uses, so the two still work side by side), and Play no longer refuses to start in a world without aiKit. The Weapon turns on markersKit's crosshair, hit markers and damage numbers by itself, so a scene needs no Feedback entity for them. Explosions, chain lightning, other players' rockets, grenades, fireballs and water balloons, and targets with no collider find their targets in any world, not only where SecondWind keeps its own list of them. The tests reach the test framework the way the engine documents, so they show up wherever the kit is installed. Hit markers and damage numbers now show for your own hits in any world: where markersKit listens to an older healthKit 1.0.0 copy, the kit hands it the hits of the SecondWind 1.2.0 the weapons use. 68 tests. Health no longer ends up split (SecondWind and the kit use the same Lifeline). The old leftover MatchFlow copy is gone, and the kit and the range wait quietly for the kits they use while those are still loading. In the range, hits land and every player sees the same health, also in a browser; the walking dummies get their 200 health one by one, so aiKit's own dummy keeps its default; and a second player sees the walking dummies (see Adjusted copies). No change to the 27 weapons, the four looks or the targets. - **1.1.0**: First published release. Multiplayer damage works for every weapon whoever fires it, and every player sees the same health: the host judges every shot and flies the one projectile that decides it; burning and poison tick only on the host's copy of a dummy every player builds, and the host's health readings keep every other copy on the same numbers; projectiles fly in fixed steps, so a grenade lands in the same place on every screen; a shot that arrives before the weapon switch it follows is taken as that switch; the host applies damage to shared targets nobody owns (through aiKit's ownership rule); the fire-rate check allows for uneven frames and network delivery. Targets with no collider (AI characters) are hit as capsules. Phones get the kit's own Fire, Reload, Aim and Swap buttons, on screen upright and sideways, with the ammo panel at the top centre; `input.force` behaves like a real button. Every sound clip has a README. Showcase: standing and armoured dummies, walking dummies and turrets that shoot back (aiKit). Built on SecondWind 1.2.0, markersKit 2.0.5, juiceKit 1.2.2, soundstage 2.0.0, controlsKit 1.1.0 and aiKit 1.1.0. 64 tests. - **1.0.0**: Built, never published. The Weapon component and 27 weapons in four look families with generated models, sounds, marks and effects; host-judged hits; first- and third-person holding; controlsKit controls with phone buttons in the engine's stack; the catalogue API (`styles`, `define`, `tweak`, `canonical`, `health`); the firing range.
▲ 0↑ born

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported on this asset or on the parts this page shows. Parts it leaves out are not counted here.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.