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