> For the complete documentation index, see [llms.txt](https://docs.rainmad.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rainmad.com/resources/gta-dual-wield-two-handed-weapons/systems/ammo-system.md).

# Ammo System

Ammo is tracked **per hand**, survives disconnects, and can travel with the item itself. This page covers how it is stored, how it flows, and when it is written.

***

### The two numbers per hand

Each hand has a magazine and a reserve:

```
magazine_capacity = 30, max_ammo = 250

  ┌─ magazine ─┐  ┌────── reserve ──────┐
  │    30      │  │        220          │   = 250 total for that hand
  └────────────┘  └─────────────────────┘
```

* **Magazine:** counts down as you fire. Hitting zero triggers a reload.
* **Reserve:** everything not currently in the magazine.
* **Persistence stores the total** (`magazine + reserve`), not the split. On re-equip the total is refilled into the magazine first, remainder to reserve.

***

### Storage modes

#### Metadata mode

Ammo is written onto the item itself:

```lua
item.metadata.dualgun_ammo = { left = 143, right = 150 }
```

The inventory persists it like any other metadata, so ammo **travels with the item**: drop it, trade it, stash it, and the ammo goes along. No SQL table, no rows to clean up.

Requires an inventory bridge that supports metadata:

| Inventory          | Metadata |
| ------------------ | -------- |
| `ox_inventory`     | Yes      |
| `origen_inventory` | Yes      |
| `ak47_inventory`   | Yes      |
| `qb-inventory`     | Yes      |

#### SQL mode

The fallback when no metadata-capable inventory is present. Ammo is keyed by player identifier plus weapon key:

```
id = "license:abc123...:rm_appistol_dual"
```

Ammo is bound to the **player**, not the item. Dropping the gun and picking it up again keeps your ammo; giving it to someone else gives them an empty gun with their own separate count.

{% hint style="warning" %}
SQL mode rows accumulate one per player per weapon key. They are never deleted automatically. If you switch inventories later, old rows stay behind harmlessly but the table will keep growing.
{% endhint %}

***

### Which mode am I in?

The resolved mode is printed once at startup:

```
[rm_dualgun] ammo persistence: metadata mode (inventory=ox_inventory)
[rm_dualgun] ammo persistence: sql mode (inventory=none)
```

Resolution follows `cfg.ammo_mode`:

```
'auto'      → metadata if a metadata-capable bridge loaded, else sql
'metadata'  → metadata, or sql + a console warning if unsupported
'sql'       → always sql
```

***

### When ammo is saved

Ammo is held in memory on the server while you play and written to the backing store at these points:

| Trigger                    | What is written          |
| -------------------------- | ------------------------ |
| Unequipping the dual gun   | That weapon              |
| Using an ammo item         | That weapon, immediately |
| Periodic background save   | Anything changed since   |
| Player disconnects         | That player's ammo       |
| Server / resource shutdown | Everything               |

The periodic save runs every `cfg.ammo_save_interval` ms (default 30 000).

{% hint style="info" %}
Because ammo is already written on unequip, on disconnect and on shutdown, the periodic save only matters for a hard server crash. Lowering the interval increases database writes without making ammo meaningfully safer.
{% endhint %}

While firing, the client reports its count to the server at most **once every 1.5 seconds**, and immediately on reload and unequip. A hard crash mid-burst can therefore lose up to 1.5 seconds of spent rounds. See Rate Limits for why this throttle exists.

### Loading ammo

Using a `dual_X_ammo` item while a matching dual gun is active splits the stack **equally between both hands**, capped at each side's `max_ammo`.

The distribution logic:

1. Split the available count in half, one half per side.
2. Cap each half at what that side actually needs.
3. If the count was odd, the spare round goes to the side with more room.
4. If one side was capped early, its surplus is redistributed to the other side.
5. **Only the rounds actually loaded are consumed** from the inventory.

Example: 100 `dual_pistol_ammo`, left needs 20, right needs 100:

```
perSide     = 50
loadL       = min(50, 20)  = 20
loadR       = min(50, 100) = 50
surplus L   = 30 → moved to R
loadR       = 80

consumed = 100, left +20, right +80
```

Rejections are notified and consume nothing:

| Condition                          | Message                                |
| ---------------------------------- | -------------------------------------- |
| No dual gun active                 | *Activate a dual gun first*            |
| Ammo item doesn't match the weapon | *This ammo doesn't fit the active gun* |
| Both hands already full            | *Both guns are already full*           |
| Inventory removal failed           | *Couldn't take ammo from inventory*    |

***

### Reloading

Reload is automatic. When a magazine hits zero and reserve remains, the reload animation plays for `reload_time_ms` and **both** magazines are topped up from their reserves, so you never end up reloading twice in a row.

With no reserve left the player is notified *Out of ammo* and that hand is blocked for 1 second before it will retry.

***

### The `/useDual` testing command

`/useDual <weaponKey>` spawns a dual gun with full ammo and no item required.

{% hint style="warning" %}
Ammo spent from a `/useDual` weapon is never persisted. This is a testing tool for confirming your setup works. Do not build gameplay on it.
{% endhint %}

### Ammo items

Ten ammo items ship by default, one per weapon family:

`dual_pistol_ammo`, `dual_smg_ammo`, `dual_rifle_ammo`, `dual_shotgun_ammo`, `dual_stungun_ammo`, `dual_grenade_ammo`, `dual_rocket_ammo`, `dual_mg_ammo`, `dual_minigun_ammo`, `dual_railgun_ammo`

Which item reloads which weapon is set by the `ammo` field in `cfg.weapons`. Several weapons can, and do, share one ammo item.
