# 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](./OWNED_STORAGE_USAGE_GUIDE.md).

## 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.

| Command | Arguments | Returns |
| --- | --- | --- |
| `locker:create` | `uid` | Empty item map as JSON. |
| `locker:get` | `uid` | Item map as JSON. |
| `locker:add` | `uid`, `item_json` | Updated item map as JSON. |
| `locker:update` | `uid`, `items_json` | Replaced item map as JSON. |
| `locker:patch` | `uid`, `patch_json` | Updated item map as JSON. |
| `locker:remove` | `uid`, `classname` | Updated item map as JSON. |
| `locker:delete` | `uid` | `OK`. |
| `locker:exists` | `uid` | `true` or `false`. |

## 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.

| Command | Arguments | Returns |
| --- | --- | --- |
| `locker:hot:init` | `uid` | Item map as JSON. |
| `locker:hot:get` | `uid` | Item map as JSON. |
| `locker:hot:override` | `uid`, `items_json` | Item map as JSON. |
| `locker:hot:save` | `uid` | Current hot item map as JSON. |
| `locker:hot:remove` | `uid` | `OK`. |

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.
