# Locker Usage Guide

The locker module stores physical player inventory items by classname. It is
separate from the virtual arsenal unlock module documented in
[Owned Storage Usage Guide](/server-modules/owned-storage).

## Storage Model

Locker data is persisted through SurrealDB by the server extension.

```json
{
  "arifle_MX_F": {
    "category": "weapon",
    "classname": "arifle_MX_F",
    "amount": 1
  }
}
```

Rules validated by the Rust service:

- A locker can contain up to 25 unique classnames.
- `category` and `classname` cannot be empty.
- `amount` must be greater than `0`.
- `locker:add` creates an empty locker automatically when one does not exist.
- `locker:get`, `locker:patch`, and `locker:remove` require an existing locker.
- `locker:remove` takes the classname directly, not a JSON object.

## Commands

All commands are called on the `locker` group.

<table>
<thead>
  <tr>
    <th>
      Command
    </th>
    
    <th>
      Arguments
    </th>
    
    <th>
      Returns
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        locker:create
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
    </td>
    
    <td>
      Empty item map as JSON.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locker:get
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
    </td>
    
    <td>
      Item map as JSON.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locker:add
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
      
      , <code>
        item_json
      </code>
    </td>
    
    <td>
      Updated item map as JSON.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locker:update
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
      
      , <code>
        items_json
      </code>
    </td>
    
    <td>
      Replaced item map as JSON.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locker:patch
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
      
      , <code>
        patch_json
      </code>
    </td>
    
    <td>
      Updated item map as JSON.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locker:remove
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
      
      , <code>
        classname
      </code>
    </td>
    
    <td>
      Updated item map as JSON.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locker:delete
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
    </td>
    
    <td>
      <code>
        OK
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locker:exists
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
      
       or <code>
        false
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

## Error Handling

Every command returns a string payload. Always check for the `Error:` prefix
before parsing JSON.

```sqf
private _result = "forge_server" callExtension ["locker:get", [getPlayerUID player]];
private _payload = _result select 0;

if (_payload find "Error:" == 0) exitWith {
    systemChat format ["Locker error: %1", _payload];
};

private _locker = fromJSON _payload;
```

## Add an Item

`locker:add` creates or overwrites one classname entry.

```sqf
private _item = createHashMapFromArray [
    ["category", "weapon"],
    ["classname", "arifle_MX_F"],
    ["amount", 1]
];

private _result = "forge_server" callExtension ["locker:add", [
    getPlayerUID player,
    toJSON _item
]];

private _payload = _result select 0;
if (_payload find "Error:" == 0) exitWith {
    hint format ["Failed to store item: %1", _payload];
};

private _locker = fromJSON _payload;
```

## Patch an Amount

`locker:patch` currently patches the `amount` field for an existing classname.

```sqf
private _patch = createHashMapFromArray [
    ["classname", "arifle_MX_F"],
    ["amount", 5]
];

private _result = "forge_server" callExtension ["locker:patch", [
    getPlayerUID player,
    toJSON _patch
]];
```

## Remove an Item

`locker:remove` takes the classname as the second argument.

```sqf
private _result = "forge_server" callExtension ["locker:remove", [
    getPlayerUID player,
    "arifle_MX_F"
]];
```

## Retrieve an Item

```sqf
fnc_retrieveLockerItem = {
    params ["_classname"];

    private _result = "forge_server" callExtension ["locker:get", [getPlayerUID player]];
    private _payload = _result select 0;

    if (_payload find "Error:" == 0) exitWith {
        hint format ["Failed to load locker: %1", _payload];
        false
    };

    private _locker = fromJSON _payload;
    private _item = _locker getOrDefault [_classname, createHashMap];
    if (_item isEqualTo createHashMap) exitWith {
        hint "Item was not found in your locker.";
        false
    };

    private _amount = _item getOrDefault ["amount", 0];
    if (_amount <= 0) exitWith {
        hint "Item is out of stock.";
        false
    };

    if !(player canAdd _classname) exitWith {
        hint "Not enough inventory space.";
        false
    };

    player addItem _classname;

    if (_amount > 1) then {
        private _patch = createHashMapFromArray [
            ["classname", _classname],
            ["amount", _amount - 1]
        ];
        "forge_server" callExtension ["locker:patch", [getPlayerUID player, toJSON _patch]];
    } else {
        "forge_server" callExtension ["locker:remove", [getPlayerUID player, _classname]];
    };

    true
};
```

## Replace the Whole Locker

`locker:update` replaces the whole item map. Use it for explicit bulk syncs,
not single-item changes.

```sqf
private _items = createHashMapFromArray [
    ["arifle_MX_F", createHashMapFromArray [
        ["category", "weapon"],
        ["classname", "arifle_MX_F"],
        ["amount", 1]
    ]]
];

private _result = "forge_server" callExtension ["locker:update", [
    getPlayerUID player,
    toJSON _items
]];
```

## Hot State

The `locker:hot:*` commands keep a runtime copy of a player's locker and write
it back only when `locker:hot:save` runs.

<table>
<thead>
  <tr>
    <th>
      Command
    </th>
    
    <th>
      Arguments
    </th>
    
    <th>
      Returns
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        locker:hot:init
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
    </td>
    
    <td>
      Item map as JSON.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locker:hot:get
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
    </td>
    
    <td>
      Item map as JSON.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locker:hot:override
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
      
      , <code>
        items_json
      </code>
    </td>
    
    <td>
      Item map as JSON.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locker:hot:save
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
    </td>
    
    <td>
      Current hot item map as JSON.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        locker:hot:remove
      </code>
    </td>
    
    <td>
      <code>
        uid
      </code>
    </td>
    
    <td>
      <code>
        OK
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

Use hot state for session-heavy locker workflows. Use the durable commands for
simple item deposits and withdrawals.

## Best Practices

- Keep categories normalized, for example `weapon`, `magazine`, `item`, or
`backpack`.
- Use `locker:patch` for quantity changes.
- Use `locker:remove` when quantity reaches zero.
- Treat the locker response as a hash map keyed by classname.
- Check capacity before bulk operations that may exceed 25 unique items.
