Guns

Usage guide of combat.ranged: hitscan or projectile guns with magazines, reload, fire rate, spread, falloff, charge, beams, jets and sentries, the guns collection, settings and defaults, service, events, hooks and verbs. The shared damage rules are in the combat guide.

Source: module-kits:kits/combat_ranged/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs combat-rangedMCP tool: gripforge_docs { "topic": "combat-ranged" }

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.

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.

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" } }
{ "id": "jet", "damage": 10, "fireRate": 10, "magazine": 50, "range": 10, "jet": { "angle": 40 } }

First example #

<!-- test: kits/game_engine/host/tests/ranged_special.rs::cone_hits_each_enemy_once_excludes_allies_back_and_walls --> 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.

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 #

SettingDefaultMeaning
meleeWeaponsfalse{{s_meleeWeapons}}
damage25damage of a shot before falloff (1 to 500)
fireRate6{{s_fireRate}}
magazine30rounds in a magazine (1 to 200)
reloadSeconds1.8{{s_reloadSeconds}}
projectilefalsefalse: hitscan; true: projectiles flown by combat.projectiles (without that kit the gun does not fire)
range100reach of a shot in metres: the hitscan ray, and where the falloff ends (1 to 1000)
falloffStart25{{s_falloffStart}}
falloffMin0.5{{s_falloffMin}}
spreadHip0.03{{s_spreadHip}}
spreadAim0.008{{s_spreadAim}}
targetingnot setoptional targeting assist of the Rust host (an object of the shared targeting core; see gripforge kit api combat.ranged)
aimFromViewfalse{{s_aimFromView}}
holdIdleidle_guns{{s_holdIdle}}
hudnonewhat 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.
{ "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.

{ "equip_gun": "rifle" }
{ "fire": "turret_1" }

When it does not do what you expect #

SymptomCause and fix
fire returns falseThe 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 firecombat.projectiles is not installed: it flies the shots of projectile: true. A hitscan-only game works without it.
A shot hits but nothing loses healthThe 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.