# Guns and ranged fire (combat.ranged) ## What it is for `combat.ranged` gives any entity a gun: the player through the inputs (`fire` held for automatic fire at `fireRate`, `aim` to tighten the spread, `reload`), and NPCs or turrets through the service. A gun has a magazine, a fire rate, a reload, spread and recoil, and damage that fades with distance. A shot is either a physics ray (instant) or handed to `combat.projectiles`, which flies it. Damage goes through `combat.damage` (or `vehicle.damage`, or the health component). A gun is the kit settings, or a model of the `guns` collection that overrides them for whoever holds it: shotguns, charged shots, melee swings, continuous beams, cone jets and hovering sentries. The rules shared by every fighting kit (health, shield, statuses, knockback) are in the [combat guide](guide:combat). ## Turn it on Install the kit in a project and set its settings with the tools below; `gripforge kit api combat.ranged` lists every setting, and `gripforge docs combat-ranged` prints this guide offline. ```text gripforge_gamekit_install { project_id: "gkp_…", id: "combat.core" } gripforge_gamekit_install { project_id: "gkp_…", id: "combat.ranged" } gripforge_gamekit_configure { project_id: "gkp_…", id: "combat.ranged", config: { "damage": 20, "fireRate": 8, "magazine": 24, "hud": "both" } } ``` ```json { "id": "jet", "damage": 10, "fireRate": 10, "magazine": 50, "range": 10, "jet": { "angle": 40 } } ``` ## First example A `jet` gun (10 damage, 50 rounds) fired at an enemy 6 m ahead hits every enemy in its cone once, leaves allies, targets behind and targets behind a wall alone, and spends one round per pulse. ```text combat.ranged.equip(player, "jet") combat.ranged.fire(player, { yaw: 0, pitch: 0 }) // true: the two enemies in the cone lose 10 each combat.ranged.ammo(player) // 49 combat.ranged.specialFrame() // the 9 rays of the pulse, to draw ``` ## Settings and defaults | Setting | Default | Meaning | | --- | --- | --- | | `meleeWeapons` | `false` | {{s_meleeWeapons}} | | `damage` | 25 | damage of a shot before falloff (1 to 500) | | `fireRate` | 6 | {{s_fireRate}} | | `magazine` | 30 | rounds in a magazine (1 to 200) | | `reloadSeconds` | 1.8 | {{s_reloadSeconds}} | | `projectile` | `false` | `false`: hitscan; `true`: projectiles flown by `combat.projectiles` (without that kit the gun does not fire) | | `range` | 100 | reach of a shot in metres: the hitscan ray, and where the falloff ends (1 to 1000) | | `falloffStart` | 25 | {{s_falloffStart}} | | `falloffMin` | 0.5 | {{s_falloffMin}} | | `spreadHip` | 0.03 | {{s_spreadHip}} | | `spreadAim` | 0.008 | {{s_spreadAim}} | | `targeting` | not set | optional targeting assist of the Rust host (an object of the shared targeting core; see `gripforge kit api combat.ranged`) | | `aimFromView` | `false` | {{s_aimFromView}} | | `holdIdle` | `idle_guns` | {{s_holdIdle}} | | `hud` | `none` | what the kit's HUD view shows of the player's gun: `ammo` (name, magazine, reloading), `reticle` (spread drawn around the centre), `both` or `none` (a game that draws its own) | ## Services - `combat.ranged` (service): `fire(entity, aim?)`, `reload(entity)`, `ammo(entity)`, `state(entity)`, `setAiming(entity, on)`, `equip(entity, gun)`, `gun(entity)`, `held()`; on the Rust host also `resolveAim(player, fromView?)` (a fresh targeting preview of the controlled player), `targeting(entity, command)`, `targetingStep(entity, observation)`, `targetingFrame(entity)`, `specialFrame()` (transient observations of jets and sentries) and `beams()` (the beams to draw). `entity` is an id or the player. - The `guns` collection holds gun models; every field a model gives wins over the kit settings for whoever holds it. Fields: `id`, `name`, the settings `damage`, `fireRate`, `magazine`, `reloadSeconds`, `range`, `falloffStart`, `falloffMin`, `spreadHip`, `spreadAim`, `projectile`, then `speed`, `gravity` and `lifetime` of a projectile, `pellets` (1 to 16 shots per pull, one round), `charge { seconds, damage, min }` (held to a full charge), `melee { range, arc, damage, windup, active, recovery, knockback, lunge }` (the trigger swings it, no round spent), `beam { width, color }` (held, an exact ray at `fireRate`, damage and rounds per tick, no spread), `jet { angle }` (Rust host: held cone pulses at `fireRate`, capped at 20 m, each enemy once per pulse, fresh line of sight, not combinable with beam, melee, charge or deploy), `deploy { lifetime, distance, maxActive, fireRate }` (Rust host: a stationary hovering sentry, 12 s, 2 m ahead, 2 active per owner and 4 pulses per second by default, using the gun damage and range), `anim`, `meleeCombat`, `model`, `hold`, `grip`, `color` and `presentationProfile`. An entity with the component `ranged { damage }` is a turret or a shooting NPC placed by hand; the component `combat.target { enabled, offset }` opts a scene instance into the Rust targeting. ## Events - `combat.fired`: a shot left the gun: `entity`, `origin`, `yaw`, `pitch`, `ammo` left, `projectile` (the id `combat.projectiles` gave the first shot), `gun`, `pellets`, `charge` (0 to 1) and `melee` for a swing. - `combat.hit`: a hitscan shot reached something: `entity` (the shooter), `target`, `damage`, `point`, `distance`, `applied` (true when the damage already went through a service or the health component), `gun`. - `combat.reloading` (a reload began: `entity`, `seconds`), `combat.reloaded` (`entity`) and `combat.empty` (the trigger was pulled on an empty magazine, and a reload starts). - `combat.equipped` (a model of `guns` is now in the hands of `entity`: `gun`, `magazine` size, `ammo` it holds) and, on the Rust host, `combat.deployed` (a hovering sentry was accepted: `entity`, `owner`, `gun`, `at`). The kit listens to `input.action` and `entity.despawned`. ## Hooks and modding - `ranged.fire`: a loaded, cooled-down gun that is not reloading is about to fire (`entity`, `yaw`, `pitch`, `spread` in radians recoil included, `damage` before falloff, `cost` in rounds, `ammo`, `aiming`, `projectile`, `gun`); `yaw`, `pitch`, `spread`, `damage` and `cost` are mutable (0 cost: a free shot), and cancelling makes `fire` return `false` with nothing drawn, spent or emitted (a jammed or holstered gun). - `ranged.reload`: a gun that is not full and not already reloading is about to reload (`entity`, `ammo`, `magazine`, `seconds`, `rounds`); `seconds` and `rounds` (how many rounds go in, fewer when a reserve runs low) are mutable, and cancelling means no reload (no reserve left, a silenced zone). - `ranged.hit`: a hitscan shot of this kit reached an entity (`entity` the shooter, `target`, `distance`, `damage` after falloff); `damage` is mutable, and cancelling stops the shot there with no damage and no `combat.hit`. Shots handed to `combat.projectiles` ask `projectile.impact` instead. - `ranged.equip`: a model of `guns` is about to go into the hands of `entity` (service `equip`, verb `equip_gun`): `gun` and `ammo` (the rounds it comes in with, full by default; an arsenal gives each gun its stored magazine back) are mutable, and cancelling makes `equip` answer `false` and keeps the gun in hand. ```json { "id": "silenced_zone", "hook": "ranged.reload", "if": [{ "var": "zone.silent", "op": "==", "value": true }], "cancel": true, "reason": "silent" } ``` ## Using it from content Script verbs: the actions `fire` (`instance` or `entity`, default the player, optional `yaw` and `pitch`; refused while reloading or cooling down, an empty magazine reloads), `reload`, `equip_gun` (`gun`, magazine full) and `damage` (`entity` or `target`, `amount`: through `combat.damage`; registered only when no other kit owns the verb). Inputs: `fire` (`Mouse0`), `aim` (`Mouse2`), `reload` (`KeyR`). Variables: `combat.ammo`, `combat.magazine`, `combat.reloading`, `combat.gun`, `combat.charge`. ```json { "equip_gun": "rifle" } { "fire": "turret_1" } ``` ## When it does not do what you expect | Symptom | Cause and fix | | --- | --- | | `fire` returns `false` | The gun is reloading, cooling down between shots, or empty (it starts a reload and emits `combat.empty`), or a `ranged.fire` tap cancelled it. | | A projectile gun does not fire | `combat.projectiles` is not installed: it flies the shots of `projectile: true`. A hitscan-only game works without it. | | A shot hits but nothing loses health | The target was not registered with `combat.health` (see the combat guide), is on the shooter's team, or is invulnerable; read `blocked` in the `combat.damage` answer. | ## Limits - The beam, jet, deploy, targeting and direct melee-weapon modes are Rust host features; the web player and the other engines run the plain guns (hitscan or projectile, charge, pellets, melee swing) and ignore them. - NPC shooters read `npc.behavior`, which no native module serves yet. Magazines and reserves are per gun: a reserve comes from `weapons.arsenal`. - The ammo view and the reticle are web display views; the gun model in hand is drawn by the display module on the hand bone of the shooter. ## Versions Kit `combat.ranged` 1.25.0 (2026-10-26): optional `meleeCombat.rangedDefense` for guard, parry and dodge decisions on direct ranged arrivals, Rust host only. 1.20.0 moved projectile flight to `combat.projectiles`; the hooks `ranged.fire`, `ranged.reload`, `ranged.hit` and `ranged.equip` run on the web and the Rust host. It needs `character.control`, `input.actions`, `spawn.entities`, `physics.port` and `game.engine` ^1.